結構類設計模式
代理模式
一、網路伺服器配置白名單
代理模式是一種使用頻率非常高的模式,在多個著名的開源軟體和當前多個著名的網際網路產品後臺程式中都有所應用。下面我們用一個抽象化的簡單例子,來說明代理模式。
首先,構造一個網路伺服器:
#該伺服器接受如下格式資料,addr代表地址,content代表接收的資訊內容 info_struct=dict() info_struct["addr"]=10000 info_struct["content"]="" class Server: content="" def recv(self,info): pass def send(self,info): pass def show(self): pass class infoServer(Server): def recv(self,info): self.content=info return "recv OK!" def send(self,info): pass def show(self): print "SHOW:%s"%self.content
infoServer有接收和傳送的功能,傳送功能由於暫時用不到,保留。另外新加一個介面show,用來展示伺服器接收的內容。接收的資料格式必須如info_struct所示,伺服器僅接受info_struct的content欄位。那麼,如何給這個伺服器設定一個白名單,使得只有白名單裡的地址可以訪問伺服器呢?修改Server結構是個方法,但這顯然不符合軟體設計原則中的單一職責原則。在此基礎之上,使用代理,是個不錯的方法。代理配置如下:
class serverProxy: pass class infoServerProxy(serverProxy): server="" def __init__(self,server): self.server=server def recv(self,info): return self.server.recv(info) def show(self): self.server.show() class whiteInfoServerProxy(infoServerProxy): white_list=[] def recv(self,info): try: assert type(info)==dict except: return "info structure is not correct" addr=info.get("addr",0) if not addr in self.white_list: return "Your address is not in the white list." else: content=info.get("content","") return self.server.recv(content) def addWhite(self,addr): self.white_list.append(addr) def rmvWhite(self,addr): self.white_list.remove(addr) def clearWhite(self): self.white_list=[]
代理中有一個server欄位,控制代理的伺服器物件,infoServerProxy充當Server的直接介面代理,而whiteInfoServerProxy直接繼承了infoServerProxy物件,同時加入了white_list和對白名單的操作。這樣,在場景中通過對白名單代理的訪問,就可以實現伺服器的白名單訪問了。
if __name__=="__main__": info_struct = dict() info_struct["addr"] = 10010 info_struct["content"] = "Hello World!" info_server = infoServer() info_server_proxy = whiteInfoServerProxy(info_server) print info_server_proxy.recv(info_struct) info_server_proxy.show() info_server_proxy.addWhite(10010) print info_server_proxy.recv(info_struct) info_server_proxy.show()
列印如下:
Your address is not in the white list.
SHOW:
recv OK!
SHOW:Hello World!
二、代理模式
代理模式定義如下:為某物件提供一個代理,以控制對此物件的訪問和控制。代理模式在使用過程中,應儘量對抽象主題類進行代理,而儘量不要對加過修飾和方法的子類代理。如上例中,如果有一個xServer繼承了Server,並新加了方法xMethod,xServer的代理應以Server為主題進行設計,而儘量不要以xServer為主題,以xServer為主題的代理可以從ServerProxy繼承並新增對應的方法。
在JAVA中,講到代理模式,不得不會提到動態代理。動態代理是實現AOP(面向切面程式設計)的重要實現手段。而在Python中,很少會提到動態代理,而AOP則會以另一種模式實現:裝飾模式。有關AOP的相關內容,我們會在裝飾模式這一節中進行說明。
三、代理模式的優點和應用場景
優點:
1、職責清晰:非常符合單一職責原則,主題物件實現真實業務邏輯,而非本職責的事務,交由代理完成;
2、擴充套件性強:面對主題物件可能會有的改變,代理模式在不改變對外介面的情況下,可以實現最大程度的擴充套件;
3、保證主題物件的處理邏輯:代理可以通過檢查引數的方式,保證主題物件的處理邏輯輸入在理想範圍內。
應用場景:
1、針對某特定物件進行功能和增強性擴充套件。如IP防火牆、遠端訪問代理等技術的應用;
2、對主題物件進行保護。如大流量代理,安全代理等;
3、減輕主題物件負載。如許可權代理等。
四、代理模式的缺點
1、可能會降低整體業務的處理效率和速度。
裝飾器模式
一、快餐點餐系統
又提到了那個快餐點餐系統,不過今天我們只以其中的一個類作為主角:飲料類。首先,回憶下飲料類:
class Beverage(): name = "" price = 0.0 type = "BEVERAGE" def getPrice(self): return self.price def setPrice(self, price): self.price = price def getName(self): return self.name class coke(Beverage): def __init__(self): self.name = "coke" self.price = 4.0 class milk(Beverage): def __init__(self): self.name = "milk" self.price = 5.0
除了基本配置,快餐店賣可樂時,可以選擇加冰,如果加冰的話,要在原價上加0.3元;賣牛奶時,可以選擇加糖,如果加糖的話,要原價上加0.5元。怎麼解決這樣的問題?可以選擇裝飾器模式來解決這一類的問題。首先,定義裝飾器類:
class drinkDecorator(): def getName(self): pass def getPrice(self): pass class iceDecorator(drinkDecorator): def __init__(self,beverage): self.beverage=beverage def getName(self): return self.beverage.getName()+" +ice" def getPrice(self): return self.beverage.getPrice()+0.3 class sugarDecorator(drinkDecorator): def __init__(self,beverage): self.beverage=beverage def getName(self): return self.beverage.getName()+" +sugar" def getPrice(self): return self.beverage.getPrice()+0.5
構建好裝飾器後,在具體的業務場景中,就可以與飲料類進行關聯。以可樂+冰為例,示例業務場景如下:
if __name__=="__main__": coke_cola=coke() print "Name:%s"%coke_cola.getName() print "Price:%s"%coke_cola.getPrice() ice_coke=iceDecorator(coke_cola) print "Name:%s" % ice_coke.getName() print "Price:%s" % ice_coke.getPrice()
列印結果如下:
Name:coke
Price:4.0
Name:coke +ice
Price:4.3
二、裝飾器模式
裝飾器模式定義如下:動態地給一個物件新增一些額外的職責。在增加功能方面,裝飾器模式比生成子類更為靈活。
裝飾器模式和上一節說到的代理模式非常相似,可以認為,裝飾器模式就是代理模式的一個特殊應用,兩者的共同點是都具有相同的介面,不同點是側重對主題類的過程的控制,而裝飾模式則側重對類功能的加強或減弱。
上一次說到,JAVA中的動態代理模式,是實現AOP的重要手段。而在Python中,AOP通過裝飾器模式實現更為簡潔和方便。
先來解釋一下什麼是AOP。AOP即Aspect Oriented Programming,中文翻譯為面向切面的程式設計,它的含義可以解釋為:如果幾個或更多個邏輯過程中(這類邏輯過程可能位於不同的物件,不同的介面當中),有重複的操作行為,就可以將這些行為提取出來(即形成切面),進行統一管理和維護。舉例子說,系統中需要在各個地方列印日誌,就可以將列印日誌這一操作提取出來,作為切面進行統一維護。
從程式設計思想的關係來看,可以認為AOP和OOP(面向物件的程式設計)是並列關係,二者是可以替換的,也可以結合起來用。實際上,在Python語言中,是天然支援裝飾器的,如下例:
def log(func): def wrapper(*args, **kw): print 'call %s():' % func.__name__ return func(*args, **kw) return wrapper @log def now(): print '2016-12-04' if __name__=="__main__": now()
列印如下:
call now():
2016-12-04
log介面就是裝飾器的定義,而Python的@語法部分則直接支援裝飾器的使用。
如果要在快餐點餐系統中列印日誌,該如何進行AOP改造呢?可以藉助類的靜態方法或者類方法來實現:
class LogManager: @staticmethod def log(func): def wrapper(*args): print "Visit Func %s"%func.__name__ return func(*args) return wrapper
在需要列印日誌的地方直接@LogManager.log,即可打印出訪問的日誌資訊。如,在beverage類的函式前加上@LogManager.log,場景類保持不變,則列印結果如下:
Visit Func getName
Name:coke
Visit Func getPrice
Price:4.0
Visit Func getName
Name:coke +ice
Visit Func getPrice
Price:4.3
三、裝飾器模式的優點和應用場景
優點:
1、裝飾器模式是繼承方式的一個替代方案,可以輕量級的擴充套件被裝飾物件的功能;
2、Python的裝飾器模式是實現AOP的一種方式,便於相同操作位於不同調用位置的統一管理。
應用場景:
1、需要擴充套件、增強或者減弱一個類的功能,如本例。
四、裝飾器模式的缺點
1、多層裝飾器的除錯和維護有比較大的困難。
介面卡模式
一、外包人員系統相容
假設某公司A與某公司B需要合作,公司A需要訪問公司B的人員資訊,但公司A與公司B協議介面不同,該如何處理?先將公司A和公司B針對各自的人員資訊訪問系統封裝了物件介面。
class ACpnStaff: name="" id="" phone="" def __init__(self,id): self.id=id def getName(self): print "A protocol getName method...id:%s"%self.id return self.name def setName(self,name): print "A protocol setName method...id:%s"%self.id self.name=name def getPhone(self): print "A protocol getPhone method...id:%s"%self.id return self.phone def setPhone(self,phone): print "A protocol setPhone method...id:%s"%self.id self.phone=phone class BCpnStaff: name="" id="" telephone="" def __init__(self,id): self.id=id def get_name(self): print "B protocol get_name method...id:%s"%self.id return self.name def set_name(self,name): print "B protocol set_name method...id:%s"%self.id self.name=name def get_telephone(self): print "B protocol get_telephone method...id:%s"%self.id return self.telephone def set_telephone(self,telephone): print "B protocol get_name method...id:%s"%self.id self.telephone=telephone
為在A公司平臺複用B公司介面,直接呼叫B公司人員介面是個辦法,但會對現在業務流程造成不確定的風險。為減少耦合,規避風險,我們需要一個幫手,就像是轉換電器電壓的介面卡一樣,這個幫手就是協議和介面轉換的介面卡。介面卡構造如下:
class CpnStaffAdapter: b_cpn="" def __init__(self,id): self.b_cpn=BCpnStaff(id) def getName(self): return self.b_cpn.get_name() def getPhone(self): return self.b_cpn.get_telephone() def setName(self,name): self.b_cpn.set_name(name) def setPhone(self,phone): self.b_cpn.set_telephone(phone)
介面卡將B公司人員介面封裝,而對外介面形式與A公司人員介面一致,達到用A公司人員介面訪問B公司人員資訊的效果。
業務示例如下:
if __name__=="__main__": acpn_staff=ACpnStaff("123") acpn_staff.setName("X-A") acpn_staff.setPhone("10012345678") print "A Staff Name:%s"%acpn_staff.getName() print "A Staff Phone:%s"%acpn_staff.getPhone() bcpn_staff=CpnStaffAdapter("456") bcpn_staff.setName("Y-B") bcpn_staff.setPhone("99987654321") print "B Staff Name:%s"%bcpn_staff.getName() print "B Staff Phone:%s"%bcpn_staff.getPhone()
列印如下:
A protocol setName method...id:123
A protocol setPhone method...id:123
A protocol getName method...id:123
A Staff Name:X-A
A protocol getPhone method...id:123
A Staff Phone:10012345678
B protocol set_name method...id:456
B protocol get_name method...id:456
B protocol get_name method...id:456
B Staff Name:Y-B
B protocol get_telephone method...id:456
B Staff Phone:99987654321
二、介面卡模式
介面卡模式定義如下:將一個類的介面變換成客戶端期待的另一種介面,從而使原本因介面不匹配而無法在一起工作的兩個類能夠在一起工作。介面卡模式和裝飾模式有一定的相似性,都起包裝的作用,但二者本質上又是不同的,裝飾模式的結果,是給一個物件增加了一些額外的職責,而介面卡模式,則是將另一個物件進行了“偽裝”。
介面卡可以認為是對現在業務的補償式應用,所以,儘量不要在設計階段使用介面卡模式,在兩個系統需要相容時可以考慮使用介面卡模式。
三、介面卡模式的優點和使用場景
優點:
1、介面卡模式可以讓兩個介面不同,甚至關係不大的兩個類一起執行;
2、提高了類的複用度,經過“偽裝”的類,可以充當新的角色;
3、介面卡可以靈活“拆卸”。
應用場景:
1、不修改現有介面,同時也要使該介面適用或相容新場景業務中,適合使用介面卡模式。例如,在一個嵌入式系統中,原本要將資料從Flash讀入,現在需要將資料從磁碟讀入,這種情況可以使用介面卡模式,將從磁碟讀入資料的介面進行“偽裝”,以從Flash中讀資料的介面形式,從磁碟讀入資料。
四、介面卡模式的缺點
1、介面卡模式與原配介面相比,畢竟增加了一層呼叫關係,所以,在設計系統時,不要使用介面卡模式。
門面模式
一、火警報警器(1)
假設有一組火警報警系統,由三個子元件構成:一個警報器,一個噴水器,一個自動撥打電話的裝置。其抽象如下:
class AlarmSensor: def run(self): print "Alarm Ring..." class WaterSprinker: def run(self): print "Spray Water..." class EmergencyDialer: def run(self): print "Dial 119..."
在業務中如果需要將三個部件啟動,例如,如果有一個煙霧感測器,檢測到了煙霧。在業務環境中需要做如下操作:
if __name__=="__main__": alarm_sensor=AlarmSensor() water_sprinker=WaterSprinker() emergency_dialer=EmergencyDialer() alarm_sensor.run() water_sprinker.run() emergency_dialer.run()
但如果在多個業務場景中需要啟動三個部件,怎麼辦?Ctrl+C加上Ctrl+V麼?當然可以這樣,但作為碼農的基本修養之一,減少重複程式碼是應該會被很輕易想到的方法。這樣,需要將其進行封裝,在設計模式中,被封裝成的新物件,叫做門面。門面構建如下:
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()
列印如下:
Alarm Ring...
Spray Water...
Dial 119...
二、門面模式
門面模式也叫外觀模式,定義如下:要求一個子系統的外部與其內部的通訊必須通過一個統一的物件進行。門面模式提供一個高層次的介面,使得子系統更易於使用。門面模式注重“統一的物件”,也就是提供一個訪問子系統的介面。門面模式與之前說過的模板模式有類似的地方,都是對一些需要重複方法的封裝。但從本質上來說,是不同的。模板模式是對類本身的方法的封裝,其被封裝的方法也可以單獨使用;而門面模式,是對子系統的封裝,其被封裝的介面理論上是不會被單獨提出來用的。
三、門面模式的優點和使用場景
優點:
1、減少了系統之間的相互依賴,提高了系統的靈活;
2、提高了整體系統的安全性:封裝起的系統對外的接口才可以用,隱藏了很多內部介面細節,若方法不允許使用,則在門面中可以進行靈活控制。
使用場景:
1、為一個複雜的子系統提供一個外界訪問的介面。這類例子是生活還是蠻常見的,例如電視遙控器的抽象模型,電信運營商的使用者互動裝置等;
2、需要簡化操作介面時。例如常見的扁平化系統操作介面等,在生活中和工業中都很常見。
四、門面模式的缺點
1、門面模式的缺點在於,不符合開閉原則,一旦系統成形後需要修改,幾乎只能重寫門面程式碼,這比繼承或者覆寫等方式,或者其它一些符合開閉原則的模式風險都會大一些。
組合模式
一、公司結構組織
每一個公司都有自己的組織結構,越是大型的企業,其組織結構就會越複雜。大多數情況下,公司喜歡用“樹形”結構來組織複雜的公司人事關係和公司間的結構關係。一般情況下,根結點代表公司的最高行政權利單位,分支節點表示一個個部門,而葉子結點則會用來代表每一個員工。每一個結點的子樹,表示該結點代表的部門所管理的單位。假設一個具有HR部門,財務部門和研發部門,同時在全國有分支公司的總公司,其公司結構,可以表示成如下邏輯:
class Company: name = '' def __init__(self, name): self.name = name def add(self, company): pass def remove(self, company): pass def display(self, depth): pass def listDuty(self): pass class ConcreteCompany(Company): childrenCompany = None def __init__(self, name): Company.__init__(self,name) self.childrenCompany = [] def add(self, company): self.childrenCompany.append(company) def remove(self, company): self.childrenCompany.remove(company) def display(self, depth): print'-'*depth + self.name for component in self.childrenCompany: component.display(depth+1) def listDuty(self): for component in self.childrenCompany: component.listDuty() class HRDepartment(Company): def __init__(self, name): Company.__init__(self,name) def display(self, depth): print '-'*depth + self.name def listDuty(self): #履行職責 print '%s\t Enrolling & Transfering management.' % self.name class FinanceDepartment(Company): def __init__(self, name): Company.__init__(self,name) def display(self, depth): print "-" * depth + self.name def listDuty(self): #履行職責 print '%s\tFinance Management.'%self.name class RdDepartment(Company): def __init__(self,name): Company.__init__(self,name) def display(self, depth): print "-"*depth+self.name def listDuty(self): print "%s\tResearch & Development."% self.name
在該例中,公司結構抽象僅考慮公司(ConcreteCompany)和部門(Department),公司有子公司的可能性,公司也有自己的部門,部門是最終的葉子結點。
假設總公司下設東邊的分公司一個,東邊的分公司下設東北公司和東南公司,顯示公司層級,並羅列這些的公司中各部門的職責,可以構建如下業務場景:
if __name__=="__main__": root = ConcreteCompany('HeadQuarter') root.add(HRDepartment('HQ HR')) root.add(FinanceDepartment('HQ Finance')) root.add(RdDepartment("HQ R&D")) comp = ConcreteCompany('East Branch') comp.add(HRDepartment('East.Br HR')) comp.add(FinanceDepartment('East.Br Finance')) comp.add(RdDepartment("East.Br R&D")) root.add(comp) comp1 = ConcreteCompany('Northast Branch') comp1.add(HRDepartment('Northeast.Br HR')) comp1.add(FinanceDepartment('Northeast.Br Finance')) comp1.add(RdDepartment("Northeast.Br R&D")) comp.add(comp1) comp2 = ConcreteCompany('Southeast Branch') comp2.add(HRDepartment('Southeast.Br HR')) comp2.add(FinanceDepartment('Southeast.Br Finance')) comp2.add(RdDepartment("Southeast.Br R&D")) comp.add(comp2) root.display(1) root.listDuty()
列印如下:
-HeadQuarter
--HQ HR
--HQ Finance
--HQ R&D
--East Branch
---East.Br HR
---East.Br Finance
---East.Br R&D
---Northast Branch
----Northeast.Br HR
----Northeast.Br Finance
----Northeast.Br R&D
---Southeast Branch
----Southeast.Br HR
----Southeast.Br Finance
----Southeast.Br R&D
HQ HR Enrolling & Transfering management.
HQ Finance Finance Management.
HQ R&D Research & Development.
East.Br HR Enrolling & Transfering management.
East.Br Finance Finance Management.
East.Br R&D Research & Development.
Northeast.Br HR Enrolling & Transfering management.
Northeast.Br Finance Finance Management.
Northeast.Br R&D Research & Development.
Southeast.Br HR Enrolling & Transfering management.
Southeast.Br Finance Finance Management.
Southeast.Br R&D Research & Development.
二、組合模式
組合模式也叫作部分-整體模式,其定義如下:將物件組合成樹形結構以表示“部分”和“整體”的層次結構,使得使用者對單個物件和組合物件的使用具有一致性。
三、組合模式的優點和使用場景
優點:
1、節點增加和減少是非常自由和方便的,這也是樹形結構的一大特點;
2、所有節點,不管是分支節點還是葉子結點,不管是呼叫一個結點,還是呼叫一個結點群,都是非常方便的。
使用場景:
1、維護部分與整體的邏輯關係,或者動態呼叫整體或部分的功能介面,可以考慮使用組合模式。例如,非常多的作業系統(如Linux)都把檔案系統設計成樹形結構,再比如說分散式應用中藉助Zookeeper,也可以組織和呼叫分散式叢集中的結點功能。
四、組合模式的缺點
1、由於葉子結點和分支結點直接使用了實現類,而不方便使用抽象類,這大大限制了介面的影響範圍;若結點介面發生變更,對系統造成的風險會比較大。
享元模式
一、網上咖啡選購平臺
假設有一個網上咖啡選購平臺,客戶可以在該平臺上下訂單訂購咖啡,平臺會根據使用者位置進行線下配送。假設其咖啡物件構造如下:
class Coffee: name = '' price =0 def __init__(self,name): self.name = name self.price = len(name)#在實際業務中,咖啡價格應該是由配置表進行配置,或者呼叫介面獲取等方式得到,此處為說明享元模式,將咖啡價格定為名稱長度,只是一種簡化 def show(self): print "Coffee Name:%s Price:%s"%(self.name,self.price)
其對應的顧客類如下:
class Customer: name="" def __init__(self,name): self.name=name def order(self,coffee_name): print "%s ordered a cup of coffee:%s"%(self.name,coffee_name) return Coffee(coffee_name)
按照一般的處理流程,使用者在網上預訂咖啡,其代表使用者的Customer類中生成一個Coffee類,直到交易流程結束。整個流程是沒有問題的。如果,隨著網站使用者越來越多,單位時間內購買咖啡的使用者也越來越多,併發量越來越大,對系統資源的消耗也會越來越大,極端情況下,會造成宕機等嚴重後果。此時,高效利用資源,就顯得非常重要了。
簡單分析下業務流程,高併發下使用者數量增加,而該模型下,每個使用者點一杯咖啡,就會產生一個咖啡例項,如果一種咖啡在該時間內被很多使用者點過,那麼就會產生很多同樣咖啡的例項。避免重複例項的出現,是節約系統資源的一個突破口。類似於單例模式,我們這裡在咖啡例項化前,增加一個控制例項化的類:咖啡工廠。
class CoffeeFactory(): coffee_dict = {} def getCoffee(self, name): if self.coffee_dict.has_key(name) == False: self.coffee_dict[name] = Coffee(name) return self.coffee_dict[name] def getCoffeeCount(self): return len(self.coffee_dict)
咖啡工廠中,getCoffeeCount直接返回當前例項個數。Customer類可以重寫下,如下:
class Customer: coffee_factory="" name="" def __init__(self,name,coffee_factory): self.name=name self.coffee_factory=coffee_factory def order(self,coffee_name): print "%s ordered a cup of coffee:%s"%(self.name,coffee_name) return self.coffee_factory.getCoffee(coffee_name)
假設業務中短時間內有多人訂了咖啡,業務模擬如下:
if __name__=="__main__": coffee_factory=CoffeeFactory() customer_1=Customer("A Client",coffee_factory) customer_2=Customer("B Client",coffee_factory) customer_3=Customer("C Client",coffee_factory) c1_capp=customer_1.order("cappuccino") c1_capp.show() c2_mocha=customer_2.order("mocha") c2_mocha.show() c3_capp=customer_3.order("cappuccino") c3_capp.show() print "Num of Coffee Instance:%s"%coffee_factory.getCoffeeCount()
列印如下:
A Client ordered a cup of coffee:cappuccino
Coffee Name:cappuccino Price:10
B Client ordered a cup of coffee:mocha
Coffee Name:mocha Price:5
C Client ordered a cup of coffee:cappuccino
Coffee Name:cappuccino Price:10
Num of Coffee Instance:2
根據結果可以得知,該模式下三個使用者點了兩種咖啡,最終的咖啡例項為2,而不是3。
二、享元模式
享元模式定義如下:使用共享物件支援大量細粒度物件。大量細粒度的物件的支援共享,可能會涉及這些物件的兩類資訊:內部狀態資訊和外部狀態資訊。內部狀態資訊就是可共享出來的資訊,它們儲存在享元物件內部,不會隨著特定環境的改變而改變;外部狀態資訊就不可共享的資訊了。享元模式中只包含內部狀態資訊,而不應該包含外部狀態資訊。這點在設計業務架構時,應該有所考慮。
三、享元模式的優點和使用場景
優點:
1、減少重複物件,大大節約了系統資源。
使用場景:
1、系統中存在大量的相似物件時,可以選擇享元模式提高資源利用率。咖啡訂購平臺比較小,若假設一個電商平臺,每個買家和賣家建立起買賣關係後,買家物件和賣家物件都是佔用資源的。如果一個賣家同時與多個買家建立起買賣關係呢?此時享元模式的優勢就體現出來了;
2、需要緩衝池的場景中,可以使用享元模式。如程序池,執行緒池等技術,就可以使用享元模式(事實上,很多的池技術中已經使得了享元模式)。
四、享元模式的缺點
1、享元模式雖然節約了系統資源,但同時也提高了系統的複雜性,尤其當遇到外部狀態和內部狀態混在一起時,需要先將其進行分離,才可以使用享元模式。否則,會引起邏輯混亂或業務風險;
2、享元模式中需要額外注意執行緒安全問題。
橋樑模式
一、畫筆與形狀
在介紹原型模式的一節中,我們舉了個圖層的例子,這一小節內容,我們同樣以類似畫圖的例子,說明一種結構類設計模式:橋樑模式。
在一個畫圖程式中,常會見到這樣的情況:有一些預設的圖形,如矩形、圓形等,還有一個物件-畫筆,調節畫筆的型別(如畫筆還是畫刷,還是毛筆效果等)並設定引數(如顏色、線寬等),選定圖形,就可以在畫布上畫出想要的圖形了。要實現以上需求,先從最抽象的元素開始設計,即形狀和畫筆(暫時忽略畫布,同時忽略畫筆引數,只考慮畫筆型別)。
class Shape: name="" param="" def __init__(self,*param): pass def getName(self): return self.name def getParam(self): return self.name,self.param class Pen: shape="" type="" def __init__(self,shape): self.shape=shape def draw(self): pass
形狀物件和畫筆物件是最為抽象的形式。接下來,構造多個形狀,如矩形和圓形:
class Rectangle(Shape): def __init__(self,long,width): self.name="Rectangle" self.param="Long:%s Width:%s"%(long,width) print "Create a rectangle:%s"%self.param class Circle(Shape): def __init__(self,radius): self.name="Circle" self.param="Radius:%s"%radius print "Create a circle:%s"%self.param
緊接著是構造多種畫筆,如普通畫筆和畫刷:
class NormalPen(Pen): def __init__(self,shape): Pen.__init__(self,shape) self.type="Normal Line" def draw(self): print "DRAWING %s:%s----PARAMS:%s"%(self.type,self.shape.getName(),self.shape.getParam()) class BrushPen(Pen): def __init__(self,shape): Pen.__init__(self,shape) self.type="Brush Line" def draw(self): print "DRAWING %s:%s----PARAMS:%s" % (self.type,self.shape.getName(), self.shape.getParam())
業務中的邏輯如下:
if __name__=="__main__": normal_pen=NormalPen(Rectangle("20cm","10cm")) brush_pen=BrushPen(Circle("15cm")) normal_pen.draw() brush_pen.draw()
列印如下:
Create a rectangle:Long:20cm Width:10cm
Create a circle:Radius:15cm
DRAWING Normal Line:Rectangle----PARAMS:('Rectangle', 'Long:20cm Width10cm')
DRAWING Brush Line:Circle----PARAMS:('Circle', 'Radius:15cm')
二、橋樑模式
橋樑模式又叫橋接模式,定義如下:將抽象與實現解耦(注意此處的抽象和實現,並非抽象類和實現類的那種關係,而是一種角色的關係,這裡需要好好區分一下),可以使其獨立變化。在形如上例中,Pen只負責畫,但沒有形狀,它終究是不知道要畫什麼的,所以我們把它叫做抽象化角色;而Shape是具體的形狀,我們把它叫做實現化角色。抽象化角色和實現化角色是解耦的,這也就意味著,所謂的橋,就是抽象化角色的抽象類和實現化角色的抽象類之間的引用關係。
三、橋樑模式的優點和應用場景
優點:
1、抽象角色與實現角色相分離,二者可以獨立設計,不受約束;
2、擴充套件性強:抽象角色和實現角色可以非常靈活地擴充套件。
應用場景:
1、不適用繼承或者原繼承關係中抽象類可能頻繁變動的情況,可以將原類進行拆分,拆成實現化角色和抽象化角色。例如本例中,若將形狀、粗細、繪畫樣式等屬於彙集在一個類中,一旦抽象類中有所變動,將造成巨大的風險;
2、重用性比較大的場景。比如開關控制邏輯的程式,開關就是抽象化角色,開關的形式有很多種,操作的實現化角色也有很多種,採用橋樑模式,(如當前例子)開關即可進行復用,整體會將設計的粒度減小。
四、橋樑模式的缺點
1、增加對系統理解的難度。