单一职责原则 (Single Responsibility Principle)
顾名思义,单一职责的原则是说一个类直负责一项职责(操作)。如果一个类负责多个职责,其中一项职责发生变化就需要修改整个类,这可能会导致其他的职责运行错误。一个类,只应该有一个引起它变化的原因。
其优点有:
- 可以降低类的复杂度,一个类只负责一项职责,其逻辑肯定要比负责多项职责简单的多;
- 提高类的可读性,提高系统的可维护性;
- 变更引起的风险降低,变更是必然的,如果单一职责原则遵守的好,当修改一个功能时,可以显著降低对其他功能的影响。
里氏替换原则 (Liskov Substitution Principle)
里氏替换的意思是说所有引用基类的地方必须能透明地使用其子类的对象。这种情况在代码中随处可以,我们在类中使用基类进行定义,而在运行时使用子类对象,为了确保代码运行正常,在实现子类时要注意以下一些地方:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法;
- 子类中可以增加自己特有的方法;
- 当子类的方法重载父类的方法时,子类方法的输入参数要比父类方法的输入参数更宽松;
依赖倒置原则 (Dependence Inversion Principle)
定义:抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。依赖倒置原则要求我们在程序代码中传递参数时或在关联关系中,尽量引用层次高的抽象层类,即使用接口和抽象类进行变量类型声明、参数类型声明、方法返回类型声明,以及数据类型的转换等,而不要用具体类来做这些事情。依赖倒置原则的本质就是通过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。在编写代码中落到实处,需要注意以下一些地方:
- 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备;
- 变量的表名类型尽量是接口或者抽象类;
- 尽量不要覆写基类的方法;
- 结合里氏替换原则使用。
由于 Python 是一门动态语言,在传递参数时不需要定义具体类型,所以依赖倒置原则其实一定程度上已经内嵌在了 Python 语言中。
接口隔离原则 (Interface Segregation Principle)
接口隔离原则提示我们客户端不应该依赖它不需要的接口,一个类对另一个类的依赖应该建立在最小的接口上。根据接口隔离原则,当一个接口太大时,我们需要将它分割成一些更细小的接口,使用该接口的客户端仅需知道与之相关的方法即可。每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干。
看到这里你们或许认为接口隔离原则与单一职责原则是相同的。其实接口隔离原则与单一职责原则的审视角度是不相同的,单一职责原则要求的是类和接口职责单一,注重的是职责,这是业务逻辑上的划分,而接口隔离原则要求接口的方法尽量少。在使用接口隔离原则时,我们需要注意控制接口的粒度,接口不能太小,如果太小会导致系统中接口泛滥,不利于维护;接口也不能太大,太大的接口将违背接口隔离原则,灵活性较差,使用起来很不方便。一般而言,接口中仅包含为某一类用户定制的方法即可,不应该强迫客户依赖于那些它们不用的方法。
迪米特原则 (Law of Demeter)
定义:一个对象应该对其他对象有最少的了解。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的公开方法,我就调用这么多,其他的我一概不关心。迪米特法则指导我们在设计系统时,应该尽量减少对象之间的交互,如果两个对象之间不必彼此直接通信,那么这两个对象就不应当发生任何直接的相互作用,如果其中的一个对象需要调用另一个对象的某一个方法的话,可以通过第三者转发这个调用。简言之,就是通过引入一个合理的第三者来降低现有对象之间的耦合度。可以看到迪米特原则在代理模式和外观模式中都有被使用。
开闭原则 (Open Closed Principle)
软件实体应该对扩展开放,对修改关闭,其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。根据开闭原则,在设计一个软件系统模块(类,方法)的时候,应该可以在不修改原有的模块(修改关闭)的基础上,能扩展其功能(扩展开放)。遵循开闭原则的系统设计,可以让软件系统可复用,并且易于维护。这也是系统设计需要遵循开闭原则的原因:
- 稳定性:开闭原则要求扩展功能不修改原来的代码,这可以让软件系统在变化中保持稳定。
- 扩展性:开闭原则要求对扩展开放,通过扩展提供新的或改变原有的功能,让软件系统具有灵活的可扩展性。
Singoleton(单例模式)
所谓单例模式,也就是说不管什么时候我们要确保只有一个对象实例存在。很多情况下,整个系统中只需要存在一个对象,所有的信息都从这个对象获取,比如系统的配置对象,或者是线程池。这些场景下,就非常适合使用单例模式。总结起来,就是说不管我们初始化一个对象多少次,真正干活的对象只会生成一次并且在首次生成。
Factory(抽象工厂模式)
“工厂”两字,一目了然。所谓工厂模式,也就是说我们可以通过工厂类创建产品。
Strategy(策略模式)
策略模式将各种操作(算法)进行封装,并使它们之间可以互换。互换的意思是说可以动态改变对象的操作方式(算法)。
Observer(观察者模式)
观察者模式,就是说当一个对象发生变化时,观察者能及时得到通知并更新。
Command(命令模式)
命令模式就是对命令的封装。所谓封装命令,就是将一系列操作封装到命令类中,并且命令类只需要对外公开一个执行方法execute,调用此命令的对象只需要执行命令的execute方法就可以完成所有的操作。这样调用此命令的对象就和命令具体操作之间解耦了。更进一步,通过命令模式我们可以抽象出调用者,接收者和命令三个对象。调用者就是简单的调用命令,然后将命令发送给接收者,而接收者则接收并执行命令,执行命令的方式也是简单的调用命令的execute方法就可以了。发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
TemplateMethod(模板方法模式)
模板,不难想到文档模板、简历模板等。其实模板方法模式中的模板就是这个意思,在模板方法模式中,我们先定义一个类模板,在这个类中,我们定义了各种操作的顺序(轮毂或者说是骨架),但是并不实现这些操作,这些操作由子类来操作。
Adapter(适配器模式)
适配器?你买过水货电子产品吗?假如你是买的港行的电子产品,那么其电源插头是香港标准的,在大陆不能直接使用。一般情况下,商家会附赠一个转换插头。你把电子产品的电源插头插在转换插头上,然后转换插头插上电源,电子产品就能正常工作了。这就是适配器模式。
Decorator(装饰者模式)
装饰者模式能动态的给对象添加行为。
Proxy(代理模式)
代理模式在生活中比比皆是。比如你通过代理上网,比如你不会去华西牛奶生产地直接买牛奶,而是到超市这个代理购买牛奶,这些例子中都存在着代理模式。所谓代理模式就是给一个对象提供一个代理,并由代理对象控制对原对象的访问。通过代理,我们可以对访问做一些控制。在开发网站的过程中,针对一些频繁访问的资源,我们会使用缓存。在开发实验楼的过程中也是如此,我们通过缓存代理解决了一些热点资源的访问问题。
Composite(组合模式)
什么是组合模式?按照定义来说,组合模式是将对象组合成树形结构表示,使得客户端对单个对象和组合对象的使用具有一致性。组合模式的使用通常会生成一颗对象树,对象树中的叶子结点代表单个对象,其他节点代表组合对象。调用某一组合对象的方法,其实会迭代调用所有其叶子对象的方法。 使用组合模式的经典例子是 Linux 系统内的树形菜单和文件系统。在树形菜单中,每一项菜单可能是一个组合对象,其包含了菜单项和子菜单,这样就形成了一棵对象树。在文件系统中,叶子对象就是文件,而文件夹就是组合对象,文件夹可以包含文件夹和文件,同样又形成了一棵对象树。
Facade(外观模式)
外观模式,就是将各种子系统的复杂操作通过外观模式简化,让客户端使用起来更方便简洁。比如你夏天晚上出门时,要关闭电灯,关闭电视机,关闭空调,如果有了一个总开关,通过它可以关闭电灯,电视机和空调,你出门的时候关闭总开关就行了。在这个例子中,你就是客户端,总开关就是外观模式的化身。
模式类型 | 名字 | 案例 |
---|---|---|
创建型 | ||
Singoleton(单例模式) | Markdown | |
Factory(抽象工厂模式) | Markdown | |
行为型 | ||
Strategy(策略模式) | Markdown | |
Observer(观察者模式) | Markdown | |
Command(命令模式) | Markdown | |
TemplateMethod(模板方法模式) | Markdown | |
结构型 | ||
Adapter(适配器模式) | Markdown | |
Decorator(装饰者模式) | Markdown | |
Proxy(代理模式) | Markdown | |
Composite(组合模式) | Markdown | |
Facade(外观模式) | Markdown |