Python 设计模式
# 什么是设计模式
在《设计模式:可复用面向对象软件的基础》(以下都简称《设计模式》)一书中提到:
Christopher Alexander 说过:“每一个模式描述了一个在我们周围不断发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动。”
Christopher Alexander 是一位建筑师,这是在他的著作中对建筑模式的总结。对应到软件工程,在长期的实践和积累中,人们总结了很多场景下提高代码的可复用性、可扩展性、可维护性和降低模块间耦合的经验,提炼成一个个框架供以后使用。
设计模式是经过总结、优化的,对我们经常会碰到的一些编程问题的可重用解决方案。一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码。反之,设计模式更为高级,它是一种必须在特定情形下实现的一种方法模板。设计模式不会绑定具体的编程语言。一个好的设计模式应该能够用大部分编程语言实现(如果做不到全部的话,具体取决于语言特性)。最为重要的是,设计模式也是一把双刃剑,如果设计模式被用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦。然而如果设计模式在正确的时间被用在正确地地方,它将是你的救星。
起初,你会认为“模式”就是为了解决一类特定问题而特别想出来的明智之举。说的没错,看起来的确是通过很多人一起工作,从不同的角度看待问题进而形成的一个最通用、最灵活的解决方案。也许这些问题你曾经见过或是曾经解决过,但是你的解决方案很可能没有模式这么完备。
最全 36 种 python 设计模式 (opens new window)
# 设计模式的前世今生
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
设计模式一般是针对 java 语言而言,一般的主推面向过程的 js 、shell、c 语言就很少谈那么多种套路了。通常说的设计模式也是从 java 经验中提取出来的。
但只要是面向对象的语言,设计模式都可以融入,如 php python,但脚本语言像 python 谈论设计模式的场景和次数比 java 少很多,估计是 python 入门低,使用灵活,很多人只习惯喜欢 OOP 方式编程,还有就是 python 人数在国内十分少,从招聘网站上基本可以得出结论,除开软件测试领域、运维领域或者一些个体户(这些个体用 python 只是辅助不是天天写项目),正规军 python 估计不到 java php 的二十分之一……
设计模式,必须是 oop 编程,如果写代码只喜欢从上往下一条条命令累加,或者提取出一些函数来复用,排斥 oop,那绝大多数的设计模式都是用不了的(可以给模块打猴子补丁来实现,但是这种方式很坑很别扭的),所以 90%的设计模式必须是 oop 方式的编程。
# 针对 Python 语言的碎碎念
python 中和 java 有点不同,python 没有接口。就比如工厂模式、外观模式、桥接模式等的实现对于 java 而言需要保证多个类有同样的方法,会使用接口。python 本身没有接口,三方包 zope.interface 里面可以实现接口,python 只要保证那些类是鸭子类型就 ok 了,没有强制性的接口,比较灵活,但约束少很难保证多个人合作时候懂这个意图,强制实现接口和鸭子类各有利弊),Python 支持多继承和 mixin 类甚至是打猴子补丁,不是需要完全与 java 一样来写。
如果没听说设计模式那也有可能经常用过某模式,或者灵感爆发用了某模式。但是只有专门学习设计模式,才能形成稳定的设计和输出。也有观点认为设计模式不重要,不需要学习,说得是类似金庸风清扬境界的无招胜有招,手中无招,心中有招。设计模式没有个死的,特别是设计模式的结构型模式中很多模式,基本通过类的组合千变万化而来,很相近的。但是成为编程界的风清扬估计没个七八年的编程经验和好悟性达不到,风清扬也是老了才那么厉害嘛。要想速成可学葵花宝典,风清扬也是先要按套路学,不学基础就达不到这种境界,无招胜有招说的是不能死按套路来。
提示
死招数破得再妙,遇上了活招数,免不了缚手缚脚,只有任人屠戮。这个‘活’字,你要牢牢记住了。学招时要活学,使招时要活使。倘若拘泥不化,便练熟了几千万手绝招,遇上了真正高手,终究还是给人家破得干干净净。”令狐冲大喜,他生性飞扬跳脱,风清扬这几句话当真说到了他心坎里去,连称:“是,是!须得活学活使。”风清扬道:“五岳剑派中各有无数蠢才,以为将师父传下来的剑招学得精熟,自然而然便成高手,哼哼,熟读唐诗三百首,不会作诗也会吟!熟读了人家诗句,做几首打油诗是可以的,但若不能自出机抒,能成大诗人么?”他这番话,自然是连岳不群也骂在其中了,但令狐冲的名字,也就没有抗辩。
风清扬道:“活学活使,只是第一步。要做到出手无招,那才真是踏入了高手的境界。你说‘各招浑成,敌人便无法可破’,这句话还只说对了一小半。不是‘浑成’,而是根本无招。你的剑招使得再浑成,只要有迹可寻,敌人便有隙可乘。但如你根本并无招式,敌人如何来破你的招式?”令狐冲一颗心怦怦乱跳,手心发热,喃喃的道:“根本无招,如何可破?根本无招,如何可破?”斗然之间,眼前出现了一个生平从所未见、连做梦也想不到的新天地。风清扬道:“要切肉,总得有肉可切;要斩柴,总得有柴可斩;敌人要破你剑招,你须得有剑招给人家来破才成。一个从未学过武功的常人,拿了剑乱挥乱舞,你见闻再博,也猜不到他下一剑要刺向哪里,砍向何处。就算是剑术至精之人,也破不了他的招式,只因并无招式,‘破招’二字,便谈不上了。只是不曾学过武功之人,虽无招式,却会给人轻而易举的打倒。真正上乘的剑术,则是能制人而决不能为人所制。”他拾起地下的一根死人腿骨,随手以一端对着令狐冲,道:“你如何破我这一招?”
令狐冲不知他这一下是甚么招式,一怔之下,便道:“这不是招式,因此破解不得。”
风清扬微微一笑,道:“这就是了。学武之人使兵刃,动拳脚,总是有招式的,你只须知道破法,一出手便能破招制敌。”令狐冲道:“要是敌人也没招式呢?”风清扬道:“那么他也是一等一的高手了,二人打到如何便如何,说不定是你高些,也说不定是他高些。”
——金庸《笑傲江湖》第十章《传剑》
主要还是有固定的模式,看代码容易些。别人接盘也方便。因为遇到过很多人所有地方所有场景全写函数,而且没有下划线来区别哪些只是内部辅助函数,或者一个函数高达 300 行,真的很坑爹。如果不会自定义项目或者完全不懂设计模式,那应该找个三方框架来用,这样接盘侠也容易看懂,因为你是按照标准的三方库来用的,别人看代码就节约很多时间了。既不引用三方框架严格按框架的规定来写,又不用设计模式,到处是原生自定义的写的很长一坨一坨的函数,接盘这样的代码那就很烦人,需要不停的 ctrl+b
跳转代码和上下翻屏查看函数怎么写的。
# 使用设计模式的优点
据我的经验改造了十几个模块,光是使用 oop 就能最起码使代码数量减少 40 到 70%,如果配合设计模式,能够使代码数量最多减少高达 90%。我说的这个优化量是针对无限 复制+ 粘贴 + 扣字 这种写法的人写出来的代码,比如写十几个平台,每个平台又分国内外,差的人写法是,无限复制粘贴一个写好的文件然后扣字母去修改,因为每个平台有 80%的共性,又有 20%的不同之处,不会模式的人,会情不自禁很容易懒惰造成不想在设计模式层面下功夫而是无限制粘贴扣字的 low 逼写法,写到最后改一个问题,需要修改几十个文件,每个文件高达 600 多行,改起来十分麻烦,容易出错。
有时候面向过程写的东西真的很复杂的,又不好扩展,特别是交给别人维护时候,非常多的函数高达几十个函数,到处在函数里面 return 和传参,接盘的人真的很难读懂代码,会造成上班工作时候心情很差。
例如五子棋,面向过程的设计思路就是首先分析问题的步骤:
1、开始游戏,
2、黑子先走,
3、绘制画面,
4、判断输赢,
5、轮到白子,
6、绘制画面,
7、判断输赢,
8、返回步骤 2,
9、输出最后结果。
2
3
4
5
6
7
8
9
把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:
1、黑白双方,这两方的行为是一模一样的,
2、棋盘系统,负责绘制画面,
3、规则系统,负责判定诸如犯规、输赢等。
2
3
第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了很多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。
python 扩展类方面,大体包括继承、组合、mixin 三种。
继承:子类继承父类,子类具有父类的属性和方法。子类还是和父类是同种东西。比如人和孩子、成年人,孩子也是属于人。all is A
组合:人和手机,人可以有一个实例属性叫 phone,phone 则是一个 Phone 类的实例,这样通过操作人这个对象的 phone 属性来操作手机浏览网页和打电话。具体的打电话和浏览网页的功能在 Phone 类中,手机可以有打电话、发短信的功能,人本身不具备这个功能。在实例方法中操作手机是 self.phone.send_msg('你好')
,而不要弄成人继承手机,然后用 self.send_msg('你好')
,人不是手机的子类,不要搞成继承,人和手机的关系是 has A。
设计模式,大多是通过组合来变换出来的,继承要少用,除非确定子类是父类的一个更小分类。例如界(Kingdom)、门(Phylum)、纲(Class)、目(Order)、科(Family)、属(Genus)、种(Species),更小的分类是上一级父类的一个子集。
class Phone:
def send_msg(self,msg):
pass
class Person:
def __init__(self):
self.phone = Phone()
pass
def make_msg(self):
pass
def social(self):
msg = self.make_msg()
self.phone.send_msg(msg)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mixin: 扩展类的功能,插件类,男学生和女学生都可以继承一个上课的 mixin 类,成年人可以继承一个打工赚钱的 mixin 类。一般情况下 mixin 类不写__init__
方法,mixin 类里面的有些属性在 mixin 类本身中不具备,所以不可以把 mixin 类单独直接的实例化。mixin 方式可以被组合替代,mixin 类的方法可以直接访问基本类的方法和属性,组合把基本类实例绑定到组合类的实例属性或者把基本类实例作为参数传给其方法,来达到访问基本类的方法和属性。使用场景有,多个类要添加相同的功能,可以去每个类写几个方法,多少个类复制粘贴多少次,重复较多,要修改的时候又要去每个类去修改。代码太长命名空间下方法太多,类变成了上帝类。
mixin 类例如:
class Person():
def __init__(self, name):
self.name = name
def walk(self):
print('行走')
2
3
4
5
6
class StudyMixin():
def study(self):
print(self.name + '...在上课...')
class Student(Person,StudyMixin):
def __init__(self, name,age):
Person.__init__(self,name)
self.age=age
def eat(self):
print(self.name + '...在吃饭...')
Student('小明',10).study()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
这样做小明本来继承自 person 只能走路,但加了 mixin 类,小明可以学习了。StudyMixin 类则不能直接实例化,因为在这个类中,没有 name 这个属性,运行 study 方法会出错。
软件工程中,设计模式是指软件设计问题的推荐方案。设计模式一般是描述如何组织代码和使用最佳实践来解决常见的设计问题。需谨记在心的一点是:设计模式是高层次的方案,并不关注具体的实现细节,比如算法和数据结构。
# 应用检验
- 大部分设计模式,是让你在在面向对象的基础上尽量消除继承的手段。所以,如果你用了一些设计模式,减少了继承,那你八成用对了。如果你用了一大堆设计模式,然而继承却越来越频繁,那你 100%用错了。 之所以说大部分,是因为个别设计模式(比如享元模式)是为了解决特殊场景特殊问题而生的。
- 一个设计合理的系统,因为解耦充分,各个模块独立性强,所以单元测试应该是比较容易写的。如果你用了一大堆设计模式,却发现给你写的类编写单元测试用例非常困难,那你一定是用错了。
- 多态的本质是运行期动态决定程序的分支走向,也就是“更好的 if”,而设计模式,至少是《设计模式》那本书中提到的那些模式,基本上是基于多态的。所以如果你合理的利用设计模式,你设计出的代码应该有较少的 if,如果你的代码越使用设计模式 if 越多,或者更直观地说,缩进越多,你一定犯了错误。
- 有些模式是很容易用错的,比如 visitor 模式,其实是为了解决 java 不支持 double dispatch 而存在的,然而其逻辑很晦涩。所以当你还在怀疑自己是否用对了设计模式的时候,你不应该使用这样的模式。
- 类继承总的来说只有 1.5 种正确的打开方式:
第一种叫做模板方法(template method),是设计模式之一。这种模式说的是基类在一个抽象的层面实现了公有方法 a,a 依赖私有方法 b,而 b 的实现是子类的工作。这种模式下,子类实现的虚方法 b 是不应该被外界调用的。当然有一种极端情况,b 即使 a 本身(比如 react 中的 render),此时模板方法退化为普通的多态。
第二种叫做 mixin,类 a 通过继承一个基类 b,获得某种相对独立的能力。然而这其实是一种不太好的设计,尤其是在需要 mixin 多种能力的时候,更合理的方式其实是在 a 内部创建 b 的实例。只是在比较简单的场景中,我们可以用继承糊弄一下,所以只能算 0.5 种。
除此之外,使用类继承基本上可以认为是错误的设计。
由此我们可以推论,在 es6 流行导致运行时 mixin 写法在前端不流行之后,前端代码中出现了一些新的设计错误。
- 设计模式是一回事,设计模式的实现是另一回事,这一点很重要。比如著名的观察者模式(observer),在 java 中有大量应用。然而我们常见的观察者模式样板代码之所以长那个样,是因为 java 中缺乏事件订阅系统。在 c++/c#以及 js 中实现观察者模式,就没必要那样写。因为 c++有 std:function,c#有委托,而 js 函数干脆就是对象。
# 设计模式分类
《设计模式》一书介绍了 23 种设计模式:
模式英文名 | 模式中文名 |
---|---|
Abstract Factory | 抽象工厂模式 |
Adapter | 适配器模式 |
Bridge | 桥接模式 |
Builder | 建造者模式 |
Chain of Responsibility | 责任链模式 |
Command | 命令模式 |
Composite | 组合模式 |
Decorator | 装饰器模式 |
Facade | 外观模式 |
Factory Method | 工厂方法模式 |
Flyweight | 享元模式 |
Interpreter | 解释器模式 |
Iterator | 迭代器模式 |
Mediator | 中介者模式 |
Memento | 备忘录模式 |
Observer | 观察者模式 |
Prototype | 原型模式 |
Proxy | 代理模式 |
Singleton | 单例模式 |
State | 状态模式 |
Strategy | 策略模式 |
Template Method | 模板方法模式 |
Visitor | 访问者模式 |
# 创建型模式
提供创建对象的机制, 增加已有代码的灵活性和可复用性。
设计模式 -创建型模式 ,工厂模式 、抽象工厂模式 (opens new window)
设计模式- 创建型模式, 建造者模式 (opens new window)
设计模式-创建型模式,原型模式 (opens new window)
设计模式-创建型模式,python 享元模式 、python 单例模式 (opens new window)
设计模式,python 延迟计算缓存模式 (opens new window)
python 对象池模式 (opens new window)
# 结构型模式
介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
设计模式-创结构型模式,python 桥接模式 (opens new window)
设计模式-结构型模式,适配器模式(4) (opens new window)
设计模式-结构型模式,python 组合模式 (opens new window)
# 行为型模式
设计模式-行为型模式,python 中介者模式 (opens new window)
设计模式-行为型模式,python 备忘录模式 (opens new window)
设计模式-行为型模式,python 访问者模式 (opens new window)
python 迭代器模式 (opens new window)
# python 独有模式
python 设计模式之猴子补丁模式 (opens new window)
# 设计模式原则
提到设计模式,不可避免地要谈谈设计模式的六大原则:
- 单一职责原则
- 里式替换原则
- 依赖倒置原则
- 接口隔离原则
- 迪米特法则
- 开闭原则
# 单一职责原则(Single Responsibility Principle, SRP)
定义:不要存在多于一个导致类变更的原因。
其实不光是类,对于接口、类和方法都要遵循单一职责原则。单一职责原则从字面上很好理解,一个接口、类和方法只做一件事。
单一职责原则(SRP)表明一个类有且只有一个职责。一个类就像容器一样,它能添加任意数量的属性、方法等。然而,如果你试图让一个类实现太多,很快这个类就会变得笨重。任意小的改变都将导致这个单一类的变化。当你改了这个类,你将需要重新测试一遍。如果你遵守 SRP,你的类将变得简洁和灵活。每一个类将负责单一的问题、任务或者它关注的点,这种方式你只需要改变相应的类,只有这个类需要再次测试。SRP 核心是把整个问题分为小部分,并且每个小部分都将通过一个单独的类负责。
假设你在构建一个应用程序,其中有个模块是根据条件搜索顾客并以 Excel 形式导出。随着业务的发展,搜索条件会不断增加,导出数据的分类也会不断增加。如果此时将搜索与数据导出功能放在同一个类中,势必会变的笨重起来,即使是微小的改动,也可能影响其他功能。所以根据单一职责原则,一个类只有一个职责,故创建两个单独的类,分别处理搜索以及导出数据
遵循单一职责原则有如下好处:
- 每个接口、类和方法只承担一项职责,意义更清晰。
- 使代码可读性、可维护性和可扩展性更佳。
- 当需求有变化需要修改代码时,只需要修改相应功能的代码,不会对其他功能的代码造成影响。
# 里式替换原则(Liskov Substitution Principle, LSP)
定义:如果对每一个类型为 S 的对象 o1,都有类型为 T 的对象 o2,使得以 T 定义的所有程序 P 在所有的对象 o1 代换 o2 时,程序 P 的行为没有变化,那么类型 S 是类型 T 的子类型。
上面的定义是严格的定义,比较通俗的定义是:所有引用基类(父类)的地方必须能透明地使用其子类的对象。
里氏替换原则指出,派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类一定可以出现。值得注意的是,当你通过继承实现多态行为时,如果派生类没有遵守 LSP,可能会让系统引发异常。所以请谨慎使用继承,只有确定是“is-a”的关系时才使用继承。
里式替换原则包含如下几个含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类已经实现的方法(即非抽象方法)。
- 子类可以增加自己特有的方法。
- 当子类重载父类的方法时,方法的前置条件(入参)要比父类更宽松。
- 当子类实现父类的抽象方法时,方法的后置条件(出参)要比父类更严格。
# 依赖倒置原则(Dependence Inversion Principle, DIP)
定义:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象。
- 抽象不应该依赖细节。
- 细节应该依赖抽象。
依赖倒置原则(DIP)表明高层模块不应该依赖低层模块,相反,他们应该依赖抽象类或者接口。这意味着你不应该在高层模块中使用具体的低层模块。因为这样的话,高层模块变得紧耦合低层模块。如果明天,你改变了低层模块,那么高层模块也会被修改。根据 DIP 原则,高层模块应该依赖抽象(以抽象类或者接口的形式),低层模块也是如此。通过面向接口(抽象类)编程,紧耦合被移除。
那么什么是高层模块,什么是低层模块呢?通常情况下,我们会在一个类(高层模块)的内部实例化它依赖的对象(低层模块),这样势必造成两者的紧耦合,任何依赖对象的改变都将引起类的改变。
设想一个情景,工厂生产零件,其中有工厂类 Factory、螺丝类 Screw、钉子类 Nail 等。如果 Factory 直接依赖于某个零件类,当需要生产新的零件时,势必要修改 Factory,这样一来 Factory 和各个零件类的耦合就非常大。为了解决这个问题,我们可以引入一个接口IProduce
,所有零件类都实现这个接口,Factory 直接使用 IProduce 接口来进行生产,之后如果有新的零件类加入进来,只需要实现 IProduce 接口,Factory 不需要做任何修改。
这个例子中,Factory 类是高层模块,具体的零件类是低层模块,引入的 IProduce 接口就是抽象,其中,Factory 使用 IProduce 接口生产零件,具体零件类实现了 IProduce 接口,这满足定义中的第一点。同时 IProduce 接口不依赖于具体零件类,它是一个独立的存在,这满足定义中的第二点。各个具体的零件类实现依赖于 IProduce 接口,这满足定义中的第三点。
依赖导致原则的好处非常明显,它降低了模块间的耦合性,提高系统的稳定性、灵活性、可维护性和可扩展性,降低了修改代码的风险和成本。
# 接口隔离原则(Interface Segregation Principle, ISP)
定义:
- 客户端不应该依赖它不需要的接口。
- 一个类对另一个类的依赖应该建立在最小的接口上。
假设有一个接口 I,定义了一些方法,类 A 通过接口 I 依赖于类 C,类 B 通过接口 I 依赖于类,但类 Ahead 类 B 都只依赖接口 I 的其中一部分方法。也就是说,接口 I 不是类 A 和类 B 的最小接口。这种情况下,类 C 和类 D 为了实现接口,就要实现那些类 A 和类 B 不需要的方法,如果后续还对接口 I 添加方法或还有其他类依赖于接口 I,就会造成接口 I 过于臃肿,这是不好的设计。
解决方案是遵循接口隔离原则,该原则实际上是指导我们面向接口编程。应该分析类 A 和类 B 对接口的需求,将接口 I 拆分成多个小接口,客户类和实现类根据拆分后的接口做调整。
接口隔离原则(ISP)表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为,它是精简的,也是单一的。
接口隔离原则要注意以下几点:
- 拆分和细化接口,不要让接口过于臃肿,另外也要注意接口细化的粒度,拆分地太细会导致复杂度升高,要适度拆分。
- 接口即契约,对一个模块制定接口时,只暴露出需要的功能,接口中不应有多余的功能存在。
- 设计模块时,要尽量提高模块的内聚性,尽量减少模块对外的依赖关系。
# 迪米特法则(Least Knowledge Principle, LKP, 最小知道原则)
定义:一个对象应该对其他对象保持最少的了解。
迪米特法则又叫作最少知道原则。软件工程提倡模块化,一个模块要低耦合,高内聚。当两个类彼此之间互相了解得越多,它们的耦合就越大,当其中一个类变化时,另一个类受的影响就越大。迪米特法则说的就是要尽量降低类与类之间的耦合。
类和类之间完全没有耦合是不可能的,如果完全不耦合,程序就没办法工作了。但是必须要保证是适当的耦合度,。个类互相耦合时,最好是将这个耦合度控制在类级别或方法级别,应避免互相去了解类的实现细节。
# 开闭原则(Open Closed Principle, OCP)
定义:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开放封闭原则(OCP)指出,一个类应该对扩展开放,对修改关闭。这意味一旦你创建了一个类并且应用程序的其他部分开始使用它,你不应该修改它。为什么呢?因为如果你改变它,很可能你的改变会引发系统的崩溃。如果你需要一些额外功能,你应该扩展这个类而不是修改它。使用这种方式,现有系统不会看到任何新变化的影响。同时,你只需要测试新创建的类。
开闭原则告诉我们当软件的需求有变化时,应尽量通过扩展软件的功能来响应变化,而不是通过修改已有的代码来响应变化。实际上开闭原则是其他五个原则的归纳汇总,其他五个原则都在教我们怎么做,其中都在阐述开闭原则这个核心思想。
开闭原则给我们的启示是:用抽象构建功能框架,用实现扩展功能细节。这是构建高可维护性、高可扩展性、高灵活性和高稳定性系统的最佳方式。
I might say that I have a visitor pattern, but in any language with first class functions it will be just a function taking a function. Instead of factory class I usually have just a factory function. I might say I have an interface, but then it's just a couple of methods marked with comments, because there wouldn't be any other implementation (of course in python an interface is always just comments, because it's duck-typed). I still speak of the code as using the pattern, because it's a useful way to think about it, but don't actually type in all the stuff until I really need it.
# 资源及其他
为保证对模式没有误解和误判,代码例子是用的从精通 python 16 种设计模式那本书中的,有的用的网上的。