一、什麼是設計模式
設計模式使得人們可以更加方便地去發展成功的軟件設計,從而能夠幫助設計者更好地完成系統設計。
設計模式通常是對於某一類軟件設計問題提供可重用的解決方案,最終目標就是幫助人們利用軟件設計師的集體經驗來設計出更加優秀的軟件,我們只要搞清楚這些設計模式,就可以完全或者說很大程度上吸收了那些蘊含在模式中的寶貴經驗,從而對軟件體系結構有了比較全面的了解,實際工作中一旦遇到具有相同背景的場合,只需要簡單地套用這些模式就可以了,從而省去了很多摸索工作。
设计模式是什么?
设计模式是对我们经常会碰到的一些编程问题可经过总结优化的可重用解决方案,換言之,设计模式是一种必须在特定情形下來实现的一种高级模板,好的设计模式应该能够实现大部分编程语言 (*如果做不到全部的话,則具体取决于语言特性),但須注意的是设计模式被用在不恰当的情形下将会造成灾难,进而带来无穷的麻烦,反之,如果设计模式被用在正确的地方,它将是你的救星。
“模式”就是为了解决一类特定问题而特别想出来的明智之举,从不同的角度看待问题进而形成的一个最通用、最灵活的解决方案,也许这些问题你之前曾经遇过或解决过,但是你的解决方案很可能没有模式这么完备。
可以通过程序设计常使用之抽象化的基本概念来理解模式之意義:抽象化可隔离任何具体细节,这么做的目的是为了将那些不变的核心部分从其他细节中分离出来,当你发现你程序中的某些部分经常因为某些原因改动,而你不想让这些改动的部分引发其他部分的改动,这时候你就需要思考把那些不会变动進行抽象化之设计,这么做不仅会使代码可维护性更高,而且会让代码更易于理解从而降低开发成本。
这里列举了三种最基本的设计模式:
- 创建模式:为适合的状况提供相应的对象创建实例化之技巧(五種型式)。
- Factory Method(工厂方法)
- Abstract Factory(抽象工厂)
- Builder(建造者)
- Prototype(原型)
- Singleton(单例)
- 结构化模式:用来处理实体之间的組成关系,使得这些实体能够更好地协同工作之技巧(七種型式)。
- Adapter Class/Object(适配器)
- Bridge(桥接)
- Composite(组合)
- Decorator(装饰)
- Facade(外观)
- Flyweight(享元)
- Proxy(代理)
- 行为模式:用于在不同的实体間进行通信交互,提供更容易灵活的技巧(十一種型式)。
- Interpreter(解释器)
- Template Method(模板方法)
- Chain of Responsibility(责任链)
- Command(命令)
- Iterator(迭代器)
- Mediator(中介者)
- Memento(备忘录)
- Observer(观察者)
- State(状态)
- Strategy(策略)
- Visitor(访问者)
三、Python與設計模式
儘管設計模式的目標是努力做到與語言的無關性,但事實上許多模式在應用時還是需要依賴於具體實現語言的某些特性,尤其是該語言的對象模型。由於《設計模式》一書採用的是C++和Smalltalk來講述設計模式,因此訪問控制符和靜態成員方法(類方法)等都可以直接使用,可惜的是這些特性在Python中都無法用到,原因是Python采了與C++完全不同的對象模式。
簡單說來,Python是一種優秀的面向對象腳本語言,它具有動態語義和快速的原型開發能力。Python豐富的工具集使得它位於傳統腳本語言(如Tcl、Perl和Scheme)和系統編程語言(如C、C++和Java)之間,既具備了腳本語言的簡單易用性,同時又能夠提供只有系統語言才可能擁有的某些高級特性。
從面向對象角度來看,Python和Smalltalk一樣都採用了完全的面向對象設計思想,其對象模型能夠支持諸如運算符重載、多重繼承等高級概念。但Python忽略了面向對象的一項基本原則,那就是數據隱藏,與C++和Java不同,Python沒有為類定義提供public、protected和private等關鍵字,這就意味着任何人都可以直接修改對象的屬性,Python之所以這麼做,也許是為了保證語法上的簡潔性,就像Python的發明人Guido van Rossum所認為的那樣:"豐富的語法帶來的負擔會多於幫助"。但在某些設計模式中,向外界隱藏數據和方法都是非常必要的,為此我們不得不利用Python對象模型提供的某些高級特性,來實現某種程度上的隱藏性。
在Python中應用設計模式的一個有利因素是它的動態類型綁定,也就是說一個對象很少只是一個類的實例,而是可以在運行時動態改變。在面向對象系統中,接口是一個基本的組成部分,對象只有通過它們的接口才能與外界進行交互。對象的接口與其功能是完全分離的,支持相同請求的不同對象針對同一請求所觸發的操作可能完全不同,這就是動態綁定的概念。動態綁定雖然看起來在一定程度上使得代碼不同那麼容易理解和維護,但它的確可以使整個軟件系統的結構顯得更加清晰和合理。
作為一門優秀的腳本語言,Python正在被越來越多的人所接受,使用Python開發的項目也越來越多,這也難怪會被大家推崇為"下一代編程語言"中的典型代表。隨着應用範圍的不斷擴展,如何在用Python開發軟件時充分利用已有的經驗和成果將成為人們關注的焦點,而設計模式作為軟件復用的一個重要方面,其價值自然是不言而喻。可問題是目前所使用的設計模式大都是人們在用Smalltalk、C++和Java開發軟件時所總結出來的,因此或多或少地帶有這些語言的影子,而要想在Python中使用這些設計模式,必須根據Python的自身特點和實際需要,靈活地加以運用。
四、Python對象模型
對一門具體的編程語言來說,在應用設計模式時影響最大的莫過於它的對象模型了,這是因為大部分設計模式都源自於C++和Java這類面向對象編程語言。要想在Python中復用這些設計模式,首先需要對Python的對象模型有一個比較清晰的認識。
4.1 類
同其它面向對象編程語言一樣,Python中的類也是一種用戶自定義的數據類型,其基本的語法格式是:
class <name>(superclass, ...): # 定義類
data = value # 共享的類變量
def method(self, ...): # 類中的方法
self.member = value # 實例的數據
類定義從關鍵字class開始,並包含整個縮進代碼塊,類中定義的方法和屬性構成了類的名字空間(name space)。一個類通常會有多個方法,它們都以關鍵字def開頭,並且第一個參數通常都是self,Python中的變量self相當於C++中的關鍵字this,其作用是傳遞一個對象的引用。
Python中的類屬性位於類的名字空間中,可以被所有的類實例所共享,這一點同C++和Java相同。訪問類屬性時不需要事先創建類的實例,直接使用類名就可以了。例如:
>>> class Friend:
default_age = 20
>>> Friend.default_age
20
除了自定義的類屬性外,Python中的每個類其實都具有一些特殊的類屬性,它們都是由Python內置對象模型所提供的。表1列出了這些類屬性:
屬性名 | 說明 |
__dict__ | 以類名空間存在之內部字典變量 |
__doc__ | 類的文檔說明字符串 |
__name__ | 類的名稱 |
__module__ | 類的模塊名 |
__bases__ | 該類所有父類組成的元組 |
4.2 實例
定義類的目的是為了創建它的實例,從面向對象的角度看,類是對數據及其相關操作的封裝,而類實例則是對現實生活中某個實體的抽象。假設定義了如下一個類:
class School:
def __init__(self, name):
self.name = name
self.students = []
def addStudent(self, student):
self.students.append(student)
要創建School類的一個實例,可以執行下面的語句:
bit = School("Beijing Institute of Technology")
在C++和Java中創建類實例時,與類具有相同名稱的構造函數被調用,而在Python中創建一個類的實例時,則是調用名為__init__的特殊方法。Python中的類實例繼承了類的所有方法和屬性,並且有自己獨立的名字空間,使用下面的方法可以訪問類實例的方法和屬性:
bit.addStudent("gary")
bit.students
Python中的對象屬性有一個非常有趣的地方,那就是使用它們之前不用像C++和Java那樣,必須先在類中進行聲明,因為這些都是可以動態創建的。作為一門動態類型語言,Python的這一特性的確非常靈活,但有時也難免產生問題。例如在許多針對接口的設計模式中,最好能事先知道對象所屬的類以便能夠調用不同的實現方法,這些在C++和Java這些強類型語言的對象模型中不難實現,但對Python來講可就不那麼簡單了,因為Python中的每個變量事實上都沒有固定的類型。
為了解決這一問題,Python的__builtin__模塊提供了兩個非常實用的內建函數:isinstance()和issubclass()。其中函數isinstance()用於測試一個對象是否是某個類的實例,如果是的話則返回1,否則返回0。其基本的語法格式是:
isinstance (object_instance, base_class)
例如:
>>> class Test:
pass
>>> inst = Test()
>>> isinstance(inst, Test)
1
而函數issubclass()則用於測試一個類是否是另一個類的子類,如果是的話則返回1,否則返回0。其基本的語法格式是:
issubclass(subclass, superclass)
例如:
>>> class TestA:
pass
>>> class TestB(TestA):
pass
>>> issubclass(TestA, TestB)
0
>>> issubclass(TestB, TestA)
1
和類一樣,Python中的每個類實例也具有一些特殊的屬性,它們都是由Python的對象模型所提供的。表2列出了這些屬性:
屬性名 | 說明 |
__dict__ | 以實例名空間存在之內部字典變量 |
__class__ | 生成該實例的類 |
__methods__ | 實例所有方法的列表 |
4.3繼承
在面向對象的程序設計中,繼承(Inheritance)允許子類從父類那裡獲得屬性和方法,同時子類可以添加或者重載其父類中的任何方法。在Python中定義繼承類的語法格式是:
class <name>(superclass, superclass, ...)
suit
例如,對於下面這個類:
class Employee:
def __init__(self, name, salary = 0):
self.name = name
self.salary = salary
def raisesalary(self, percent):
self.salary = self.salary * (1 + percent)
def work(self):
print self.name, "writes computer code"
可以為其定義如下的子類:
class Designer(Employee):
def __init__(self, name):
Employee.__init__(self, name, 5000)
def work(self):
print self.name, "writes design document"
在C++和Java的對象模型中,子類的構造函數會自動調用父類的構造函數,但在Python中卻不是這樣,你必須在子類中自行調用父類的構造函數,這就是為什麼在Designer. __init__方法中必須調用Employee.__init__方法的原因。
人們對多重繼承的看法一直褒貶不一,C++對象模型允許多重繼承,而Java對象模型則是通過接口(Interface)來間接實現多重繼承的。在對多重繼承的處理上,Python採用了和C++類似的方法,即允許多重繼承,例如:
class A:
pass
class B(A):
pass
class C:
pass
class D(B, C):
pass
4.4 多態
嚴格說來,像C++和Java這些強類型語言對象模型中的多態概念並不適用於Python,因為Python沒有提供類型聲明機制。但由於Python本身是一種動態類型語言,允許將任意值賦給任何一個變量,如果我們對多態的概念稍加擴充,將其理解為具有能同時處理多種數據類型的函數或方法,那麼Python對象模型實際上也支持經過弱化後的多態。
Python直到代碼運行之時才去決定一個變量所屬的類型,這一特性稱為運行時綁定(runtime binding)。Python解析器內部雖然也會對變量進行類型分配,但卻十分模糊,並且只有在真正使用它們時才會隱式地分配類型。例如,如果程序調用abs(num),若要求除數字之外的任何類型對變量num都沒有意義,此時變量num事實上就進行了非正式的類型分配。
能夠處理不同抽象層次的對象,是面向對象編程最重要的特性之一,也是Python的一個非常重要的組成部分。下面的例子示範了如何讓Python中的一個函數能夠同時處理多種類型的數據,在C++的對象模型中,這種多態被稱為方法重載。
class Polymorph:
def deal_int(self, arg):
print '%d is an integer' % arg
def deal_str(self, arg):
print '%s is a string' % arg
def deal(self, arg):
if type(arg) == type(1):
self.deal_int(arg)
elif type(arg) == type(' '):
self.deal_str(arg)
else:
print '%s is not an integer or a string' % arg
這樣,Polymorph類中的方法deal就可以同時處理數字和字符串了:
>>> p = Polymorph()
>>> p.deal(100)
100 is an integer
>>> p.deal("Hello World!")
Hello World! is a string
4.5 可見性
Python對象模型對可見性的處理與C++和Java完全不同。在C++和Java中,如果屬性或者方法被聲明為private,那就意味着它們只能在類中被訪問,而如果被聲明為protected,則只有該類或者其子類中的代碼能夠訪問這些屬性和方法。但在Python對象模型中,所有屬性和方法都是public的,也就是說數據沒有做相應的保護,你可以在任何地方對它們進行任意的修改。
能夠對可見性進行約束是面向對象編程的一個重要特點,其目的是使對象具有優良的封裝性:對象僅僅向外界提供訪問接口,而內部實現細節則被很好地隱藏起來。奇怪的是作為一門面向對象腳本語言,Python並沒有提供對可見性進行約束的機制,所有屬性和方法對任何人都是可見的,任何人想知道對象的內部實現細節都是可能的。雖然這樣做能夠帶來部分效率上的優化,但卻無法阻止其它程序員對已經封裝好的類進行破壞,從某種程度上這不得不說是Python帶給我們的一絲的缺憾。
直到Python 1.5,Guido才引入了名字壓縮(name mangling)的概念,使得類中的一些屬性得以局部化。在進行定義類時,如果一個屬性的名稱是以兩個下劃線開始,同時又不是以下劃線結束的,那麼它在編譯時將自動地被改寫為_類名__屬性名。例如:
class Greeting:
__data = "Hello World!"
def __init__(self, str):
Greeting.__data = str
>>> g = Greeting("Hello Gary!")
>>> dir (g)
['_Greeting__data', '__doc__', '__init__', '__module__']
從上面的顯示結果可以看出,Greeting類的屬性__data變成了_Greeting__data。雖然這樣仍然無法阻止外界對它的訪問,但的確使得訪問變得不再那麼直接了,從而在一定程序上保護了類中的數據不被外界破壞。
五、在Python中應用設計模式
《設計模式》一書總結了23個模式,依據各自的目的又被分為創建型模式(creational pattern)、結構型模式(structural pattern)和行為型模式(behavioral patterns),它們分別從對象的創建,對象間的組合結構以及對象之間如何通信交互這三個方面入手,對系統建模給予了解釋和指導。
- 創建型模式描述怎樣創建一個對象,以及如何隱藏對象創建的細節。
- 結構型模式描述類和對象之間如何進行有效的組織合成。
- 行為型模式描述類或對象之間如何通訊交互。
如果想在Python中靈活地運行這些設計模式,你必須仔細研究每一種設計模式,學習如何在Python中應用這些模式,以便在今後需要時能夠用到它們。最後,你要努力做到對各個設計模式都有非常清晰的認識,最好能夠形成自己的獨到見解,清楚哪個模式能夠解決哪個設計上的問題,並將它們真正應用到你用Python開發的軟件中去。所有的設計模式來源於實踐,最終也將付諸於實踐,只有通過實踐中你才可能掌握每個模式的精髓所在。
六、小結
設計模式就是解決軟件開發和設計過程中某個特定問題的特定模式,可作為軟件復用形式所提出之解決方案,理論上它與具體的語言無關,但實際應用時通常會依賴於語言所提供的某些特性,Python是一門優秀的面向對象腳本語言,它的對象模型會影響到部分設計模式的實現,設計模式按其目的可以被劃分成不同的種類,分別用於解決不同方面的實際問題。
轉自:http://www.ibm.com/developerworks/cn/linux/l-pypt/part1/index.html