文章目录
Python设计模式
1.0 前言
得益于Python的鸭子类型设计原则,Python相比于 C++ 和 Java 在实现设计模式上更加灵活,甚至可以轻松的将多种设计模式组合出更加强大的组合模式。
文章不会依照常见书籍的做法实现设计模式,而是编写出更加Pythonic
的设计模式。
主要包括如下几个主题:
- 设计模式——元素
- 设计模式分类
- Python模式——创建模式
- 单例模式
- 工厂模式
- 原型模式
- 建造者模式
- Python模式——结构化模式
- 适配器模式
- 外观模式
- 代理模式
- Python模式——行为模式
- 迭代器模式
- 观察者模式
- 状态模式
1.1 设计模式——元素
首先了解下设计模式的通用元素
设计模式是用来解决面向对象系统中一个或一类问题的重复设计。
几乎所有的设计模式都有以下元素:
-
名字(name):描述模式的标题。
比如
观察者模式
,一个好的标题可以让人见名知意。 -
上下文(context):问题出现的情景。
前边说了设计模式是解决一类问题的,既然是问题肯定就会有遇到该问题的情景,就是上下文。
-
问题(problem):需要解决的问题。
可以从以下几个方面描述一个问题:
- 需求(requirement):解决方案要满足的需求。比如,发布者 - 订阅者模式的实现必须支持HTTP。
- 约束(constraint):解决方案的约束(如果有)。比如,可扩展的对等发布者模式不应该交换三个以上的消息来发布通知。
- 性质(property):解决方案想要的一些性质。比如,该解决方案要在不同的操作系统通用。
-
解决方案(solution):问题的实际解决方案。
包括元素的结构和职责、静态关系以及运行时的交互(协作)。
还包括解决问题的程度,和没有解决的程度,还应当尽量提及它的后果,即应用一个设计模式的结果和权衡。
注:一种设计模式几乎不可能完全解决导致问题的所有因素,但却让其中一些因素对相关或替代的实现开放。
1.2 设计模式分类
根据解决问题的类型,设计模式被划分为三种,当然随着语言的发展,以后可能会出现新的解决新的问题的方法,这些方法被重复使用,进而出现新的设计模式种类:
- 创建模式:主要解决对象
创建和初始化
相关的问题。- 工厂模式:确保以可重复和可预测的方式创建相关类的实例。
- 原型模式:通过复制对象来创建相似的对象。
- 单例模式:类实例化时只被创建和初始化一次。
- 相关模式:确保一个类的任何实例能够共享相同的初始状态。
- 结构化模式:将对象往有意义的结构中
组合和组装
,提供可重复使用的行为。- 代理模式:通过上层行为的包装层(wrapper)控制对一个对象及其方法的访问。
- 组合模式:使用一个类表示一个由许多组件同时组成的对象。
- 行为模式:解决由对象的
运行时
交互引起的问题,以及它们如何分配责任的问题。- 中介者模式:确保所有对象在运行时使用松耦合方式来相互引用。
- 观察者模式:当资源的状态发生变化时,对象希望被通知,但是不希望保持轮询资源来发现改变(系统中可能有很多这样的对象实例)。
设计模式为什么被划分为三种?
-
我们以面向对象的思维思考一下一个系统的生命周期:
-
初期:系统中的各个对象被初始化创建,这个时候会出现需要一些定制化的创建:比如说重复创建问题,对象共享状态等问题。
此时就出现了创建模式,解决对象初始化创建的问题。
-
中期:当各个对象被创建出来后,会出现对象过多或者过于复杂、独立单一等问题,导致难以利用这些对象去构成系统中的组织。
此时就出现了结构化模式,去解决对象组合和组装,提供系统使用的组织行为的问题。
-
后期:当有了系统的组织行为后,系统基本上可以运行,但是此时又出现了新的问题:组织行为越来越多,并且行为之间的交互也越来越麻烦。
此时就出现了行为模式,解决对象的运行时交互引起的问题,以及它们如何分配责任的问题
-
注:可以发现创建模式、结构化模式和行为模式的顺序隐式地嵌入在一个系统运行时对象的生命周期中。对象首先被创建,然后被组合成有用的结构,再然后它们进行交互。
1.3 Python模式——创建模式
单例模式
要求
- 一个类必须只有一个实例,该实例可以通过一个公开的访问点访问。
- 该类在继承扩展后任然符合单例模式。
实现
- 重写Python的魔法方法
__new__
可以实现单例模式:
def test_single(cls):
"""测试是否为单例类"""
return cls() == cls()
class Singleton:
"""单例类"""
_instance = None
def __new__(cls):
if cls._instance == None:
cls._instance = object.__new__(cls)
return cls._instance
if __name__ == "__main__":
print(test_single(Singleton)) # true
继承Singleton
类的类也符合单例模式:
class SingletonA(Singleton):
"""继承单例类的类也是单例类"""
pass
if __name__ == "__main__":
print(test_single(SingletonA)) # true
- 定义一个单例元类,重写该类的
__call__
方法,也可以实现单例类。
class MetaSingleton(type):
"""单例元类, 继承该类的所有类都是单例类"""
def __init__(cls, *args):
print(cls, "__init__ method called with args", args)
type.__init__(cls, *args)
cls.instance = None
def __call__(cls, *args, **kwargs):
if not cls.instance:
print(cls, "creating instance", args, kwargs)
return cls.instance
class SingletonM(metaclass=MetaSingleton):
"""继承单例元类的顶级单例类,后续继承该类的类都是单例类"""
pass
if __name__ == "__main__":
print(test_single(SingletonM))
共享初始状态
类必须为所有实例提供一种共享相同初始状态的方法。确保在特定内存位置只有一个实例存在的技术只是实现此目的的一种方法。
抛开教科书式地设计模式,可以使用更加pythonic
的方式实现共享。
class Borg:
"""非单例实现初始状态共享"""
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
class IBorg(Borg):
"""共享Borg类初始状态"""
def __init__(self):
Borg.__init__(self)
self.state = 'init'
def __str__(self):
return self.state
>>> i1 = IBorg()
>>> i2 = IBorg()
>>> print(i1)
init
>>> print(i2)
init
>>> print(i1.name)
zzy
>>> print(i2.name)
zzy
>>> print(i1==i2)
False
>>>
Borg共享类与单例类比较
-
先看下单例类在继承后共享状态情况,可以发现在继承的兄弟类中并没有共享状态:
>>> class SingletonA(Singleton): pass ... >>> class SingletonB(Singleton): pass ... >>> class SingletonA1(SingletonA): pass ... >>> a = SingletonA() >>> a1 = SingletonA1() >>> b = SingletonB() >>> a.x = 100 >>> print(a.x) 100 >>> print(a1.x) 100 >>> print(b.x) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'SingletonB' object has no attribute 'x'
-
看下共享类Borg的共享状态情况,可以发现继承Borg共享类的所有子类都共享了Borg类的状态:
>>> class ABorg(Borg): pass ... >>> class BBorg(Borg): pass ... >>> class A1Borg(Borg): pass ... >>> a = ABorg() >>> b = BBorg() >>> a1 = A1Borg() >>> a.x = 100 >>> print(a1.x) 100 >>> print(a.x) 100 >>> print(b.x) 100
工厂模式
要求
- 通过类的入口点将不同的参数传递给工厂方法,返回不同的实例对象。
实现
- 定义一个员工的抽象基类:初始化员工的信息,定义一个定义角色的抽象基类方法。
- 定义不同的角色类继承抽象基类,重写抽象基类方法定义角色。
- 定义产生员工的工厂类:定义一个工厂方法接受参数,返回不同的员工实例对象。
from abc import ABCMeta, abstractclassmethod
class Employee(metaclass=ABCMeta):
"""员工抽象基类"""
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
@abstractclassmethod
def get_role(self):
pass
def __str__(self):
return f"{self.__class__.__name__} - {self.name}, {self.age} years old {self.gender}"
class Engineer(Employee):
"""继承抽象基类,重写抽象方法: 定义员工"""
def get_role(self):
return "engineering"
class Accountant(Employee):
"""继承抽象基类,重写抽象方法: 定义员工"""
def get_role(self):
return "accountant"
class Admin(Employee):
"""继承抽象基类,重写抽象方法: 定义员工"""
def get_role(self):
return "administration"
class EmployeeFactory:
"""定义员工工厂类"""
@classmethod
def create(cls, name, *args):
"""生成员工实例的工厂方法"""
name = name.lower().strip()
if name == 'engineer':
return Engineer(*args)
elif name == 'accountant':
return Accountant(*args)
elif name == 'admin':
return Admin(*args)
>>> factory = EmployeeFactory()
>>> print(factory.create('engineer', 'Sam', 25, 'M'))
Engineer - Sam, 25 years old M
>>> print(factory.create('accountant', 'ZZY', 22, 'M'))
Accountant - ZZY, 22 years old M
>>> admin = factory.create('admin', 'Superli', 32, 'F')
>>> print(admin)
Admin - Superli, 32 years old F
>>> admin.get_role()
'administration'
# 因为工厂方法定义为了类方法,我们可以无需实例化工厂类就产生员工
>>> print(EmployeeFactory.create('admin', 'ZZY', 32, 'M'))
Admin - ZZY, 32 years old M
注:抽象基类中的抽象方法一般不写内容,但是继承该类的子类必须重写抽象方法实现具体内容。将工厂类保留的接口方法定义为类方法,可以在直接通过工厂类去调用该方法而无需去实例化化一个工厂类对象。这在有时候可能会比较方便。
原型模式
要求
- 以一个类的实例作为模板实例,通过复制或克隆此原型来创建新的实例。
实现
-
使用深度复制实现
import copy class Prototype(object): """ 原型基类 """ def clone(self): return copy.deepcopy(self) class Register(Prototype): """ 一个学生注册类 """ def __init__(self, names=[]): self.names = names
>>> r1 = Register(names=['bobo', 'jack', 'rose']) >>> r2 = r1.clone() >>> print(r1) <__main__.Register object at 0x000001B2F3BA9198> >>> print(r2) <__main__.Register object at 0x000001B2F3BA92B0> >>> r2.__class__ <class '__main__.Register'> >>> r2.names.append('zzy') >>> r2.names ['bobo', 'jack', 'rose', 'zzy'] >>> r1.names ['bobo', 'jack', 'rose']
-
使用元类的原型
import copy class MetaPrototype(type): """ 实现原型模式的元类 """ def __init__(cls, *args): type.__init__(cls, *args) cls.clone = lambda self: copy.deepcopy(self) class PrototypeM(metaclass=MetaPrototype): """ 继承原型元类的原型模式基类 """ pass class ItemCollection(PrototypeM): """ 一个列表容器原型类 """ def __init__(self, items=[]): self.items = items
i1 = ItemCollection(items=['apples', 'bananas', 'oranges']) i2 = i1.clone() i1 <__main__.ItemCollection object at 0x0000018D57207240> i2 <__main__.ItemCollection object at 0x0000018D57207390> i1.items is i2.items false
-
使用元类来合成模式
使用元类可以将看似冲突的设计模式合成为新的设计模式。现在使用元类将单例模式和原型模式合并,使其既有单例模式的特性,又有原型模式的特性。
import copy class MetaSingletonPrototype(type): """单例和原型合并的元类""" def __init__(cls, *args): print(cls, "__init__ method called with args", args) type.__init__(cls, *args) cls.instance = None cls.clone = lambda self: copy.deepcopy(cls.instance) def __call__(cls, *args, **kwargs): if not cls.instance: print(cls, "creating prototypeical instance", args, kwargs) cls.instance = type.__call__(cls, *args, **kwargs) return cls.instance class PrototypeM(metaclass=MetaSingletonPrototype): pass class ItemCollection(PrototypeM): def __init__(self, items=[]): self.items = items
>>> i1 = ItemCollection(items=['apples', 'bananas', 'oranges']) <class '__main__.ItemCollection'> creating prototypeical instance () {'items': ['apples', 'bananas', 'oranges']} >>> i2 = i1.clone() >>> i1 <__main__.ItemCollection object at 0x0000021151AA9400> >>> i2 <__main__.ItemCollection object at 0x0000021151AA94A8> >>> i2.items.append('zzy') >>> i1.items ['apples', 'bananas', 'oranges'] >>> i2.items ['apples', 'bananas', 'oranges', 'zzy'] >>> i1.items is i2.items False >>> i3 = ItemCollection(items=['a', 'b', 'c']) >>> i3 is i1 True
-
原型工厂
- 将相关模式与工厂模式结合,创建具有共享初始状态的对象。
- 使用原型模式来实现克隆
- 结合上边实现一个注册表的功能:注册后的实例对象可以克隆
import copy class Sprototype: """原型类基类""" def clone(self): """返回深度复制的self""" return copy.deepcopy(self) class Borg: """共享初始状态基类""" _shared_state = {} def __init__(self): self.__dict__ = self._shared_state class PrototypeFactory(Borg): """拥有共享初始状态的工厂注册类""" def __init__(self): self._registry = {} def registry(self, instance): """注册给定实例""" self._registry[instance.__class__] = instance def clone(self, klass): """返回给定注册后的类的克隆后的实例""" instance = self._registry.get(klass) if instance == None: print('Error:', klass, 'not registered') else: return instance.clone() class Name(Sprototype): """名称原型类""" def __init__(self, first, second): self.first = first self.second = second def __str__(self): return ' '.join((self.first, self.second)) class Animal(Sprototype): """动物原型类""" def __init__(self, name, a_type='Wild'): self.name = name self.type = a_type def __str__(self): return ' '.join((str(self.type), self.name))
>>> name = Name('Bill', 'Bryson') >>> animal = Animal('Elephant') >>> print(name) Bill Bryson >>> print(animal) Wild Elephant >>> # 实例化原型工厂对象 ... >>> factory = PrototypeFactory() # 注册对象 >>> factory.registry(animal) >>> factory.registry(name) >>> # 注册完毕实例对象后,克隆任意数量的实例 ... >>> factory.clone(Name) <__main__.Name object at 0x000001504B6F9908> >>> factory.clone(Animal) <__main__.Animal object at 0x000001504B6F9978> >>> # 克隆没有注册的实例会提示未注册 ... >>> class C: pass ... >>> factory.clone(C) Error: <class '__main__.C'> not registered >>>
建造者模式
要求
- 将对象的构造和组装分离,确保可以使用相同的构建过程构建不同的表现形式。
实现
-
使用建造者模式来建造拥有不同房间数量和门廊数量等配置的房屋
-
通过封装原始的建造者类,可以更加个性化的、方便的定制不同配置的建造者类
class Room: """房间类:窗户和门""" def __init__(self, nwindows=2, doors=1, direction='S'): self.nwindows = nwindows self.doors = doors self.direction = direction def __str__(self): return "Room <facing:%s, windows=#%d>" % (self.direction, self.nwindows) class Porch: """门廊类:门""" def __init__(self, ndoors=2, direction='W'): self.ndoors = ndoors self.direction = direction def __str__(self): return "Porch <facing:%s, doors=#%d>" % (self.direction, self.ndoors) class LegoHouse: """ 搭建的整个房屋类:房间和门廊 """ def __init__(self, nrooms=0, nwindows=0, nporches=0): self.nwindows = nwindows self.nporches = nporches self.nrooms = nrooms self.rooms = [] self.porches = [] def __str__(self): msg="LegoHouse<rooms=#%d, porches=#%d>" %( self.nrooms, self.nporches ) for i in self.rooms: msg += str(i) for i in self.porches: msg += str(i) return msg def add_room(self, room): """ 给整件房屋增加一个房间 """ self.rooms.append(room) def add_porch(self, porch): """ 给整件房屋增加一个门廊 """ self.porches.append(porch) class LegoHouseBuilder: """ 房屋建造者 """ def __init__(self, *args, **kwargs): self.house = LegoHouse(*args, **kwargs) def build(self): """ 构造一个房屋实例并返回 """ self.build_rooms() self.build_porches() return self.house def build_rooms(self): """ 构建房间方法 """ for i in range(self.house.nrooms): room = Room(self.house.nwindows) self.house.add_room(room) def build_porches(self): """ 构建门廊方法 """ for i in range(self.house.nporches): porch = Porch(1) self.house.add_porch(porch) if __name__ == "__main__": builder = LegoHouseBuilder(nrooms=2, nporches=1, nwindows=1) print(builder.build()) class SmallLegoHouseBuilder(LegoHouseBuilder): """ 将常用的建造者配置封装为子类 """ def __init__(self): self.house = LegoHouse(nrooms=2, nporches=1, nwindows=2) # 使用封装的建造者配置使用起来更加方便 small_house = SmallLegoHouseBuilder().build() print(small_house) # 封装一个建造面向北方的房屋类 class NorthFacingHouseBuilder(LegoHouseBuilder): """ 面向北方的房屋建造者类 """ def build_rooms(self): for i in range(self.house.nrooms): room = Room(self.house.nwindows, direction='N') self.house.add_room(room) def build_porches(self): for i in range(self.house.nporches): porch = Porch(1, direction='N') self.house.add_porch(porch) print(NorthFacingHouseBuilder(nrooms=2, nporches=1, nwindows=1).build())
LegoHouse<rooms=#2, porches=#1>Room <facing:S, windows=#1>Room <facing:S, windows=#1>Porch <facing:W, doors=#1> LegoHouse<rooms=#2, porches=#1>Room <facing:S, windows=#2>Room <facing:S, windows=#2>Porch <facing:W, doors=#1> LegoHouse<rooms=#2, porches=#1>Room <facing:N, windows=#1>Room <facing:N, windows=#1>Porch <facing:N, doors=#1>
-
注意:
-
建造者模式中具体的执行动作(如:
add_room, add_porch
)应该放在需要构造的对象的类中(LegoHouse
),而不是放在建造者类中(LegoHouseBuilder
)。建造者类(
LegoHouseBuilder
)负责的是对执行动作的调用(build_rooms、build_porches
)和组装(build
)。所以一般建造者类在初始化方法中就会获取构造对象(
self.house = LegoHouse(*args, **kwargs)
),然后在调用和组装的过程中使用该对象调用具体的执行动作。 -
建造者模式与工厂模式的区别
两种模式有相似的地方:都是传递不同的入参最终生成不同的对象。
但是两者有本质上的区别:
- 工厂模式重点强调的是入参的不同,创造不同的对象,也就是说无中生有,强调创造。
- 建造者模式重点强调的是执行动作的不同,组合出不同的对象,重点强调组合。
-
1.4 Python模式——结构化模式
适配器模式
需求
- 将现有接口包装或调整为另一个期望的接口。
实现
-
将一个多边形类适配为不同形状的类,并提供预期的接口。
-
有两种实现方式,一种是通过继承基类,实现预期接口方法的方式。一种是通过在实例化基类对象,通过对象组合的方式,即对象适配器。
-
继承基类的方式
多边形基类:
class Polygon: """ 多边形基类 """ def __init__(self, *sides): """ 边的数量 """ self.sides = sides def perimeter(self): """ 周长 """ return sum(self.sides) def is_valid(self): """ 判断是否合法 """ raise NotImplementedError def is_regular(self): """ 判断是否为规则多变形 """ side = self.sides[0] return all([x==side for x in self.sides[1:]]) def area(self): """ 计算并返回面积 """ raise NotImplementedError
使用适配器模式实现三角形类,实现预期接口为
is_equilateral,is_isosceles
:class Triangle(Polygon): """ 使用适配器模式实现三角形类 """ def is_equilateral(self): """ 判断是否等边 """ if self.is_valid(): return super(Triangle, self).is_regular() def is_isosceles(self): """ 判断是否为等腰三角形 """ if self.is_valid(): for a,b in itertools.combinations(self.sides, 2): if a == b: return True return False def area(self): """ 计算面积 """ p = self.perimeter() / 2.0 total = p for side in self.sides: total *= abs(p-side) return pow(total, 0.5) def is_valid(self): """ 判断是否合法 """ # 两边之和大于第三边 perimeter = self.perimeter() for side in self.sides: sum_two = perimeter - side if sum_two <= side: raise InvalidePolygonError(str(self.__class__) + "is invalid") return True
>>> t1 = Triangle(20, 20, 20) >>> t1.is_valid() True >>> t1.is_equilateral() True >>> t1.area() 173.20508075688772 >>> # 创建一个不合法的三角形 ... >>> t2 = Triangle(10, 20, 30) >>> t2.is_valid() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 28, in is_valid __main__.InvalidePolygonError: <class '__main__.Triangle'>is invalid
使用适配器模式实现矩形类,预期接口为
is_square
:class Rectangle(Polygon): """ 使用适配器模式实现矩形 """ def is_square(self): """ 判断是否为正方形 """ if self.is_valid(): return self.is_regular() def is_valid(self): """ 判断是否合法 """ # 4条边 if len(self.sides) != 4: return False # 对边相等 for a,b in [(0,2), (1,3)]: if self.sides[a] != self.sides[b]: return False return True def area(self): """ 计算面积 """ if self.is_valid(): return self.sides[0]*self.sides[1]
>>> r1 = Rectangle(10, 20, 10, 20) >>> r1.is_valid() True >>> r1.is_square() False >>> r1.area() 200 >>> # 创建一个正方形 ... r2 = Rectangle(10, 10, 10, 10) >>> r2.is_square() True
-
对象适配器的方式
class Triangle2: """ 对象适配器方式实现三角形类 """ def __init__(self, *sides): """ 实例化基类对象 """ self.polygon = Polygon def perimeter(self): return self.polygon.perimeter def is_valid(f): """ 判断是否为三角形 """ def inner(self, *args): perimeter = self.polygon.perimeter() sides = self.polygon.sides for side in sides: sum_two <= side: raise InvalidePolygonError(str(self.__class__) + "is invalid" ) result = f(self, *args) return result return inner @is_valid def is_equilateral(self): """ 判断是否为等边三角形 """ return self.polygon.is_regular() @is_valid def is_isosceles(self): """ 判断是否为等腰三角形 """ for a,b in itertools.combinations(self.polygon.sides, 2): if a == b: return True return False def area(self): """ 计算面积 """ p = self.polygon.perimeter() / 2.0 total = p for side in self.polygon.sides: total *= abs(p-side) return pow(total, 0.5)
-
对象适配器方式与类适配器方式的主要区别:
- 对象适配器无需继承想要适配的类,需要实例化适配的类。
- 对象适配器类中用到的适配的类中的属性和方法都必须显示的通过实例化对象获取。
-
升级版对象适配方式:重写
___getatttr__
方法动态获取需要适配的类中的属性。class Triangle2: """ 对象适配器方式实现三角形类 """ # 如果想要调用的方法与实例对象上的方法内容一样,但是方法名不一样, # 可以通过映射的方式来定义两个方法名间的关系 method_mapper = {'is_equilateral':'is_regular'} def __init__(self, *sides): """ 实例化基类对象 """ self.polygon = Polygon(*sides) def is_valid(f): """ 判断是否为三角形 """ def inner(self, *args): perimeter = self.perimeter() sides = self.sides for side in sides: sum_two = perimeter - side if sum_two <= side: raise InvalidePolygonError(str(self.__class__) + "is invalid") result = f(self, *args) return result return inner @is_valid def is_isosceles(self): """ 判断是否为等腰三角形 """ for a,b in itertools.combinations(self.sides, 2): if a == b: return True return False @is_valid def area(self): """ 计算面积 """ p = self.perimeter() / 2.0 total = p for side in self.sides: total *= abs(p-side) return pow(total, 0.5) def ___getatttr__(self, name): """ 首先查找在映射字典中是否存在对应的方法名, 如果存在就拿对应的实例对象的方法名去获取其属性。 否则就直接从实例对象中查找属性。 """ name = self.method_mapper.get(name, name) return getattr(self.polygon, name)
>>> t1 = Triangle(2, 2, 2) >>> t1.is_equilateral() True >>> t1.area() 1.7320508075688772 >>>
-
外观模式
需求
- 当系统比较复杂时,无需关心系统内部实现,只需要将对应操作的接口暴露出来即可。
实现
-
定义汽车系统的各个类,然后使用外观模式去提供启动和停止接口。
汽车系统类:
class Engine: """ 汽车引擎类 """ def __init__(self, name, bhp, rpm, volume, cylinders=4, type='petrol'): self.name = name self.bhp = bhp self.rpm = rpm self.volume = volume self.cylinders = cylinders self.type = type def start(self): """启动引擎 """ print('Engine started') def stop(self): """停止引擎 """ print('Engine stopped') class Transmission: """变速类 """ def __init__(self, gears, torque): self.gears = gears self.torque = torque self.gears_pos = 0 def shift_up(self): """ 加速 """ if self.gears_pos == self.gears: print('不能加速了') else: self.gears_pos += 1 print('加速至', self.gears_pos) def shift_down(self): """ 减速 """ if self.gears_pos == -1: print('倒挡,不能减速了!') else: self.gears_pos -= 1 print('减速至', self.gears_pos) def shift_reverse(self): """ 倒挡 """ self.gears_pos = -1 print('倒挡!') def shift_to(self, gear): """ 跳档 """ self.gears_pos = gear print(f'调制{gear}档') class Brake: """ 刹车类 """ def __init__(self, number, type='disc'): self.type = type self.number = number def engage(self): """ 进行刹车 """ print('%s %d engage' % (self.__class__.__name__, self.number)) def release(self): """ 恢复 """ print('%s %d released' % (self.__class__.__name__, self.number)) class ParkingBrake(Brake): """ 停车 """ def __init__(self, type='drum'): super(ParkingBrake, self).__init__(type=type, number=1) class Suspension: """ 悬停 """ def __init__(self, load, type='mcpherson'): self.type = type self.load = load class Wheel: """ 轮胎材料 """ def __init__(self, material, diameter, pitch): self.material = material self.diameter = diameter self.pitch = pitch class WheelAssembly: """ 车轮 """ def __init__(self, brake, suspension): self.brake = brake self.suspension = suspension self.wheels = Wheel('alloy', 'M12', 1.25) def aaply_brakes(self): """ 踩刹车 """ print('踩刹车') self.brake.engage() class Frame: """ 外壳 """ def __init__(self, length, width): self.length = length self.width = width
组装汽车类,并提供启动和停止的接口:
class Car: """ 汽车类——外观模式 """ def __init__(self, model, manufacturer): self.engine = Engine('K-series',85,5000, 1.3) self.frame = Frame(385, 170) self.wheel_assembiles = [] for i in range(4): self.wheel_assembiles.append(WheelAssembly(Brake(i+1), Suspension(1000))) self.transmission = Transmission(5, 115) self.model = model self.manufacturer = manufacturer self.park_brake = ParkingBrake() self.ignition = False def start(self): """ 启动 """ print('启动汽车~') self.ignition = True self.park_brake.release() self.engine.start() self.transmission.shift_up() print('汽车启动了。。') def stop(self): """ 停止汽车 """ print('停车~') for wheel_a in self.wheel_assembiles: wheel_a.apply_brakes() # 调制2挡然后1挡 self.transmission.shift_to(2) self.transmission.shift_to(1) self.engine.stop() self.transmission.shift_to(0) self.park_brake.engage() print('停车完毕..')
运行:
>>> car = Car('Swift', 'Suzuki') >>> car.start() 启动汽车~ ParkingBrake 1 released Engine started 加速至 1 汽车启动了。。 >>> car.stop() 停车~ 踩刹车 Brake 1 engage 踩刹车 Brake 2 engage 踩刹车 Brake 3 engage 踩刹车 Brake 4 engage 调制2档 调制1档 Engine stopped 调制0档 ParkingBrake 1 engage 停车完毕..
代理模式
需求
- 包装另一个对象来控制对此对象的访问
实现
-
记数代理:记录前边工厂模式产生的实例对象数量。
-
这里重用了Employee类。
from abc import ABCMeta, abstractclassmethod class Employee(metaclass=ABCMeta): """员工抽象基类""" def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender @abstractclassmethod def get_role(self): pass def __str__(self): return f"{self.__class__.__name__} - {self.name}, {self.age} years old {self.gender}" class Engineer(Employee): """继承抽象基类,重写抽象方法: 定义员工""" def get_role(self): return "engineering" class Accountant(Employee): """继承抽象基类,重写抽象方法: 定义员工""" def get_role(self): return "accountant" class Admin(Employee): """继承抽象基类,重写抽象方法: 定义员工""" def get_role(self): return "administration"
创建代理工厂类:这里使用的是对象代理,重载
___getatttr__
方法重定向访问权,获得原实例对象中的属性。class EmployeeProxy: """ 员工计数代理类 """ count = 0 def __new__(cls, *args): """ 重写构造方法,记录员工数量 """ instance = object.__new__(cls) cls.incr_count() return instance def __init__(self, employee): self.employee = employee @classmethod def incr_count(cls): """ 增加员工数量 """ cls.count += 1 @classmethod def decr_count(cls): """ 减少员工数量 """ cls.count -= 1 @classmethod def get_count(cls): """ 得到员工数量""" return cls.count def __str__(self): return str(self.employee) def __getattr__(self, name): """ 重定向获取员工实例对象属性 """ return getattr(self.employee, name) def __del__(self): self.decr_count() class EmployeeProxyFactory: """ 返回计数代理对象的员工工厂类 """ @classmethod def create(cls, name, *args): """ 生产员工的工厂类 """ name = name.lower().strip() if name == 'engineer': return EmployeeProxy(Engineer(*args)) elif name == 'accountant': return EmployeeProxy(Accountant(*args)) elif name == 'admin': return EmployeeProxy(Admin(*args))
>>> factory= EmployeeProxyFactory() >>> engineer = factory. create ('engineer','Sam', 25,'M' ) >>> EmployeeProxy.get_count() 1 >>> admin = factory. create ('admin','Sam', 25,'M' ) >>> EmployeeProxy.get_count() 2 >>> accountant = factory. create ('accountant','Sam', 25,'M' ) >>> EmployeeProxy.get_count() 3 >>> del admin >>> EmployeeProxy.get_count() 2 >>> del accountant >>> EmployeeProxy.get_count() 1
-
Python的弱引用模块提供了实现代理对象的方法:
>>> import weakref >>> import gc >>> engineer=Engineer('zzy', 25, 'M') >>> # 查看对象的引用计数 ... len(gc.get_referrers(engineer)) 1 >>> # 现在创建一个对它的弱引用 ... engineer_proxy = weakref.proxy(engineer) >>> # 若引用具有和代理对象一样的属性 ... print(engineer_proxy) Engineer - zzy, 25 years old M >>> engineer_proxy.get_role() 'engineering' >>> # 查看代理对象的引用计数并没有增加 ... len(gc.get_referrers(engineer)) 1 >>>
1.5 Python模式——行为模式
行为模式是系统生命周期最后阶段的模式,封装了对象之间的通信和交互,该模式往往倾向于对象组合。
迭代器模式
需求
- 提供一种连续访问容器对象中的元素而不会暴露底层对象本身的方法。
实现
-
Python中的迭代器必须实现
__iter__
方法,并且该对象还要能响应返回迭代器实例的iter函数。 -
实现一个质数迭代器:
class Prime: """ 一个质数迭代器 """ def __init__(self, initial, final=0): self.current = initial # 迭代终止位,如果不传,则会无限迭代 self.final = final def __iter__(self): return self def __next__(self): return self._compute() def _compute(self): """ 计算下一个质数 """ num = self.current while True: is_prime = True for x in range(2, int(pow(self.current, 0.5)+1)): if self.current %x == 0: is_prime = False break num = self.current self.current += 1 if is_prime: return num if self.final > 0 and self.current > self.final: raise StopIteration
>>> p = Prime(2, 10) >>> for num in p: ... print(num) ... 2 3 5 7
不传入标志位,结合
itertools
模块的方法可以达到多种结果:-
计算前100个质数:
>>> p = Prime(2) >>> import itertools >>> list(itertools.islice(Prime(2), 100)) [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541]
-
过滤出个位以1结尾的前10个质数:
>>> list(itertools.islice(itertools.filterfalse(lambda x: x % 10 != 1, Prime(2)), 10)) [11, 31, 41, 61, 71, 101, 131, 151, 181, 191]
-
观察者模式
需求
- 解耦对象,允许在同一时刻一组对象(订阅者)跟踪另一组对象(发布者)的改变。避免一对多的依赖和引用,同时保持它们的交互活动。
- 该模式又叫做发布者—订阅模式。
实现
-
实现一个Alam类,该类在自己的线程中运行,并且每隔1s生成定期报警。它也可以作为发布者类,当发生报警时通知其订阅者。
import threading import time import pdb from datetime import datetime class Alarm(threading.Thread): """ 发布者:定期通知类 """ def __init__(self, duration=1): self.duration = duration self.subscribers = [] self.flag = True threading.Thread.__init__(self) def register(self, subscriber): """ 为警告通知注册一个订阅者 """ self.subscribers.append(subscriber) def notify(self): """ 通知所有订阅者 """ for subscriber in self.subscribers: subscriber.update(self.duration) def stop(self): """ 停止线程 """ print('线程终止') self.flag = False def run(self): """ 运行警告通知 """ while self.flag: time.sleep(self.duration) self.notify() class DumbClock: """ 订阅者:订阅Alarm对象并接收通知,同时利用通知更新它的时间 """ def __init__(self): self.current = time.time() def update(self, *args): """ 发布者回调方法 """ self.current += args[0] def __str__(self): return datetime.fromtimestamp(self.current).strftime('%H:%M:%S') if __name__ == "__main__": alarm = Alarm(duration=1) clock = DumbClock() alarm.register(clock) alarm.start() print(clock) time.sleep(5) print(clock)
PS C:\Users\ZZY> & python c:/Users/ZZY/Desktop/a.py 11:46:39 11:46:43
- 该模式的核心是要在发布者类中要有注册订阅者的方法,也就是获取订阅者对象的方法,还有一个通知订阅者的方法(该方法调用订阅者中的接受更新的方法),所以在订阅者中要有一个供发布者调用的接口用来接受发布者的消息。
状态模式
需求
- 将对象的内部状态封装到另一个类(状态对象)。对象通过将内部封装的状态切换到不同的值来更改其自身状态。
实现
-
Python中的对象可以通过
__class__
类属性获取类中的属性和方法。我们可以通过修改__class__
属性达到切换状态的效果。 -
实现一个可以切换电脑运行状态的案例,该案例使用迭代器的方式去切换状态:
import random class ComputerState: """ 电脑状态基类 """ name = "state" next_states = [] random_states = [] def __init__(self): self.index = 0 def __str__(self): return self.__class__.__name__ def __iter__(self): return self def change(self): return self.__next__() def set(self, state): """ 设置状态: 如果该状态存在于下个状态列表中,首先会切换至下个状态, 如果不存在下个状态列表中,就切换至随机状态列表的状态 """ if self.index < len(self.next_states): if state in self.next_states: self.index = self.next_states.index(state) self.__class__ = eval(state) return self.__class__ else: current = self.__class__ new = eval(state) raise Exception(f'Illegal transition from {current} to {new}') else: self.index = 0 if state in self.random_states: self.__class__ = eval(state) return self.__class__ def __next__(self): """ 切换至下个状态: 如果存在下个状态,首先会切换至下个状态, 如果不存在下个状态,就切换至随机状态 """ if self.index < len(self.next_states): self.__class__ = eval(self.next_states[self.index]) self.index += 1 return self.__class__ else: self.index = 0 if len(self.random_states): state = random.choice(self.random_states) self.__class__ = eval(state) return self.__class__ else: raise StopIteration class ComputerOff(ComputerState): """ 关机状态: 关机状态固定下一个状态为开机状态, 随机状态有挂起,休眠,关机 """ next_states = ['ComputerOn'] random_states= ['ComputerSuspend', 'ComputerHibernate', 'ComputerOff'] class ComputerOn(ComputerState): """ 开机状态: 没有固定的下一个状态, 随机状态有挂起,休眠,关机 """ random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff'] class ComputerWakeUp(ComputerState): """ 运行状态: 没有固定的下一个状态, 随机状态有挂起、休眠、关机 """ random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff'] class ComputerSuspend(ComputerState): """ 挂起状态: 挂起状态下一个状态为运行状态, 随机状态有挂起、休眠、关机 """ next_states = ['ComputerWakeUp'] random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff'] class ComputerHibernate(ComputerState): """ 休眠状态: 休眠状态下一个固定状态为开机状态, 随机状态有挂起、休眠、关机 """ next_states = ['ComputerOn'] random_states = ['ComputerSuspend','ComputerHibernate','ComputerOff'] class Computer: """ 可以改变状态的电脑类 """ def __init__(self, model): self.model = model # 默认为关机 self.state = ComputerOff() def change(self, state=None): """ 修改至指定状态 """ if state == None: return self.state.change() else: return self.state.set(state) def __str__(self): """ 返回状态 """ return str(self.state)
运行结果:
>>> c = Computer('HUAWEI') >>> c.change() <class '__main__.ComputerOn'> >>> c.change('ComputerOff') <class '__main__.ComputerOff'> >>> c.change() <class '__main__.ComputerOn'> >>> c.change() <class '__main__.ComputerSuspend'> >>> c.change() <class '__main__.ComputerWakeUp'> >>> c.change() <class '__main__.ComputerOff'>
总结
对Python学习了解越深,越能体会人生苦短,我用Python
这短短几个字中的含义:
- 优雅
- 人性化思维
当我们学会以面向对象的思维去学习设计模式,会发现设计模式并没有想象的复杂:不就是变着法去操作过来操作过去一些对象嘛。
个人觉得学习设计模式更重要的是理解它为什么要这样设计,以及它解决了什么问题。
关于如何实现这点要结合具体的编程语言,但核心点就是对象
:如何去获取、改变、组装对象的属性。