《HF 设计模式》 C8 模板方法模式

1 模板方法模式

1.1 茶和咖啡的制作过程

在学习模板方法模式前,先来看看茶和咖啡的制作过程:

Tea:
1,把水煮沸
2,用沸水冲泡茶叶
3,把茶倒进杯子里
4,加柠檬

Coffee:
1,把水煮沸
2,用沸水冲泡咖啡
3,把咖啡倒进杯子里
4,加糖和牛奶

根据这些步骤,我们可以写两个类来模拟它们的制作过程

/**
 * @author 雫
 * @date 2021/3/8 - 11:46
 * @function
 */
public class Tea {

    public Tea() {}

    public void boilWater() {
        System.out.println("把水煮沸");
    }

    public void steepTeaBag() {
        System.out.println("用沸水冲泡茶叶");
    }

    public void pourInCup() {
        System.out.println("把茶倒进杯子里");
    }

    public void addLemon() {
        System.out.println("加柠檬");
    }

    public void prepareTea() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

}


/**
 * @author 雫
 * @date 2021/3/8 - 11:46
 * @function
 */
public class Coffee {

    public Coffee() {}

    public void boilWater() {
        System.out.println("把水煮沸");
    }

    public void brewCoffeeGrinds() {
        System.out.println("用沸水冲泡咖啡");
    }

    public void pourInCup() {
        System.out.println("把咖啡倒进杯子里");
    }

    public void addSugarAndMilk() {
        System.out.println("加糖和牛奶");
    }

    public void prepareCoffee() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }

}

观察上面两个类,我们发现了一些重复的代码,boilWate()和pourInCup()方法完全相同,这些代码重复出现在两个类中,如果以后需要扩展产品,这两个方法也可能还需要用到,并且上述两个类,功能和实现被捆绑到一起,如果要对功能进行更改,就必须对源码进行修改,违反了开闭原则

1.2 优化饮料的制作过程

为此我们可以创建一个超类,在超类中编写好boilWater和pourInCup()方法,这样子类就可以直接调用,并且这两个步骤如果出现了变化,只需要在超类中更改即可

/**
 * @author 雫
 * @date 2021/3/8 - 12:04
 * @function
 */
public abstract class Drink {

    public Drink() {}

    public abstract void prepare();

    public void boilWater() {
        System.out.println("把水煮沸");
    }

    public void pourInCup() {
        System.out.println("倒进杯子里");
    }
}

/**
 * @author 雫
 * @date 2021/3/8 - 11:46
 * @function
 */
public class Tea extends Drink {

    public Tea() {}

    public void steepTeaBag() {
        System.out.println("用沸水冲泡茶叶");
    }

    public void addLemon() {
        System.out.println("加柠檬");
    }

    @Override
    public void prepare() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

}

但这个过程中,仍然有优化的空间,如茶和咖啡的煮沸过程,和最后添加调料的过程,大致上是一致的,接着可以新增brew()和add()两个抽象方法,放在Drink中,等待子类来实现

/**
 * @author 雫
 * @date 2021/3/8 - 12:04
 * @function
 */
public abstract class Drink {

    public Drink() {}

    public final void prepare() {
        boilWater();
        brew();
        pourInCup();
        add();
    }

    public abstract void brew();

    public abstract void add();

    public void boilWater() {
        System.out.println("把水煮沸");
    }

    public void pourInCup() {
        System.out.println("倒进杯子里");
    }
}

/**
 * @author 雫
 * @date 2021/3/8 - 11:46
 * @function
 */
public class Tea extends Drink {

    public Tea() {}

    @Override
    public void brew() {
        System.out.println("用沸水冲泡茶叶");
    }

    @Override
    public void add() {
        System.out.println("加柠檬");
    }

}

Drink控制了生成产品的过程,但是具体的实现交给了子类完成,prepare()就是一个模板方法

1.3 模板方法模式

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

回到上面的prepare()方法
在这里插入图片描述
即超类中有一个包含了若干个抽象方法的模板方法,不同的子类继承该超类,不同地实现该超类中的抽象方法,模板方法就有了“不同的功能”

模板方法所在的超类:

/**
 * @author 雫
 * @date 2021/3/8 - 12:56
 * @function 模板方法所在的超类
 */
public abstract class Template {

    final void templateMethod() {
        primitiveOperation1();
        primitiveOperation2();
        concreteOperation();
        hook();
    }

    abstract void primitiveOperation1();

    abstract void primitiveOperation2();

    final void concreteOperation() {
        //具体实现
    }

    void hook() {
        //钩子
    }

}

1.4 对模板方法进行挂钩(hook)

在这里插入图片描述
对于hook() 钩子,是一种被声明在抽象类中的方法,但只有空的或者默认的实现,可以被子类覆盖,钩子的存在,可以让子类有能力对算法的不同点进行挂钩,要不要挂钩,由子类自己决定

一种简单使用钩子的方式:

/**
 * @author 雫
 * @date 2021/3/8 - 12:04
 * @function 
 */
public abstract class Drink {

    public Drink() {}

    public final void prepare() {
        boilWater();
        brew();
        pourInCup();
        //挂钩点
        if(wantAddSomething()) {
            add();
        }
    }

    public abstract void brew();

    public abstract void add();

    public void boilWater() {
        System.out.println("把水煮沸");
    }

    public void pourInCup() {
        System.out.println("倒进杯子里");
    }

    //hook,可以被子类覆盖,从而对模板方法进行一定的控制
    public boolean wantAddSomething() {
        return true;
    }
}


/**
 * @author 雫
 * @date 2021/3/8 - 11:46
 * @function
 */
public class Coffee extends Drink {

    public Coffee() {}

    @Override
    public void brew() {
        System.out.println("用沸水冲泡咖啡");
    }

    @Override
    public void add() {
        System.out.println("加糖和牛奶");
    }

	//重写hook,可以一定地控制模板方法
    @Override
    public boolean wantAddSomething() {
        return false;
    }
}

在这里插入图片描述
hook能作为条件控制,影响模板方法的流程,使子类有一定能力来控制模板方法

1.5 好莱坞原则

设计原则

别调用我们,我们会调用你
(别给我打电话,我 会打给你)

好莱坞原则可以让我们降低对象依赖

如在一个系统中,高层组件依赖低层组件,低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又依赖低层组件,想要搞清这种系统实在是让人头大

好莱坞原则允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件

在这里插入图片描述
理解这个原则还得再回到先前的例子中:
在这里插入图片描述
在coffee.prepare()这个过程中发生了什么?

1,调用Drink超类中的prepare()方法
2,Drink超类中的preapre()开始执行
3,Drink超类中的prepare()遇到了抽象方法brew()add()
4,到Coffee子类中执行已实现的 brew()add()
5,执行完毕

Drink就是高层组件,Coffee就是低层组件,当执行prepare()时,是Drink调用了Coffee,而不是Drink依赖了Coffee

通过这样的方式来减少了对象依赖,让系统更干净,更具有弹性,低层组件被挂钩到了高层组件中,而不是让高层组件依赖低层组件

好莱坞原则依赖倒置原则都是为了解耦,减少对象依赖,注意要避免循环依赖

1.6 JavaAPI中的模板方法模式

模板方法模式常用于创建“框架”,由框架来控制如何做事情,用户来指定框架算法中的每个步骤的细节

在java.util中,存在着一个便捷的排序功能,通过Arrays.sort()进行排序,对于简单的数组,如Integer类型的数组就可以直接进行排序:
在这里插入图片描述
现在我们更改一下Coffee,增加一个int类型的price成员,想对Coffee对象根据price来进行排序,为此让Coffee实现Comparable接口,并实现CompareTo()方法即可:
在这里插入图片描述
排序结果:
在这里插入图片描述
sort可以看作是模板方法,CompareTo()是用户实现的抽象方法,是模板方法中的一环,这就是一种变形的模板方法模式,在Java中存在着许多这样变形的模板方法模式,提供一个功能,但功能不是完整的,需要用户实现一部分,使用时功能因为用户不同的实现而不同,并且可以重写钩子来控制模板方法的运行流程

1.7 模板方法模式小结

在这里插入图片描述

1,模板方法模式定义了算法的步骤,把部分步骤的实现延迟到子类

2,模板方法的抽象类可以定义 具体方法,抽象方法,钩子,抽象方法由子类实现

3,为了防止子类改变模板方法,可以将模板方法和具体方法声明为final

4,策略模式和模板方法模式有相似的功能,即提供方法的不同实现,但是策略模式使用组合,有更好的系统弹性模板方法模式采用继承,能更好的代码复用

5,模板方法模式中的钩子,可以在子类中被重写,从而控制模板方法的运行流程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值