Template Method Design Pattern - tenji/ks GitHub Wiki

模板方法模式

模板方法是 Gamma 等人在《设计模式》一书中确定的**行为设计模式(Behavior Design Pattern)**之一。模板方法在超类中定义算法的骨架,但允许子类覆盖算法的特定步骤而不更改其结构。

零、引申问题

Day1:需要一个泡茶的类(包含烧水、酿造、添加调味品等流程),于是新增了一个类

Day2:需要一个泡咖啡的类(包含烧水、酿造、添加调味品等流程),于是又新增了一个类

...

DayN:需要一个泡XX的类(包含烧水、酿造、添加调味品等流程),于是又新增了一个类

上面这些类有大量重复的流程和代码,需要重构。

一、结构

template-method-structure

二、代码示例

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 工厂方法模式

关于两者的比较,传送门

∞、参考链接