设计模式之模板方法模式

        “在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。”

        模板方法模式主要解决的是写了大量重复代码的情况。两个方法完成的功能类似,代码逻辑也相似,只是内部有些具体实现细节有差异。这种情况就适合用模板方法模式来解决。下面以《Head First 设计模式》中的具体例子来讲解该模式的使用。

        假如说我现在有一个新需求,煮咖啡和泡茶。相关步骤如上图。可以看到,分别有四步操作。最简单的做法就是写两个方法,分别完成这四步就行了。这种方式实现起来最easy,但是代码质量最糟糕。写了很多重复代码不提,假如说我现在第一步不用水煮沸了,改用别的饮料煮沸,那么这两个方法都得进行修改,耦合度高。

        稍微好一点的做法是定义一个基类,提出共同的部分放进基类里。由上可以看到煮咖啡和泡茶的第一步和第三步是完全一样的,都是把水煮沸和倒进杯子里。所以把这两个方法放进基类里,子类去实现第二步和第四步。类图如下:

        这里的prepareRecipe方法暂且可以忽略。它只是一个总控方法,由它来去调用一二三四步骤,相当于main方法。

        我们已经优化过了,这种方式也是在实际项目开发中使用最多的方式,但是这种方式是否足够好?是否已经优化到底了?答案肯定是否定的,这种方式并没有考虑到不同部分的相同性。即使第二步和第四步不同,它们之间也是有逻辑相似的点。接下来我们进一步优化,抽象出来一个咖啡因饮料的类,如下:

        可以看到第二步和第四步也可以进行抽象,模板方法模式的具体代码如下:

public abstract class CaffeineBeverage {

    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();

    abstract void addCondiments();

    void boilWater() {
        System.out.println("Boiling water");
    }

    void pourInCup() {
        System.out.println("Pouring into cup");
    }
}

        首先定义一个抽象基类CaffeineBeverage。prepareRecipe方法即为模板方法,即骨架。一般定义成final类型,防止子类去覆写该方法。煮咖啡和泡茶的第一步和第三步是完全一样的,所以在抽象类里直接实现,而不同的第二步和第四步做成抽象方法,交给子类具体去实现。这也就是定义中说的延迟到子类来实现的概念。下面来看一下具体的咖啡和茶类的实现代码:

public class Coffee extends CaffeineBeverage {

    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    @Override
    public void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
}
​public class Tea extends CaffeineBeverage {

    @Override
    public void brew() {
        System.out.println("Steeping the tea");
    }

    @Override
    public void addCondiments() {
        System.out.println("Adding Lemon");
    }
}

​

        可以看到子类只要实现父类的抽象方法就可以了,只要把变化、不同的部分实现了就行,不需要再进行别的额外编写,算法逻辑已经在抽象基类中实现了。使用了模板方法模式的好处如下,读者可自行体会。 

        接下来要讲的是一个在模板方法模式中使用的小技巧,对模板方法模式进行挂钩。
“钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。”

        假如说现在我有了一个新需求,制作一种新咖啡,前三步都跟之前的煮咖啡一样,只是最后一步,这种咖啡不需要添加任何调料。那么我们就可以用钩子来实现这种需求。直接给出代码实现:

public abstract class CaffeineBeverageWithHook {

    void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    abstract void brew();

    abstract void addCondiments();

    void boilWater() {
        System.out.println("Boiling water");
    }

    void pourInCup() {
        System.out.println("Pouring into cup");
    }

    boolean customerWantsCondiments() {
        return true;
    }
}

        其中customerWantsCondiments方法即为钩子。在基类中是return true,如果return true才执行添加调料的动作。

public class CoffeeWithHook extends CaffeineBeverageWithHook {

    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    @Override
    public void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }

    @Override
    public boolean customerWantsCondiments() {
        return false;
    }
}

        在子类中覆写了该方法,变成了return false。所以在制作这种咖啡的时候,在进行第四步操作时,就不会执行addCondiments添加调料的方法了。

        但是通过我的理解,这种方式仅适合减少步骤的需求。假如我现在想在一种新式咖啡中添加第五步操作:加冰块。那么使用钩子则会很尴尬(真正的尴尬点在于使用了模板方法模式)。尴尬在于这第五步操作只是适合这种咖啡的,其他的饮料不会有这步操作。

  1. 一种解决方法是在这种新式咖啡本身这个子类中去实现第五步操作。但这样的话就违背了使用模板方法模式的大前提:通过调用抽象基类的模板方法(骨架),就可以完成所有的操作逻辑。这样也违背了针对接口编程而不针对实现编程的原则。
  2. 另一种解决办法是第五步方法仍然做成抽象方法,这就意味着每种实现了该抽象类的子类都得实现该抽象方法,哪怕它没有第五步操作。
  3. 还一种解决办法是第五步操作就做成一个普通方法,让新式咖啡去实现该方法。这样的话其他的子类不需要去实现该方法。

        后两种方案各有利弊,前者确保了子类一定要去实现抽象类中变化的方法这一前提,但却使子类做了不该这个子类做的功能;后者则正好相反,如果新式咖啡没有去实现第五步操作,不会报编译时异常,但在运行时则会生成错误的结果。我倾向于后一种解决方案,在实际项目中也是这么做的。即宁愿牺牲子类一定要去实现抽象类中变化的方法这一前提,也要保证每个类的高内聚性。解决方案本身就是不断取舍的过程,需要我们考虑到各种情况,选择一种较好的方式解决。

        在一些框架的设计中,也可以看到模板方法模式的存在。例如在spring mvc的View设计中。下面取自《深入分析Java Web技术内幕(修订版)》中的原话:

        “View只定义了接口方法,AbstractView类实现了在View中定义的所有方法,并留有一个抽象方法renderMergedOutputModel给子类去实现。而AbstractJasperReportsView和AbstractTemplateView抽象类又进一步实现了AbstractView的抽象方法renderMergedOutputModel,并分别进一步细化出renderReport抽象方法和renderMergedTemplateModel给子类去进一步实现。越往下面的子类需要实现的功能越少,整个模板已经建立,所以模板模式能加快整个程序的开发进度。”

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值