行為類設計模式
策略模式
一、客戶訊息通知
假設某司維護著一些客戶資料,需要在該司有新產品上市或者舉行新活動時通知客戶。現通知客戶的方式有兩種:簡訊通知、郵件通知。應如何設計該系統的客戶通知部分?為解決該問題,我們先構造客戶類,包括客戶常用的聯絡方式和基本資訊,同時也包括要傳送的內容。
class customer: customer_name="" snd_way="" info="" phone="" email="" def setPhone(self,phone): self.phone=phone def setEmail(self,mail): self.email=mail def getPhone(self): return self.phone def getEmail(self): return self.email def setInfo(self,info): self.info=info def setName(self,name): self.customer_name=name def setBrdWay(self,snd_way): self.snd_way=snd_way def sndMsg(self): self.snd_way.send(self.info)
snd_way向客戶傳送資訊的方式,該方式置為可設,即可根據業務來進行策略的選擇。
傳送方式構建如下:
class msgSender: dst_code="" def setCode(self,code): self.dst_code=code def send(self,info): pass class emailSender(msgSender): def send(self,info): print "EMAIL_ADDRESS:%s EMAIL:%s"%(self.dst_code,info) class textSender(msgSender): def send(self,info): print "TEXT_CODE:%s EMAIL:%s"%(self.dst_code,info)
在業務場景中將傳送方式作為策略,根據需求進行傳送。
if __name__=="__main__": customer_x=customer() customer_x.setName("CUSTOMER_X") customer_x.setPhone("10023456789") customer_x.setEmail("[email protected]") customer_x.setInfo("Welcome to our new party!") text_sender=textSender() text_sender.setCode(customer_x.getPhone()) customer_x.setBrdWay(text_sender) customer_x.sndMsg() mail_sender=emailSender() mail_sender.setCode(customer_x.getEmail()) customer_x.setBrdWay(mail_sender) customer_x.sndMsg()
結果列印如下:
PHONE_NUMBER:10023456789 TEXT:Welcome to our new party!
EMAIL_ADDRESS:[email protected] EMAIL:Welcome to our new party!
二、策略模式
策略模式定義如下:定義一組演算法,將每個演算法都封裝起來,並使他們之間可互換。以上述例子為例,customer類扮演的角色(Context)直接依賴抽象策略的介面,在具體策略實現類中即可定義個性化的策略方式,且可以方便替換。
上一節中我們介紹了橋接模式,仔細比較一下橋接模式和策略模式,如果把策略模式的Context設計成抽象類和實現類的方式,那麼策略模式和橋接模式就可以劃等號了。從類圖看上去,橋接模式比策略模式多了對一種角色(抽象角色)的抽象。二者結構的高度同構,也只能讓我們從使用意圖上去區分兩種模式:橋接模式解決抽象角色和實現角色都可以擴充套件的問題;而策略模式解決演算法切換和擴充套件的問題。
三、策略模式的優點和應用場景
優點:
1、各個策略可以自由切換:這也是依賴抽象類設計介面的好處之一;
2、減少程式碼冗餘;
3、擴充套件性優秀,移植方便,使用靈活。
應用場景:
1、演算法策略比較經常地需要被替換時,可以使用策略模式。如現在超市前臺,會常遇到刷卡、某寶支付、某信支付等方式,就可以參考策略模式。
四、策略模式的缺點
1、專案比較龐大時,策略可能比較多,不便於維護;
2、策略的使用方必須知道有哪些策略,才能決定使用哪一個策略,這與迪米特法則是相違背的。
責任鏈模式
一、請假系統
假設有這麼一個請假系統:員工若想要請3天以內(包括3天的假),只需要直屬經理批准就可以了;如果想請3-7天,不僅需要直屬經理批准,部門經理需要最終批准;如果請假大於7天,不光要前兩個經理批准,也需要總經理最終批准。類似的系統相信大家都遇到過,那麼該如何實現呢?首先想到的當然是if…else…,但一旦遇到需求變動,其臃腫的程式碼和複雜的耦合缺點都顯現出來。簡單分析下需求,“假條”在三個經理間是單向傳遞關係,像一條鏈條一樣,因而,我們可以用一條“鏈”把他們進行有序連線。
構造抽象經理類和各個層級的經理類:
class manager(): successor = None name = '' def __init__(self, name): self.name = name def setSuccessor(self, successor): self.successor = successor def handleRequest(self, request): pass class lineManager(manager): def handleRequest(self, request): if request.requestType == 'DaysOff' and request.number <= 3: print '%s:%s Num:%d Accepted OVER' % (self.name, request.requestContent, request.number) else: print '%s:%s Num:%d Accepted CONTINUE' % (self.name, request.requestContent, request.number) if self.successor != None: self.successor.handleRequest(request) class departmentManager(manager): def handleRequest(self, request): if request.requestType == 'DaysOff' and request.number <= 7: print '%s:%s Num:%d Accepted OVER' % (self.name, request.requestContent, request.number) else: print '%s:%s Num:%d Accepted CONTINUE' % (self.name, request.requestContent, request.number) if self.successor != None: self.successor.handleRequest(request) class generalManager(manager): def handleRequest(self, request): if request.requestType == 'DaysOff': print '%s:%s Num:%d Accepted OVER' % (self.name, request.requestContent, request.number) class request(): requestType = '' requestContent = '' number = 0
request類封裝了假期請求。在具體的經理類中,可以通過setSuccessor介面來構建“責任鏈”,並在handleRequest介面中實現邏輯。場景類中實現如下:
if __name__=="__main__": line_manager = lineManager('LINE MANAGER') department_manager = departmentManager('DEPARTMENT MANAGER') general_manager = generalManager('GENERAL MANAGER') line_manager.setSuccessor(department_manager) department_manager.setSuccessor(general_manager) req = request() req.requestType = 'DaysOff' req.requestContent = 'Ask 1 day off' req.number = 1 line_manager.handleRequest(req) req.requestType = 'DaysOff' req.requestContent = 'Ask 5 days off' req.number = 5 line_manager.handleRequest(req) req.requestType = 'DaysOff' req.requestContent = 'Ask 10 days off' req.number = 10 line_manager.handleRequest(req)
列印如下:
LINE MANAGER:Ask 1 day off Num:1 Accepted OVER
LINE MANAGER:Ask 5 days off Num:5 Accepted CONTINUE
DEPARTMENT MANAGER:Ask 5 days off Num:5 Accepted OVER
LINE MANAGER:Ask 10 days off Num:10 Accepted CONTINUE
DEPARTMENT MANAGER:Ask 10 days off Num:10 Accepted CONTINUE
GENERAL MANAGER:Ask 10 days off Num:10 Accepted OVER
二、責任鏈模式
責任鏈模式的定義如下:使多個物件都有機會處理請求,從而避免了請求的傳送者和接收者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳遞該請求,直到有物件處理它為止。
需要說明的是,責任鏈模式中的應該只有一個處理者,也就是說,本例中的“最終批准”為該物件所謂的“請求處理”。
三、責任鏈模式的優點和應用場景
優點:
1、將請求者與處理者分離,請求者並不知道請求是被哪個處理者所處理,易於擴充套件。
應用場景:
1、若一個請求可能由一個對請求有鏈式優先順序的處理群所處理時,可以考慮責任鏈模式。除本例外,銀行的客戶請求處理系統也可以用責任鏈模式實現(VIP客戶和普通使用者處理方式當然會有不同)。
四、責任鏈模式的缺點
1、如果責任鏈比較長,會有比較大的效能問題;
2、如果責任鏈比較長,若業務出現問題,比較難定位是哪個處理者的問題。
命令模式
一、飯店點餐系統
又是一個點餐系統(原諒作者的吃貨屬性)。不過這次的點餐系統是個飯店的點餐系統。飯店的點餐系統有什麼不同嘛?大夥想想看,在大多數飯店中,當服務員已經接到顧客的點單,錄入到系統中後,根據不同的菜品,會有不同的後臺反應。比如,飯店有冷盤間、熱菜間、主食間,那當服務員將菜品錄入到系統中後,冷盤間會打印出顧客所點的冷盤條目,熱菜間會打印出顧客所點的熱菜條目,主食間會打印出主食條目。那這個系統的後臺模式該如何設計?當然,直接在場景程式碼中加if…else…語句判斷是個方法,可這樣做又一次加重了系統耦合,違反了單一職責原則,遇到系統需求變動時,又會輕易違反開閉原則。所以,我們需要重新組織一下結構。
可以將該系統設計成前臺服務員系統和後臺系統,後臺系統進一步細分成主食子系統,冷盤子系統,熱菜子系統。後臺三個子系統設計如下:
class backSys(): def cook(self,dish): pass class mainFoodSys(backSys): def cook(self,dish): print "MAINFOOD:Cook %s"%dish class coolDishSys(backSys): def cook(self,dish): print "COOLDISH:Cook %s"%dish class hotDishSys(backSys): def cook(self,dish): print "HOTDISH:Cook %s"%dish
前臺服務員系統與後臺系統的互動,我們可以通過命令的模式來實現,服務員將顧客的點單內容封裝成命令,直接對後臺下達命令,後臺完成命令要求的事,即可。前臺系統構建如下:
class waiterSys(): menu_map=dict() commandList=[] def setOrder(self,command): print "WAITER:Add dish" self.commandList.append(command) def cancelOrder(self,command): print "WAITER:Cancel order..." self.commandList.remove(command) def notify(self): print "WAITER:Nofify..." for command in self.commandList: command.execute()
前臺系統中的notify介面直接呼叫命令中的execute介面,執行命令。命令類構建如下:
class Command(): receiver = None def __init__(self, receiver): self.receiver = receiver def execute(self): pass class foodCommand(Command): dish="" def __init__(self,receiver,dish): self.receiver=receiver self.dish=dish def execute(self): self.receiver.cook(self.dish) class mainFoodCommand(foodCommand): pass class coolDishCommand(foodCommand): pass class hotDishCommand(foodCommand): pass
Command類是個比較通過的類,foodCommand類是本例中涉及的類,相比於Command類進行了一定的改造。由於後臺系統中的執行函式都是cook,因而在foodCommand類中直接將execute介面實現,如果後臺系統執行函式不同,需要在三個子命令系統中實現execute介面。這樣,後臺三個命令類就可以直接繼承,不用進行修改了。(這裡子系統沒有變動,可以將三個子系統的命令廢棄不用,直接用foodCommand嗎?當然可以,各有利蔽。請讀者結合自身開發經驗,進行思考相對於自己業務場景的使用,哪種方式更好。)
為使場景業務精簡一些,我們再加一個選單類來輔助業務,選單類在本例中直接寫死。
class menuAll: menu_map=dict() def loadMenu(self):#載入選單,這裡直接寫死 self.menu_map["hot"] = ["Yu-Shiang Shredded Pork", "Sauteed Tofu, Home Style", "Sauteed Snow Peas"] self.menu_map["cool"] = ["Cucumber", "Preserved egg"] self.menu_map["main"] = ["Rice", "Pie"] def isHot(self,dish): if dish in self.menu_map["hot"]: return True return False def isCool(self,dish): if dish in self.menu_map["cool"]: return True return False def isMain(self,dish): if dish in self.menu_map["main"]: return True return False
業務場景如下:
if __name__=="__main__": dish_list=["Yu-Shiang Shredded Pork","Sauteed Tofu, Home Style","Cucumber","Rice"]#顧客點的菜 waiter_sys=waiterSys() main_food_sys=mainFoodSys() cool_dish_sys=coolDishSys() hot_dish_sys=hotDishSys() menu=menuAll() menu.loadMenu() for dish in dish_list: if menu.isCool(dish): cmd=coolDishCommand(cool_dish_sys,dish) elif menu.isHot(dish): cmd=hotDishCommand(hot_dish_sys,dish) elif menu.isMain(dish): cmd=mainFoodCommand(main_food_sys,dish) else: continue waiter_sys.setOrder(cmd) waiter_sys.notify()
列印如下:
WAITER:Add dish
WAITER:Add dish
WAITER:Add dish
WAITER:Add dish
WAITER:Nofify...
HOTDISH:Cook Yu-Shiang Shredded Pork
HOTDISH:Cook Sauteed Tofu, Home Style
COOLDISH:Cook Cucumber
MAINFOOD:Cook Rice
二、命令模式
命令模式的定義為:將一個請求封裝成一個物件,從而可以使用不同的請求將客戶端引數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。命令模式中通常涉及三類物件的抽象:Receiver,Command,Invoker(本例中的waiterSys)。
只有一個Invoker的命令模式也可以抽象成一個類似的“星形網路”,但與之前介紹的中介者模式不同,單純的命令模式更像是一個輻射狀的結構,由Invoker直接對Receiver傳遞命令,而一般不反向傳遞,中介者模式“星形網路”的中心,是個協調者,抽象結節間的資訊流全部或者部分是雙向的。
另外,命令模式的定義中提到了“撤銷和恢復功能”,也給了各位開發人員一個命令模式使用過程中的建議:各個Receiver中可以設計一個回滾介面,支援命令的“撤銷”。
三、命令模式的優點和應用場景
優點:
1、低耦合:呼叫者和接收者之間沒有什麼直接關係,二者通過命令中的execute介面聯絡;
2、擴充套件性好:新命令很容易加入,也很容易拼出“組合命令”。
應用場景:
1、觸發-反饋機制的系統,都可以使用命令模式思想。如基於管道結構的命令系統(如SHELL),可以直接套用命令模式;此外,GUI系統中的操作反饋(如點選、鍵入等),也可以使用命令模式思想。
四、命令模式的缺點
1、如果業務場景中命令比較多,那麼對應命令類和命令物件的數量也會增加,這樣系統會膨脹得很大。
中介者模式
一、倉儲管理系統
有一個手機倉儲管理系統,使用者有三方:銷售、倉庫管理員、採購。需求是:銷售一旦達成訂單,銷售人員會通過系統的銷售子系統部分通知倉儲子系統,倉儲子系統會將可出倉手機數量減少,同時通知採購管理子系統當前銷售訂單;倉儲子系統的庫存到達閾值以下,會通知銷售子系統和採購子系統,並督促採購子系統採購;採購完成後,採購人員會把採購資訊填入採購子系統,採購子系統會通知銷售子系統採購完成,並通知倉庫子系統增加庫存。
從需求描述來看,每個子系統都和其它子系統有所交流,在設計系統時,如果直接在一個子系統中整合對另兩個子系統的操作,一是耦合太大,二是不易擴充套件。為解決這類問題,我們需要引入一個新的角色-中介者-來將“網狀結構”精簡為“星形結構”。(為充分說明設計模式,某些系統細節暫時不考慮,例如:倉庫滿了怎麼辦該怎麼設計。類似業務性的內容暫時不考慮)
首先構造三個子系統,即三個類(在中介者模式中,這些類叫做同事些):
class colleague(): mediator = None def __init__(self,mediator): self.mediator = mediator class purchaseColleague(colleague): def buyStuff(self,num): print "PURCHASE:Bought %s"%num self.mediator.execute("buy",num) def getNotice(self,content): print "PURCHASE:Get Notice--%s"%content class warehouseColleague(colleague): total=0 threshold=100 def setThreshold(self,threshold): self.threshold=threshold def isEnough(self): if self.total<self.threshold: print "WAREHOUSE:Warning...Stock is low... " self.mediator.execute("warning",self.total) return False else: return True def inc(self,num): self.total+=num print "WAREHOUSE:Increase %s"%num self.mediator.execute("increase",num) self.isEnough() def dec(self,num): if num>self.total: print "WAREHOUSE:Error...Stock is not enough" else: self.total-=num print "WAREHOUSE:Decrease %s"%num self.mediator.execute("decrease",num) self.isEnough() class salesColleague(colleague): def sellStuff(self,num): print "SALES:Sell %s"%num self.mediator.execute("sell",num) def getNotice(self, content): print "SALES:Get Notice--%s" % content
當各個類在初始時都會指定一箇中介者,而各個類在有變動時,也會通知中介者,由中介者協調各個類的操作。
中介者實現如下:
class abstractMediator(): purchase="" sales="" warehouse="" def setPurchase(self,purchase): self.purchase=purchase def setWarehouse(self,warehouse): self.warehouse=warehouse def setSales(self,sales): self.sales=sales def execute(self,content,num): pass class stockMediator(abstractMediator): def execute(self,content,num): print "MEDIATOR:Get Info--%s"%content if content=="buy": self.warehouse.inc(num) self.sales.getNotice("Bought %s"%num) elif content=="increase": self.sales.getNotice("Inc %s"%num) self.purchase.getNotice("Inc %s"%num) elif content=="decrease": self.sales.getNotice("Dec %s"%num) self.purchase.getNotice("Dec %s"%num) elif content=="warning": self.sales.getNotice("Stock is low.%s Left."%num) self.purchase.getNotice("Stock is low. Please Buy More!!! %s Left"%num) elif content=="sell": self.warehouse.dec(num) self.purchase.getNotice("Sold %s"%num) else: pass
中介者模式中的execute是最重要的方法,它根據同事類傳遞的資訊,直接協調各個同事的工作。
在場景類中,設定倉儲閾值為200,先採購300,再賣出120,實現如下:
if __name__=="__main__": mobile_mediator=stockMediator()#先配置 mobile_purchase=purchaseColleague(mobile_mediator) mobile_warehouse=warehouseColleague(mobile_mediator) mobile_sales=salesColleague(mobile_mediator) mobile_mediator.setPurchase(mobile_purchase) mobile_mediator.setWarehouse(mobile_warehouse) mobile_mediator.setSales(mobile_sales) mobile_warehouse.setThreshold(200) mobile_purchase.buyStuff(300) mobile_sales.sellStuff(120)
列印結果如下:
PURCHASE:Bought 300
MEDIATOR:Get Info--buy
WAREHOUSE:Increase 300
MEDIATOR:Get Info--increase
SALES:Get Notice--Inc 300
PURCHASE:Get Notice--Inc 300
SALES:Get Notice--Bought 300
SALES:Sell 120
MEDIATOR:Get Info--sell
WAREHOUSE:Decrease 120
MEDIATOR:Get Info--decrease
SALES:Get Notice--Dec 120
PURCHASE:Get Notice--Dec 120
WAREHOUSE:Warning...Stock is low...
MEDIATOR:Get Info--warning
SALES:Get Notice--Stock is low.180 Left.
PURCHASE:Get Notice--Stock is low. Please Buy More!!! 180 Left
PURCHASE:Get Notice--Sold 120
二、中介者模式
中介者模式的定義為:用一箇中介物件封裝一系列的物件互動。中介者使各物件不需要顯式地互相作用,從而使其耦合鬆散,並可以獨立地改變它們之間的互動。
三、中介者模式的優點和應用場景
優點:
1、減少類與類的依賴,降低了類和類之間的耦合;
2、容易擴充套件規模。
應用場景:
1、設計類圖時,出現了網狀結構時,可以考慮將類圖設計成星型結構,這樣就可以使用中介者模式了。如機場排程系統(多個跑道、飛機、指揮塔之間的排程)、路由系統;著名的MVC框架中,其中的C(Controller)就是M(Model)和V(View)的中介者。
四、中介者模式的缺點
1、中介者本身的複雜性可能會很大,例如,同事類的方法如果很多的話,本例中的execute邏輯會很複雜。
模板模式
一、股票查詢客戶端
投資股票是種常見的理財方式,我國股民越來越多,實時查詢股票的需求也越來越大。今天,我們通過一個簡單的股票查詢客戶端來認識一種簡單的設計模式:模板模式。
根據股票程式碼來查詢股價分為如下幾個步驟:登入、設定股票程式碼、查詢、展示。構造如下的虛擬股票查詢器:
class StockQueryDevice(): stock_code="0" stock_price=0.0 def login(self,usr,pwd): pass def setCode(self,code): self.stock_code=code def queryPrice(self): pass def showPrice(self): pass
現在查詢機構很多,我們可以根據不同的查詢機構和查詢方式,來通過繼承的方式實現其對應的股票查詢器類。例如,WebA和WebB的查詢器類可以構造如下:
class WebAStockQueryDevice(StockQueryDevice): def login(self,usr,pwd): if usr=="myStockA" and pwd=="myPwdA": print "Web A:Login OK... user:%s pwd:%s"%(usr,pwd) return True else: print "Web A:Login ERROR... user:%s pwd:%s"%(usr,pwd) return False def queryPrice(self): print "Web A Querying...code:%s "%self.stock_code self.stock_price=20.00 def showPrice(self): print "Web A Stock Price...code:%s price:%s"%(self.stock_code,self.stock_price) class WebBStockQueryDevice(StockQueryDevice): def login(self,usr,pwd): if usr=="myStockB" and pwd=="myPwdB": print "Web B:Login OK... user:%s pwd:%s"%(usr,pwd) return True else: print "Web B:Login ERROR... user:%s pwd:%s"%(usr,pwd) return False def queryPrice(self): print "Web B Querying...code:%s "%self.stock_code self.stock_price=30.00 def showPrice(self): print "Web B Stock Price...code:%s price:%s"%(self.stock_code,self.stock_price)
在場景中,想要在網站A上查詢股票,需要進行如下操作:
if __name__=="__main__": web_a_query_dev=WebAStockQueryDevice() web_a_query_dev.login("myStockA","myPwdA") web_a_query_dev.setCode("12345") web_a_query_dev.queryPrice() web_a_query_dev.showPrice()
列印結果如下:
Web A:Login OK... user:myStockA pwd:myPwdA
Web A Querying...code:12345
Web A Stock Price...code:12345 price:20.0
每次操作,都會呼叫登入,設定程式碼,查詢,展示這幾步,是不是有些繁瑣?既然有些繁瑣,何不將這幾步過程封裝成一個介面。由於各個子類中的操作過程基本滿足這個流程,所以這個方法可以寫在父類中:
class StockQueryDevice(): stock_code="0" stock_price=0.0 def login(self,usr,pwd): pass def setCode(self,code): self.stock_code=code def queryPrice(self): pass def showPrice(self): pass def operateQuery(self,usr,pwd,code): self.login(usr,pwd) self.setCode(code) self.queryPrice() self.showPrice() return True
這樣,在業務場景中,就可以通過operateQuery一氣呵成了。
if __name__=="__main__": web_a_query_dev=WebAStockQueryDevice() web_a_query_dev.operateQuery("myStockA","myPwdA","12345")
這種基本每個程式設計師都會想到的解決方案,就是模板模式。很簡單吧。
但也許你會問,登入並不一定每次都會成功呀?是的,所以在operateQuery介面中需要做一重判斷,寫成:
def operateQuery(self,usr,pwd,code): if not self.login(usr,pwd): return False self.setCode(code) self.queryPrice() self.showPrice() return True
在模板模式中,像這樣類似於login等根據特定情況,定製某些特定動作的函式,被稱作鉤子函式。此例中,如果登入失敗(user:myStock B,pwd:myPwdA),會列印如下結果:
Web A:Login ERROR... user:myStockB pwd:myPwdA
二、模板模式
模板模式定義如下:定義一個操作中的演算法的框架,而將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構即可重新定義該演算法的某些特定的步驟。子類實現的具體方法叫作基本方法,實現對基本方法高度的框架方法,叫作模板方法。
三、模板模式的優點和應用
優點:
1、可變的部分可以充分擴充套件,不變的步驟可以充分封裝;
2、提取公共程式碼,減少冗餘程式碼,便於維護;
3、具體過程可以定製,總體流程方便掌控。
使用場景:
1、某超類的子類中有公有的方法,並且邏輯基本相同,可以使用模板模式。必要時可以使用鉤子方法約束其行為。具體如本節例子;
2、比較複雜的演算法,可以把核心演算法提取出來,周邊功能在子類中實現。例如,機器學習中的監督學習演算法有很多,如決策樹、KNN、SVM等,但機器學習的流程大致相同,都包含輸入樣本、擬合(fit)、預測等過程,這樣就可以把這些過程提取出來,構造模板方法,並通過鉤子方法控制流程。
四、模板模式的缺點
1、模板模式在抽象類中定義了子類的方法,即子類對父類產生了影響,部分影響了程式碼的可讀性。
迭代器模式
一、迭代器與生成器
今天的主角是迭代器模式。在python中,迭代器並不用舉太多的例子,因為python中的迭代器應用實在太多了(不管是python還是其它很多的程式語言中,實際上迭代器都已經納入到了常用的庫或者包中)。而且在當前,也幾乎沒有人專門去開發一個迭代器,而是直接去使用list、string、set、dict等python可迭代物件,或者直接使用__iter__和next函式來實現迭代器。如下例:
if __name__=="__main__": lst=["hello Alice","hello Bob","hello Eve"] lst_iter=iter(lst) print lst_iter print lst_iter.next() print lst_iter.next() print lst_iter.next() print lst_iter.next()
列印如下:
hello Alice
hello Bob
hello Eve
Traceback (most recent call last):
File "D:/WorkSpace/Project/PyDesignMode/example.py", line 719, in
print lst_iter.next()
StopIteration
在這種迭代器的使用過程中,如果next超過了迭代範圍,會丟擲異常。
在python物件的方法中,也可以輕易使用迭代器模式構造可迭代物件,如下例:
class MyIter(object): def __init__(self, n): self.index = 0 self.n = n def __iter__(self): return self def next(self): if self.index < self.n: value = self.index**2 self.index += 1 return value else: raise StopIteration()
__iter__和next實現了迭代器最基本的方法。如下方式進行呼叫:
if __name__=="__main__": x_square=MyIter(10) for x in x_square: print x
列印如下:
0
1
4
9
16
25
36
49
64
81
注意__iter__方法中的返回值,由於直接返回了self,因而該迭代器是無法重複迭代的,如以下業務場景:
if __name__=="__main__": x_square=MyIter(10) for x in x_square: print x for x in x_square: print x
只能列印一遍平方值。解決辦法是,在__iter__中不返回例項,而再返回一個物件,寫成:
def __iter__(self): return MyIter(self.n)
這樣,在每次迭代時都可以將迭代器“初始化”,就可以多次迭代了。
另外,在python中,使用生成器可以很方便的支援迭代器協議。生成器通過生成器函式產生,生成器函式可以通過常規的def語句來定義,但是不用return返回,而是用yield一次返回一個結果,在每個結果之間掛起和繼續它們的狀態,來自動實現迭代協議。
如下例:
def MyGenerater(n): index=0 while index<n: yield index**2 index+=1
注意,這是個函式。在每次呼叫生成器,得到返回結果後,現場得以保留,下次再呼叫該生 成器時,返回保留的現場從yield後繼續執行程式。
if __name__=="__main__": x_square=MyGenerater(10) for x in x_square: print x
列印結果與上面一致。
二、迭代器模式
迭代器模式的定義如下:它提供一種方法,訪問一個容器物件中各個元素,而又不需要暴露物件的內部細節。
訪問者模式
一、藥房業務系統
假設一個藥房,有一些大夫,一個藥品劃價員和一個藥房管理員,它們通過一個藥房管理系統組織工作流程。大夫開出藥方後,藥品劃價員確定藥品是否正常,價格是否正確;通過後藥房管理員進行開藥處理。該系統可以如何實現?最簡單的想法,是分別用一個一個if…else…把劃價員處理流程和藥房管理流程實現,這樣做的問題在於,擴充套件性不強,而且單一性不強,一旦有新藥的加入或者劃價流程、開藥流程有些變動,會牽扯比較多的改動。今天介紹一種解決這類問題的模式:訪問者模式。
首先,構造藥品類和工作人員類:
class Medicine: name="" price=0.0 def __init__(self,name,price): self.name=name self.price=price def getName(self): return self.name def setName(self,name): self.name=name def getPrice(self): return self.price def setPrice(self,price): self.price=price def accept(self,visitor): pass class Antibiotic(Medicine): def accept(self,visitor): visitor.visit(self) class Coldrex(Medicine): def accept(self,visitor): visitor.visit(self)
藥品類中有兩個子類,抗生素和感冒藥;
class Visitor: name="" def setName(self,name): self.name=name def visit(self,medicine): pass class Charger(Visitor): def visit(self,medicine): print "CHARGE: %s lists the Medicine %s. Price:%s " % (self.name,medicine.getName(),medicine.getPrice()) class Pharmacy(Visitor): def visit(self,medicine): print "PHARMACY:%s offers the Medicine %s. Price:%s" % (self.name,medicine.getName(),medicine.getPrice())
工作人員分為劃價員和藥房管理員。
在藥品類中,有一個accept方法,其引數是個visitor;而工作人員就是從Visitor類中繼承而來的,也就是說,他們就是Visitor,都包含一個visit方法,其引數又恰是medicine。藥品作為處理元素,依次允許(Accept)Visitor對其進行操作,這就好比是一條流水線上的一個個工人,對產品進行一次次的加工。整個業務流程還差一步,即藥方類的構建(流水線大機器)。
class ObjectStructure: pass class Prescription(ObjectStructure): medicines=[] def addMedicine(self,medicine): self.medicines.append(medicine) def rmvMedicine(self,medicine): self.medicines.append(medicine) def visit(self,visitor): for medc in self.medicines: medc.accept(visitor)
藥方類將待處理藥品進行整理,並組織Visitor依次處理。
業務程式碼如下:
if __name__=="__main__": yinqiao_pill=Coldrex("Yinqiao Pill",2.0) penicillin=Antibiotic("Penicillin",3.0) doctor_prsrp=Prescription() doctor_prsrp.addMedicine(yinqiao_pill) doctor_prsrp.addMedicine(penicillin) charger=Charger() charger.setName("Doctor Strange") pharmacy=Pharmacy() pharmacy.setName("Doctor Wei") doctor_prsrp.visit(charger) doctor_prsrp.visit(pharmacy)
列印如下:
CHARGE: Doctor Strange lists the Medicine Yinqiao Pill. Price:2.0
CHARGE: Doctor Strange lists the Medicine Penicillin. Price:3.0
PHARMACY:Doctor Wei offers the Medicine Yinqiao Pill. Price:2.0
PHARMACY:Doctor Wei offers the Medicine Penicillin. Price:3.0
二、訪問者模式
訪問者模式的定義如下:封裝一些作用於某種資料結構中的各元素的操作,它可以在不改變資料結構的前提下定義於作用於這些元素的新操作。
提到訪問者模式,就不得不提一下雙分派。分派分為靜態分派和動態分派。首先解釋下靜態分派,靜態分派即根據請求者的名稱和接收到的引數,決定多型時處理的操作。比如在Java或者C++中,定義名稱相同但引數不同的函式時,會根據最終輸入的引數來決定呼叫哪個函式。雙分派顧名思義,即最終的操作決定於兩個接收者的型別,在本例中,藥品和工作人員互相呼叫了對方(藥品的accept和工作人員的visit中,對方都是引數),就是雙分派的一種應用。
那麼Python支援靜態分派麼?先看下面的一個例子。
def max_num(x,y,z): return max(max(x,y),z) def max_num(x,y): return max(x,y) if __name__=="__main__": print max_num(1,2,4)
列印如下:
Traceback (most recent call last):
File "D:/WorkSpace/Project/PyDesignMode/example.py", line 786, in
print max_num(1,2,4)
TypeError: max_num() takes exactly 2 arguments (3 given)
可見,Python原生是不支援靜態分派的,因而也不直接支援更高層次的分派。訪問者模式實現的分派,是一種動態雙分派。但這並不妨礙Python通過訪問者模式實現一種基於類的“雙分派效果”。Python多分派可以參考David Mertz 博士的一篇文章:可愛的Python:多分派—用多元法泛化多樣性。
三、訪問者模式的優點和應用場景
優點:
1、將不同的職責非常明確地分離開來,符合單一職責原則;
2、職責的分開也直接導致擴充套件非常優良,靈活性非常高,加減元素和訪問者都非常容易。
應用場景:
1、要遍歷不同的物件,根據物件進行不同的操作的場景;或者一個物件被多個不同物件順次處理的情況,可以考慮使用訪問者模式。除本例外,報表生成器也可以使用訪問者模式實現,報表的資料來源由多個不同的物件提供,每個物件都是Visitor,報表這個Element順次Accept各訪問者完善並生成物件。
四、訪問者模式的缺點
1、訪問者得知了元素細節,與最小隔離原則相悖;
2、元素變更依舊可能引起Visitor的修改。
觀察者模式
一、火警報警器
在門面模式中,我們提到過火警報警器。在當時,我們關注的是通過封裝減少程式碼重複。而今天,我們將從業務流程的實現角度,來再次實現該火警報警器。
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 Observer: def update(self): pass class AlarmSensor(Observer): def update(self,action): print "Alarm Got: %s" % action self.runAlarm() def runAlarm(self): print "Alarm Ring..." class WaterSprinker(Observer): def update(self,action): print "Sprinker Got: %s" % action self.runSprinker() def runSprinker(self): print "Spray Water..." class EmergencyDialer(Observer): def update(self,action): print "Dialer Got: %s"%action self.runDialer() def runDialer(self): print "Dial 119..."
觀察者中定義了update介面,如果被觀察者狀態比較多,或者每個具體的觀察者方法比較多,可以通過update傳引數進行更豐富的控制。
下面構造被觀察者。
class Observed: observers=[] action="" def addObserver(self,observer): self.observers.append(observer) def notifyAll(self): for obs in self.observers: obs.update(self.action) class smokeSensor(Observed): def setAction(self,action): self.action=action def isFire(self): return True
被觀察者中首先將觀察物件加入到觀察者陣列中,若發生情況,則通過notifyAll通知各觀察者。
業務程式碼如下:
if __name__=="__main__": alarm=AlarmSensor() sprinker=WaterSprinker() dialer=EmergencyDialer() smoke_sensor=smokeSensor() smoke_sensor.addObserver(alarm) smoke_sensor.addObserver(sprinker) smoke_sensor.addObserver(dialer) if smoke_sensor.isFire(): smoke_sensor.setAction("On Fire!") smoke_sensor.notifyAll()
列印如下:
Alarm Got: On Fire!
Alarm Ring...
Sprinker Got: On Fire!
Spray Water...
Dialer Got: On Fire!
Dial 119...
二、觀察者模式
觀察者模式也叫釋出-訂閱模式,其定義如下:定義物件間一種一對多的依賴關係,使得當該物件狀態改變時,所有依賴於它的物件都會得到通知,並被自動更新。
觀察者模式的通知方式可以通過直接呼叫等同步方式實現(如函式呼叫,HTTP介面呼叫等),也可以通過訊息佇列非同步呼叫(同步呼叫指被觀察者釋出訊息後,必須等所有觀察者響應結束後才可以進行接下來的操作;非同步呼叫指被觀察者釋出訊息後,即可進行接下來的操作。)。事實上,許多開源的訊息佇列就直接支援釋出-訂閱模式,如Zero MQ等。
三、觀察者模式的優點和應用場景
優點:
1、觀察者與被觀察者之間是抽象耦合的;
2、可以將許多符合單一職責原則的模組進行觸發,也可以很方便地實現廣播。
應用場景:
1、訊息交換場景。如上述說到的訊息佇列等;
2、多級觸發場景。比如支援中斷模式的場景中,一箇中斷即會引發一連串反應,就可以使用觀察者模式。
四、觀察者模式的缺點
1、觀察者模式可能會帶來整體系統效率的浪費;
2、如果被觀察者之間有依賴關係,其邏輯關係的梳理需要費些心思。
直譯器模式
一、模擬吉他
要開發一個自動識別譜子的吉他模擬器,達到錄入譜即可按照譜發聲的效果。除了發聲裝置外(假設已完成),最重要的就是讀譜和譯譜能力了。分析其需求,整個過程大致上分可以分為兩部分:根據規則翻譯譜的內容;根據翻譯的內容演奏。我們用一個直譯器模型來完成這個功能。
class PlayContext(): play_text = None class Expression(): def interpret(self, context): if len(context.play_text) == 0: return else: play_segs=context.play_text.split(" ") for play_seg in play_segs: pos=0 for ele in play_seg: if ele.isalpha(): pos+=1 continue break play_chord = play_seg[0:pos] play_value = play_seg[pos:] self.execute(play_chord,play_value) def execute(self,play_key,play_value): pass class NormGuitar(Expression): def execute(self, key, value): print "Normal Guitar Playing--Chord:%s Play Tune:%s"%(key,value)
PlayContext類為譜的內容,這裡僅含一個欄位,沒有方法。Expression即表示式,裡面僅含兩個方法,interpret負責轉譯譜,execute則負責演奏;NormGuitar類覆寫execute,以吉他 的方式演奏。
業務場景如下:
if __name__=="__main__": context = PlayContext() context.play_text = "C53231323 Em43231323 F43231323 G63231323" guitar=NormGuitar() guitar.interpret(context)
列印如下:
Normal Guitar Playing--Chord:C Play Tune:53231323
Normal Guitar Playing--Chord:Em Play Tune:43231323
Normal Guitar Playing--Chord:F Play Tune:43231323
Normal Guitar Playing--Chord:G Play Tune:63231323
二、直譯器模式
直譯器模式定義如下:給定一種語言,定義它的文法表示,並定義一個直譯器,該直譯器使用該表示來解釋語言中的句子。典型的直譯器模式中會有終結符和非終結符之說,語法也根據兩種終結符,決定語句最終含義。上例中,非終結符就是空格,終結符就是整個句尾。
三、直譯器模式的優點和應用場景
優點:
1、在語法分析的場景中,具有比較好的擴充套件性。規則修改和制訂比較靈活。
應用場景:
1、若一個問題重複發生,可以考慮使用直譯器模式。這點在資料處理和日誌處理過程中使用較多,當資料的需求方需要將資料納為己用時,必須將資料“翻譯”成本系統的資料規格;同樣的道理,日誌分析平臺也需要根據不同的日誌格式翻譯成統一的“語言”。
2、特定語法直譯器。如各種解釋型語言的直譯器,再比如自然語言中基於語法的文字分析等。
四、直譯器模式的缺點
1、解釋規則多樣化會導致直譯器的爆炸;
2、直譯器目標比較單一,行為模式比較固定,因而重要的模組中儘量不要使用直譯器模式。
備忘錄模式
一、遊戲進度儲存
打過遊戲的朋友一定知道,大多數遊戲都有儲存進度的功能,如果一局遊戲下來,忘儲存了進度,那麼下次只能從上次進度點開始重新打了。一般情況下,儲存進度是要存在可持久化儲存器上,本例中先以儲存在記憶體中來模擬實現該場景的情形。
以模擬一個戰鬥角色為例。首先,建立遊戲角色。
class GameCharacter(): vitality = 0 attack = 0 defense = 0 def displayState(self): print 'Current Values:' print 'Life:%d' % self.vitality print 'Attack:%d' % self.attack print 'Defence:%d' % self.defense def initState(self,vitality,attack,defense): self.vitality = vitality self.attack = attack self.defense = defense def saveState(self): return Memento(self.vitality, self.attack, self.defense) def recoverState(self, memento): self.vitality = memento.vitality self.attack = memento.attack self.defense = memento.defense class FightCharactor(GameCharacter): def fight(self): self.vitality -= random.randint(1,10)
GameCharacter定義了基本的生命值、攻擊值、防禦值以及實現角色狀態控制的方法,FightCharactor實現具體的“戰鬥”介面。為實現儲存進度的細節,還需要一個備忘錄,來儲存進度。
class Memento: vitality = 0 attack = 0 defense = 0 def __init__(self, vitality, attack, defense): self.vitality = vitality self.attack = attack self.defense = defense
萬事俱備,在業務邏輯中可以進行類的排程了。
if __name__=="__main__": game_chrctr = FightCharactor() game_chrctr.initState(100,79,60) game_chrctr.displayState() memento = game_chrctr.saveState() game_chrctr.fight() game_chrctr.displayState() game_chrctr.recoverState(memento) game_chrctr.displayState()
列印如下:
Current Values:
Life:100
Attack:79
Defence:60
Current Values:
Life:91
Attack:79
Defence:60
Current Values:
Life:100
Attack:79
Defence:60
由生命值變化可知,先儲存狀態值,經過一輪打鬥後,生命值由100變為91,而後恢復狀態值,生命值又恢復成100。
二、備忘錄模式
備忘錄模式定義如下:在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態。這樣以後就可以將該物件恢復到原來儲存的狀態。在備忘錄模式中,如果要儲存的狀態多,可以創造一個備忘錄管理者角色來管理備忘錄。
三、備忘錄模式應用場景
1、需要儲存和恢復資料的相關狀態場景。如儲存遊戲狀態的場景;撤銷場景,如Ctrl-Z操作;事務回滾的應用。一般情況下事務回滾有兩種方式:一是把從恢復點開始的操作都反向執行一遍;二是直接恢復到恢復點的各種狀態。兩種方式各有優缺點,要結合業務場景,決定使用哪種模式;
2、副本監控場景。備忘錄可以當作一個臨時的副本監控,實現非實時和準實時的監控。
狀態模式
一、電梯控制器
電梯在我們周邊隨處可見,電梯的控制邏輯中心是由電梯控制器實現的。電梯的控制邏輯,即使簡單點設計,把狀態分成開門狀態,停止狀態和執行狀態,操作分成開門、關門、執行、停止,那流程也是很複雜的。首先,開門狀態不能開門、執行、停止;停止狀態不能關門,停止;執行狀態不能開門、關門、執行。要用一個一個if…else…實現,首先程式碼混亂,不易維護;二是不易擴充套件。至於各種設計原則什麼的……
那該如何實現?在上邊的邏輯中,每個操作僅僅是一個操作,狀態切換與操作是分離的,這也造成後來操作和狀態“相互配合”的“手忙腳亂”。如果把狀態抽象成一個類,每個狀態為一個子類,每個狀態實現什麼操作,不實現什麼操作,僅僅在這個類中具體實現就可以了。
下面我們實現這個邏輯。
先實現抽象的狀態類:
class LiftState: def open(self): pass def close(self): pass def run(self): pass def stop(self): pass
而後實現各個具體的狀態類:
class OpenState(LiftState): def open(self): print "OPEN:The door is opened..." return self def close(self): print "OPEN:The door start to close..." print "OPEN:The door is closed" return StopState() def run(self): print "OPEN:Run Forbidden." return self def stop(self): print "OPEN:Stop Forbidden." return self class RunState(LiftState): def open(self): print "RUN:Open Forbidden." return self def close(self): print "RUN:Close Forbidden." return self def run(self): print "RUN:The lift is running..." return self def stop(self): print "RUN:The lift start to stop..." print "RUN:The lift stopped..." return StopState() class StopState(LiftState): def open(self): print "STOP:The door is opening..." print "STOP:The door is opened..." return OpenState() def close(self): print "STOP:Close Forbidden" return self def run(self): print "STOP:The lift start to run..." return RunState() def stop(self): print "STOP:The lift is stopped." return self
為在業務中排程狀態轉移,還需要將上下文進行記錄,需要一個上下文的類。
class Context: lift_state="" def getState(self): return self.lift_state def setState(self,lift_state): self.lift_state=lift_state def open(self): self.setState(self.lift_state.open()) def close(self): self.setState(self.lift_state.close()) def run(self): self.setState(self.lift_state.run()) def stop(self): self.setState(self.lift_state.stop())
這樣,在進行電梯的排程時,只需要排程Context就可以了。業務邏輯中如下所示:
if __name__=="__main__": ctx = Context() ctx.setState(StopState()) ctx.open() ctx.run() ctx.close() ctx.run() ctx.stop()
列印如下:
STOP:The door is opening...
STOP:The door is opened...
OPEN:Run Forbidden.
OPEN:The door start to close...
OPEN:The dorr is closed
STOP:The lift start to run...
RUN:The lift start to stop...
RUN:The lift stopped...
由邏輯中可知,電梯先在STOP狀態,然後開門,開門時執行Run,被禁止,然後,關門、執行、停止。
二、狀態模式
狀態模式的定義如下:當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。
三、狀態模式的優點和應用場景
優點:
1、狀態模式的優點是結構清晰,相比於if…else…簡約了不少;
2、封裝性好,外部呼叫不必知道內部實現細節。
應用場景:
1、行為狀態改變的場景。這點在各種控制器中非常常見,同時,邏輯結構為狀態轉移圖的場景中都非常適用。
四、狀態模式的缺點
1、在狀態比較多時,子類也會非常多,不便於管理。