Python设计模式
世界是充满套路的,编程也不例外:主要的两大套路分类是算法/数据结构和设计模式/原则。来总结下设计模式吧
Python设计模式
(个人理解)传统的设计模式是为了解决C++/Java在面对组织大量代码的经验汇总,以及弥补一些语言语法本身的缺陷。而之后出现的编程语言从发明之初就解决了C++/Java的一部分问题。很多设计模式在Python中是语法的一部分。所以对于Python来说设计模式非常的弱化(这就是你工作三年还没系统学过设计模式的理由? →_→)
不过既然是前人套路的总结,了解下还是能扩宽自己的思路的。更多的还是为了方便的和其他工程师聊天吧(说模式的名字一脸懵逼,了解了实现方式后才知道都是自己做过的东西)
这篇文章是基于《Python编程实战:运用设计模式、并发和程序库创建高质量程序》设计模式部分的总结,结合了一些其他地方的资料。
对于初学者来说,遵照KISS和DRY原则就足够了。没有写过大量代码就去学习怎么组织大量代码的方法是事倍功半的。
设计模式分类
- 创建型模式
- 结构型模式
- 行为型模式
创建型模式
创建型模式提供了一些创建对象的套路
工厂模式
总觉得《Python编程实战》的代码diagram*.py有点问题:工厂方法create_diagram接受的参数是产品类,而不是通过参数判断实例化哪个产品类。那要你有何用?
关于简单工厂/工厂/抽象工厂模式的区别见参考资料一
举个栗子,健身房管理SaaS很常见的一个需求是根据用户提供的excel表格向系统中导入数据。excel表格的格式有很多(只讨论文件格式,内容格式是规定好的):xls,xlsx,csv等等。都是要从表格中获取数据,但是不同文件格式要调用不同的库来处理。所以我们有了三种表格的操作类
class BaseSheet(object):
def open(self, file_path):
raise NotImplementedError
def save(self, data):
raise NotImplementedError
class XLSSheet(BaseSheet):
def open(self, file_path):
# 调用xlrd实现open方法
pass
def save(self, data):
# 调用xlwt实现save方法
pass
class XLSXSheet(BaseSheet):
# 调用openpyxl实现open和save方法
pass
class CSVSheet(BaseSheet):
# 调用csv实现open和save方法
pass
工厂模式就是实现一个工厂类(方法),用于调用不同的Sheet类(实例化对象,调用类/对象的方法等)
class SheetFactory(object):
SHEET_DICT = {
'xls': XLSSheet,
'xlsx': XLSXSheet,
'csv': CSVSheet,
}
@classmethod
def get_sheet(cls, extensions)
if extensions not in cls.SHEET_DICT:
raise Exception('不支持的文件类型')
return cls.SHEET_DICT[extensions]
建造者模式
如果一个对象实例化非常的复杂(需要很多的参数,调用很多方法才能成功的实例化这个对象)。那么可以实现一个建造者类(方法)来实例化这个对象
原型模式
如何复制一个对象?deepcopy
走起啊
《Python编程实战》总结了几种对象实例化的方法(有些东西看看就好,不要去学)
class Point:
__slots__ = ('x', 'y')
def __init__(x, y):
self.x = x
self.y = y
def make_object(Class, *args, **kwargs):
return Class(*args, **kwargs)
point1 = Point(1, 2)
point2 = eval('%s(%d, %d)' % ('Point', 2, 4)) # Risky
point3 = getattr(sys.modules[__name__], "Point")(3, 6)
point4 = globals()['Points'](4, 8)
point5 = make_object(Point, 5, 10)
point6 = copy.deepcopy(point5)
point6.x = 6
point6.y = 12
point7 = point1.__class__(7, 16)
单例模式
单例模式可以很方便的管理全局唯一的状态或者全局变量,Python怎么实现单例也是个很经典的问题了。还衍生出了Brog模式
基于元类的单例实现
# 基类定义
class SingletonMetaclass(type):
def __init__(self, *args, **kwargs):
self.__instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super(SingletonMetaclass, self).__call__(*args, **kwargs)
return self.__instance
else:
return self.__instance
# 单例类
class Foo(metaclass=SingletonMetaclass):
"""注意: 根据Singleton的定义, 构造函数一般需要使用默认的构造函数"""
def get_addr(self):
return id(self)
修改__new__
方法实现单例
class Singleton(object):
__instance = None
def __new__(cls, *args, **kw):
if not cls.__instance:
cls.__instance = super().__new__(cls, *args, **kw)
return cls.__instance
class Foo(Singleton):
pass
一个实现方法类似的装饰器
# singleton装饰器
def singleton(cls, *args, **kw):
instances = {}
def _singleton():
if cls not in instances:
instances[cls] = cls(*args, **kw)
return instances[cls]
return _singleton
# 单例类
@singleton
class Foo(object):
pass
定义完类直接实例化,要啥自行车
class _MySingleton(object):
"""
我们使用下划线开头, 告诫调用者, 不要直接 new
也不要来访问这个class
"""
def __init__(self, name, age):
self._name = name
self._age = age
def print_name(self):
print(self._name)
# 可以定制多个全局实例
S1 = _MySingleton('s1', 22)
S2 = _MySingleton('s2', 11)
# 之后只需要import S1, S2就好了
最后说下Brog模式: python中对象的所有属性存放在__dict__
中,直接更改它来实现单例
class Borg(object):
__shared_state = {}
def __init__(self, *args, **kwargs):
self.__dict__ = self.__shared_state
# 另一个装饰器的版本
def borg(cls):
cls.__shared_state = {}
orig_init = cls.__init__
def new_init(self, *args, **kwargs):
self.__dict__ = cls.__shared_state
orig_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
@borg
class TestBorg(object):
def say_borg(self):
print "i am borg"
结构型模式
结构型模式提供了一些继承重写对象,合并分拆对象的套路
适配器模式
前面说到的Excel操作类。如果我们已经有了一个OldXLSSheet
类能完成Excel文件操作,但是它的方法并不是open
,save
,无法直接被使用。
我们可以继承这个类,用父类的其他方法来实现我们想要的open
,save
方法。
桥接模式
还是前面说到的表格操作类,工厂模式关注的是类的创建;桥接模式关注的是在其他的类里面可以调用任何一种表格操作类的open
,save
方法,而不用关心调用的是哪个表格操作类以及它们的具体实现(毕竟实现了一样的方法/接口)
组合模式
组合模式是一个对象可以把多个对象组合在一起,可以增加,移除,遍历子对象(类似二叉树?)
对于Python来说,Dict对象是天然的组合模式(感觉并没有什么用的样子)
装饰器模式(略)
外观模式
如果一个类的方法过于晦涩难用,或者缺乏安全性检查,或者输出不友好:可以再写几个好用的方法来取代它们(和适配器模式做的事情一样,目的不同)
享元模式
一些很小而且功能相同的对象,可以只给每个类实例化一个对象,然后共享它(似乎想到了单例的某种实现?
代理模式
某个对象在一个无法触及到的地方(另一个项目?)想要调用它的方法和数据。需要一个本地的Proxy对象,通过网络调用实现目的。RPC的既视感~
代理模式也可以用作单元测试,所需要的类尚未开发完毕,缺失很多方法的时候。“模拟对象”可以提供所有的方法/功能,用桩(stub)来表示那些缺失的功能。
书上还举了一个ImageProxy
类的例子。Image
类提供了各种在画布上绘制图形的方法。而ImageProxy
的对应方法只会把操作记下来,并不会直接执行。直到调用保存方法的时候,才会去依次执行绘制方法并保存。lazy loading
行为型模式
行为型模式提供了组织程序运行过程的套路
责任链模式
一个事件可以被很多事件处理程序类来处理。每个时间处理程序类只需要关注自己属否处理,怎么处理这个事件;以及是否交给后面的程序类继续处理这个事件。
最常见的责任链模式应该就是管道(pipeline)了吧
命令模式
类似于GUI软件,支持保持做过的每一个操作,撤销和重试,延迟执行,以及宏机制。之前的ImageProxy
已经有一点这个意思了。
解释器模式
自己写一个DSL的样子。。。略过吧
迭代器模式
参考Python标准的迭代器
中介者模式
又是个很适合GUI的模式:两组互相通讯的类不需要知道对方的实现,只通过中间层进行通讯(是不是很像代理模式?
备忘录模式
备忘录模式可以在不破坏封装的前提下把一个对象保存和读取(序列化和反序列化)
观察者模式
有一目标物(subject,即observable),有众多观察者(observers)关注其异动;
目标物维护观察者列表,故若要新增观察者,则需向此目标物注册该新观察者;
当目标物有状态变化时,会主动通知在其注册的所有观察者,方式为:以for循环调用所有观察者的notify()方法;故所有观察者都需要有notify()方法
然后抽象出观察者和目标基类
状态模式
在调用对象的同一个方法时,这个方法会根据对象中某个属性的值完成不同的行为(也可能根据属性的值调用不同的方法
策略模式 && 模版方法模式
策略模式能把一系列“可互换”的算法封装起来,并根据用户需求来选择使用哪一种
模版方法模式定义了一个算法步骤,其中一些步骤依赖子类的实现。可以通过调用不同的子类还完成不同的实现方式
怎么看都是同一个需求不同层面上的实现
访问者模式
对于一组固定的数据,可以使用不同的访问者类来对这些数据进行不同的操作。目标是不耦合数据和操作
感想
好像一开始看书还会把每个模式的代码实现都写下,后面的越看越觉得无聊就不想写了(虽然确实扩宽了思路)。可能是很多设计模式更适合解决客户端开发遇到的问题而我日常开发并没有遇到这些问题,或是Python设计之初就内置了很多设计模式让我觉得这些东西都是理所当然的。
当然以上对设计模式的理解都是非常粗浅的。还需要多看一些他人多心得和代码。不过作为对组织代码的指导来说,设计原则(类似KISS,SOLID)还是更加普适一些