1、策略模式
策略模式将各种操作(算法)进行封装,并使它们之间可以互换。互换的意思是说可以动态改变对象的操作方式(算法)。
# -*- coding: utf-8 -*-
import abc
class AbsShow(object):
"""
抽象显示对象
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def show(self):
pass
class AdminShow(AbsShow):
"""
管理员的显示操作
"""
def show(self):
return "show with admin"
class UserShow(AbsShow):
"""
普通用户的显示操作
"""
def show(self):
return "show with user"
class Question(object):
"""
问题对象,使用策略模式之后的作法
"""
def __init__(self, show_obj):
self.show_obj = show_obj
`
def show(self):
return self.show_obj.show()
`
if __name__ == '__main__':
q = Question(show_obj=AdminShow())
print(q.show())
# 替换原来的显示对象,体现了策略模式的互换行为
q.show_obj = UserShow()
print(q.show())
上面的代码中,我们将原来的 Question.show 抽象成了 AbsShow ,这个操作类负责显示信息。然后我们分别基于该抽象类实现管理员显示类 AdminShow 和用户显示类 UserShow ,这样一来我们就使操作和使用这些操作的客户端完全分开了。在最后我们重新实现了 Question ,并且 Question.show 方法直接调用显示对象的显示方法。这样一来我们将 Question 对象和显示方法进行了解耦,增加新的显示方法时,只需要增加新的显示对象就可以了。同时,在代码中还可以看到我们可以动态改变 Question 的显示方式,这也体现了策略模式的互换行为。
二、观察者模式
所谓观察者模式,就是说当一个对象发生变化时,观察者能及时得到通知并更新。观察者模式在很多地方都有应用,比如在实验楼上关注课程。下面就让我们看下实验楼针对课程关注功能是怎么实现观察者模式的。
# -*- coding: utf-8 -*-
import abc
class Subject(object):
"""
被观察对象的基类
"""
def __init__(self):
self._observers = []
def attach(self, observer):
"""
注册一个观察者
"""
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer):
"""
注销一个观察者
"""
try:
self._observers.remove(observer)
except ValueError:
pass
def notify(self):
"""
通知所有观察者,执行观察者的更新方法
"""
for observer in self._observers:
observer.update(self)
class Course(Subject):
"""
课程对象,被观察的对象
"""
def __init__(self):
super(Course, self).__init__()
self._message = None
@property
def message(self):
"""
message 是一个属性
"""
return self._message
@message.setter
def message(self, msg):
"""
message 属性设置器
"""
self._message = msg
self.notify()
class Observer(object):
"""
观察者抽象类
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def update(self, subject):
pass
class UserObserver(Observer):
"""
用户观察者
"""
def update(self, subject):
print("User observer: %s" % subject.message)
class OrgObserver(Observer):
"""
机构观察者
"""
def update(self, subject):
print("Organization observer: %s" % subject.message)
if __name__ == '__main__':
# 初始化一个用户观察者
user = UserObserver()
# 初始化一个机构观察者
org = OrgObserver()
# 初始化一个课程
course = Course()
# 注册观察者
course.attach(user)
course.attach(org)
# 设置course.message,这时观察者会收到通知
course.message = "two observers"
# 注销一个观察者
course.detach(user)
course.message = "single observer"
在上面的代码中,最重要的就是Subject类了,它实现了观察者模式中大部分功能。作为一个被观察的对象,Subject实现了注册观察者,注销观察者和通知观察者的功能。接着我们基于Subject创建了我们的课程Course类,并且当我们设置Course.message属性时,Course对象会通知到所有观察者。可以看出,观察者模式使被观察的对象(主题)和观察者之间解耦了。
三、命令模式
顾名思义,命令模式就是对命令的封装。所谓封装命令,就是将一系列操作封装到命令类中,并且命令类只需要对外公开一个执行方法execute,调用此命令的对象只需要执行命令的execute方法就可以完成所有的操作。这样调用此命令的对象就和命令具体操作之间解耦了。更进一步,通过命令模式我们可以抽象出调用者,接收者和命令三个对象。调用者就是简单的调用命令,然后将命令发送给接收者,而接收者则接收并执行命令,执行命令的方式也是简单的调用命令的execute方法就可以了。发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。下面让我们使用 Python 来实现命令模式。
# -*- coding: utf-8 -*-
import abc
class VmReceiver(object):
"""
命令接收者,真正执行命令的地方
"""
def start(self):
print("Virtual machine start")
def stop(self):
print("Virtual machine stop")
class Command(object):
"""
命令抽象类
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def execute(self):
"""
命令对象对外只提供 execute 方法
"""
pass
class StartVmCommand(Command):
"""
开启虚拟机的命令
"""
def __init__(self, recevier):
"""
使用一个命令接收者初始化
"""
self.recevier = recevier
def execute(self):
"""
真正执行命令的时候命令接收者开启虚拟机
"""
self.recevier.start()
class StopVmCommand(Command):
"""
停止虚拟机的命令
"""
def __init__(self, recevier):
"""
使用一个命令接收者初始化
"""
self.recevier = recevier
def execute(self):
"""
真正执行命令的时候命令接收者关闭虚拟机
"""
self.recevier.stop()
class ClientInvoker(object):
"""
命令调用者
"""
def __init__(self, command):
self.command = command
def do(self):
self.command.execute()
if __name__ == '__main__':
recevier = VmReceiver()
start_command = StartVmCommand(recevier)
# 命令调用者同时也是客户端,通过命令实例也执行真正的操作
client = ClientInvoker(start_command)
client.do()
# 能告诉命令接收者执行不同的操作
stop_command = StopVmCommand(recevier)
client.command = stop_command
client.do()
以上代码中,我们通过启动和停止 Linux 虚拟机的例子实现了命令模式。通过命令模式,使命令调用者ClinetInvoker和命令接收者VmRecevier之间解耦,前者不必知道后者具体是怎么操作虚拟机的,只需要通过ClientInvoker.do()方法调用Command.execute()方法能完成虚拟机的相关操作。
总的来说,命令模式的封装性很好:每个命令都被封装起来,对于客户端来说,需要什么功能就去调用相应的命令,而无需知道命令具体是怎么执行的。同时命令模式的扩展性很好,在命令模式中,在接收者类中一般会对操作进行最基本的封装,命令类则通过对这些基本的操作进行二次封装,当增加新命令的时候,对命令类的编写一般不是从零开始的,有大量的接收者类可供调用,也有大量的命令类可供调用,代码的复用性很好。
四、模板方法模式
提到模板,不难想到文档模板、简历模板等。其实模板方法模式中的模板就是这个意思,在模板方法模式中,我们先定义一个类模板,在这个类中,我们定义了各种操作的顺序(轮毂或者说是骨架),但是并不实现这些操作,这些操作由子类来操作。
举个例子,假如有一天我们想去去三岔湖钓鱼,那么需要怎么操作呢?第一步:准备鱼饵;第二步:到达三岔湖;第三步;选择钓点。这三步操作不能乱序,否则我们就钓不成鱼啦。但是在准备鱼饵的时候,可以通过淘宝购买,也可以在渔具店里购买,这些都是不确定。同时怎么去三岔湖也是不确定的,你可以开车去,也可以搭车去。在这种情况下,模板方法模式就非常有用了。在模板方法模式中我们先定义去三岔湖钓鱼的操作步骤,每一步的具体操作在不同的子类中可能都有不同的实现。下面让我们看看具体的代码吧。
# -*- coding: utf-8 -*-
import abc
class Fishing(object):
"""
钓鱼模板基类
"""
__metaclass__ = abc.ABCMeta
def finishing(self):
"""
钓鱼方法中,确定了要执行哪些操作才能钓鱼
"""
self.prepare_bait()
self.go_to_riverbank()
self.find_location()
print("start fishing")
@abc.abstractmethod
def prepare_bait(self):
pass
@abc.abstractmethod
def go_to_riverbank(self):
pass
@abc.abstractmethod
def find_location(self):
pass
class JohnFishing(Fishing):
"""
John 也想去钓鱼,它必须实现钓鱼三步骤
"""
def prepare_bait(self):
"""
从淘宝购买鱼饵
"""
print("John: buy bait from Taobao")
def go_to_riverbank(self):
"""
开车去钓鱼
"""
print("John: to river by driving")
def find_location(self):
"""
在岛上选择钓点
"""
print("John: select location on the island")
class SimonFishing(Fishing):
"""
Simon 也想去钓鱼,它也必须实现钓鱼三步骤
"""
def prepare_bait(self):
"""
从京东购买鱼饵
"""
print("Simon: buy bait from JD")
def go_to_riverbank(self):
"""
骑自行车去钓鱼
"""
print("Simon: to river by biking")
def find_location(self):
"""
在河边选择钓点
"""
print("Simon: select location on the riverbank")
if __name__ == '__main__':
# John 去钓鱼
f = JohnFishing()
f.finishing()
# Simon 去钓鱼
f = SimonFishing()
f.finishing()
怎么样?模板方法模式是不是简单易懂呢?模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。
到目前为止,这一节实验就要结束了。在本节实验中,我们学习了四种设计模式:策略模式,观察者模式,命令模式以及模板方法模式。这四种设计模式都是行为型模式。什么是行为型模式呢?
按照定义,行为型模式是对在不同的对象之间划分责任和算法的抽象化。行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
五、适配器模式
何为适配器?你买过水货电子产品吗?假如你是买的港行的电子产品,那么其电源插头是香港标准的,在大陆不能直接使用。一般情况下,商家会附赠一个转换插头。你把电子产品的电源插头插在转换插头上,然后转换插头插上电源,电子产品就能正常工作了。这就是适配器模式。下面让我们看看适配器模式在实验楼中使用吧。
# -*- coding: utf-8 -*-
class OldCourse(object):
"""
老的课程类
"""
def show(self):
"""
显示关于本课程的所有信息
"""
print("show description")
print("show teacher of course")
print("show labs")
class Page(object):
"""
使用课程对象的客户端
"""
def __init__(self, course):
self.course = course
def render(self):
self.course.show()
class NewCourse(object):
"""
新的课程类, 为了模块化显示课程信息,实现了新的课程类
"""
def show_desc(self):
"""
显示描述信息
"""
print("show description")
def show_teacher(self):
"""
显示老师信息
"""
print("show teacher of course")
def show_labs(self):
"""
显示实验
"""
print("show labs")
class Adapter(object):
"""
适配器, 尽管实现了新的课程类,但是在很多代码中还是需要使用 OldCourse.show() 方法
"""
def __init__(self, course):
self.course = course
def show(self):
"""
适配方法,调用真正的操作
"""
self.course.show_desc()
self.course.show_teacher()
self.course.show_labs()
if __name__ == '__main__':
old_course = OldCourse()
page = Page(old_course)
page.render()
print("")
new_course = NewCourse()
# 新课程类没有 show 方法,我们需要使用适配器进行适配
adapter = Adapter(new_course)
page = Page(adapter)
page.render()
在上面的代码中,我们原本有一个OldCourse类,它有一个方法OldCourse.show用于显示课程的所有相关信息,并且在Page对象中用到。现在,为了适应模块化显示的需求,我们开发了新的课程类NewCourse,它只能分别显示课程的部分信息。现在为了使NewCourse对象能在Page对象中也能正常工作,我们使用了适配器模式来兼容。在适配器Adapter中,我们实现了Adapter.show()方法,它会调用NewCourse的一系列方法来完成显示整个课程信息的需求。这样一来,我们直接将Adapter对象传递给Page对象就可以兼容老的接口,使系统正常运行。
适配器模式就是把一个类的接口变换成客户端所期待的另一种接口,使原本因接口不兼容而无法在一起工作的两个类能够在一起工作。
六、装饰者模式
装饰者模式?装饰器?对于一个 Python 程序员来说,这再熟悉不过了。准确来说,装饰者模式能动态的给对象添加行为。如果你对 Flask 比较熟悉的话,应该知道在使用 Flask-Login 的时候可以使用 login_required 装饰器包装一个需要用户登录访问的view。直接看看我们实现的装饰者模式吧。
# -*- coding: utf-8 -*-
from functools import wraps
HOST_DOCKER = 0
def docker_host_required(f):
"""
装饰器,必须要求 host 类型是 HOST_DOCKER
"""
@wraps(f)
def wrapper(*args, **kwargs):
if args[0].type != HOST_DOCKER:
raise Exception("Not docker host")
else:
return f(*args, **kwargs)
return wrapper
class Host(object):
"""
host 类
"""
def __init__(self, type):
self.type = type
# 装饰这一方法
@docker_host_required
def create_container(self):
print("create container")
if __name__ == '__main__':
# 初始化 Host
host = Host(HOST_DOCKER)
host.create_container()
print("")
# 再次初始化 Host
host = Host(1)
host.create_container()
在上面的代码中,Host有一个方法Host.create_container,只有当Host实例的类型是DOCKER_HOST的时候才能执行该方法。为了加上这一行为,我们使用了装饰者模式。可以看出使用装饰者模式,我们可以动态改变类的行为,同时能提高代码复用性,因为任何类型为HOST_DOCKER的Host都可以使用该装饰器。另外要说明下:为了更好的实现装饰器,我们使用functools.wrap函数。
七、代理模式
代理模式在生活中比比皆是。比如你通过代理上网,比如你不会去华西牛奶生产地直接买牛奶,而是到超市这个代理购买牛奶,这些例子中都存在着代理模式。所谓代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的访问。通过代理,我们可以对访问做一些控制。在开发网站的过程中,针对一些频繁访问的资源,我们会使用缓存。在开发实验楼的过程中也是如此,我们通过缓存代理解决了一些热点资源的访问问题。下面让我们看看是怎么实现的吧。
# -*- coding: utf-8 -*-
from time import sleep
class Redis(object):
"""
用于模拟 redis 服务
"""
def __init__(self):
"""
使用字典存储数据
"""
self.cache = dict()
def get(self, key):
"""
获取数据
"""
return self.cache.get(key)
def set(self, key, value):
"""
设置数据
"""
self.cache[key] = value
class Image(object):
"""
图片对象,图片存在七牛云存储中,我们只保存了一个地址
"""
def __init__(self, name):
self.name = name
@property
def url(self):
sleep(2)
return "https://dn-syl-static.qbox.me/img/logo-transparent.png"
class Page(object):
"""
用于显示图片
"""
def __init__(self, image):
"""
需要图片进行初始化
"""
self.image = image
def render(self):
"""
显示图片
"""
print(self.image.url)
redis = Redis()
class ImageProxy(object):
"""
图片代理,首次访问会从真正的图片对象中获取地址,以后都从 Redis 缓存中获取
"""
def __init__(self, image):
self.image = image
@property
def url(self):
addr = redis.get(self.image.name)
if not addr:
addr = self.image.url
print("Set url in redis cache!")
redis.set(self.image.name, addr)
else:
print("Get url from redis cache!")
return addr
if __name__ == '__main__':
img = Image(name="logo")
proxy = ImageProxy(img)
page = Page(proxy)
# 首次访问
page.render()
print("")
# 第二次访问
page.render()
在上面的代码中我们使用代理模式实现了对图片的缓存。在使用缓存之前,我们实现了Redis对象简单模拟了Redis服务。可以看到访问Image.url属性是比较耗时的操作(我们使用time.sleep模拟了耗时操作),如果每次都是直接访问该属性,就会浪费大量的时间。通过实现ImageProxy缓存代理,我们将图片地址缓存到 Redis 中,提高了后续的访问速度。
从上面的代码可以看出,代理对象和真实的对象之间都实现了共同的接口,这使我们可以在不改变原接口情况下,使用真实对象的地方都可以使用代理对象。其次,代理对象在客户端和真实对象之间直接起到了中介作用,同时通过代理对象,我们可以在将客户请求传递给真实对象之前做一些必要的预处理。
八、组合模式
什么是组合模式?按照定义来说,组合模式是将对象组合成树形结构表示,使得客户端对单个对象和组合对象的使用具有一致性。组合模式的使用通常会生成一颗对象树,对象树中的叶子结点代表单个对象,其他节点代表组合对象。调用某一组合对象的方法,其实会迭代调用所有其叶子对象的方法。
使用组合模式的经典例子是 Linux 系统内的树形菜单和文件系统。在树形菜单中,每一项菜单可能是一个组合对象,其包含了菜单项和子菜单,这样就形成了一棵对象树。在文件系统中,叶子对象就是文件,而文件夹就是组合对象,文件夹可以包含文件夹和文件,同样又形成了一棵对象树。同样的例子还有员工和领导之间的关系,下面就让我们实现下吧。
# -*- coding: utf-8 -*-
import abc
class Worker(object):
"""
员工抽象类
"""
__metaclass__ = abc.ABCMeta
def __init__(self, name):
self.name = name
@abc.abstractmethod
def work(self):
pass
class Employe(Worker):
"""
员工类
"""
__metaclass__ = abc.ABCMeta
def work(self):
print("Employ: %s start to work " % self.name)
class Leader(Worker):
"""
领导类
"""
def __init__(self, name):
self.members = []
super(Leader, self).__init__(name)
def add_member(self, employe):
if employe not in self.members:
self.members.append(employe)
def remove_member(self, employe):
if employe in self.members:
self.members.remove(employe)
def work(self):
print("Leader: %s start to work" % self.name)
for employe in self.members:
employe.work()
if __name__ == '__main__':
employe_1 = Employe("employe_1")
employe_2 = Employe("employe_2")
leader_1 = Leader("leader_1")
leader_1.add_member(employe_1)
leader_1.add_member(employe_2)
employe_3 = Employe("employe_3")
leader_2 = Leader("leader_2")
leader_2.add_member(employe_3)
leader_2.add_member(leader_1)
leader_2.work()
在以上的代码中,雇员和领导都属于员工,都会实现Worker.work()方法,只要执行了该方法就代表这个员工开始工作了。我们也注意到一个领导名下,可能有多个次级领导和其他雇员,如果一个领导开始工作,那这些次级领导和雇员都需要开工。员工和领导组成了一个对象树,领导是组合对象,员工是叶子对象。还可以看到 Leader类通常会实现类似于Leader.add_member的方法来用于添加另一个组合对象或者是叶子对象,并且调用组合对象的Leader.work方法会遍历调用(通过迭代器)其子对象work方法。客户端使用组合模式实现的对象时,不必关心自己处理的是单个对象还是组合对象,降低了客户端的使用难度,降低了耦合性。
在最后的测试代码中,我们首先创建了2个雇员: employe_1, employe_2 和1个领导leader_1, 前2个雇员被后一个领导管理。接着我们又创建了第3个雇员employe_3,和第2个领导leader_2,其中 leader_2是大领导,他管理employe_3和leader_1。
十、外观模式
所谓外观模式,就是将各种子系统的复杂操作通过外观模式简化,让客户端使用起来更方便简洁。比如你夏天晚上出门时,要关闭电灯,关闭电视机,关闭空调,如果有了一个总开关,通过它可以关闭电灯,电视机和空调,你出门的时候关闭总开关就行了。在这个例子中,你就是客户端,总开关就是外观模式的化身。在实验楼中,外观模式应用在创建实验环境的接口上。让我们看看具体的代码吧。
# -*- coding: utf-8 -*-
class User(object):
"""
用户类
"""
def is_login(self):
return True
def has_privilege(self, privilege):
return True
class Course(object):
"""
课程类
"""
def can_be_learned(self):
return True
class Lab(object):
"""
实验类
"""
def can_be_started(self):
return True
class Client(object):
"""
客户类,用于开始一个实验
"""
def __init__(self, user, course, lab):
self.user = user
self.course = course
self.lab = lab
def start_lab(self):
"""
开始实验,需要一系列的判断:用户是否登陆,课程是否可以学习,实验是否可以开始。判断非常繁琐!
"""
if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started():
print("start lab")
else:
print("can not start lab")
class FacadeLab(object):
"""
新的Lab类,应用了面向对象模式
"""
def __init__(self, user, course, lab):
self.user = user
self.course = course
self.lab = lab
def can_be_started(self):
if self.user.is_login() and self.course.can_be_learned() and self.lab.can_be_started():
return True
else:
return False
class NewClient(object):
"""
新的客户类,使用外观模式
"""
def __init__(self, facade_lab):
self.lab = facade_lab
def start_lab(self):
"""
开始实验,只需要判断 FacadeLab 是否可以开始
"""
if self.lab.can_be_started:
print("start lab")
else:
print("can not start lab")
if __name__ == '__main__':
user = User()
course = Course()
lab = Lab()
client = Client(user, course, lab)
client.start_lab()
print("Use Facade Pattern:")
facade_lab = FacadeLab(user, course, lab)
facade_client = NewClient(facade_lab)
facade_client.start_lab()
以上代码中,我们使用了在实验楼中启动实验的案例实现了外观模式。正常情况下,我们开始一个实验,需要判断一系列前置条件:用户是否已经登陆,课程是否满足学习的条件,实验是否满足可以启动等。如果我们直接将这些对象在客户端Client类中使用,无疑增加了客户端类和User,Course和Lab类的耦合度。另外如果我们要增加新的前置条件判断时,我们就要修改Client类。为了解决这些问题,我们引入了外观模式实现了FacadeLab类,在这个类中,我们通过对外提供接口FacadeLab.can_be_started来屏蔽客户端类对子系统的直接访问,使得新的客户端类NewClient的代变得简洁。
总的来说外观模式的主要目的在于降低系统的复杂程度,在面向对象软件系统中,类与类之间的关系越多,不能表示系统设计得越好,反而表示系统中类之间的耦合度太大,这样的系统在维护和修改时都缺乏灵活性,因为一个类的改动会导致多个类发生变化,而外观模式的引入在很大程度上降低了类与类之间的耦合关系。引入外观模式之后,增加新的子系统或者移除子系统都非常方便,客户类无须进行修改(或者极少的修改),只需要在外观类中增加或移除对子系统的引用即可。
到这里所有的设计模式就已经学习完啦,总的来说设计模式的目的就是为了是代码解耦。设计模式的学习是一个长期的过程,在平时的代码编写过程中要多思考能否应用设计模式。良好的应用设计模式,能使我们的代码更加灵活,适应性更强。除了设计模式,其实还有六大设计原则可以指导我们的代码设计。
六大设计准则
1 单一职责原则 (Single Responsibility Principle)
顾名思义,单一职责的原则是说一个类直负责一项职责(操作)。如果一个类负责多个职责,其中一项职责发生变化就需要修改整个类,这可能会导致其他的职责运行错误。一个类,只应该有一个引起它变化的原因。
其优点有:
可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
提高类的可读性,提高系统的可维护性;
变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
2 里氏替换原则 (Liskov Substitution Principle)
里氏替换的意思是说所有引用基类的地方必须能透明地使用其子类的对象。这种情况在代码中随处可以,我们在类中使用基类进行定义,而在运行时使用子类对象,为了确保代码运行正常,在实现子类时要注意以下一些地方:
子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
子类中可以增加自己特有的方法;
当子类的方法重载父类的方法时,子类方法的输入参数要比父类方法的输入参数更宽松;
3 依赖倒置原则 (Dependence Inversion Principle)
定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。依赖倒置原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。在编写代码中落到实处,需要注意以下一些地方:
每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备;
变量的表名类型尽量是接口或者抽象类;
尽量不要覆写基类的方法;
结合里氏替换原则使用。
由于 Python 是一门动态语言,在传递参数时不需要定义具体类型,所以依赖倒置原则其实一定程度上已经内嵌在了 Python 语言中。
4 接口隔离原则 (Interface Segregation Principle)
接口隔离原则提示我们客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
看到这里你们或许认为接口隔离原则与单一职责原则是相同的。其实接口隔离原则与单一职责原则的审视角度是不相同的,单一职责原则要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。
5 迪米特原则 (Law of Demeter)
定义:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的公开方法,我就调用这么多,其他的我一概不关心。迪米特法则指导我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。可以看到迪米特原则在代理模式和外观模式中都有被使用。
6 开闭原则 (Open Closed Principle)
软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。遵循开闭原则的系统设计,可以让软件系统可复用,并且易于维护。这也是系统设计需要遵循开闭原则的原因:
稳定性:开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。
扩展性:开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。