Template Method Design Pattern - tenji/ks GitHub Wiki
模板方法模式
模板方法是 Gamma 等人在《设计模式》一书中确定的**行为设计模式(Behavior Design Pattern)**之一。模板方法在超类中定义算法的骨架,但允许子类覆盖算法的特定步骤而不更改其结构。
零、引申问题
Day1:需要一个泡茶的类(包含烧水、酿造、添加调味品等流程),于是新增了一个类
Day2:需要一个泡咖啡的类(包含烧水、酿造、添加调味品等流程),于是又新增了一个类
...
DayN:需要一个泡XX的类(包含烧水、酿造、添加调味品等流程),于是又新增了一个类
上面这些类有大量重复的流程和代码,需要重构。
一、结构
二、代码示例
BeverageMaker 类(Abstract Class)
// Abstract class defining the template method
abstract class BeverageMaker {
// Template method defining the overall process
public final void makeBeverage() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// Abstract methods to be implemented by subclasses
abstract void brew();
abstract void addCondiments();
// Common methods
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
TeaMaker 类(Concrete1)
// Concrete subclass for making tea
class TeaMaker extends BeverageMaker {
// Implementing abstract methods
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding lemon");
}
}
CoffeeMaker 类(Concrete2)
// Concrete subclass for making coffee
class CoffeeMaker extends BeverageMaker {
// Implementing abstract methods
@Override
void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding sugar and milk");
}
}
Driver Program
public class Main {
public static void main(String[] args) {
System.out.println("Making tea:");
BeverageMaker teaMaker = new TeaMaker();
teaMaker.makeBeverage();
System.out.println("\nMaking coffee:");
BeverageMaker coffeeMaker = new CoffeeMaker();
coffeeMaker.makeBeverage();
}
}
三、优点
- 通过把子类中不变的部分抽离到子类中,从而达到去除子类方法的重复代码便于维护;
- 可以让子类仅覆盖大型算法的某些部分,从而减少它们受到算法其他部分发生的更改的影响。
四、缺点
- 某些子类可能会受到所提供的算法框架的限制,因为算法的骨架已经在超类中定死了;
- 模板方法的步骤越多,维护起来就越困难;
- 子类执行的结果影响了父类的结果(算法的执行一会在子类,一会又回到父类),这和我们平时设计习惯颠倒了,在复杂项目中,会带来阅读上的难度;
- 可能引起子类泛滥和为了继承而继承的问题。
如何避免可能出现的子类泛滥的问题?
我们可以利用回调函数(Callback)代替子类继承。比如使用回调函数来替代模板方法来完成 brew 这个步骤:
public interface Callback {
void brew();
}
public class SubCallback implements Callback {
@Override
public void brew() {
System.out.println("SubCallback brew");
}
}
// Abstract class defining the template method
abstract class BeverageMaker {
// Template method defining the overall process
public final void makeBeverage(Callback callback) {
boilWater();
// 使用 Callback 代替子类的方法
callback.brew();
pourInCup();
addCondiments();
}
// Abstract methods to be implemented by subclasses
abstract void addCondiments();
// Common methods
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
五、什么时候适合使用模板方法模式?
- 具有变体的通用算法:当您有一个具有通用结构但具有一些不同步骤或实现的算法时,模板方法模式有助于将公共步骤封装在超类中,同时允许子类覆盖特定步骤;
- 代码可重用性:如果你有类似的任务或流程需要在不同的上下文中执行,模板方法模式通过在一处定义公共步骤来促进代码重用;
- 强制结构:当你想要在算法中强制执行特定结构或步骤序列,同时允许某些部分具有灵活性时,这是很有用的;
- 减少重复:通过将常见行为集中在抽象类中并避免子类中的代码重复,模板方法模式有助于维护干净且有组织的代码库。
六、什么时候不适合使用模板方法模式?
- 当算法高度可变时:如果你正在使用的算法在结构和步骤上差异很大,并且它们之间的共同点极少,使用模板方法模式可能不合适,因为它可能导致过度的复杂性或不必要的抽象;
- 步骤之间的紧密耦合:如果算法的步骤之间存在紧密耦合,使得一个步骤的更改需要其他步骤的更改,模板方法模式可能无法提供足够的灵活性;
- 抽象的开销:如果抽象和继承的成本超过了代码重用和结构实施的好处,那么最好避免使用模板方法模式并选择更简单的解决方案。
七、模板方法模式 vs 策略模式
- 策略类似于模板方法,只不过其粒度(granularity)不同;
- 模板方法使用继承来改变算法的一部分,策略使用委托来改变整个算法;
- 策略修改单个对象的逻辑,模板方法修改整个类的逻辑。
更多关于策略模式,传送门
八、模板方法模式 vs 工厂方法模式
关于两者的比较,传送门