Python设计模式
1. The Fctory Pattern(工厂模式: 解决对象创建问题)
先来看三种创建模式中的第一种工厂模式。 解释:处理对象创建,客户端可以申请一个对象而不用知道对象被哪个class创建。可以方便地解耦对象的使用和创建。有两种实现,工厂方法和抽象工厂.
Method(工厂方法):
执行单独的函数,通过传参提供需要的对象的信息。通过一个demo看看例子:
1 | import json |
Abstract Factory(抽象工厂: 解决复杂对象创建问题):
工厂方法适合对象种类较少的情况,如果有多种不同类型对象需要创建,使用抽象工厂模式。以实现一个游戏的例子说明,在一个抽象工厂类里实现多个关联对象的创建:
1 | class Frog: |
2: The Builder Pattern(构造模式: 控制复杂对象的构造)
当对象需要多个部分组合起来一步步创建,并且创建和表示分离的时候。可以这么理解,你要买电脑,工厂模式直接返回一个你需要型号的电脑,但是构造模式允许你自定义电脑各种配置类型,组装完成后给你。这个过程你可以传入builder从而自定义创建的方式。
1 | # factory pattern |
3:The Prototype Pattern(原型模式:解决对象拷贝问题)
这是创建模式中的最后一个,用来克隆一个对象,有点像生物学中的有丝分裂。我们可以使用python内置的copy模块实现。拷贝分为深拷贝和浅拷贝,深拷贝会递归复制并创建新对象,而浅拷贝会利用引用指向同一个对象.深拷贝的优点是对象之间互不影响,但是会耗费资源,创建比较耗时;如果不会修改对象可以使用浅拷贝,更加节省资源和创建时间。
- “A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
- A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.”
1 | import copy |
4: The Adapter Pattern(适配器模式: 解决接口不兼容问题)
开始介绍结构型设计模式,结构型设计模式通过组合对象来实现新功能。适配器模式通过引入间接层来实现不兼容接口之间的适配。现实中最好的例子就是手机充电口,不同型号安卓手机都可以用同样的充电线充电。在python中可以通过继承实现适配,也可以通过使用class的dict属性。 开闭原则:适配器模式和OOP中的开闭原则关系密切,开闭原则强调对扩展开放,对修改关闭。通过适配器模式我们可以通过创建适配器模式在不修改原有类代码的情况下实现新的功能。
1 | class Computer: |
5: The Decorator Pattern(装饰器模式: 无需子类化实现扩展对象功能问题)
通常给一个对象添加新功能有三种方式: - 直接给对象所属的类添加方法。 - 使用『组合』 - 使用『继承』,优先使用组合而非继承。 装饰器模式提供了第四种选择,通过动态改变对象扩展对象功能。其他编程语言通常使用继承实现装饰器装饰器模式,而python内置了装饰器。装饰器有很多用途,比如数据校验,事务处理,缓存,日志等。比如用装饰器实现一个简单的缓存,python3.5自带了functools.lru_cache
1 | from functools import wraps |
6: The Facade Pattern(外观模式: 简化复杂对象的访问问题)
外观模式用来简化复杂系统的访问。通过简化的接口只访问需要的部分,隐藏系统复杂性。想象一下公司接线员,虽然公司内部运行机制比较复杂,但是接线员可以迅速帮你解决特定问题。 我们以实现个简单的操作系统示例说明外观模式:
1 | from abc import ABCMeta, abstractmethod |
7: The Flyweight Pattern(享元模式: 实现对象复用从而改善资源使用)
Flyweight design pattern is a technique used to minimize memory usage and improve performance by introducing data sharing between similar objects.
OOP编程中容易出现对象创建带来的性能和内存占用问题,需要满足以下条件:
- 需要使用大量对象(python里我们可以用slots节省内存占用)
- 对象太多难以存储或解析大量对象。
- 对象识别不是特别重要,共享对象中对象比较会失败。
- 经常使用对象池技术来实现共享对象,比如数据库中经常使用连接池来减少开销,预先建立一些连接池,每次取一个连接和数据库交互。
1 | # 使用对象池技术实现享元模式 |
8: The Model-View-Controller Pattern(mvc模式:解耦展示逻辑和业务逻辑)
When using MVC, make sure that you creating smart models (core functionality), thin controllers (functionality required for the communication between the view and the controller), and dumb views (representation and minimal processing).
MVC模式既是一种设计模式,也是软件架构模式。比如流行的django框架就是mvc(MTV)模式。Model层负责和数据库交互,View层负责展现逻辑,Controller层负责粘合Model和View层,将各个部分解耦,使代码更易扩展和维护。
1 | quotes = ('A man is not complete until he is married. Then he is finished.', |
9: The Proxy Pattern(代理模式:通过一层间接保护层实现更安全的接口访问)
在访问真正的对象之前做一些操作。有四种常用的代理类型:
- A remote proxy.使得访问远程对象就像本地访问一样,例如网络服务器。隐藏复杂性,使得访问本地远程统一。比如ORM
- A virtual proxy。用来实现延迟访问,比如一些需要复杂计算的对象,python里可以实现lazy_property,性能改善
- A protection/protective proxy. 控制敏感对象的访问,加上一层保护层,实现安全控制
- A smart(reference) proxy. 访问对象时加上一层额外操作,例如引用计数和线程安全检查。weakref.proxy()
代理模式的功能还是很强大的,先来看看使用描述符实现LazyProperty,在对象创建以后第一次访问才会真正生成
1 | class LazyProperty: |
再看那个用代理实现安全控制的例子,我们给SensitiveInfo里的add操作加上密钥验证,例子也很简单
1 | class SensitiveInfo: |
上面这个示例有几个缺点 1. SensitiveInfo可以被直接实例化使用,绕过Info类,可以考虑使用abc模块避免SensitiveInfo被直接实例化 2. 密钥直接写死在代码里,应该用安全性较高密钥写到配置或者环境变量里
我们使用抽象基类来修正第一个缺陷,只需要修正类代码而不用改main函数里的使用代码
1 | from abc import ABCMeta, abstractmethod |
10: The Chain of Responsibility Pattern (责任链模式:创建链式对象用来接收广播消息)
The Chain of Responsibility pattern is used when we want to give a chance to multiple objects to satisfy a single request, or when we don’t know which object (from a chain of objects) should process a specific request in advance.
开始介绍行为型设计模式,行为型设计模式处理对象之间的交互和算法问题。在责任连模式中,我们把消息发送给一系列对象的首个节点,对象可以选择处理消息或者向下一个对象传递,只有对消息感兴趣的节点处理。用来解耦发送者和接收者。在python里通过dynamic dispatching来实现,以一个事件驱动系统来说明:
1 | class Event: |
11: The Command Pattern(命令模式:用来给应用添加Undo操作)
命令模式帮助我们把一个操作(undo,redo,copy,paste等)封装成一个对象,通常是创建一个包含Operation所有逻辑和方法的类。 通过命令模式可以控制命令的执行时间和过程,还可以用来组织事务。 用一些文件操作类来说明命令模式的使用
1 | import os |
12: The Interpreter Pattern(解释器模式:用来实现Domain Specific Language(DSL))
本章我们实现一个简单的控制大门Gate类的DSL。使用pyparsing来解析我们定义的控制大门的语法命令。 pyparsing自带了很多有用的函数和类帮助我们从文本中抽取需要的信息,比如我们方便地处理c++源文件中的注释:(pip install pyparsing)
1 | >> text = '// Look out of the yard? What will we see?' |
//TODO
13: The Observer Pattern(发布订阅模式:用来处理多个对象之间的发布订阅问题)
如果用过blinker库或者redis的pub,sub,对发布订阅应该会比较熟悉。该模式用在当一个对象的状态变更需要通知其他很多对象的时候,比如rss订阅或者在社交网站上订阅某个频道的更新。事件驱动系统也是一种发布订阅模式,事件作为发布者,监听器作为订阅者,只不过这里多个事件监听器可以监听同一个事件。 我们这里实现一个”Data Formatter”来解释发布订阅模式,一种数据可以有多个格式化Formatter,当数据更新的时候,会通知所有的Formatter格式化新的数据。使用继承来实现。
1 | class Publisher: |
14: The State Pattern(状态模式:实现有限状态机)
A state machine is an abstract machine that has two key components: states and transitions. A state is the current (active) status of a system. A transition is the switch from one state to another. A state meachine has only one active state at a specific point in the time.
我们通过实现操作系统中进程的生命周期来演示状态模式的使用:
1 | # 先装下pip3 install state_machine |
15: The Strategy Pattern(策略模式:动态选择算法策略)
现实中往往解决问题的方式不止一种,我们可能需要根据问题的特征选择最优的实现策略,以排序算法为例子,挑选一个合适的排序算法的时候,需要考虑待排序数组的以下特征:
- 元素个数。算法输入规模,大部分排序算法在输入规模很小的时候效率相差不大,只有一部分nlogn平均时间复杂度的适合排序大规模。
- 最好/平均/最坏时间复杂度.这个往往是挑选排序算法时候优先考虑的。
- 空间复杂度。是不是原地排序(inplace),需要额外的空间吗?在内存限制苛刻的时候就需要考虑
- 稳定性。排序算法是稳定的吗?稳定是指相同大小的值排序后保持相对顺序。
- 实现复杂度。算法是否容易实现,其他大致相同的情况下,优先考虑易维护的代码。
策略模式允许我们根据待处理数据的特征灵活选用当前特征下最优的实现,比如常见库的排序算法一般都是混合了多种排序算法的实现,python使用的是Tim Peters在2002年设计的结合了合并排序和插入排序的Timsort. 函数在python里是一等公民,可以简化策略模式的实现。
1 | def f1(seq): |
16: The Template Pattern(模板模式:抽象出算法公共部分从而实现代码复用)
Don’t repeat yourself.
模板模式中,我们可以把代码中重复的部分抽出来作为一个新的函数,把可变的部分作为函数参数,从而消除代码冗余。实际上这种模式在代码重构的时候是经常使用的 ,使用一个有意思的例子来说明下,请安装pip install cowpy,真有人闲的*疼写这个玩意
1 | from cowpy import cow |
单例模式: 使得一个类最多生成一个实例。
Design ptterns are discoverd, not invented. - Alex Martelli
很奇怪,本书讲完了都没有讲到单例模式。python的单例模式有各种实现,元类、装饰器等,但是还有一种说法:
I don’t really see the need, as a module with functions (and not a class) would serve well as a singleton. All its variables would be bound to the module, which could not be instantiated repeatedly anyway. If you do wish to use a class, there is no way of creating private classes or private constructors in Python, so you can’t protect against multiple instantiations, other than just via convention in use of your API. I would still just put methods in a module, and consider the module as the singleton.
也就是说,实际上,python中,如果我们只用一个实例,直接这么写就行
1 | # some module.py |
其他实现:
1 | # http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python |