我先问个问题,把大象放冰箱,总共分几步?你一定脱口而出:三步!第一步把冰箱门打开,第二步把大象放进去,第三部把冰箱门关上。这道题是不是太简单了?但当我们考虑细节的时候就没这么简单了。
假如你的冰箱门加了锁,那么第一步开门时就需要开锁。第二步把大象放进去也有细节要考虑。如果你的冰箱是卧式的,那么大象需要躺在里面。如果你的冰箱是立式的,那么大象可以站在里面。像这种主体逻辑一样,细节实现不同的场景,我们可以考虑使用模版方法模式模版方法模式 - 图1
在软件建模中,把大象放冰箱是一个算法,我们将其定义为一个模版方法。模版方法用一系列抽象的操作定义一个算法。就像我们例子中的三步,打开冰箱、大象放进去、关上冰箱门。具体如何开门和关门,如何放大象进去,则在子类中实现。冰箱不同,采用的方式自然也不同。
模版方法中定义操作的方法和先后步骤。而真正的操作方法实现则在子类中。

1. 实现模版方法

我们看看用代码如何实现把大象放冰箱。为了便于理解,我们尽量少引入类,我们假设冰箱自身有个行为是把大象放进来
冰箱抽象类 Fridge:

  1. public abstract class Fridge {
  2. public void placeElephant(){
  3. System.out.println("开始装大象");
  4. openDoor();
  5. putElephant();
  6. closeDoor();
  7. System.out.println("结束");
  8. }
  9. public abstract void openDoor();
  10. public abstract void putElephant();
  11. public abstract void closeDoor();
  12. }

Fridge 类中定义了一个模版方法 placeELephan, 里面按照顺序调用 openDoor、putELephant、closeDoor。这三个方法留待子类实现。
下面是两个具体的冰箱实现类代码。
立式冰箱类 VerticalFridge:

  1. public class VerticalFridge extends Fridge{
  2. @Override
  3. public void openDoor() {
  4. System.out.println("打开立式冰箱门");
  5. }
  6. @Override
  7. public void putElephant() {
  8. System.out.println("将大象站着放进去");
  9. }
  10. @Override
  11. public void closeDoor() {
  12. System.out.println("关上立式冰箱门");
  13. }
  14. }

卧式冰箱 HorizontalFridge:

  1. public class HorizontalFridge extends Fridge{
  2. @Override
  3. public void openDoor() {
  4. System.out.println("打开卧式冰箱门");
  5. }
  6. @Override
  7. public void putElephant() {
  8. System.out.println("将大象躺着放进去");
  9. }
  10. @Override
  11. public void closeDoor() {
  12. System.out.println("关上卧式冰箱门");
  13. }
  14. }

两个冰箱子类各自实现三个抽象方法。
客户端代码:

  1. public class Client {
  2. public static void main(String[] args) {
  3. Fridge fridge;
  4. fridge = new HorizontalFridge();
  5. fridge.placeElephant();
  6. System.out.println("---------I'm a line-----------");
  7. fridge = new VerticalFridge();
  8. fridge.placeElephant();
  9. }
  10. }

客户端代码中,两个不同的子类分表调用了placeElephant 方法,输出如下:

  1. 开始装大象
  2. 打开卧式冰箱门
  3. 将大象躺着放进去
  4. 关上卧式冰箱门
  5. 结束
  6. ---------I'm a line-----------
  7. 开始装大象
  8. 打开立式冰箱门
  9. 将大象站着放进去
  10. 关上立式冰箱门
  11. 结束

两个子类开始和结束的步骤一样,中间步骤的顺序也一样。这些逻辑在父类中实现。每个步骤具体的逻辑则在子类中各自实现。
类图:
模版方法模式 - 图2

2. 模版方法优缺点

2.1 优点

分离了算法中变和不变的部分。不变的部分定义在父类的模版方法中。变的部分通过子类实现。不变的算法部分可以被充分复用。当变的部分有新需求时,可以定义新的子类。从而实现了开闭原则。

2.2 缺点

模版意味着死板,我们设定好模版就必须按照模版的一、二、三步来执行。如果我们想调换顺序,或者增加几步就很难做到。除非定义新的模版。或者很小心的改动已有模版,避免影响现有程序逻辑。但这已经违反了开闭原则。

3. 模版方法适用场景

如果我们发现一系列的算法,主干一样,只是在局部的实现上有区别。此时我们可以考虑使用模版方法。把算法主干及不变的部分提炼出来,在父类中实现。抽象出变化部分的方法,交由不同的子类自己去实现。

4.小结

一般来说我们都是在子类中调用父类的方法。而模版方法恰恰相反,是父类的方法中调用子类的实现。正是因为这样,我们才能把不变的行为抽象到父类中,变化的部分留给子类实现。此外还有一类方法叫做钩子方法,它不是抽象的方法,父类有其缺省实现,一般是空方法。但是子类也可以通过重写去覆盖。