Python設(shè)計(jì)模式(第2版)_第1頁(yè)
Python設(shè)計(jì)模式(第2版)_第2頁(yè)
Python設(shè)計(jì)模式(第2版)_第3頁(yè)
Python設(shè)計(jì)模式(第2版)_第4頁(yè)
Python設(shè)計(jì)模式(第2版)_第5頁(yè)
已閱讀5頁(yè),還剩100頁(yè)未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶(hù)提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

設(shè)計(jì)模式(第版)123456789.HYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINKHYPERLINK%(,self.age)p=Person("John",32)#pisanobjectoftypePersonprint("TypeofObject:",type(p),"MemoryAddress:",id(p))上述代碼的輸出結(jié)果如圖1-1所示。圖1-1?面向?qū)ο缶幊痰闹饕拍瞵F(xiàn)在我們已經(jīng)了解了面向?qū)ο缶幊痰幕A(chǔ)知識(shí),下面讓我們深入了解面向?qū)ο缶幊痰闹饕拍?。封裝的主要特點(diǎn)如下所示。對(duì)象的行為對(duì)于外部世界來(lái)說(shuō)是不可見(jiàn)的,或者說(shuō)對(duì)象的狀態(tài)信息是私密的??蛻?hù)端不能通過(guò)直接操作來(lái)改變對(duì)象的內(nèi)部狀態(tài)。相反,客戶(hù)端需要通過(guò)發(fā)送消息來(lái)請(qǐng)求對(duì)象改變其內(nèi)部狀態(tài)。對(duì)象可以根據(jù)請(qǐng)求的類(lèi)型,通過(guò)特定的成員函數(shù)(例如get和set)改變它們的內(nèi)部狀態(tài),以做出相應(yīng)的響應(yīng)。在Python中,封裝(數(shù)據(jù)和方法的隱藏)的概念不是隱式的,因?yàn)樗鼪](méi)有提供封裝所需的關(guān)鍵字,諸如public、private和protected(在諸如C++或Java之類(lèi)的語(yǔ)言中,都提供了這些關(guān)鍵字)。當(dāng)然,如果在變量或函數(shù)名前面加上前綴__,就可以將其可訪問(wèn)性變?yōu)樗接?。多態(tài)的主要特征如下所示。多態(tài)有兩種類(lèi)型。對(duì)象根據(jù)輸入?yún)?shù)提供方法的不同實(shí)現(xiàn)。不同類(lèi)型的對(duì)象可以使用相同的接口。對(duì)于Python來(lái)說(shuō),多態(tài)是該語(yǔ)言的內(nèi)置功能。例如,操作符“+”可以應(yīng)用于兩個(gè)整數(shù)以進(jìn)行加法運(yùn)算,也可以應(yīng)用于字符串來(lái)連接它們。在下面的示例中,字符串、元組或列表都可以通過(guò)整數(shù)索引進(jìn)行訪問(wèn)。它為我們展示了Python內(nèi)置類(lèi)型的多態(tài):a="John"b=(1,2,3)c=[3,4,6,8,9]print(a[1],b[0],c[2])以下幾點(diǎn)有助于我們更好地理解繼承過(guò)程。繼承表示一個(gè)類(lèi)可以繼承父類(lèi)的(大部分)功能。繼承被描述為一個(gè)重用基類(lèi)中定義的功能并允許對(duì)原始軟件的實(shí)現(xiàn)進(jìn)行獨(dú)立擴(kuò)展的選項(xiàng)。繼承可以利用不同類(lèi)的對(duì)象之間的關(guān)系建立層次結(jié)構(gòu)。與Java不同,Python支持多重繼承(繼承多個(gè)基類(lèi))。在下面的代碼示例中,類(lèi)A是基類(lèi),類(lèi)B繼承了類(lèi)A的特性。因此,類(lèi)B的對(duì)象可以訪問(wèn)類(lèi)A的方法:classA:defa1(self):print("a1")classB(A):defb(self):print("b")?b=B()b.a1()抽象的主要特征如下所示:它提供了一個(gè)簡(jiǎn)單的客戶(hù)端接口,客戶(hù)端可以通過(guò)該接口與類(lèi)的對(duì)象進(jìn)行交互,并可以調(diào)用該接口中定義的各個(gè)方法。它將內(nèi)部類(lèi)的復(fù)雜性抽象為一個(gè)接口,這樣客戶(hù)端就不需要知道內(nèi)部實(shí)現(xiàn)了。在下面的例子中,我們通過(guò)add()方法對(duì)類(lèi)Adder的內(nèi)部細(xì)節(jié)進(jìn)行了抽象處理:classAdder:def__init__(self):self.sum=0defadd(self,value):self.sum+=valueacc=Adder()foriinrange(99):acc.add(i)print(acc.sum)1組合是指以下幾點(diǎn)。它是一種將對(duì)象或類(lèi)組合成更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)或軟件實(shí)現(xiàn)的方法。在組合中,一個(gè)對(duì)象可用于調(diào)用其他模塊中的成員函數(shù),這樣一來(lái),無(wú)需通過(guò)繼承就可以實(shí)現(xiàn)基本功能的跨模塊使用。在下面的示例中,類(lèi)A的對(duì)象被組合到了類(lèi)B中:classA(object):defa1(self):print("a1")classB(object):defb(self):print("b")A().a1()objectB=B()objectB.b()1?面向?qū)ο蟮脑O(shè)計(jì)原則現(xiàn)在,讓我們探討另一組概念,這些概念對(duì)我們接下來(lái)的學(xué)習(xí)至關(guān)重要。這些只是面向?qū)ο蟮脑O(shè)計(jì)原則,當(dāng)我們深入細(xì)致地學(xué)習(xí)設(shè)計(jì)模式時(shí),將作為工具箱使用。1封閉原則開(kāi)放/封閉原則規(guī)定,類(lèi)或?qū)ο蠹捌浞椒▽?duì)于擴(kuò)展來(lái)說(shuō),應(yīng)該是開(kāi)放的,但是對(duì)于修改來(lái)說(shuō),應(yīng)該是封閉的。簡(jiǎn)單地說(shuō),這意味著當(dāng)你開(kāi)發(fā)軟件應(yīng)用的時(shí)候,一定確保以通用的方式來(lái)編寫(xiě)類(lèi)或模塊,以便每當(dāng)需要擴(kuò)展類(lèi)或?qū)ο笮袨榈臅r(shí)候不必修改類(lèi)本身。相反,類(lèi)的簡(jiǎn)單擴(kuò)展將有助于建立新的行為。例如,開(kāi)放/封閉原則能夠在下列情形中表現(xiàn)得淋漓盡致:為了實(shí)現(xiàn)所需行為,用戶(hù)必須通過(guò)擴(kuò)展抽象基類(lèi)來(lái)創(chuàng)建類(lèi)的實(shí)現(xiàn),而不是通過(guò)修改抽象類(lèi)。本設(shè)計(jì)原則的優(yōu)點(diǎn)如下?,F(xiàn)有的類(lèi)不會(huì)被修改,因此退化的可能性較小。它還有助于保持以前代碼的向后兼容性。?控制反轉(zhuǎn)原則控制反轉(zhuǎn)原則是指,高層級(jí)的模塊不應(yīng)該依賴(lài)于低層級(jí)的模塊,它們應(yīng)該都依賴(lài)于抽象。細(xì)節(jié)應(yīng)該依賴(lài)于抽象,而不是抽象依賴(lài)于細(xì)節(jié)。該原則建議任何兩個(gè)模塊都不應(yīng)以緊密方式相互依賴(lài)。事實(shí)上,基本模塊和從屬模塊應(yīng)當(dāng)在它們之間提供一個(gè)抽象層來(lái)耦合。這個(gè)原則還建議,類(lèi)的細(xì)節(jié)應(yīng)該描繪抽象。在某些情況下,這種觀念會(huì)反轉(zhuǎn),也就是實(shí)現(xiàn)細(xì)節(jié)本身決定了抽象,這種情況是應(yīng)該避免的。控制反轉(zhuǎn)原則的優(yōu)點(diǎn)如下。消弱了模塊間的緊耦合,因此消除了系統(tǒng)中的復(fù)雜性/剛性。由于在依賴(lài)模塊之間有一個(gè)明確的抽象層(由鉤子或參數(shù)提供),因此便于通過(guò)更好的方式處理模塊之間的依賴(lài)關(guān)系。?接口隔離原則接口隔離原則規(guī)定,客戶(hù)端不應(yīng)該依賴(lài)于它們不需要使用的接口。接口隔離原則的意思就是,軟件開(kāi)發(fā)人員應(yīng)該仔細(xì)地處理接口。例如,它提醒開(kāi)發(fā)人員/架構(gòu)師開(kāi)發(fā)的方法要與特定功能緊密相關(guān)。如果存在與接口無(wú)關(guān)的方法,那么依賴(lài)于該接口的類(lèi)就必須實(shí)現(xiàn)它,實(shí)際上這是毫無(wú)必要的。例如,一個(gè)Pizza接口不應(yīng)該提供名為add_chicken()的方法。基于Pizza接口的VegPizza類(lèi)不應(yīng)該強(qiáng)制實(shí)現(xiàn)該方法。本設(shè)計(jì)原則的優(yōu)點(diǎn)如下所示。它強(qiáng)制開(kāi)發(fā)人員編寫(xiě)“瘦身型”接口,并使方法與接口緊密相關(guān)。防止向接口中隨意添加方法。?單一職責(zé)原則單一職責(zé)的含義是:類(lèi)的職責(zé)單一,引起類(lèi)變化的原因單一。這個(gè)原則是說(shuō),當(dāng)我們開(kāi)發(fā)類(lèi)時(shí),它應(yīng)該為特定的功能服務(wù)。如果一個(gè)類(lèi)實(shí)現(xiàn)了兩個(gè)功能,那么最好將它們分開(kāi)。也就是說(shuō),功能才是改變的理由。例如,一個(gè)類(lèi)可以因?yàn)樗栊袨榈淖兓M(jìn)行修改,但是如果一個(gè)類(lèi)由于兩個(gè)因素(基本上是兩個(gè)功能的改變)而改變,那么該類(lèi)就應(yīng)該進(jìn)行相應(yīng)的分割。本設(shè)計(jì)原則的優(yōu)點(diǎn)如下所示。每當(dāng)一個(gè)功能發(fā)生變化時(shí),除了特定的類(lèi)需要改變外,其他類(lèi)無(wú)需變動(dòng)。此外,如果一個(gè)類(lèi)有多種功能,那么依賴(lài)它的類(lèi)必定會(huì)由于多種原因而經(jīng)歷多次修改,這是應(yīng)該避免的。1?替換原則替換原則規(guī)定,派生類(lèi)必須能夠完全取代基類(lèi)。這個(gè)原則很簡(jiǎn)單,當(dāng)應(yīng)用程序開(kāi)發(fā)人員編寫(xiě)派生類(lèi)時(shí),該原則的含義就是他們應(yīng)該擴(kuò)展基類(lèi)。此外,它還建議派生類(lèi)應(yīng)該盡可能對(duì)基類(lèi)封閉,以至于派生類(lèi)本身可以替換基類(lèi),而無(wú)需修改任何代碼。1?設(shè)計(jì)模式的概念現(xiàn)在終于到了談?wù)撛O(shè)計(jì)模式的時(shí)候了!那么,什么是設(shè)計(jì)模式呢?設(shè)計(jì)模式是由GoF(GangofFour)首先提出的,根據(jù)他們的觀點(diǎn),設(shè)計(jì)模式就是解決特定問(wèn)題的解決方案。如果你想進(jìn)一步了解其定義,請(qǐng)參閱DesignPatterns:ElementsofReusableObject-OrientedSoftware一書(shū),而GoF指的就是該書(shū)的4位作者。這本書(shū)的作者是ErichGamma、RichardHelm、RalphJohnson和JohnVlissides,前言由GradyBooch撰寫(xiě)。本書(shū)涵蓋了軟件設(shè)計(jì)方面常見(jiàn)問(wèn)題的軟件工程解決方案,書(shū)中提供了23種設(shè)計(jì)模式,并首次利用Java語(yǔ)言給出了程序?qū)崿F(xiàn)。設(shè)計(jì)模式本身是一種發(fā)現(xiàn),而不是一種發(fā)明。設(shè)計(jì)模式的主要特點(diǎn)如下所示。它們是語(yǔ)言無(wú)關(guān)的,可以用多種語(yǔ)言實(shí)現(xiàn)。它們是動(dòng)態(tài)的,隨時(shí)會(huì)有新的模式引入。它們可以進(jìn)行定制,因此對(duì)開(kāi)發(fā)人員非常有用。當(dāng)你第一次聽(tīng)說(shuō)設(shè)計(jì)模式時(shí),可能會(huì)有以下聯(lián)想。這是針對(duì)目前所有設(shè)計(jì)問(wèn)題的靈丹妙藥。這是一個(gè)卓越的、特別明智的解決問(wèn)題的方法。許多軟件開(kāi)發(fā)專(zhuān)家都贊同這些解決方案。在設(shè)計(jì)方面,有許多東西是可重復(fù)的,因此才使用了“模式”這個(gè)詞。你必須嘗試解決設(shè)計(jì)模式想要解決的問(wèn)題,也許你的解決方案并不完善,而我們所追求的完善性正是設(shè)計(jì)模式中固有的或隱含的。當(dāng)我們提到完整性時(shí),它可以指許多因素,例如設(shè)計(jì)、可擴(kuò)展性、重用、內(nèi)存利用率等。從本質(zhì)上說(shuō),設(shè)計(jì)模式就是從別人的成功而非自己的失敗中進(jìn)行學(xué)習(xí)!關(guān)于設(shè)計(jì)模式的另一個(gè)有趣的討論是,什么時(shí)候使用它們?它是應(yīng)用在軟件開(kāi)發(fā)生命周期(SoftwareDevelopmentLifeCycle,SDLC)的分析或設(shè)計(jì)階段嗎?有趣的是,設(shè)計(jì)模式是已知問(wèn)題的解決方案。因此,設(shè)計(jì)模式在分析或設(shè)計(jì)階段非常有用,并且如預(yù)期的那樣,在開(kāi)發(fā)階段也非常有用,因?yàn)樗鼈兣c應(yīng)用的編程直接相關(guān)。1?設(shè)計(jì)模式的優(yōu)點(diǎn)設(shè)計(jì)模式的優(yōu)點(diǎn)如下所示。它們可以在多個(gè)項(xiàng)目中重復(fù)使用。問(wèn)題可以在架構(gòu)級(jí)別得到解決。它們都經(jīng)過(guò)了時(shí)間的驗(yàn)證和良好的證明,是開(kāi)發(fā)人員和架構(gòu)師的寶貴經(jīng)驗(yàn)。它們具有可靠性和依賴(lài)性。1?設(shè)計(jì)模式的分類(lèi)不是每一段代碼或每一種設(shè)計(jì)都可以叫作設(shè)計(jì)模式。例如,解決一個(gè)問(wèn)題的編程構(gòu)造或數(shù)據(jù)結(jié)構(gòu)就不能被稱(chēng)為模式。下面讓我們通過(guò)一種簡(jiǎn)單的方式來(lái)理解這些術(shù)語(yǔ)。代碼段:用某種語(yǔ)言編寫(xiě)的一段具有特定用途的代碼,例如,它可以是Python中的DB連接代碼。設(shè)計(jì):用來(lái)解決某個(gè)特定問(wèn)題的優(yōu)秀解決方案。標(biāo)準(zhǔn):這是一種解決某類(lèi)問(wèn)題的方法,它非常通用,并且適用于當(dāng)前的情況。模式:這是一個(gè)經(jīng)過(guò)時(shí)間考驗(yàn)的、高效、可擴(kuò)展的解決方案,能夠解決一類(lèi)已知問(wèn)題。1?上下文設(shè)計(jì)模式的適用性為了有效地使用設(shè)計(jì)模式,應(yīng)用程序開(kāi)發(fā)人員必須了解設(shè)計(jì)模式所適用的上下文。我們可以將上下文分為以下幾種主要類(lèi)型。參與者:它們是在設(shè)計(jì)模式中用到的類(lèi)。類(lèi)可以在模式中扮演不同的角色,以完成多個(gè)目標(biāo)。非功能需求:諸如內(nèi)存優(yōu)化、可用性和性能等需求都屬于此類(lèi)型。由于這些因素影響整個(gè)軟件解決方案,因此至關(guān)重要。權(quán)衡:并非所有的設(shè)計(jì)模式都適合于應(yīng)用程序開(kāi)發(fā),因此需要權(quán)衡。這些是在應(yīng)用程序中使用設(shè)計(jì)模式時(shí)所做的決策。結(jié)果:如果上下文不合適,設(shè)計(jì)模式可能對(duì)代碼的其他部分產(chǎn)生負(fù)面影響。開(kāi)發(fā)人員應(yīng)該了解設(shè)計(jì)模式的結(jié)果和用途。1?動(dòng)態(tài)語(yǔ)言的設(shè)計(jì)模式就像Lisp一樣,Python也是一種動(dòng)態(tài)語(yǔ)言。Python的動(dòng)態(tài)特性如下所示。類(lèi)型或類(lèi)是運(yùn)行時(shí)對(duì)象。變量可以根據(jù)賦值來(lái)確定類(lèi)型,并且類(lèi)型可以在運(yùn)行時(shí)改變。例如,a=5和a="John",變量a在運(yùn)行時(shí)被賦值,而且其類(lèi)型也發(fā)生了變化。動(dòng)態(tài)語(yǔ)言在類(lèi)限制方面具有更大的靈活性。例如,在Python中,多態(tài)性是該語(yǔ)言所固有的,并沒(méi)有諸如private和protected之類(lèi)的關(guān)鍵字,因?yàn)槟J(rèn)情況下一切都是公共的??梢允褂脛?dòng)態(tài)語(yǔ)言輕松實(shí)現(xiàn)設(shè)計(jì)模式的用例。?模式的分類(lèi)GoF在他們的設(shè)計(jì)模式書(shū)中講到了23種設(shè)計(jì)模式,并將它們分為三大類(lèi)。創(chuàng)建型模式。結(jié)構(gòu)型模式。行為型模式。模式的分類(lèi)主要基于對(duì)象的創(chuàng)建方式、軟件應(yīng)用程序中類(lèi)和對(duì)象的構(gòu)造方式,同時(shí)還涉及對(duì)象之間的交互方式。我們將在本節(jié)中詳細(xì)介紹所有類(lèi)型。?創(chuàng)建型模式以下是創(chuàng)建型模式的性質(zhì)。它們的運(yùn)行機(jī)制基于對(duì)象的創(chuàng)建方式。它們將對(duì)象創(chuàng)建的細(xì)節(jié)隔離開(kāi)來(lái)。代碼與所創(chuàng)建的對(duì)象的類(lèi)型無(wú)關(guān)。單例模式是創(chuàng)建型模式的一個(gè)例子。?結(jié)構(gòu)型模式以下是結(jié)構(gòu)型模式的性質(zhì)。它們致力于設(shè)計(jì)出能夠通過(guò)組合獲得更強(qiáng)大功能的對(duì)象和類(lèi)的結(jié)構(gòu)。重點(diǎn)是簡(jiǎn)化結(jié)構(gòu)并識(shí)別類(lèi)和對(duì)象之間的關(guān)系。它們主要關(guān)注類(lèi)的繼承和組合。適配器模式是結(jié)構(gòu)型模式的一個(gè)例子。?行為型模式行為型模式具有下列性質(zhì)。它們關(guān)注對(duì)象之間的交互以及對(duì)象的響應(yīng)性。對(duì)象應(yīng)該能夠交互,同時(shí)仍然保持松散耦合。觀察者模式是行為型模式的一個(gè)例子。在本章中,我們介紹了面向?qū)ο缶幊痰幕靖拍睿鐚?duì)象、類(lèi)、變量,并通過(guò)示例代碼解釋了面向?qū)ο缶幊讨T如多態(tài)、繼承和抽象等特性。然后,我們講解了在設(shè)計(jì)應(yīng)用程序時(shí),作為開(kāi)發(fā)人員或架構(gòu)師應(yīng)遵循的面向?qū)ο蟮脑O(shè)計(jì)原則。接著,我們深入探討了設(shè)計(jì)模式及其應(yīng)用,同時(shí),介紹了其適用的上下文和分類(lèi)。在閱讀本章之后,讀者就為將來(lái)進(jìn)一步深入學(xué)習(xí)各種設(shè)計(jì)模式打下了牢固的基礎(chǔ)。第2章?單例設(shè)計(jì)模式在上一章中,我們探討了設(shè)計(jì)模式及其分類(lèi)。我們都知道,設(shè)計(jì)模式可以分三大類(lèi):結(jié)構(gòu)型、行為型和創(chuàng)建型模式。在這一章中,我們將學(xué)習(xí)單例設(shè)計(jì)模式。單例設(shè)計(jì)模式是應(yīng)用開(kāi)發(fā)過(guò)程中最簡(jiǎn)單和最著名的一種創(chuàng)建型設(shè)計(jì)模式。本章首先會(huì)對(duì)單例模式進(jìn)行簡(jiǎn)要介紹,然后提供一個(gè)采用了該模式的實(shí)際例子,在Python代碼示例的幫助下,對(duì)其進(jìn)行深入的剖析。此外,本章還會(huì)介紹Monostate(或者Borg)設(shè)計(jì)模式,它是單例設(shè)計(jì)模式的一個(gè)變種。在本章中,我們將會(huì)涉及以下主題:理解單例設(shè)計(jì)模式;單例模式實(shí)例;單例設(shè)計(jì)模式的Python實(shí)現(xiàn);Monostate(Borg)模式。在本章的結(jié)尾部分,我們將對(duì)單例模式進(jìn)行簡(jiǎn)要總結(jié)。這將有助于讀者針對(duì)單例設(shè)計(jì)模式的各個(gè)方面進(jìn)行獨(dú)立思考。2?理解單例設(shè)計(jì)模式單例模式提供了這樣一個(gè)機(jī)制,即確保類(lèi)有且只有一個(gè)特定類(lèi)型的對(duì)象,并提供全局訪問(wèn)點(diǎn)。因此,單例模式通常用于下列情形,例如日志記錄或數(shù)據(jù)庫(kù)操作、打印機(jī)后臺(tái)處理程序,以及其他程序——該程序運(yùn)行過(guò)程中只能生成一個(gè)實(shí)例,以避免對(duì)同一資源產(chǎn)生相互沖突的請(qǐng)求。例如,我們可能希望使用一個(gè)數(shù)據(jù)庫(kù)對(duì)象對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,以維護(hù)數(shù)據(jù)的一致性;或者希望使用一個(gè)日志類(lèi)的對(duì)象,將多項(xiàng)服務(wù)的日志信息按照順序轉(zhuǎn)儲(chǔ)到一個(gè)特定的日志文件中。簡(jiǎn)言之,單例設(shè)計(jì)模式的意圖如下所示。確保類(lèi)有且只有一個(gè)對(duì)象被創(chuàng)建。為對(duì)象提供一個(gè)訪問(wèn)點(diǎn),以使程序可以全局訪問(wèn)該對(duì)象??刂乒蚕碣Y源的并行訪問(wèn)。圖2-1是單例模式的UML圖。圖2-1實(shí)現(xiàn)單例模式的一個(gè)簡(jiǎn)單方法是,使構(gòu)造函數(shù)私有化,并創(chuàng)建一個(gè)靜態(tài)方法來(lái)完成對(duì)象的初始化。這樣,對(duì)象將在第一次調(diào)用時(shí)創(chuàng)建,此后,這個(gè)類(lèi)將返回同一個(gè)對(duì)象。在使用Python的時(shí)候,我們的實(shí)現(xiàn)方式要有所變通,因?yàn)樗鼰o(wú)法創(chuàng)建私有的構(gòu)造函數(shù)。下面,我們一起看看如何利用Python語(yǔ)言來(lái)實(shí)現(xiàn)單例模式。利用實(shí)現(xiàn)經(jīng)典的單例模式下面是基于Pythonv3.5的單例模式實(shí)現(xiàn)代碼,它主要完成了兩件事情。12.只允許Singleton類(lèi)生成一個(gè)實(shí)例。.如果已經(jīng)有一個(gè)實(shí)例了,我們會(huì)重復(fù)提供同一個(gè)對(duì)象。具體代碼如下所示:classSingleton(object):def__new__(cls):ifnothasattr(cls,'instance'):cls.instance=super(Singleton,cls).__new__(cls)returncls.instance?s=Singleton()print("Objectcreated",s)s1=Singleton()print("Objectcreated",s1)圖2-2是以上代碼的輸出結(jié)果。圖2-2在上面的代碼中,我們通過(guò)覆蓋__new__方法(Python用于實(shí)例化對(duì)象的特殊方法)來(lái)控制對(duì)象的創(chuàng)建。對(duì)象s就是由__new__方法創(chuàng)建的,但在創(chuàng)建之前,該方法會(huì)檢查對(duì)象是否已存在。方法hasattr(Python的特殊方法,用來(lái)了解對(duì)象是否具有某個(gè)屬性)用于查看對(duì)象cls是否具有屬性instance,該屬性的作用是檢查該類(lèi)是否已經(jīng)生成了一個(gè)對(duì)象。當(dāng)對(duì)象s1被請(qǐng)求的時(shí)候,hasattr()發(fā)現(xiàn)對(duì)象已經(jīng)存在,所以,對(duì)象s1將被分配已有的對(duì)象實(shí)例(地址位于0x102078ba8)。?單例模式中的懶漢式實(shí)例化單例模式的用例之一就是懶漢式實(shí)例化。例如,在導(dǎo)入模塊的時(shí)候,我們可能會(huì)無(wú)意中創(chuàng)建一個(gè)對(duì)象,但當(dāng)時(shí)根本用不到它。懶漢式實(shí)例化能夠確保在實(shí)際需要時(shí)才創(chuàng)建對(duì)象。所以,懶漢式實(shí)例化是一種節(jié)約資源并僅在需要時(shí)才創(chuàng)建它們的方式。在下面的代碼示例中,執(zhí)行s=singleton()的時(shí)候,它會(huì)調(diào)用__init__方法,但沒(méi)有新的對(duì)象被創(chuàng)建。然而,實(shí)際的對(duì)象創(chuàng)建發(fā)生在調(diào)用Singleton.getInstance()的時(shí)候,我們正是通過(guò)這種方式來(lái)實(shí)現(xiàn)懶漢式實(shí)例化的。classSingleton:__instance=Nonedef__init__(self):ifnotSingleton.__instance:print("__init__methodcalled..")else:print("Instancealreadycreated:",self.getInstance())classmethod@defgetInstance(cls):ifnotcls.__instance:cls.__instance=Singleton()returncls.__instances=Singleton()##classinitialized,butobjectnotcreatedprint("Objectcreated",Singleton.getInstance())#Objectgetscreatedheres1=Singleton()##instancealreadycreated?模塊級(jí)別的單例模式默認(rèn)情況下,所有的模塊都是單例,這是由Python的導(dǎo)入行為所決定的。Python通過(guò)下列方式來(lái)工作。123.檢查一個(gè)Python模塊是否已經(jīng)導(dǎo)入。.如果已經(jīng)導(dǎo)入,則返回該模塊的對(duì)象。如果還沒(méi)有導(dǎo)入,則導(dǎo)入該模塊,并實(shí)例化。.因此,當(dāng)模塊被導(dǎo)入的時(shí)候,它就會(huì)被初始化。然而,當(dāng)同一個(gè)模塊被再次導(dǎo)入的時(shí)候,它不會(huì)再次初始化,因?yàn)閱卫J街荒苡幸粋€(gè)對(duì)象,所以,它會(huì)返回同一個(gè)對(duì)象。2單例模式這里我們討論的是GoF(theGangofFour,GoF)編寫(xiě)的設(shè)計(jì)模式圖書(shū)中的“第1章,設(shè)計(jì)模式入門(mén)”中的相關(guān)內(nèi)容。GoF(theGangofFour,GoF)的單例設(shè)計(jì)模式是指,一個(gè)類(lèi)有且只有一個(gè)對(duì)象。然而,根據(jù)AlexMartelli的說(shuō)法,通常程序員需要的是讓實(shí)例共享相同的狀態(tài)。他建議開(kāi)發(fā)人員應(yīng)該關(guān)注狀態(tài)和行為,而不是同一性。由于該概念基于所有對(duì)象共享相同狀態(tài),因此它也被稱(chēng)為Monostate(單態(tài))模式。Monostate模式可以通過(guò)Python輕松實(shí)現(xiàn)。在下面的代碼中,我們將類(lèi)變量__shared_state賦給了變量__dict__(它是Python的一個(gè)特殊變量)。Python使用__dict__存儲(chǔ)一個(gè)類(lèi)所有對(duì)象的狀態(tài)。在下面的代碼中,我們故意把__shared_state賦給所有已經(jīng)創(chuàng)建的實(shí)例。所以,如果我們創(chuàng)建了兩個(gè)實(shí)例“b”和“b1”,我們將得到兩個(gè)不同的對(duì)象,這一點(diǎn)與單例模式大為不同,后者只能生成一個(gè)對(duì)象。然而,對(duì)象的狀態(tài),即b.__dict__和b.__dict__卻是相同的?,F(xiàn)在,就算對(duì)象b的對(duì)象變量x發(fā)生了變化,這個(gè)變化也會(huì)復(fù)制到被所有對(duì)象共享的__dict__變量,即b1的變量x的值也會(huì)從1變?yōu)?。classBorg:__shared_state={"1":"2"}def__init__(self):self.x=1self.__dict__=self.__shared_statepassb=Borg()b1=Borg()b.x=4print("BorgObject'b':",b)##bandb1aredistinctobjectsprint("BorgObject'b1':",b1)print("ObjectState'b':",b.__dict__)##bandb1sharesamestateprint("ObjectState'b1':",b1.__dict__)圖2-3是以上代碼的輸出。圖2-3除此之外,我們還可以通過(guò)修改__new__方法本身來(lái)實(shí)現(xiàn)Borg模式。我們知道,__new__方法是用來(lái)創(chuàng)建對(duì)象的實(shí)例的,具體如下所示:classBorg(object):_shared_state={}def__new__(cls,*args,**kwargs):obj=super(Borg,cls).__new__(cls,*args,**kwargs)obj.__dict__=cls._shared_statereturnobj?單例和元類(lèi)讓我們先來(lái)了解一下元類(lèi)。元類(lèi)是一個(gè)類(lèi)的類(lèi),這意味著該類(lèi)是它的元類(lèi)的實(shí)例。使用元類(lèi),程序員有機(jī)會(huì)從預(yù)定義的Python類(lèi)創(chuàng)建自己類(lèi)型的類(lèi)。例如,如果你有一個(gè)對(duì)象MyClass,你可以創(chuàng)建一個(gè)元類(lèi)MyKls,它按照你需要的方式重新定義MyClass的行為。下面,讓我們來(lái)深入介紹它們。在Python中,一切皆對(duì)象。如果我們說(shuō)a=5,則type(a)返回<type'int'>,這意味著a是int類(lèi)型。但是,type(int)返回<type'type'>,這表明存在一個(gè)元類(lèi),因?yàn)閕nt是type類(lèi)型的類(lèi)。類(lèi)的定義由它的元類(lèi)決定,所以當(dāng)我們用類(lèi)A創(chuàng)建一個(gè)類(lèi)時(shí),Python通過(guò)A=type(name,bases,dict)創(chuàng)建它。name:這是類(lèi)的名稱(chēng)。base:這是基類(lèi)。dict:這是屬性變量?,F(xiàn)在,如果一個(gè)類(lèi)有一個(gè)預(yù)定義的元類(lèi)(名為Metals),那么Python就會(huì)通過(guò)A=MetaKls(name,bases,dict)來(lái)創(chuàng)建類(lèi)。讓我們看看在Python3.5中的一個(gè)示例元類(lèi)的實(shí)現(xiàn):classMyInt(type):def__call__(cls,*args,**kwds):print("*****Here'sMyint*****",args)print("Nowdowhateveryouwantwiththeseobjects...")returntype.__call__(cls,*args,**kwds)classint(metaclass=MyInt):def__init__(self,x,y):self.x=xself.y=yi=int(4,5)圖2-4是上述代碼的輸出。圖2-4對(duì)于已經(jīng)存在的類(lèi)來(lái)說(shuō),當(dāng)需要?jiǎng)?chuàng)建對(duì)象時(shí),將調(diào)用Python的特殊方法__call__。在這段代碼中,當(dāng)我們使用int(4,5)實(shí)例化int類(lèi)時(shí),MyInt元類(lèi)的__call__方法將被調(diào)用,這意味著現(xiàn)在元類(lèi)控制著對(duì)象的實(shí)例化。前面的思路同樣適用于單例設(shè)計(jì)模式。由于元類(lèi)對(duì)類(lèi)創(chuàng)建和對(duì)象實(shí)例化有更多的控制權(quán),所以它可以用于創(chuàng)建單例。(注意:為了控制類(lèi)的創(chuàng)建和初始化,元類(lèi)將覆蓋__new__和__init__方法。)以下示例代碼能夠更好地幫我們解釋基于元類(lèi)的單例實(shí)現(xiàn):classMetaSingleton(type):_instances={}def__call__(cls,*args,**kwargs):ifclsnotincls._instances:cls._instances[cls]=super(MetaSingleton,\cls).__call__(*args,**kwargs)returncls._instances[cls]classLogger(metaclass=MetaSingleton):passlogger1=Logger()logger2=Logger()print(logger1,logger2)2?單例模式Ⅰ作為一個(gè)實(shí)際的用例,我們將通過(guò)一個(gè)數(shù)據(jù)庫(kù)應(yīng)用程序來(lái)展示單例的應(yīng)用。這里不妨以需要對(duì)數(shù)據(jù)庫(kù)進(jìn)行多種讀取和寫(xiě)入操作的云服務(wù)為例進(jìn)行講解。完整的云服務(wù)被分解為多個(gè)服務(wù),每個(gè)服務(wù)執(zhí)行不同的數(shù)據(jù)庫(kù)操作。針對(duì)UI(Web應(yīng)用程序)上的操作將導(dǎo)致調(diào)用API,最終產(chǎn)生相應(yīng)的DB操作。很明顯,跨不同服務(wù)的共享資源是數(shù)據(jù)庫(kù)本身。因此,如果我們需要更好地設(shè)計(jì)云服務(wù),必須注意以下幾點(diǎn)。數(shù)據(jù)庫(kù)中操作的一致性,即一個(gè)操作不應(yīng)與其他操作發(fā)生沖突。優(yōu)化數(shù)據(jù)庫(kù)的各種操作,以提高內(nèi)存和CPU的利用率。這里提供了一個(gè)示例Python實(shí)現(xiàn):importsqlite3classMetaSingleton(type):_instances={}def__call__(cls,*args,**kwargs):ifclsnotincls._instances:cls._instances[cls]=super(MetaSingleton,\cls).__call__(*args,**kwargs)returncls._instances[cls]classDatabase(metaclass=MetaSingleton):connection=Nonedefconnect(self):ifself.connectionisNone:self.connection=sqlite3.connect("db.sqlite3")self.cursorobj=self.connection.cursor()returnself.cursorobjdb1=Database().connect()db2=Database().connect()print("DatabaseObjectsDB1",db1)print("DatabaseObjectsDB2",db2)上面代碼的輸出如圖2-5所示。圖2-5通過(guò)閱讀上面的代碼,我們會(huì)發(fā)現(xiàn)以下幾點(diǎn)。1.我們以MetaSingleton為名創(chuàng)建了一個(gè)元類(lèi)。就像在上一節(jié)中解釋的那樣,Python的特殊方法__call__可以通過(guò)元類(lèi)創(chuàng)建單例。2.?dāng)?shù)據(jù)庫(kù)類(lèi)由MetaSingleton類(lèi)裝飾后,其行為就會(huì)表現(xiàn)為單例。因此,當(dāng)數(shù)據(jù)庫(kù)類(lèi)被實(shí)例化時(shí),它只創(chuàng)建一個(gè)對(duì)象。3.當(dāng)Web應(yīng)用程序?qū)?shù)據(jù)庫(kù)執(zhí)行某些操作時(shí),它會(huì)多次實(shí)例化數(shù)據(jù)庫(kù)類(lèi),但只創(chuàng)建一個(gè)對(duì)象。因?yàn)橹挥幸粋€(gè)對(duì)象,所以對(duì)數(shù)據(jù)庫(kù)的調(diào)用是同步的。此外,這樣還能夠節(jié)約系統(tǒng)資源,并且可以避免消耗過(guò)多的內(nèi)存或CPU資源。假如我們要開(kāi)發(fā)的不是單個(gè)Web應(yīng)用程序,而是集群化的情形,即多個(gè)Web應(yīng)用共享單個(gè)數(shù)據(jù)庫(kù)。當(dāng)然,單例在這種情況下好像不太好使,因?yàn)槊吭黾右粋€(gè)Web應(yīng)用程序,就要新建一個(gè)單例,添加一個(gè)新的對(duì)象來(lái)查詢(xún)數(shù)據(jù)庫(kù)。這導(dǎo)致數(shù)據(jù)庫(kù)操作無(wú)法同步,并且要耗費(fèi)大量的資源。在這種情況下,數(shù)據(jù)庫(kù)連接池比實(shí)現(xiàn)單例要好得多。2?單例模式Ⅱ讓我們考慮另一種情況,即為基礎(chǔ)設(shè)施提供運(yùn)行狀況監(jiān)控服務(wù)(就像Nagios所作的那樣)。我們創(chuàng)建了HealthCheck類(lèi),它作為單例實(shí)現(xiàn)。我們還要維護(hù)一個(gè)被監(jiān)控的服務(wù)器列表。當(dāng)一個(gè)服務(wù)器從這個(gè)列表中刪除時(shí),監(jiān)控軟件應(yīng)該覺(jué)察到這一情況,并從被監(jiān)控的服務(wù)器列表中將其刪除。在下面的代碼中,hc1和hc2對(duì)象與單例中的類(lèi)相同。我們可以使用addServer()方法將服務(wù)器添加到基礎(chǔ)設(shè)施中,以進(jìn)行運(yùn)行狀況檢查。首先,通過(guò)迭代對(duì)這些服務(wù)器的運(yùn)行狀況進(jìn)行檢查。之后,changeServer()方法會(huì)刪除最后一個(gè)服務(wù)器,并向計(jì)劃進(jìn)行運(yùn)行狀況檢查的基礎(chǔ)設(shè)施中添加一個(gè)新服務(wù)器。因此,當(dāng)運(yùn)行狀況檢查進(jìn)行第二次迭代時(shí),它會(huì)使用修改后的服務(wù)器列表。所有這一切都可以借助單例模式來(lái)完成。當(dāng)添加或刪除服務(wù)器時(shí),運(yùn)行狀況的檢查工作必須由了解基礎(chǔ)設(shè)施變動(dòng)情況的同一個(gè)對(duì)象來(lái)完成:classHealthCheck:_instance=Nonedef__new__(cls,*args,**kwargs):ifnotHealthCheck._instance:HealthCheck._instance=super(HealthCheck,\cls).__new__(cls,*args,**kwargs)returnHealthCheck._instancedef__init__(self):self._servers=[]defaddServer(self):self._servers.append("Server1")self._servers.append("Server2")self._servers.append("Server3")self._servers.append("Server4")defchangeServer(self):self._servers.pop()self._servers.append("Server5")hc1=HealthCheck()hc2=HealthCheck()hc1.addServer()print("Schedulehealthcheckforservers(1)..")foriinrange(4):print("Checking",hc1._servers[i])hc2.changeServer()print("Schedulehealthcheckforservers(2)..")foriinrange(4):print("Checking",hc2._servers[i])代碼的輸出如圖2-6所示。圖2-6?單例模式的缺點(diǎn)雖然單例模式在許多情況下效果很好,但這種模式仍然存在一些缺陷。由于單例具有全局訪問(wèn)權(quán)限,因此可能會(huì)出現(xiàn)以下問(wèn)題。全局變量可能在某處已經(jīng)被誤改,但是開(kāi)發(fā)人員仍然認(rèn)為它們沒(méi)有發(fā)生變化,而該變量還在應(yīng)用程序的其他位置被使用??赡軙?huì)對(duì)同一對(duì)象創(chuàng)建多個(gè)引用。由于單例只創(chuàng)建一個(gè)對(duì)象,因此這種情況下會(huì)對(duì)同一個(gè)對(duì)象創(chuàng)建多個(gè)引用。所有依賴(lài)于全局變量的類(lèi)都會(huì)由于一個(gè)類(lèi)的改變而緊密耦合為全局?jǐn)?shù)據(jù),從而可能在無(wú)意中影響另一個(gè)類(lèi)。?提示:?在本章中,我們學(xué)習(xí)了關(guān)于單例的許多內(nèi)容。對(duì)于單例模式來(lái)說(shuō),以下幾點(diǎn)需要牢記。在許多實(shí)際應(yīng)用程序中,我們只需要?jiǎng)?chuàng)建一個(gè)對(duì)象,如線(xiàn)程池、緩存、對(duì)話(huà)框、注冊(cè)表設(shè)置等。如果我們?yōu)槊總€(gè)應(yīng)用程序創(chuàng)建多個(gè)實(shí)例,則會(huì)導(dǎo)致資源的過(guò)度使用。單例模式在這種情況下工作得很好。單例是一種經(jīng)過(guò)時(shí)間考驗(yàn)的成熟方法,能夠在不帶來(lái)太多缺陷的情況下提供全局訪問(wèn)點(diǎn)。當(dāng)然,該模式也有幾個(gè)缺點(diǎn)。當(dāng)使用全局變量或類(lèi)的實(shí)例化非常耗費(fèi)資源但最終卻沒(méi)有用到它們的情況下,單例的影響可以忽略不計(jì)。在本章中,我們介紹了單例設(shè)計(jì)模式及其應(yīng)用的上下文。我們知道,當(dāng)要求一個(gè)類(lèi)只有一個(gè)對(duì)象時(shí),就可以使用單例模式。我們還研究了利用Python實(shí)現(xiàn)單例模式的各種方法。對(duì)于經(jīng)典的實(shí)現(xiàn)方式來(lái)說(shuō),允許進(jìn)行多次實(shí)例化,但返回同一個(gè)對(duì)象。我們還討論了Borg或Monostate模式,這是單例模式的一個(gè)變體。Borg允許創(chuàng)建共享相同狀態(tài)的多個(gè)對(duì)象,這與GoF描述的單例模式有所不同。之后,我們繼續(xù)探討了Web應(yīng)用程序,其中單例模式可以用于在多個(gè)服務(wù)間實(shí)現(xiàn)一致的數(shù)據(jù)庫(kù)操作。最后,我們還研究了單例可能出現(xiàn)的錯(cuò)誤,以及開(kāi)發(fā)人員需要避免的情況。在本章內(nèi)容的基礎(chǔ)上,讀者就可以順利研究其他創(chuàng)建型模式并從中獲益了。在下一章中,我們將考察其他創(chuàng)建型模式和工廠設(shè)計(jì)模式,介紹工廠方法和抽象工廠模式,并通過(guò)Python的代碼示例來(lái)加深理解。第3章?工廠模式:建立創(chuàng)建對(duì)象的工廠在上一章中,你已經(jīng)了解了什么是單例設(shè)計(jì)模式,以及如何通過(guò)Python實(shí)現(xiàn)該設(shè)計(jì)模式并將其應(yīng)用到現(xiàn)實(shí)世界中。我們知道,單例設(shè)計(jì)模式是一種創(chuàng)建型設(shè)計(jì)模式。在這一章中,我們繼續(xù)學(xué)習(xí)另一種創(chuàng)建型模式,即工廠模式。工廠模式可以說(shuō)是最常用的設(shè)計(jì)模式。在這一章中,我們將了解工廠的概念,并深入探討簡(jiǎn)單工廠模式。然后,本章將通過(guò)UML圖來(lái)介紹工廠方法模式和抽象工廠模式,了解現(xiàn)實(shí)世界的應(yīng)用場(chǎng)景以及基于Pythonv3.5的實(shí)現(xiàn)。此外,我們還會(huì)對(duì)工廠方法和抽象工廠方法進(jìn)行比較。在本章中,我們將簡(jiǎn)要介紹以下主題:了解簡(jiǎn)單工廠設(shè)計(jì)模式;討論工廠方法和抽象工廠方法及其差異;利用Python代碼實(shí)現(xiàn)真實(shí)場(chǎng)景;討論模式的優(yōu)缺點(diǎn)并進(jìn)行相應(yīng)的比較。3?了解工廠模式在面向?qū)ο缶幊讨?,術(shù)語(yǔ)“工廠”表示一個(gè)負(fù)責(zé)創(chuàng)建其他類(lèi)型對(duì)象的類(lèi)。通常情況下,作為一個(gè)工廠的類(lèi)有一個(gè)對(duì)象以及與它關(guān)聯(lián)的多個(gè)方法??蛻?hù)端使用某些參數(shù)調(diào)用此方法,之后,工廠會(huì)據(jù)此創(chuàng)建所需類(lèi)型的對(duì)象,然后將它們返回給客戶(hù)端。所以,這里的問(wèn)題實(shí)際上是,既然客戶(hù)端可以直接創(chuàng)建對(duì)象,那為什么我們還需要一個(gè)工廠呢?答案在于,工廠具有下列優(yōu)點(diǎn)。松耦合,即對(duì)象的創(chuàng)建可以獨(dú)立于類(lèi)的實(shí)現(xiàn)??蛻?hù)端無(wú)需了解創(chuàng)建對(duì)象的類(lèi),但是照樣可以使用它來(lái)創(chuàng)建對(duì)象。它只需要知道需要傳遞的接口、方法和參數(shù),就能夠創(chuàng)建所需類(lèi)型的對(duì)象了。這簡(jiǎn)化了客戶(hù)端的實(shí)現(xiàn)??梢暂p松地在工廠中添加其他類(lèi)來(lái)創(chuàng)建其他類(lèi)型的對(duì)象,而這無(wú)需更改客戶(hù)端代碼。最簡(jiǎn)單的情況下,客戶(hù)端只需要傳遞另一個(gè)參數(shù)就可以了。工廠還可以重用現(xiàn)有對(duì)象。但是,如果客戶(hù)端直接創(chuàng)建對(duì)象的話(huà),總是創(chuàng)建一個(gè)新對(duì)象。讓我們探討制造玩具車(chē)或玩偶的公司的情況。假設(shè)公司里的一臺(tái)機(jī)器目前正在制造玩具車(chē)。后來(lái),公司的CEO認(rèn)為,迫切需要根據(jù)市場(chǎng)的需求來(lái)制造玩偶。這時(shí),工廠模式就派上用場(chǎng)了。在這種情況下,機(jī)器成為接口,CEO是客戶(hù)端。CEO只關(guān)心要制造的對(duì)象(或玩具)和創(chuàng)建對(duì)象的接口——機(jī)器。Factory模式有3種變體,如下所示。簡(jiǎn)單工廠模式:允許接口創(chuàng)建對(duì)象,但不會(huì)暴露對(duì)象的創(chuàng)建邏輯。工廠方法模式:允許接口創(chuàng)建對(duì)象,但使用哪個(gè)類(lèi)來(lái)創(chuàng)建對(duì)象,則是交由子類(lèi)決定的。抽象工廠模式:抽象工廠是一個(gè)能夠創(chuàng)建一系列相關(guān)的對(duì)象而無(wú)需指定/公開(kāi)其具體類(lèi)的接口。該模式能夠提供其他工廠的對(duì)象,在其內(nèi)部創(chuàng)建其他對(duì)象。?簡(jiǎn)單工廠模式對(duì)于一些人來(lái)說(shuō),簡(jiǎn)單工廠本身不是一種模式。開(kāi)發(fā)人員在進(jìn)一步了解這個(gè)概念之前,首先需要詳細(xì)了解工廠方法和抽象工廠方法。工廠可以幫助開(kāi)發(fā)人員創(chuàng)建不同類(lèi)型的對(duì)象,而不是直接將對(duì)象實(shí)例化。圖3-1是簡(jiǎn)單工廠的UML圖,將有助于我們理解這一點(diǎn)。這里,客戶(hù)端類(lèi)使用的是Factory類(lèi),該類(lèi)具有create_type()方法。當(dāng)客戶(hù)端使用類(lèi)型參數(shù)調(diào)用create_type()方法時(shí),F(xiàn)actory會(huì)根據(jù)傳入的參數(shù),返回Product1或Product2?,F(xiàn)在,讓我們借助Pythonv3.5代碼示例來(lái)進(jìn)一步理解簡(jiǎn)單工廠模式。在下面的代碼段中,我們將創(chuàng)建一個(gè)名為Animal的抽象產(chǎn)品。Animal是一個(gè)抽象的基類(lèi)(ABCMeta是Python的特殊元類(lèi),用來(lái)生成類(lèi)Abstract),它帶有方法do_say()。我們利用Animal接口創(chuàng)建了兩種產(chǎn)品(Cat和Dog),并實(shí)現(xiàn)了do_say()方法來(lái)提供這些動(dòng)物的相應(yīng)的叫聲。ForestFactory是一個(gè)帶有make_sound()方法的工廠。根據(jù)客戶(hù)端傳遞的參數(shù)類(lèi)型,它就可以在運(yùn)行時(shí)創(chuàng)建適當(dāng)?shù)腁nimal實(shí)例,并輸出正確的聲音:圖3-1fromabcimportABCMeta,abstractmethodclassAnimal(metaclass=ABCMeta):@abstractmethoddefdo_say(self):passclassDog(Animal):defdo_say(self):print("BhowBhow!!")classCat(Animal):defdo_say(self):print("MeowMeow!!")##forestfactorydefinedclassForestFactory(object):defmake_sound(self,object_type):returneval(object_type)().do_say()##clientcodeif__name__=='__main__':ff=ForestFactory()animal=input("Whichanimalshouldmake_soundDogorCat?")ff.make_sound(animal)圖3-2是上述代碼段的輸出。圖3-2?工廠方法模式以下幾點(diǎn)可以幫助我們了解工廠方法模式。我們定義了一個(gè)接口來(lái)創(chuàng)建對(duì)象,但是工廠本身并不負(fù)責(zé)創(chuàng)建對(duì)象,而是將這一任務(wù)交由子類(lèi)來(lái)完成,即子類(lèi)決定了要實(shí)例化哪些類(lèi)。Factory方法的創(chuàng)建是通過(guò)繼承而不是通過(guò)實(shí)例化來(lái)完成的。工廠方法使設(shè)計(jì)更加具有可定制性。它可以返回相同的實(shí)例或子類(lèi),而不是某種類(lèi)型的對(duì)象(就像在簡(jiǎn)單工廠方法中的那樣)。在圖3-3所示的UML圖中,有一個(gè)包含factoryMethod()方法的抽象類(lèi)Creator。FactoryMethod()方法負(fù)責(zé)創(chuàng)建指定類(lèi)型的對(duì)象。ConcreteCreator類(lèi)提供了一個(gè)實(shí)現(xiàn)Creator抽象類(lèi)的factoryMethod()方法,這種方法可以在運(yùn)行時(shí)修改已創(chuàng)建的對(duì)象。ConcreteCreator創(chuàng)建ConcreteProduct,并確保其創(chuàng)建的對(duì)象實(shí)現(xiàn)了Product類(lèi),同時(shí)為Product接口中的所有方法提供相應(yīng)的實(shí)現(xiàn)。簡(jiǎn)而言之,Creator接口的factoryMethod()方法和ConcreteCreator類(lèi)共同決定了要?jiǎng)?chuàng)建Product的哪個(gè)子類(lèi)。因此,工廠方法模式定義了一個(gè)接口來(lái)創(chuàng)建對(duì)象,但具體實(shí)例化哪個(gè)類(lèi)則是由它的子類(lèi)決定的。圖3-33?實(shí)現(xiàn)工廠方法讓我們拿一個(gè)現(xiàn)實(shí)世界的場(chǎng)景來(lái)理解工廠方法的實(shí)現(xiàn)。假設(shè)我們想在不同類(lèi)型的社交網(wǎng)絡(luò)例如LinkedIn、Facebook等)上為個(gè)人或公司建立簡(jiǎn)介。那么,每個(gè)簡(jiǎn)介都有某些特定(的組成章節(jié)。在LinkedIn的簡(jiǎn)介中,有一個(gè)章節(jié)是關(guān)于個(gè)人申請(qǐng)的專(zhuān)利或出版作品的。在Facebook上,你將在相冊(cè)中看到最近度假地點(diǎn)的照片區(qū)。此外,在這兩個(gè)簡(jiǎn)介中,都有一個(gè)個(gè)人信息的區(qū)。因此,簡(jiǎn)而言之,我們要通過(guò)將正確的區(qū)添加到相應(yīng)的簡(jiǎn)介中來(lái)創(chuàng)建不同類(lèi)型的簡(jiǎn)介。下面讓我們來(lái)看看具體如何實(shí)現(xiàn)。在下面的代碼示例中,首先定義接口Product。我們將創(chuàng)建一個(gè)Section抽象類(lèi)來(lái)定義一個(gè)區(qū)是關(guān)于哪方面內(nèi)容的,讓它盡量保持簡(jiǎn)單,同時(shí)還提供一個(gè)抽象方法describe()。然后,我們會(huì)創(chuàng)建多個(gè)ConcreteProduct、PersonalSection、AlbumSection、PatentSection和PublicationSection類(lèi)。這些類(lèi)用于實(shí)現(xiàn)describe()抽象方法并打印它們各自的區(qū)名稱(chēng):fromabcimportABCMeta,abstractmethodclassSection(metaclass=ABCMeta):@abstractmethoddefdescribe(self):passclassPersonalSection(Section):defdescribe(self):print("PersonalSection")classAlbumSection(Section):defdescribe(self):print("AlbumSection")classPatentSection(Section):defdescribe(self):print("PatentSection")classPublicationSection(Section):defdescribe(self):print("PublicationSection")我們創(chuàng)建了一個(gè)名為Profile的抽象類(lèi)Creator。Profile[Creator]抽象類(lèi)提供了一個(gè)工廠方法,即createProfile()。createProfile()方法應(yīng)該由ConcreteClass實(shí)現(xiàn),來(lái)實(shí)際創(chuàng)建帶有適當(dāng)區(qū)的簡(jiǎn)介。Profile抽象類(lèi)不知道每個(gè)簡(jiǎn)介應(yīng)具有哪些區(qū)。例如,F(xiàn)acebook的簡(jiǎn)介應(yīng)該提供個(gè)人信息區(qū)和相冊(cè)區(qū)。所以,我們將讓子類(lèi)來(lái)決定這些事情。我們創(chuàng)建了兩個(gè)ConcreteCreator類(lèi),即linkedin和facebook。每個(gè)類(lèi)都實(shí)現(xiàn)createProfile()抽象方法,由該方法在運(yùn)行時(shí)實(shí)際創(chuàng)建(實(shí)例化)多個(gè)區(qū)(ConcreteProducts):classProfile(metaclass=ABCMeta):def__init__(self):self.sections=[]self.createProfile()abstractmethod@defcreateProfile(self):passdefgetSections(self):returnself.sectionsdefaddSections(self,section):self.sections.append(section)classlinkedin(Profile):defcreateProfile(self):self.addSections(PersonalSection())self.addSections(PatentSection())self.addSections(PublicationSection())classfacebook(Profile):defcreateProfile(self):self.addSections(PersonalSection())self.addSections(AlbumSection())最后,我們開(kāi)始編寫(xiě)決定實(shí)例化哪個(gè)Creator類(lèi)的客戶(hù)端代碼,以便讓它根據(jù)指定的選項(xiàng)創(chuàng)建所需的簡(jiǎn)介:if__name__=='__main__':profile_type=input("WhichProfileyou'dliketocreate?LinkedInorFaceBook]")[profile=eval(profile_type.lower())()print("CreatingProfile..",type(profile).__name__)print("Profilehassections--",profile.getSections())現(xiàn)在,如果你運(yùn)行完整的代碼,它會(huì)首先要求輸入要?jiǎng)?chuàng)建的簡(jiǎn)介名稱(chēng)。在圖3-4中,我們以Facebook為例。然后,它實(shí)例化facebook[ConcreateCreator]類(lèi)。它會(huì)在內(nèi)部創(chuàng)建ConcreteProduct,也就是說(shuō),將實(shí)例化PersonalSection和AlbumSection。如果選擇Linkedin,則會(huì)創(chuàng)建PersonalSection、PatentSection和PublicationSection。上述代碼段的輸出如圖3-4所示。圖3-43?工廠方法模式的優(yōu)點(diǎn)在前面部分,你已經(jīng)學(xué)習(xí)了工廠方法模式,并掌握了工廠方法的實(shí)現(xiàn),接下來(lái)讓我們看看工廠方法模式的優(yōu)點(diǎn)。它具有更大的靈活性,使得代碼更加通用,因?yàn)樗皇菃渭兊貙?shí)例化某個(gè)類(lèi)。這樣,實(shí)現(xiàn)哪些類(lèi)取決于接口(Product),而不是ConcreteProduct類(lèi)。它們是松耦合的,因?yàn)閯?chuàng)建對(duì)象的代碼與使用它的代碼是分開(kāi)的。客戶(hù)端完全不需要關(guān)心要傳遞哪些參數(shù)以及需要實(shí)例化哪些類(lèi)。由于添加新類(lèi)更加容易,所以降低了維護(hù)成本。3?抽象工廠模式抽象工廠模式的主要目的是提供一個(gè)接口來(lái)創(chuàng)建一系列相關(guān)對(duì)象,而無(wú)需指定具體的類(lèi)。工廠方法將創(chuàng)建實(shí)例的任務(wù)委托給了子類(lèi),而抽象工廠方法的目標(biāo)是創(chuàng)建一系列相關(guān)對(duì)象。如圖3-5所示,ConcreteFactory1和ConcreteFactory2是通過(guò)AbstractFactory接口創(chuàng)建的。此接口具有創(chuàng)建多種產(chǎn)品的相應(yīng)方法。圖3-5ConcreteFactory1和ConcreteFactory2實(shí)現(xiàn)了AbstractFactory,并創(chuàng)建實(shí)例ConcreteProduct1、ConcreteProduct2、AnotherConcreteProduct1和AnotherConcreteProduct2。在這里,ConcreteProduct1和ConcreteProduct2是通過(guò)AbstractProduct接口創(chuàng)建的,而AnotherConcreteProduct1和AnotherConcreteProduct2則是通過(guò)AnotherAbstractProduct接口創(chuàng)建的。實(shí)際上,抽象工廠模式不僅確??蛻?hù)端與對(duì)象的創(chuàng)建相互隔離,同時(shí)還確??蛻?hù)端能夠使用創(chuàng)建的對(duì)象。但是,客戶(hù)端只能通過(guò)接口訪問(wèn)對(duì)象。如果要使用一個(gè)系列中的多個(gè)產(chǎn)品,那么抽象工廠模式能夠幫助客戶(hù)端一次使用來(lái)自一個(gè)產(chǎn)品/系列的多個(gè)對(duì)象。例如,如果正在開(kāi)發(fā)的應(yīng)用應(yīng)該是平臺(tái)無(wú)關(guān)的,則它需要對(duì)各種依賴(lài)項(xiàng)進(jìn)行抽象處理,這些依賴(lài)項(xiàng)包括操作系統(tǒng)、文件系統(tǒng)調(diào)用,等等。抽象工廠模式負(fù)責(zé)為整個(gè)平臺(tái)創(chuàng)建所需的服務(wù),這樣的話(huà),客戶(hù)端就不必直接創(chuàng)建平臺(tái)對(duì)象了。實(shí)現(xiàn)抽象工廠模式設(shè)想一下你最喜歡的披薩餅店的情況。它提供多種披薩餅,對(duì)吧?等等,我知道你想立即訂購(gòu)一份,現(xiàn)在讓我們討論這個(gè)場(chǎng)景吧!現(xiàn)在,想象一下,我們開(kāi)辦了一家披薩店,供應(yīng)美味的印式和美式披薩餅。為此,我們首先創(chuàng)建一個(gè)抽象基類(lèi)——PizzaFactory(AbstractFactory見(jiàn)前面的UML圖)。PizzaFactory類(lèi)有兩個(gè)抽象方法即createVegPizza()和createNonVegPizza(),它們需要通過(guò)ConcreteFactory實(shí)現(xiàn)。在這個(gè)例子中,我們創(chuàng)造了兩個(gè)具體的工廠,分別名為IndianPizzaFactory和USPizzaFactory。下面讓我們看看這兩個(gè)具體工廠的實(shí)現(xiàn)代碼:fromabcimportABCMeta,abstractmethodclassPizzaFactory(metaclass=ABCMeta):@abstractmethoddefcreateVegPizza(self):pass@abstractmethoddefcreateNonVegPizza(self):passclassIndianPizzaFactory(PizzaFactory):defcreateVegPizza(self):returnDeluxVeggiePizza()defcreateNonVegPizza(self):returnChickenPizza()classUSPizzaFactory(PizzaFactory):defcreateVegPizza(self):returnMexicanVegPizza()defcreateNonVegPizza(self):returnHamPizza()現(xiàn)在,讓我們進(jìn)一步定義AbstractProducts。在下面的代碼中,我們將創(chuàng)建兩個(gè)抽象類(lèi):VegPizza和NonVegPizza(AbstractProduct和AnotherAbstractProduct見(jiàn)前面的UML圖)。它們都定義了自己的方法,分別是prepare()和serve()。這里的想法是,素食披薩餅配有適當(dāng)?shù)耐馄?、蔬菜和調(diào)味料,非素食披薩餅在素食披薩餅上面搭配非素食食材。然后我們?yōu)槊總€(gè)AbstractProducts定義ConcreteProducts?,F(xiàn)在,就本例而言,我們將創(chuàng)建DeluxVeggiePizza和MexicanVegPizza,并實(shí)現(xiàn)prepare()方法。ConcreteProducts1和ConcreteProducts2將代表UML圖中的這些類(lèi)。接下來(lái),我們來(lái)定義ChickenPizza和HamPizza,并實(shí)現(xiàn)server()方法——它們代表AnotherConcreteProducts1和AnotherConcreteProducts2:classVegPizza(metaclass=ABCMeta):@abstractmethoddefprepare(self,VegPizza):passclassNonVegPizza(metaclass=ABCMeta):@abstractmethoddefserve(self,VegPizza):passclassDeluxVeggiePizza(VegPizza):defprepare(self):print("Prepare",type(self).__name__)classChickenPizza(NonVegPizza):defserve(self,VegPizza):print(type(self).__name__,"isservedwithChickenon",type(VegPizza).__name__)classMexicanVegPizza(VegPizza):defprepare(self):print("Prepare",type(self).__name__)classHamPizza(NonVegPizza):defserve(self,VegPizza):print(type(self).__name__,"isservedwithHamon",type(VegPizza).__name__)當(dāng)最終用戶(hù)來(lái)到PizzaStore并要一份美式非素食披薩的時(shí)候,USPizzaFactory負(fù)責(zé)準(zhǔn)備素食,然后在上面加上火腿,馬上就變成非素食披薩了!classPizzaStore:def__init__(self):passdefmakePizzas(self):forfactoryin[IndianPizzaFactory(),USPizzaFactory()]:self.factory=factoryself.NonVegPizza=self.factory.createNonVegPizza()self.VegPizza=self.factory.createVegPizza()self.VegPizza.prepare()self.NonVegPizza.serve(self.VegPizza)pizza=PizzaStore()pizza.makePizzas()上述示例代碼的輸出如圖3-6所示。圖3-6?工廠方法與抽象工廠方法現(xiàn)在,你已經(jīng)學(xué)習(xí)了工廠方法和抽象工廠方法,讓我們對(duì)兩者進(jìn)行一番比較。表3-1??工廠方法抽象工廠方法它向客戶(hù)端開(kāi)放了一個(gè)創(chuàng)建對(duì)抽象工廠方法包含一個(gè)或多個(gè)工廠方法來(lái)創(chuàng)建一象的方法個(gè)系列的相關(guān)對(duì)象它使用繼承和子類(lèi)來(lái)決定要?jiǎng)?chuàng)建哪個(gè)對(duì)象它使用組合將創(chuàng)建對(duì)象的任務(wù)委托給其他類(lèi)工廠方法用于創(chuàng)建一個(gè)產(chǎn)品抽象工廠方法用于創(chuàng)建相關(guān)產(chǎn)品的系列在本章中,我們介紹了工廠設(shè)計(jì)模式及其使用的上下文。同時(shí),我們還講解了工廠的基礎(chǔ)知識(shí),以及如何在軟件架構(gòu)中有效地使用它。我們還考察了簡(jiǎn)單工廠,它可以在運(yùn)行時(shí)根據(jù)客戶(hù)端傳入的參數(shù)類(lèi)型來(lái)創(chuàng)建相應(yīng)的實(shí)例。我們還討論了工廠方法模式,它是簡(jiǎn)單工廠的一個(gè)變體。在這種模式中,我們定義了一個(gè)接口來(lái)創(chuàng)建對(duì)象,但是對(duì)象的創(chuàng)建卻是交由子類(lèi)完成的。我們接著探索了抽象工廠方法,它提供了一個(gè)接口,無(wú)需指定具體的類(lèi)就能創(chuàng)建一系列的相關(guān)對(duì)象。此外,對(duì)于這3種模式,我們都提供了實(shí)際的Python實(shí)現(xiàn),并比較了工廠方法與抽象工廠方法。最后,我們已經(jīng)準(zhǔn)備好進(jìn)一步研究其他類(lèi)型的模式,敬請(qǐng)期待。第4章?門(mén)面模式——與門(mén)面相適在上一章中,你已經(jīng)學(xué)習(xí)了工廠設(shè)計(jì)模式。并討論了該模式的3種變體——簡(jiǎn)單工廠、工廠方法和抽象工廠模式。此外,你還學(xué)習(xí)了如何將它們應(yīng)用于現(xiàn)實(shí)世界,并給出了相應(yīng)的Python實(shí)現(xiàn)。我們還將工廠方法與抽象工廠模式進(jìn)行了一番比較,并列出了其優(yōu)缺點(diǎn)。我們知道,無(wú)論工廠設(shè)計(jì)模式還是單例設(shè)計(jì)模式(參見(jiàn)第2章),都屬于創(chuàng)建型設(shè)計(jì)模式。在這一章中,我們繼續(xù)學(xué)習(xí)另外一種類(lèi)型的設(shè)計(jì)模式,即結(jié)構(gòu)型設(shè)計(jì)模式。這里,我們要介紹的是門(mén)面設(shè)計(jì)模式,以及如何將其應(yīng)用于軟件應(yīng)用程序開(kāi)發(fā)。我們將提供一個(gè)示例,并通過(guò)Pythonv3.5實(shí)現(xiàn)了該示例。簡(jiǎn)而言之,我們將在本章中討論下列主題:結(jié)構(gòu)型設(shè)計(jì)模式概要;利用UML圖理解門(mén)面設(shè)計(jì)模式;提供了Pythonv3.5實(shí)現(xiàn)代碼的真實(shí)用例;門(mén)面模式與最少知識(shí)原則。4?理解結(jié)構(gòu)型設(shè)計(jì)模式以下幾點(diǎn)將有助于我們更好地了解結(jié)構(gòu)型設(shè)計(jì)模式。結(jié)構(gòu)型模式描述如何將對(duì)象和類(lèi)組合成更大的結(jié)構(gòu)。結(jié)構(gòu)型模式是一種能夠簡(jiǎn)化設(shè)計(jì)工作的模式,因?yàn)樗軌蛘页龈?jiǎn)單的方法來(lái)認(rèn)識(shí)或表示實(shí)體之間的關(guān)系。在面向?qū)ο笫澜缰?,?shí)體指的是對(duì)象或類(lèi)。類(lèi)模式可以通過(guò)繼承來(lái)描述抽象,從而提供更有用的程序接口,而對(duì)象模式則描述了如何將對(duì)象聯(lián)系起來(lái)從而組合成更大的對(duì)象。結(jié)構(gòu)型模式是類(lèi)和對(duì)象模式的綜合體。下面給出結(jié)構(gòu)型設(shè)計(jì)模式的幾個(gè)例子。你會(huì)注意到,它們都是通過(guò)對(duì)象或類(lèi)之間的交互來(lái)實(shí)現(xiàn)更高級(jí)的設(shè)計(jì)或架構(gòu)目標(biāo)的。下面是一些結(jié)構(gòu)型設(shè)計(jì)模式的例子。適配器模式:將一個(gè)接口轉(zhuǎn)換成客戶(hù)希望的另外一個(gè)接口。它試圖根據(jù)客戶(hù)端的需求來(lái)匹配不同類(lèi)的接口。橋接模式:該模式將對(duì)象的接口與其實(shí)現(xiàn)進(jìn)行解耦,使得兩者可以獨(dú)立工作。裝飾器模式:該模式允許在運(yùn)行時(shí)或以動(dòng)態(tài)方式為對(duì)象添加職責(zé)。我們可以通過(guò)接口給對(duì)象添加某些屬性。除此之外,本書(shū)還會(huì)介紹其他一些結(jié)構(gòu)型模式。所以,讓我們首先從門(mén)面設(shè)計(jì)模式開(kāi)始學(xué)起。4?理解門(mén)面設(shè)計(jì)模式門(mén)面(facade)通常是指建筑物的表面,尤其是最有吸引力的那一面。它也可以表示一種容易讓人誤解某人的真實(shí)感受或情況的行為或面貌。當(dāng)人們從建筑物外面經(jīng)過(guò)時(shí),可以欣賞其外部面貌,卻不了解建筑物結(jié)構(gòu)的復(fù)雜性。這就是門(mén)面模式的使用方式。門(mén)面在隱藏內(nèi)部系統(tǒng)復(fù)雜性的同時(shí),為客戶(hù)端提供了一個(gè)接口,以便它們可以非常輕松地訪問(wèn)系統(tǒng)。下面,我們以店主為例進(jìn)行介紹?,F(xiàn)在,假設(shè)你要到某個(gè)商店去買(mǎi)東西,但是你對(duì)這個(gè)商店的布局并不清楚。通常情況下,你會(huì)去找店主,因?yàn)榈曛鲗?duì)整個(gè)商店都很清楚。只要你告訴他/她要買(mǎi)什么,店主就會(huì)把這些商品拿給你。這不是很容易嗎?顧客不必了解店面的情況,可以通過(guò)一個(gè)簡(jiǎn)單的接口來(lái)完成購(gòu)物,這里的接口就是店主。門(mén)面設(shè)計(jì)模式實(shí)際上完成了下列事項(xiàng)。它為子系統(tǒng)中的一組接口提供一個(gè)統(tǒng)一的接口,并定義一個(gè)高級(jí)接口來(lái)幫助客戶(hù)端通過(guò)更加簡(jiǎn)單的方式使用子系統(tǒng)。門(mén)面所解決問(wèn)題是,如何用單個(gè)接口對(duì)象來(lái)表示復(fù)雜的子系統(tǒng)。實(shí)際上,它并不是封裝子系統(tǒng),而是對(duì)底層子系統(tǒng)進(jìn)行組合。它促進(jìn)了實(shí)現(xiàn)與多個(gè)客戶(hù)端的解耦。類(lèi)圖現(xiàn)在,我們可以借助于圖4-1的UML圖來(lái)深入探討門(mén)面模式。圖4-1就像你在UML圖中看到的那樣,這個(gè)模式有3個(gè)主要的參與者。門(mén)面:門(mén)面的主要責(zé)任是,將一組復(fù)雜導(dǎo)致系統(tǒng)封裝起來(lái),從而為外部世界提供一個(gè)舒適的外觀。系統(tǒng):這代表一組不同的子系統(tǒng),使整個(gè)系統(tǒng)混雜在一起,難以觀察或使用??蛻?hù)端:客戶(hù)端與門(mén)面進(jìn)行交互,這樣就可以輕松地與子系統(tǒng)進(jìn)行通信并完成工作了。不必?fù)?dān)心系統(tǒng)的復(fù)雜性?,F(xiàn)在,我們將會(huì)從數(shù)據(jù)結(jié)構(gòu)的角度進(jìn)一步介紹這3個(gè)主要參與者。以下幾點(diǎn)可以幫助我們更好地理解門(mén)面。它是一個(gè)接口,它知道某個(gè)請(qǐng)求可以交由哪個(gè)子系統(tǒng)進(jìn)行處理。它使用組合將客戶(hù)端的請(qǐng)求委派給相應(yīng)的子系統(tǒng)對(duì)象。例如,如果客戶(hù)端正在了解哪些工作已完成,則不需要到各個(gè)子系統(tǒng)去,相反,它只需要聯(lián)系完成工作的接口(門(mén)面)就可以了。在門(mén)面的世界里,系統(tǒng)就是執(zhí)行以下操作的實(shí)體。它實(shí)現(xiàn)子系統(tǒng)的功能,同時(shí),系統(tǒng)由一個(gè)類(lèi)表示。理想情況下,系統(tǒng)應(yīng)該由一組負(fù)責(zé)不同任務(wù)的類(lèi)來(lái)表示。它處理門(mén)面對(duì)象分配的工作,但并不知道門(mén)面,而且不引用它。例如,當(dāng)客戶(hù)端向門(mén)面請(qǐng)求某項(xiàng)服務(wù)時(shí),門(mén)面會(huì)根據(jù)服務(wù)的類(lèi)型來(lái)選擇提供該服務(wù)的相應(yīng)子系統(tǒng)。4以下是我們對(duì)客戶(hù)端的描述。客戶(hù)端是實(shí)例化門(mén)面的類(lèi)。為了讓子系統(tǒng)完成相應(yīng)的工作,客戶(hù)端需要向門(mén)面提出請(qǐng)求。4?在現(xiàn)實(shí)世界中實(shí)現(xiàn)門(mén)面模式為了演示門(mén)面模式的應(yīng)用,讓我們舉個(gè)生活中會(huì)遇到的例子。假設(shè)你要在家中舉行一場(chǎng)婚禮,并且由你來(lái)張羅這一切。這真是一個(gè)艱巨的任務(wù)。你必須預(yù)訂一家酒店或場(chǎng)地,與餐飲人員交代酒菜、布置場(chǎng)景,并安排背景音樂(lè)。在不久以前,你已經(jīng)自己搞定了一切,例如找相關(guān)人員談話(huà)、與他們進(jìn)行協(xié)調(diào)、敲定價(jià)格等,那么現(xiàn)在你就很輕松了。此外,你還可以去找會(huì)務(wù)經(jīng)理,讓他/她為你處理這些事情。會(huì)務(wù)經(jīng)理負(fù)責(zé)跟各個(gè)服務(wù)提供商交涉,并為你爭(zhēng)取最優(yōu)惠的價(jià)格。下面我們從門(mén)面模式的角度來(lái)看待這些事情??蛻?hù)端:你需要在婚禮前及時(shí)完成所有的準(zhǔn)備工作。每一項(xiàng)安排都應(yīng)該是頂級(jí)的,這樣客人才會(huì)喜歡這些慶?;顒?dòng)。門(mén)面:會(huì)務(wù)經(jīng)理負(fù)責(zé)與所有相關(guān)人員進(jìn)行交涉,這些人員負(fù)責(zé)處理食物、花卉裝飾等。子系統(tǒng):它們代表提供餐飲、酒店管理和花卉裝飾等服務(wù)的系統(tǒng)。現(xiàn)在,讓我們利用Pythonv3.5開(kāi)發(fā)一個(gè)應(yīng)用程序,實(shí)現(xiàn)這個(gè)示例。我們首先從客戶(hù)端開(kāi)始。記住,你是確保婚姻準(zhǔn)備工作和事件順利的總負(fù)責(zé)人!讓我們繼續(xù),接下來(lái)要談?wù)摰氖荈acade類(lèi)。如前所述,F(xiàn)acade類(lèi)簡(jiǎn)化了客戶(hù)端的接口。就本例來(lái)說(shuō),EventManager扮演了門(mén)面的角色,并簡(jiǎn)化了你的工作。Facade與子系統(tǒng)進(jìn)行交流,并代表你為婚姻完成所有的預(yù)訂和準(zhǔn)備工作。下面是EventManager類(lèi)的Python代碼:classEventManager(object):def__init__(self):print("EventManager::Letmetalktothefolks\n")defarrange(self):self.hotelier=Hotelier()self.hotelier.bookHotel()self.florist=Florist()self.florist.setFlowerRequirements()self.caterer=Caterer()self.caterer.setCuisine()self.musician=Musician()self.musician.setMusicType()現(xiàn)在我們已經(jīng)搞定了門(mén)面和客戶(hù)端,下面讓我們開(kāi)始深入了解子系統(tǒng)。我們?yōu)檫@個(gè)應(yīng)用場(chǎng)景開(kāi)發(fā)了以下類(lèi)。Hotelier類(lèi)用于預(yù)訂酒店。它有一個(gè)方法,用于檢查當(dāng)天是否有免費(fèi)的酒店(__isAvailable)。Florist類(lèi)負(fù)責(zé)花卉裝飾。這個(gè)類(lèi)提供了setFlowerRequirements()方法,用于指定要使用哪些種類(lèi)的花卉來(lái)裝飾婚禮。Caterer類(lèi)用于跟備辦宴席者打交道,并負(fù)責(zé)安排餐飲。Caterer提供了一個(gè)公開(kāi)的setCuisine()方法,用來(lái)指定婚宴的菜肴類(lèi)型。Musician類(lèi)用來(lái)安排婚禮的音樂(lè),它使用setMusicType()方法來(lái)了解會(huì)務(wù)的音樂(lè)要求。接下來(lái),讓我們先來(lái)考察Hotelier對(duì)象,其次是Florist對(duì)象及其方法:classHotelier(object):def__init__(self):print("ArrangingtheHotelforMarriage?--")def__isAvailable(self):print("IstheHotelfreefortheeventongivenday?")returnTruedefbookHotel(self):ifself.__isAvailable():print("RegisteredtheBooking\n\n")classFlorist(object):def__init__(self):print("FlowerDecorationsfortheEvent?--")defsetFlowerRequirements(self):print("Carnations,RosesandLilieswouldbeusedforDecorations\n\n")classCaterer(object):def__init__(self):print("FoodArrangementsfortheEvent--")defsetCuisine(self):print("Chinese&ContinentalCuisinetobeserved\n\n")classMusician(object):def__init__(self):print("MusicalArrangementsfortheMarriage--")defsetMusicType(self):print("JazzandClassicalwillbeplayed\n\n")但是,你很聰明,所以將這些事情都委托給了會(huì)務(wù)經(jīng)理,不是嗎?讓我們來(lái)看看You類(lèi)。在本示例中,創(chuàng)建了一個(gè)EventManager類(lèi)的對(duì)象,這樣經(jīng)理就會(huì)通過(guò)與相關(guān)人員進(jìn)行交涉來(lái)籌備婚禮,而你則可以找個(gè)地方喝大茶了。classYou(object):def__init__(self):print("You::Whoa!MarriageArrangements??!!!")defaskEventManager(self):print("You::Let'sContacttheEventManager\n\n")em=EventManager()em.arrange()def__del__(self):print("You::ThankstoEventManager,allpreparationsdone!Phew!")you=You()you.askEventManager()上面代碼的輸出結(jié)果如圖4-2所示。圖4-2我們可以通過(guò)以下方式將門(mén)面模式與真實(shí)世界場(chǎng)景相關(guān)聯(lián)。EventManager類(lèi)是簡(jiǎn)化接口的門(mén)面。EventManager通過(guò)組合創(chuàng)建子系統(tǒng)的對(duì)象,如Hotelier、Caterer,等等。4?最少知識(shí)原則正如本章的開(kāi)始部分介紹的那樣,門(mén)面為我們提供了一個(gè)統(tǒng)一的系統(tǒng),它使得子系統(tǒng)更加易于使用。它還將客戶(hù)端與子系統(tǒng)解耦。門(mén)面模式背后的設(shè)計(jì)原理就是最少知識(shí)原則。最少知識(shí)原則指導(dǎo)我們減少對(duì)象之間的交互,就像跟你親近的只有某幾個(gè)朋友那樣。實(shí)際上,它意味著:在設(shè)計(jì)系統(tǒng)時(shí),對(duì)于創(chuàng)建的每個(gè)對(duì)象,都應(yīng)該考察與之交互的類(lèi)的數(shù)量,以及交互的方式;遵循這個(gè)原則,就能夠避免創(chuàng)建許多彼此緊密耦合的類(lèi)的情況;如果類(lèi)之間存在大量依賴(lài)關(guān)系,那么系統(tǒng)就會(huì)變得難以維護(hù)。如果對(duì)系統(tǒng)中的任何一部分進(jìn)行修改,都可能導(dǎo)致系統(tǒng)的其他部分被無(wú)意改變,這意味著系統(tǒng)會(huì)退化,是應(yīng)該堅(jiān)決避免的。4?常見(jiàn)問(wèn)答Q1.迪米特法則是什么,它與工廠模式有何關(guān)聯(lián)?A:迪米特法則是一個(gè)設(shè)計(jì)準(zhǔn)則,涉及以下內(nèi)容:123.每個(gè)單元對(duì)系統(tǒng)中其他單元知道的越少越好;.單位應(yīng)該只與其朋友交流;.單元不應(yīng)該知道它操作的對(duì)象的內(nèi)部細(xì)節(jié)。最少知識(shí)原則和迪米特法則是一致的,都是指向松耦合理論。就像它的名稱(chēng)所暗示的那樣,最少知識(shí)原則適用于門(mén)面模式的用例,并且“原則”這個(gè)詞是指導(dǎo)方針的意思,而不是嚴(yán)格遵守的意思,并且只在有需求的時(shí)候才用。Q2.子系統(tǒng)可以有多個(gè)門(mén)面嗎?A:是的,可以為一組子系統(tǒng)組件實(shí)現(xiàn)多個(gè)門(mén)面。Q3.最少知識(shí)原則的缺點(diǎn)是什么?A:門(mén)面提供了一個(gè)簡(jiǎn)化的接口供客戶(hù)端與子系統(tǒng)交互。本著提供簡(jiǎn)化接口的精神,應(yīng)用可能會(huì)建立多個(gè)不必要的接口,這增加了系統(tǒng)的復(fù)雜性并且降低了運(yùn)行時(shí)的性能。Q4.客戶(hù)端可以獨(dú)立訪問(wèn)子系統(tǒng)嗎?答:是的,事實(shí)上,由于門(mén)面模式提供了簡(jiǎn)化的接口,這使得客戶(hù)端不必?fù)?dān)心子系統(tǒng)的復(fù)雜性。Q5.門(mén)面是否可以添加自己的功能?A:門(mén)面可以將其“想法”添加到子系統(tǒng)中,例如確保子系統(tǒng)的改進(jìn)順序由門(mén)面來(lái)決定。本章首先對(duì)結(jié)構(gòu)型設(shè)計(jì)模式進(jìn)行了介紹,然后探討了門(mén)面設(shè)計(jì)模式及其使用的上下文。接著,我們介紹了門(mén)面的基礎(chǔ)知識(shí),以及如何將其高效地應(yīng)用于軟件架構(gòu)中。我們還研究了如何利用門(mén)面設(shè)計(jì)模式創(chuàng)建一個(gè)簡(jiǎn)單的接口來(lái)供客戶(hù)使用。因?yàn)闃O大地簡(jiǎn)化了子系統(tǒng)的復(fù)雜性,所以該模式能夠使客戶(hù)端受益匪淺。由于門(mén)面并沒(méi)有對(duì)子系統(tǒng)進(jìn)行封裝,因此即使不通過(guò)門(mén)面,客戶(hù)端也可以自由訪問(wèn)子系統(tǒng)。本章還介紹了該模式的UML圖,并給出了實(shí)現(xiàn)該模式的Pythonv3.5示例代碼。通過(guò)本章的學(xué)習(xí),你還了解了最少知識(shí)原則,以及該原則是如何指導(dǎo)門(mén)面設(shè)計(jì)模式的。我們還提供了一個(gè)有關(guān)常見(jiàn)問(wèn)題的部分,這將有助于你進(jìn)一步了解該模式及它可能的缺點(diǎn)?,F(xiàn)在,我們已經(jīng)做好了充分的準(zhǔn)備,在接下來(lái)的幾章中,將進(jìn)一步學(xué)習(xí)更多的結(jié)構(gòu)型模式。第5章?代理模式——控制對(duì)象的訪問(wèn)在上一章中,我們首先簡(jiǎn)要介紹了結(jié)構(gòu)型模式,并討論了門(mén)面設(shè)計(jì)模式。我們通過(guò)UML圖加深了對(duì)門(mén)面概念的理解,并且借助Python的實(shí)現(xiàn)展示了該模式在現(xiàn)實(shí)世界中的應(yīng)用。最后,我們?cè)诔R?jiàn)問(wèn)題解答部分講解了門(mén)面模式的優(yōu)點(diǎn)和缺點(diǎn)。在本章中,我們將進(jìn)一步學(xué)習(xí)結(jié)構(gòu)型設(shè)計(jì)模式中的代理模式。我們首先介紹代理模式的概念,然后繼續(xù)深入討論該設(shè)計(jì)模式,并展示如何將其應(yīng)用于軟件程序開(kāi)發(fā)。我們將提供一個(gè)示例,并通過(guò)Pythonv3.5實(shí)現(xiàn)該示例。簡(jiǎn)而言之,我們將在本章中討論下列主題:介紹代理和代理設(shè)計(jì)模式;代理模式的UML圖;代理模式的變體;利用Pythonv3.5代碼實(shí)現(xiàn)的真實(shí)用例;代理模式的優(yōu)點(diǎn);門(mén)面模式和代理模式之間的比較;常見(jiàn)問(wèn)答。5?理解代理設(shè)計(jì)模式代理通常就是一個(gè)介于尋求方和提供方之間的中介系統(tǒng)。尋求方是發(fā)出請(qǐng)求的一方,而提供方則是根據(jù)請(qǐng)求提供資源的一方。在Web世界中,它相當(dāng)于代理服務(wù)器??蛻?hù)端(萬(wàn)維網(wǎng)中的用戶(hù))在向網(wǎng)站發(fā)出請(qǐng)求時(shí),首先連接到代理服務(wù)器,然后向它請(qǐng)求諸如網(wǎng)頁(yè)之類(lèi)的資源。代理服務(wù)器在內(nèi)部評(píng)估此請(qǐng)

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶(hù)所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶(hù)上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶(hù)上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶(hù)因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論