YAZONG 我的开源

设计模式(十)行为模式(类行为型模式)模板方法-TEMPLATE METHOD

 
0 评论0 浏览

2、10模板方法模式

定义

1)定义一个操作中的算法的框架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

2)模板方法模式是通过把不变行为搬移到超类,去除子类中的重复代码来体现它的优势。

3)修改了子类,影响了父类行为的结果,曲线救国的方式实现了父类依赖子类的场景,模板方法模式就是这种效果。

4)当我们要完成在某一细节层次一致的一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时,我们通常要考虑用模板方法模式来处理。

通用类图

image.png

模板方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:

1)抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。

2)模板方法:由抽象类声明并加以实现。一般来说,模板方法调用抽象方法来完成主要的逻辑功能,并且,模板方法大多会定义成final类型,指明主要的逻辑功能在子类中不能被覆写。

3)钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模板方法的逻辑。

4)抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏决定了程序是否稳定性。

案例

public class Client1 {

    public static void main(String[] args) {

 

        AbstractClass abstractClass1 = new ConcreteClass1();

        abstractClass1.doAnything();

        abstractClass1.doSomething();

        AbstractClass abstractClass2 = new ConcreteClass2();

        abstractClass2.doAnything();

        abstractClass2.doSomething();

    }

}
 

/**

 * 抽象模板类

 * 1)为了防止恶意操作,一般模板方法都加上final关键字,不允许被覆写.

 * 2)抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型.

 * 实现类若非必要,尽量不要扩大父类中的访问权限.

 */

abstract class AbstractClass{

    //基本方法

    protected abstract void doSomething();

    //基本方法

    protected abstract void doAnything();

 

    //模板方法

    public void templateMethod(){

        /**

         * 调用基本方法,完成相关的逻辑

         */

        this.doAnything();

        this.doSomething();

    }

 

}

 

/**

 * 具体模板类1

 */

class ConcreteClass1 extends AbstractClass{

    @Override

    protected void doSomething() {

        System.out.println("ConcreteClass1----doSomething()");

    }

 

    @Override

    protected void doAnything() {

        System.out.println("ConcreteClass1----doAnything()");

    }

}

 

/**

 * 具体模板类2

 */

class ConcreteClass2 extends AbstractClass{

    @Override

    protected void doSomething() {

        System.out.println("ConcreteClass2----doSomething()");

    }

 

    @Override

    protected void doAnything() {

        System.out.println("ConcreteClass2----doAnything()");

    }

}

  

ConcreteClass1----doAnything()

ConcreteClass1----doSomething()

ConcreteClass2----doAnything()

ConcreteClass2----doSomething()

优点

1)封装不变部分,扩展可变部分

把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。

2)提取公共部分代码,便于维护

3)行为由父类控制,子类实现

基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

缺点

按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响到了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中带来了影响,当然也违反了里氏替换原则。

使用场景

1)多个子类有公有的方法,并且逻辑基本相同时。

2)重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。

3)重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为。

#应该保持抽象方法的数目越少越好,可以让算法内的步骤不要切割的太细,但是如果步骤太少的话,会比较没有弹性,所以要看情况折中。

#避免让高层和底层组件之间有明显的环状依赖。

#和依赖倒置原则的目的都在于解耦,但是依赖倒置原则更加注重如何在设计中避免依赖。

钩子函数的运用(案例扩展)

概述

钩子是一种声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

钩子的另一个用法,是让子类能够有机会对模板方法中某些即将发生的步骤作出反应。

比方说,名为XX的钩子方法允许子类在内部列表重新组织后执行某些动作。

钩子也可以让子类有能力为其抽象类做一些决定。

类图结构

image.png

代码示例

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Client2 {
    public static void main(String[] args) throws Exception{
        String type = (new BufferedReader(new InputStreamReader(System.in))).readLine();
        HummerH1Model hummerH1Model = new HummerH1Model();
        if("0".equals(type)){
            hummerH1Model.setAlarm(false);
        }
        hummerH1Model.run();
System.out.println("-------------------------");
        HummerH2Model hummerH2Model = new HummerH2Model();
        hummerH2Model.run();
    }
}

/**
 * 扩展后的抽象模板类
 */
abstract class HummerModel{

    protected abstract void start();
    protected abstract void stop();
    protected abstract void alarm();
    protected abstract void engineBoom();

    final public void run(){
        this.start();
        this.engineBoom();
        if(this.isAlarm()){
            this.alarm();
        }
        this.stop();

    }

    protected boolean isAlarm(){
        return true;
    }

}
class HummerH1Model extends HummerModel{

    private boolean alarmFlag = true;

    @Override
    protected void start() {
        System.out.println("h1...........start()");
    }

    @Override
    protected void stop() {
        System.out.println("h1...........stop()");
    }

    @Override
    protected void alarm() {
        System.out.println("h1...........alarm()");
    }

    @Override
    protected void engineBoom() {
        System.out.println("h1...........engineBoom()");
    }

    @Override
    protected boolean isAlarm() {
        return this.alarmFlag;
    }

    //能否执行由客户端决定
    public void setAlarm(boolean isAlarm){
        this.alarmFlag = isAlarm;
    }

}
class HummerH2Model extends HummerModel{
    @Override
    protected void start() {
        System.out.println("h2...........start()");
    }

    @Override
    protected void stop() {
        System.out.println("h2...........stop()");
    }

    @Override
    protected void alarm() {
        System.out.println("h2...........alarm()");
    }

    @Override
    protected void engineBoom() {
        System.out.println("h2...........engineBoom()");
    }


    @Override
    protected boolean isAlarm() {
        return false;
    }
}

h1...........start()
h1...........engineBoom()
h1...........alarm()
h1...........stop()
-------------------------
h2...........start()
h2...........engineBoom()
h2...........stop()

要点

1)模板方法定义了算法的步骤,把这些步骤的实现延迟到子类。

2)模板方法模式为我们提供了一种代码复用的重要技巧。

3)模板方法的抽象类可以定义具体方法、抽象方法和钩子。

4)抽象方法由子类实现。

5)钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,可以将模板方法声明为final。

6)为了防止子类改变模板方法中的算法,可以将模板方法声明为final。

7)好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用低层模块。

8)我们将在真实的世界中看到模板方法模式的许多变体,不要期待它们全都是一眼就可以被我们认出的。

9)策略模式和模板方法模式都封装算法,一个用组合,一个用继承。

10)工厂方法是模板方法的一种特殊版本。


标题:设计模式(十)行为模式(类行为型模式)模板方法-TEMPLATE METHOD
作者:yazong
地址:https://blog.llyweb.com/articles/2020/06/29/1593438781373.html