首先先介绍一下咖啡和茶的冲泡方法:
茶
1. 把水煮沸
2. 用沸水浸泡茶叶
3. 把茶放到杯子里
咖啡
1. 把水煮沸
2. 用沸水冲泡咖啡
3. 把咖啡倒进杯子
4. 加糖和牛奶
用 python 代码实现冲泡方法大概是这个样子:
- # 茶的制作方法
- class Tea:
- def prepare_recipe(self):
- # 在下边实现具体步骤
- self.boil_water()
- self.brew_tea_bag()
- self.pour_in_cup()
- def boil_water(self):
- print("Boiling water")
- def brew_tea_bag(self):
- print("Steeping the tea")
- def pour_in_cup(self):
- print("Pouring into cup")
- # 咖啡的制作方法
- class Coffee:
- def prepare_recipe(self):
- # 在下边实现具体步骤
- self.boil_water()
- self.brew_coffee_grinds()
- self.pour_in_cup()
- self.add_sugar_and_milk()
- def boil_water(self):
- print("Boiling water")
- def brew_coffee_grinds(self):
- print("Dripping Coffee through filter")
- def pour_in_cup(self):
- print("Pouring into cup")
- def add_sugar_and_milk(self):
- print("Adding Sugar and Milk")
仔细看上边两端代码会发现, 茶和咖啡的实现方式基本类似, 都有 prepare_recipe,boil_water,pour_in_cup 这三个方法.
问题: 如何重新设计这两个类来让代码更简洁呢?
首先看一下两个类的类图:
每个类中都有
prepare_recipe() boil_water() pour_in_cup()
方法.
每个类中 prepare_recipe() 方法的实现都不一样.
现在把 prepare_recipe() boil_water() pour_in_cup() 三个方法抽取出来做成一个父类 CoffeineBeverage(),Tea 和 Coffee 都继自 CoffeineBeverage().
因为每个类中 prepare_recipe() 实现的方法不一样, 所以 Tea 和 Coffee 类都分别实现了 prepare_recipe(). 问题: 那么, 有没有办法将 prepare_recipe() 也抽象化?
对比 Tea 和 Coffee 的 prepare_recipe() 方法会发现, 他们之间的差异主要是:
- def prepare_recipe(self):
- # 相同部分隐藏
- # self.boil_water()
- self.brew_tea_bag() # 差异 1
- #self.pour_in_cup()
- def prepare_recipe(self):
- # 相同部分隐藏
- # self.boil_water()
- self.brew_coffee_grinds() # 差异 1
- # self.pour_in_cup()
- self.add_sugar_and_milk() # 差异 2
这里的实现思路是, 将两处差异分别用新的方法名代替, 替换后结果如下:
- def prepare_recipe(self):
- # 新的实现方法
- self.boil_water()
- self.brew() # 差异 1 使用 brew 代替 brew_tea_bag 和 brew_coffee_grinds
- self.pour_in_cup()
- self.add_condiments() # 差异 2 Tea 不需要此方法, 可以用空的实现代替
新的类图如下:
现在, 类 Tea 和 Coffee 只需要实现具体的 brew() 和 add_condiments() 方法即可. 代码实现如下:
- class CoffeineBeverage:
- def prepare_recipe(self):
- # 新的实现方法
- self.boil_water()
- self.brew()
- self.pour_in_cup()
- self.add_condiments()
- def boil_water(self):
- print("Boiling water")
- def brew(self):
- # 需要在子类实现
- raise NotImplementedError
- def pour_in_cup(self):
- print("Pouring into cup")
- def add_condiments(self):
- # 这里其实是个钩子方法, 子类可以视情况选择是否覆盖
- # 钩子方法是一个可选方法, 也可以让钩子方法作为某些条件触发后的动作
- pass
- # 茶的制作方法
- class Tea(CoffeineBeverage):
- def brew(self):
- # 父类中声明了 raise NotImplementedError, 这里必须要实现此方法
- print("Steeping the tea")
- # Tea 不需要 add_condiments 方法, 所以这里不需要实现
- # 咖啡的制作方法
- class Coffee(CoffeineBeverage):
- def brew(self):
- # 父类中声明了 raise NotImplementedError, 这里必须要实现此方法
- print("Dripping Coffee through filter")
- def add_condiments(self):
- print("Adding Sugar and Milk")
模板方法
上述抽象过程使用的就是模板方法. 模板方法定义了一个算法的步骤, 并且允许子类为一个或多个步骤提供实现. 在这个例子中, prepare_recipe 就是一个模板方法.
定义: 模板方法牧师在一个方法中定义一个算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变算法结构的情况下, 重新定义算法中的某些步骤.
优点
使用模板方法可以将代码的复用最大化
子类只需要实现自己的方法, 将算法和实现的耦合降低.
好莱坞原则
模板方法使用到了一个原则, 好莱坞原则.
好莱坞原则, 别调用我, 我会调用你.
在这个原则之下, 允许低层组件将自己挂钩到系统上, 但是由高层组件来决定什么时候使用这些低层组件.
在上边的例子中, CoffeineBeverage 是高层组件, Coffee 和 Tea 是低层组件, 他们不会之间调用抽象类 (CoffeineBeverage).
一个例子
Python 第三方表单验证包 wtforms 的表单验证部分就使用到了模板方法模式. Field 类中 validate 方法就是一个模板方法, 在这个方法中, 会调用 pre_validate, _run_validation_chain,post_validate 方法来验证表单, 这些方法也都可以在子类中重新实现. 具体实现可以参考以下源码.
源码地址:
参考链接
本文例子来自《Head First 设计模式》
最后, 感谢女朋友支持和包容, 比
来源: https://juejin.im/post/5c03ab9bf265da616916fc74