源码地址: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)”。在模板方法模式中,好莱坞原则体现在:子类不需要调用父类,而通过父类来调用子类,将某些步骤的实现写在子类中,由父类来控制整个过程。