signed

QiShunwang

“诚信为本、客户至上”

设计模式 - 模板模式

2021/6/24 17:36:08   来源:

模板模式:属于行为型设计模式,是设计模式中使用比较多,比较简单的设计模式之一了。虽然或许你现在还不知道怎样的写法是模板模式,但是或许你早就已经用过它了。
先来个定义:
模板模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
转成大白话就是,我在抽象的父类里定义了一些行为规则,和规定了这些行为规则的执行顺序。而身为子类的你只需要按自己的情况实现我定义的行为规则的具体内容即可。
举个简单的例子:
我规定了晚上睡觉前的应该要完成以下的事情,躺床上,刷牙,换上睡衣,而事情的执行顺序我规定为刷牙 -> 换睡衣 -> 躺床上,而大家只需要按自己实际情况做这几件事就好了,就像

  • 有人用黑人牙膏+普通牙刷,然后换上真丝睡衣,躺在他的席梦思床垫上
  • 有人用佳洁士+电动牙膏,然后换上皇帝的新装,躺在他的地铺上

好了,下面举个具体的代码例子,上码:
现在身为音乐盒上市公司的工程部老大的你,接收到上级发来的任务,说客户要做音乐盒了!!!
而且一做就是两款。这时,你满脸不屑,(ˉ▽ ̄~) 切~~才两款,so easy。
先拖来一个音乐盒标准设计须知

原始方式

public abstract class MusicBox {

    //打开开关
    abstract void start();

    //播放音乐
    abstract void sing();

    //音乐盒上的装饰开始动,风车转动,人物跳舞
    abstract void action();

    //关闭开关
    abstract void end();

    //让音乐盒工作
    abstract void play();
}

两种音乐盒的具体实现:

public class Box1 extends MusicBox{
    @Override
    void start() {
        System.out.println("打开第一种音乐盒的开始开关");
    }

    @Override
    void sing() {
        System.out.println("播放天空之城");
    }

    @Override
    void action() {
        System.out.println("音乐盒上面的风车在转动");
    }

    @Override
    void end() {
        System.out.println("关闭第一种音乐盒的开始开关");
    }

    @Override
    void play() {
        this.start();
        this.sing();
        this.action();
        this.end();
    }
}

public class Box2 extends MusicBox{
    @Override
    void start() {
        System.out.println("打开第二种音乐盒的开始开关");
    }


    @Override
    void sing() {
        System.out.println("播放梦中的婚礼");
    }

    @Override
    void action() {
        System.out.println("音乐盒上的情侣公仔开始跳舞");
    }

    @Override
    void end() {
        System.out.println("关闭第二种音乐盒的开始开关");
    }

    @Override
    void play() {
        this.start();
        this.sing();
        this.action();
        this.end();
    }
}

给客户演示两种音乐盒:

public class Client {
    public static void main(String[] args) {
        MusicBox box1 = new Box1();
        box1.play();
        System.out.println("===================");
        MusicBox box2 = new Box2();
        box2.play();
    }
}

运行结果:
在这里插入图片描述

哟嚯,好像是完美实现了客户的功能哟。但是仔细看了一下,两种音乐盒具体实现类里,使音乐盒工作的方法实现是一模一样的。啧啧啧,这不应该提到抽象父类中吗?是吧,来,搞起!

模板模式方式

public abstract class MusicBox {

    //打开开关
    abstract void start();

    //播放音乐
    abstract void sing();

    //音乐盒上的装饰开始动,风车转动,人物跳舞
    abstract void action();

    //关闭开关
    abstract void end();

    //让音乐盒工作
    public void play(){
        this.start();
        this.sing();
        this.action();
        this.end();
    }
}

public class Box1 extends MusicBox{
    @Override
    void start() {
        System.out.println("打开第一种音乐盒的开始开关");
    }

    @Override
    void sing() {
        System.out.println("播放天空之城");
    }

    @Override
    void action() {
        System.out.println("音乐盒上面的风车在转动");
    }

    @Override
    void end() {
        System.out.println("关闭第一种音乐盒的开始开关");
    }
}

public class Box2 extends MusicBox{
    @Override
    void start() {
        System.out.println("打开第二种音乐盒的开始开关");
    }


    @Override
    void sing() {
        System.out.println("播放梦中的婚礼");
    }

    @Override
    void action() {
        System.out.println("音乐盒上的情侣公仔开始跳舞");
    }

    @Override
    void end() {
        System.out.println("关闭第二种音乐盒的开始开关");
    }
}

给客户演示还是原来的方式:

public class Client {
    public static void main(String[] args) {
        MusicBox box1 = new Box1();
        box1.play();
        System.out.println("===================");
        MusicBox box2 = new Box2();
        box2.play();
    }
}

运行结果:
在这里插入图片描述
这里就是对应我在开始所说的:start(),sing(),action(),end()这些就是规则,而 play()就是我指定的规则执行顺序。
这就是模板模式,是不是so easy。使用模板模式的方式跟原始方式运行的结果时一样的,而且模板模式减少的代码冗余,使代码更加简洁。

优化

  • 在模板方法中,基本方法和尽量设计为protected,其他的属性和方法尽量别设置为protected
  • 为了防止被恶意操作,模板方法一版都会加上final关键字,不允许覆写
    所谓的基本方法就是我上面说的规则,模板方法就是我定义规则执行顺序的方法,所以上面的代码可以写成这样:
public abstract class MusicBox {

    //打开开关
    protected abstract void start();

    //播放音乐
    protected abstract void sing();

    //音乐盒上的装饰开始动,风车转动,人物跳舞
    protected abstract void action();

    //关闭开关
    protected abstract void end();

    //让音乐盒工作
    final public void play(){
        this.start();
        this.sing();
        this.action();
        this.end();
    }
}

public class Box1 extends MusicBox{
    @Override
    protected void start() {
        System.out.println("打开第一种音乐盒的开始开关");
    }

    @Override
    protected void sing() {
        System.out.println("播放天空之城");
    }

    @Override
    protected void action() {
        System.out.println("音乐盒上面的风车在转动");
    }

    @Override
    protected void end() {
        System.out.println("关闭第一种音乐盒的开始开关");
    }
}

public class Box2 extends MusicBox{
    @Override
    protected void start() {
        System.out.println("打开第二种音乐盒的开始开关");
    }


    @Override
    protected void sing() {
        System.out.println("播放梦中的婚礼");
    }

    @Override
    protected void action() {
        System.out.println("音乐盒上的情侣公仔开始跳舞");
    }

    @Override
    protected void end() {
        System.out.println("关闭第二种音乐盒的开始开关");
    }
}

给客户演示还是原来的方式:

public class Client {
    public static void main(String[] args) {
        MusicBox box1 = new Box1();
        box1.play();
        System.out.println("===================");
        MusicBox box2 = new Box2();
        box2.play();
    }
}

运行结果:
在这里插入图片描述

优点

  • 封装不变部分,扩展可变部分
  • 提取公共部分代码,便于维护
  • 行为由父类控制,子类实现

缺点

  • 跟我们的涉及习惯颠倒,子类执行的结果影响了父类的结果,也就是说子类对父类产生了影响
  • 如果执行顺序需要更改的话,要去修改抽象类的代码

扩展

客户看完演示的2种音乐盒后,提出了一个新的需求:我们播放天空之城的音乐盒有的有可动的装饰例如风车,例如水车,但是有的却是没有可动装饰,那咋搞?
看着客户这个需求,你陷入了沉思,不能为了这样一个小不同重新再开一个种类吧,那也太浪费资源了吧,一定有什么方法可以做这个兼容的

钩子方法

没错,可以通过在抽象类中用钩子方法做控制,让一种音乐盒根据不同装饰生产出不同产品。这里,添加一个判断是否有可动装饰的钩子方法进行控制,
所以我们有可以修改成这样:

public abstract class MusicBox {

    //打开开关
    protected abstract void start();

    //播放音乐
    protected abstract void sing();

    //音乐盒上的装饰开始动,风车转动,人物跳舞
    protected abstract void action();

    //关闭开关
    protected abstract void end();

    //判断是否有可动装饰  --- 钩子函数  这里通过外部的更改引起内部结果的更变
    protected boolean haveActionThings(){
        return true;
    }

    //让音乐盒工作
    final public void play(){
        this.start();
        this.sing();
        if(this.haveActionThings()) {
            this.action();
        }
        this.end();
    }
}

public class Box1 extends MusicBox{

    private boolean actionThingsTag = true;

    @Override
    protected void start() {
        System.out.println("打开第一种音乐盒的开始开关");
    }

    @Override
    protected void sing() {
        System.out.println("播放天空之城");
    }

    @Override
    protected void action() {
        System.out.println("音乐盒上面的风车在转动");
    }

    @Override
    protected void end() {
        System.out.println("关闭第一种音乐盒的开始开关");
    }

    @Override
    protected boolean haveActionThings() {
        return actionThingsTag;
    }
    public void setActionThingsTag(boolean actionThingsTag) {
        this.actionThingsTag = actionThingsTag;
    }
}

public class Box2 extends MusicBox{
    @Override
    protected void start() {
        System.out.println("打开第二种音乐盒的开始开关");
    }


    @Override
    protected void sing() {
        System.out.println("播放梦中的婚礼");
    }

    @Override
    protected void action() {
        System.out.println("音乐盒上的情侣公仔开始跳舞");
    }

    @Override
    protected void end() {
        System.out.println("关闭第二种音乐盒的开始开关");
    }
}

public class Client {
    public static void main(String[] args) {
        Box1 box1 = new Box1();
        box1.play();
        System.out.println("===================");
        Box1 box11 = new Box1();
        box11.setActionThingsTag(false);
        box11.play();
        System.out.println("===================");
        Box2 box2 = new Box2();
        box2.play();
    }
}

运行结果:
在这里插入图片描述

总结

模板方法模式通过父类搭建框架,子类重写父类方法,让子类更加专注于业务代码的实现。可以把重要的核心算法设计为模板方法,让其他细节功能放到子类中实现。

一千个读者就有一千个哈姆雷特,以上纯属于我个人的学习心得,如果有哪里写得不对,请大家指出,让我好学习一波,纠正错误,谢谢!