設計模式(Design Patterns)——可復用面向對象軟件的基礎
設計模式(Design pattern)是一套被反復使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的,設計模式使代碼編制真正工程化,設計模式是軟件工程的基石,如同大廈的一塊塊磚石一樣。
項目中合理的運用設計模式可以完美的解決很多問題,每種模式在現在中都有相應的原理來與之對應,每一個模式描述了一個在我們周圍不斷重復發生的問題,以及該問題的核心解決方案,這也是它能被廣泛應用的原因。
經典的《設計模式》一書歸納出23種設計模式,這23種模式又可歸為,創建型、結構型和行為型3大類
設計模式6大原則
1、開閉原則(Open Close Principle)
開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:為了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,后面的具體設計中我們會提到這點。
2、里氏代換原則(Liskov Substitution Principle)
里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。—— From Baidu 百科
3、依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體內容:是對接口編程,依賴於抽象而不依賴於具體。
4、接口隔離原則(Interface Segregation Principle)
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,為了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。
5、迪米特法則(最少知道原則)(Demeter Principle)
為什么叫最少知道原則,就是說:一個實體應當盡量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。
6、合成復用原則(Composite Reuse Principle)
原則是盡量使用合成/聚合的方式,而不是使用繼承。
1.創建型模式
前面講過,社會化的分工越來越細,自然在軟件設計方面也是如此,因此對象的創建和對象的使用分開也就成為了必然趨勢。因為對象的創建會消耗掉系統的很多資源,所以單獨對對象的創建進行研究,從而能夠高效地創建對象就是創建型模式要探討的問題。這里有6個具體的創建型模式可供研究,它們分別是:
單例模式
單例模式(Singleton Pattern)是一種常用的軟件設計模式,該模式的主要目的是確保某一個類只有一個實例存在。當你希望在整個系統中,某個類只能出現一個實例時,單例對象就能派上用場。
比如,某個服務器程序的配置信息存放在一個文件中,客戶端通過一個 AppConfig 的類來讀取配置文件的信息。如果在程序運行期間,有很多地方都需要使用配置文件的內容,也就是說,很多地方都需要創建 AppConfig 對象的實例,這就導致系統中存在多個 AppConfig 的實例對象,而這樣會嚴重浪費內存資源,尤其是在配置文件內容很多的情況下。事實上,類似 AppConfig 這樣的類,我們希望在程序運行期間只存在一個實例對象
1 class Singleton(object):
2 def __init__(self):
3 pass
4
5 def __new__(cls, *args, **kwargs):
6 if not hasattr(Singleton, "_instance"): # 反射
7 Singleton._instance = object.__new__(cls)
8 return Singleton._instance
9
10 obj1 = Singleton()
11 obj2 = Singleton()
12 print(obj1, obj2) #<__main__.Singleton object at 0x004415F0> <__main__.Singleton object at 0x004415F0>
工廠模式
工廠模式是一個在軟件開發中用來創建對象的設計模式。
工廠模式包涵一個超類。這個超類提供一個抽象化的接口來創建一個特定類型的對象,而不是決定哪個對象可以被創建。
為了實現此方法,需要創建一個工廠類創建並返回。
當程序運行輸入一個“類型”的時候,需要創建於此相應的對象。這就用到了工廠模式。在如此情形中,實現代碼基於工廠模式,可以達到可擴展,可維護的代碼。當增加一個新的類型,不在需要修改已存在的類,只增加能夠產生新類型的子類。
簡短的說,當以下情形可以使用工廠模式:
1.不知道用戶想要創建什么樣的對象
2.當你想要創建一個可擴展的關聯在創建類與支持創建對象的類之間。
一個例子更能很好的理解以上的內容:
- 我們有一個基類Person ,包涵獲取名字,性別的方法 。有兩個子類male 和female,可以打招呼。還有一個工廠類。
- 工廠類有一個方法名getPerson有兩個輸入參數,名字和性別。
- 用戶使用工廠類,通過調用getPerson方法。
在程序運行期間,用戶傳遞性別給工廠,工廠創建一個與性別有關的對象。因此工廠類在運行期,決定了哪個對象應該被創建
class Person:
def __init__(self):
self.name = None
self.gender = None
def getName(self):
return self.name
def getGender(self):
return self.gender
class Male(Person):
def __init__(self, name):
print "Hello Mr." + name
class Female(Person):
def __init__(self, name):
print "Hello Miss." + name
class Factory:
def getPerson(self, name, gender):
if gender == ‘M':
return Male(name)
if gender == 'F':
return Female(name)
if __name__ == '__main__':
factory = Factory()
person = factory.getPerson("Chetan", "M")
建造者模式
將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
相關模式:思路和模板方法模式很像,模板方法是封裝算法流程,對某些細節,提供接口由子類修改,建造者模式更為高層一點,將所有細節都交由子類實現
一個例子更能很好的理解以上的內容:
- 有一個接口類,定義創建對象的方法。一個指揮員類,接受創造者對象為參數。兩個創造者類,創建對象方法相同,內部創建可自定義
- 一個指揮員,兩個創造者(瘦子 胖子),指揮員可以指定由哪個創造者來創造
from abc import ABCMeta, abstractmethod
class Builder():
__metaclass__ = ABCMeta
@abstractmethod
def draw_left_arm(self):
pass
@abstractmethod
def draw_right_arm(self):
pass
@abstractmethod
def draw_left_foot(self):
pass
@abstractmethod
def draw_right_foot(self):
pass
@abstractmethod
def draw_head(self):
pass
@abstractmethod
def draw_body(self):
pass
class Thin(Builder):
def draw_left_arm(self):
print '畫左手'
def draw_right_arm(self):
print '畫右手'
def draw_left_foot(self):
print '畫左腳'
def draw_right_foot(self):
print '畫右腳'
def draw_head(self):
print '畫頭'
def draw_body(self):
print '畫瘦身體'
class Fat(Builder):
def draw_left_arm(self):
print '畫左手'
def draw_right_arm(self):
print '畫右手'
def draw_left_foot(self):
print '畫左腳'
def draw_right_foot(self):
print '畫右腳'
def draw_head(self):
print '畫頭'
def draw_body(self):
print '畫胖身體'
class Director():
def __init__(self, person):
self.person=person
def draw(self):
self.person.draw_left_arm()
self.person.draw_right_arm()
self.person.draw_left_foot()
self.person.draw_right_foot()
self.person.draw_head()
self.person.draw_body()
if __name__=='__main__':
thin=Thin()
fat=Fat()
director_thin=Director(thin)
director_thin.draw()
director_fat=Director(fat)
director_fat.draw()
原型模式
用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。
原型模式本質就是克隆對象,所以在對象初始化操作比較復雜的情況下,很實用,能大大降低耗時,提高性能,因為“不用重新初始化對象,而是動態地獲得對象運行時的狀態”。
淺拷貝(Shallow Copy):指對象的字段被拷貝,而字段引用的對象不會被拷貝,拷貝的對象和源對象只是名稱相同,但是他們共用一個實體。
深拷貝(deep copy):對對象實例中字段引用的對象也進行拷貝。
import copy
from collections import OrderedDict
class Book:
def __init__(self, name, authors, price, **rest):
'''rest的例子有:出版商、長度、標簽、出版日期'''
self.name = name
self.authors = authors
self.price = price # 單位為美元
self.__dict__.update(rest)
def __str__(self):
mylist = []
ordered = OrderedDict(sorted(self.__dict__.items()))
for i in ordered.keys():
mylist.append('{}: {}'.format(i, ordered[i]))
if i == 'price':
mylist.append('$')
mylist.append('\n')
return ''.join(mylist)
class Prototype:
def __init__(self):
self.objects = dict()
def register(self, identifier, obj):
self.objects[identifier] = obj
def unregister(self, identifier):
del self.objects[identifier]
def clone(self, identifier, **attr):
found = self.objects.get(identifier)
if not found:
raise ValueError('Incorrect object identifier: {}'.format(identifier))
obj = copy.deepcopy(found)
obj.__dict__.update(attr)
return obj
def main():
b1 = Book('The C Programming Language', ('Brian W. Kernighan', 'Dennis M.Ritchie'),
price=118, publisher='Prentice Hall', length=228, publication_date='1978-02-22',
tags=('C', 'programming', 'algorithms', 'data structures'))
prototype = Prototype()
cid = 'k&r-first'
prototype.register(cid, b1)
b2 = prototype.clone(cid, name='The C Programming Language(ANSI)', price=48.99,
length=274, publication_date='1988-04-01', edition=2)
for i in (b1, b2):
print(i)
print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2)))
if __name__ == '__main__':
main()
2.結構型模式
在解決了對象的創建問題之后,對象的組成以及對象之間的依賴關系就成了開發人員關注的焦點,因為如何設計對象的結構、繼承和依賴關系會影響到后續程序的維護性、代碼的健壯性、耦合性等。對象結構的設計很容易體現出設計人員水平的高低,這里有7個具體的結構型模式可供研究,它們分別是:
適配器模式
所謂適配器模式是指是一種接口適配技術,它可通過某個類來使用另一個接口與之不兼容的類,運用此模式,兩個類的接口都無需改動。
適配器模式主要應用於希望復用一些現存的類,但是接口又與復用環境要求不一致的情況,比如在需要對早期代碼復用一些功能等應用上很有實際價值。
解釋二:
適配器模式(Adapter Pattern):將一個類的接口轉換成為客戶希望的另外一個接口.Adapter Pattern使得原本由於接口不兼容而不能一起工作的那些類可以一起工作.
應用場景:系統數據和行為都正確,但接口不符合時,目的是使控制范圍之外的一個原有對象與某個接口匹配,適配器模式主要應用於希望復用一些現存的類,但接口又與復用環境不一致的情況
class Target(object):
def request(self):
print "普通請求"
class Adaptee(object):
def specific_request(self):
print "特殊請求"
class Adapter(Target):
def __init__(self):
self.adaptee = Adaptee()
def request(self):
self.adaptee.specific_request()
if __name__ == "__main__":
target = Adapter()
target.request()
修飾器模式
在不影響其他對象的情況下,以動態、透明的方式給單個對象添加職責
該模式雖名為修飾器,但這並不意味着它應該只用於讓產品看起來更漂亮。修飾器模式通常用於擴展一個對象的功能。這類擴展的實際例子有,給槍加一個消音器、使用不同的照相機鏡頭
__author__ = "Burgess Zheng"
#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
Decorator
'''
class foo(object):
def f1(self):
print("original f1")
def f2(self):
print("original f2")
class foo_decorator(object):
def __init__(self, decoratee):
self._decoratee = decoratee
def f1(self):
print("decorated f1")
self._decoratee.f1()
def __getattr__(self, name):
return getattr(self._decoratee, name)
u = foo()
v = foo_decorator(u)
v.f1()
v.f2()
外觀模式
外觀模式又叫做門面模式。在面向對象程序設計中,解耦是一種推崇的理念。但事實上由於某些系統中過於復雜,從而增加了客戶端與子系統之間的耦合度。例如:在家觀看多媒體影院時,更希望按下一個按鈕就能實現影碟機,電視,音響的協同工作,而不是說每個機器都要操作一遍。這種情況下可以采用外觀模式,即引入一個類對子系統進行包裝,讓客戶端與其進行交互。
外觀模式(Facade Pattern):外部與一個子系統的通信必須通過一個統一的外觀對象進行,為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。外觀模式又稱為門面模式,它是一種對象結構型模式。
假設有一組火警報警系統,由三個子元件構成:一個警報器,一個噴水器,一個自動撥打電話的裝置。
class AlarmSensor:
def run(self):
print("Alarm Ring...")
class WaterSprinker:
def run(self):
print("Spray Water...")
class EmergencyDialer:
def run(self):
print("Dial 119...")
class EmergencyFacade:
"""
外觀類中封裝了對子系統的操作
"""
def __init__(self):
self.alarm_sensor=AlarmSensor()
self.water_sprinker=WaterSprinker()
self.emergency_dialer=EmergencyDialer()
def runAll(self):
self.alarm_sensor.run()
self.water_sprinker.run()
self.emergency_dialer.run()
if __name__=="__main__":
emergency_facade=EmergencyFacade()
emergency_facade.runAll()
根據“單一職責原則”,在軟件中將一個系統划分為若干個子系統有利於降低整個系統的復雜性,一個常見的設計目標是使子系統間的通信和相互依賴關系達到最小,而達到該目標的途徑之一就是引入一個外觀對象,它為子系統的訪問提供了一個簡單而單一的入口。 外觀模式也是“迪米特法則”的體現,通過引入一個新的外觀類可以降低原有系統的復雜度,同時降低客戶類與子系統類的耦合度。
外觀模式要求一個子系統的外部與其內部的通信通過一個統一的外觀對象進行,外觀類將客戶端與子系統的內部復雜性分隔開,使得客戶端只需要與外觀對象打交道,而不需要與子系統內部的很多對象打交道。 外觀模式的目的在於降低系統的復雜程度。 外觀模式從很大程度上提高了客戶端使用的便捷性,使得客戶端無須關心子系統的工作細節,通過外觀角色即可調用相關功能。
優點:
主要優點在於對客戶屏蔽子系統組件,減少了客戶處理的對象數目並使得子系統使用起來更加容易,它實現了子系統與客戶之間的松耦合關系,並降低了大型軟件系統中的編譯依賴性,簡化了系統在不同平台之間的移植過程;
缺點:
其缺點在於不能很好地限制客戶使用子系統類,而且在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。
享元模式
運用共享技術有效地支持大量細粒度的對象。
內部狀態:享元對象中不會隨環境改變而改變的共享部分。比如圍棋棋子的顏色。
外部狀態:隨環境改變而改變、不可以共享的狀態就是外部狀態。比如圍棋棋子的位置。
應用場景:程序中使用了大量的對象,如果刪除對象的外部狀態,可以用相對較少的共享對象取代很多組對象,就可以考慮使用享元模式。
1 import random
2 from enum import Enum
3 TreeType = Enum('TreeType', 'apple_tree cherry_tree peach_tree')
4
5 class Tree:
6 pool = dict()
7 def __new__(cls, tree_type):
8 obj = cls.pool.get(tree_type, None)
9 if not obj:
10 obj = object.__new__(cls)
11 cls.pool[tree_type] = obj
12 obj.tree_type = tree_type
13 return obj
14
15 def render(self, age, x, y):
16 print('render a tree of type {} and age {} at ({}, {})'.format(self.tree_type, age, x, y))
17
18
19 def main():
20 rnd = random.Random()
21 age_min, age_max = 1, 30 # 單位為年
22 min_point, max_point = 0, 100
23 tree_counter = 0
24 for _ in range(10):
25 t1 = Tree(TreeType.apple_tree)
26 t1.render(rnd.randint(age_min, age_max),
27 rnd.randint(min_point, max_point),
28 rnd.randint(min_point, max_point))
29 tree_counter += 1
30 for _ in range(3):
31 t2 = Tree(TreeType.cherry_tree)
32 t2.render(rnd.randint(age_min, age_max),
33 rnd.randint(min_point, max_point),
34 rnd.randint(min_point, max_point))
35 tree_counter += 1
36 for _ in range(5):
37 t3 = Tree(TreeType.peach_tree)
38 t3.render(rnd.randint(age_min, age_max),
39 rnd.randint(min_point, max_point),
40 rnd.randint(min_point, max_point))
41 tree_counter += 1
42
43 print('trees rendered: {}'.format(tree_counter))
44 print('trees actually created: {}'.format(len(Tree.pool)))
45 t4 = Tree(TreeType.cherry_tree)
46 t5 = Tree(TreeType.cherry_tree)
47 t6 = Tree(TreeType.apple_tree)
48 print('{} == {}? {}'.format(id(t4), id(t5), id(t4) == id(t5)))
49 print('{} == {}? {}'.format(id(t5), id(t6), id(t5) == id(t6)))
50
51 main()
代理模式
在直接訪問對象時帶來的問題,比如說:要訪問的對象在遠程的機器上。在面向對象系統中,有些對象由於某些原因(比如對象創建開銷很大,或者某些操作需要安全控制,或者需要進程外的訪問),直接訪問會給使用者或者系統結構帶來很多麻煩,我們可以在訪問此對象時加上一個對此對象的訪問層。
__author__ = "Burgess Zheng"
#!/usr/bin/env python
#-*- coding:utf-8 -*-
'''
Proxy
'''
# 代理模式
# 應用特性:需要在通信雙方中間需要一些特殊的中間操作時引用,多加一個中間控制層。
# 結構特性:建立一個中間類,創建一個對象,接收一個對象,然后把兩者聯通起來
class sender_base:
def __init__(self):
pass
def send_something(self, something):
pass
class send_class(sender_base):
def __init__(self, receiver):
self.receiver = receiver
def send_something(self, something):
print("SEND " + something + ' TO ' + self.receiver.name)
class agent_class(sender_base):
def __init__(self, receiver):
self.send_obj = send_class(receiver)
def send_something(self, something):
self.send_obj.send_something(something)
class receive_class:
def __init__(self, someone):
self.name = someone
if '__main__' == __name__:
receiver = receive_class('Burgess')
agent = agent_class(receiver)
agent.send_something('agentinfo')
print(receiver.__class__)
print(agent.__class__)
3.行為型模式
在對象的結構和對象的創建問題都解決了之后,就剩下對象的行為問題了,如果對象的行為設計的好,那么對象的行為就會更清晰,它們之間的協作效率就會提高,這里有11個具體的行為型模式可供研究,它們分別是: