源碼地址:https://github.com/weilanhanf/PythonDesignPatterns
說明:
模板方法模式時行為模式中比較簡單的設計模式之一。模板方法關注這樣的一類行為:該類行為在執行過程中擁有大致相同的動作次序,只是動作在實現的具體細節上有所差異。例如:泡茶和泡咖啡,泡茶:把水煮沸,沸水加入茶葉,把倒進杯子。泡咖啡:把水煮沸,用沸水沖咖啡粉,把咖啡倒進杯子。這樣看來泡茶和泡咖啡的三個步驟基本相似。我們可以報這一類行為抽象成一個算法,並將其中的動作序列按1其先后順序也抽象出來作為該算法的一些步驟。至於這些步驟的實現細節,則有算法的子類去實現。
模板方法模式:定義一個操作中算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類不改變一個算法的結構即可重定義該算法的某些特定步驟。
模板方法模式 是一種基於繼承的代碼復用技術 ,將一些復雜流程的實現步驟封裝在一系列基本方法中 ,在抽象父類中提供一個稱之為模板方法的方法來定義這些基本方法的執行次序,而通過其子類來覆蓋某些步驟,從而使得相同的算法框架可以有不同的執行結果。
模板方法模式的結構
模板方法模式包含以下兩個角色: AbstractClass(抽象類) ConcreteClass(具體子類)
模板方法模式的實現:
模板方法 (Template Method)
基本方法 (Primitive Method) :1、抽象方法(Abstract Method)2、 具體方法(Concrete Method) 3、鈎子方法(Hook Method) :“掛鈎”方法和空方法
實例:
投資股票是種常見的理財方式,我國股民越來越多,實時查詢股票的需求也越來越大。今天,我們通過一個簡單的股票查詢客戶端來認識一種簡單的設計模式:模板模式。
根據股票代碼來查詢股價分為如下幾個步驟:登錄、設置股票代碼、查詢、展示。
#構造如下的虛擬股票查詢器: 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): if not self.login(usr, pwd): return False self.setCode(code) self.queryPrice() self.showPrice() return True 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)) if __name__=="__main__": web_a_query_dev=WebAStockQueryDevice() web_a_query_dev.operateQuery("myStockA","myPwdA","12345")
打印結果相同:
Web A:Login OK... user:myStockA pwd:myPwdA
Web A Querying...code:12345
Web A Stock Price...code:12345 price:20.0
模式優點
在父類中形式化地定義一個算法,而由它的子類來實現細節的處理,在子類實現詳細的處理算法時並不會改變算法中步驟的執行次序 。提取了類庫中的公共行為,將公共行為放在父類中,而通過其子類來實現不同的行為。 可實現一種反向控制結構,通過子類覆蓋父類的鈎子方法來決定某一特定步驟是否需要執行 更換和增加新的子類很方便,符合單一職責原則和開閉原則
模式缺點
需要為每一個基本方法的不同實現提供一個子類,如果父類中可變的基本方法太多,將會導致類的個數增加,系統會更加龐大,設計也會更加抽象(可結合橋接模式)
模式適用環境
一次性實現一個算法的不變部分,並將可變的行為留給子類來實現 。各子類中公共的行為應被提取出來,並集中到一個公共父類中,以避免代碼重復。 需要通過子類來決定父類算法中某個步驟是否執行,實現子類對父類的反向控制
另外:
在模板方法模式中,子類不顯式調用父類的方法,而是通過覆蓋父類的方法來實現某些具體的業務邏輯,父類控制對子類的調用,這種機制被稱為好萊塢原則(Hollywood Principle),好萊塢原則的定義為:“不要給我們打電話,我們會給你打電話(Don‘t call us, we’ll call you)”。在模板方法模式中,好萊塢原則體現在:子類不需要調用父類,而通過父類來調用子類,將某些步驟的實現寫在子類中,由父類來控制整個過程。