模版方法模式

我先问个问题,把大象放冰箱,总共分几步?你一定脱口而出:三步!第一步把冰箱门打开,第二步把大象放进去,第三部把冰箱门关上。这道题是不是太简单了?但当我们考虑细节的时候就没这么简单了。

假如你的冰箱门加了锁,那么第一步开门时就需要开锁。第二步把大象放进去也有细节要考虑。如果你的冰箱是卧式的,那么大象需要躺在里面。如果你的冰箱是立式的,那么大象可以站在里面。像这种主体逻辑一样,细节实现不同的场景,我们可以考虑使用模版方法模式
图片描述

在软件建模中,把大象放冰箱是一个算法,我们将其定义为一个模版方法。模版方法用一系列抽象的操作定义一个算法。就像我们例子中的三步,打开冰箱、大象放进去、关上冰箱门。具体如何开门和关门,如何放大象进去,则在子类中实现。冰箱不同,采用的方式自然也不同。

模版方法中定义操作的方法和先后步骤。而真正的操作方法实现则在子类中。

1. 实现模版方法

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

冰箱抽象类 Fridge

public abstract class Fridge {
    public void placeElephant(){
        System.out.println("开始装大象");
        openDoor();
        putElephant();
        closeDoor();
        System.out.println("结束");
    }

    public abstract void openDoor();

    public abstract void putElephant();

    public abstract void closeDoor();
}

Fridge 类中定义了一个模版方法 placeELephan, 里面按照顺序调用 openDoor、putELephant、closeDoor。这三个方法留待子类实现。

下面是两个具体的冰箱实现类代码。

立式冰箱类 VerticalFridge

public class VerticalFridge extends Fridge{
    @Override
    public void openDoor() {
        System.out.println("打开立式冰箱门");
    }

    @Override
    public void putElephant() {
        System.out.println("将大象站着放进去");
    }

    @Override
    public void closeDoor() {
        System.out.println("关上立式冰箱门");
    }
}

卧式冰箱 HorizontalFridge

public class HorizontalFridge extends Fridge{
    @Override
    public void openDoor() {
        System.out.println("打开卧式冰箱门");
    }

    @Override
    public void putElephant() {
        System.out.println("将大象躺着放进去");
    }

    @Override
    public void closeDoor() {
        System.out.println("关上卧式冰箱门");
    }
}

两个冰箱子类各自实现三个抽象方法。

客户端代码:

public class Client {
    public static void main(String[] args) {
        Fridge fridge;

        fridge = new HorizontalFridge();
        fridge.placeElephant();

        System.out.println("---------I'm a line-----------");

        fridge = new VerticalFridge();
        fridge.placeElephant();
    }
}

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

开始装大象
打开卧式冰箱门
将大象躺着放进去
关上卧式冰箱门
结束
---------I'm a line-----------
开始装大象
打开立式冰箱门
将大象站着放进去
关上立式冰箱门
结束

两个子类开始和结束的步骤一样,中间步骤的顺序也一样。这些逻辑在父类中实现。每个步骤具体的逻辑则在子类中各自实现。

类图:
图片描述

2. 模版方法优缺点

2.1 优点

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

2.2 缺点

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

3. 模版方法适用场景

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

4.小结

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