引言:Python 设计模式
最全 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、输出最后结果。
把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:
1、黑白双方,这两方的行为是一模一样的,
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 + '...在吃饭...'
2
3
4
5
6
7
8
9
10
11
12
13
Student('小明',10).study()
这样做小明本来继承自 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 函数干脆就是对象。
ref: 编辑于 2020-03-04 02:10 (opens new window)
# 设计模式分类
# 创建型模式
提供创建对象的机制, 增加已有代码的灵活性和可复用性。
设计模式 -创建型模式 ,工厂模式 、抽象工厂模式 (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)
为保证对模式没有误解和误判,代码例子是用的从精通 python 16 种设计模式那本书中的,有的用的网上的。
一个在线的 http://www.pythontip.com/pythonPatterns/ (opens new window)
github 的 https://github.com/faif/python-patterns (opens new window)
菜鸟教程的 http://www.runoob.com/design-pattern/design-pattern-tutorial.html (opens new window)
反对极端面向过程编程思维方式,喜欢面向对象和设计模式的解读,喜欢对比极端面向过程编程和 oop 编程消耗代码代码行数的区别和原因。致力于使用 oop 和 36 种设计模式写出最高可复用的框架级代码和使用最少的代码行数完成任务,致力于使用 oop 和设计模式来使部分代码减少 90%行,使绝大部分 py 文件最低减少 50%-80%行的写法。