設計模式在Python中的完美實現
文章目錄
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
這短短几個字中的含義:
- 優雅
- 人性化思維
當我們學會以物件導向的思維去學習設計模式,會發現設計模式並沒有想象的複雜:不就是變著法去操作過來操作過去一些物件嘛。
個人覺得學習設計模式更重要的是理解它為什麼要這樣設計,以及它解決了什麼問題。
關於如何實現這點要結合具體的程式語言,但核心點就是物件
:如何去獲取、改變、組裝物件的屬性。
相關文章
- 在 Laravel 中實現「福勒的貨幣設計模式」Laravel設計模式
- 在Python中實現單例模式Python單例模式
- 設計模式(python實現):觀察者模式設計模式Python
- 用Python實現設計模式——單例模式Python設計模式單例
- 用Python實現設計模式——工廠模式Python設計模式
- 設計模式學習筆記(十五)命令模式及在Spring JdbcTemplate 中的實現設計模式筆記SpringJDBC
- 設計模式詳解及Python實現設計模式Python
- 淺談設計模式及python實現設計模式Python
- 裝飾者設計模式在業務中的實踐設計模式
- 用Python實現設計模式——建造者模式和原型模式Python設計模式原型
- PHP 實戰之設計模式:PHP 中的設計模式PHP設計模式
- 設計模式在 TypeScript 中的應用 – 策略模式設計模式TypeScript
- 設計模式學習筆記(十四)責任鏈模式實現以及在Filter中的應用設計模式筆記Filter
- Python程式設計中的反模式Python程式設計模式
- 分析模式-計量的C++實現——完美版本 (轉)模式C++
- Python 中的設計模式詳解之:策略模式Python設計模式
- 【併發程式設計】Future模式及JDK中的實現程式設計模式JDK
- 設計模式在美團外賣營銷業務中的實踐設計模式
- 談談我現在使用的設計模式設計模式
- 設計模式在vue中的應用(六)設計模式Vue
- 設計模式在vue中的應用(七)設計模式Vue
- 設計模式在vue中的應用(五)設計模式Vue
- 設計模式在vue中的應用(三)設計模式Vue
- 設計模式在vue中的應用(四)設計模式Vue
- 設計模式在vue中的應用(一)設計模式Vue
- 設計模式在vue中的應用(二)設計模式Vue
- 設計模式,及在Java中的應用設計模式Java
- Python中實現單例模式Python單例模式
- Go 實現常用設計模式(九)模式Go設計模式
- 設計模式——命令模式實現撤銷設計模式
- 設計模式-Java實現單例模式設計模式Java單例
- 設計模式之--策略模式及其在JDK中的應用設計模式JDK
- 設計模式:建造者模式及在jdk中的體現,建造者模式和工廠模式區別設計模式JDK
- 常見的Golang設計模式實現?Golang設計模式
- MVVM設計模式和在WPF中的實現(四) 事件繫結MVVM設計模式事件
- MVVM設計模式和WPF中的實現(四)事件繫結MVVM設計模式事件
- TypeScript實現所有設計模式TypeScript設計模式
- Laravel 中設計模式的實戰分享Laravel設計模式