设计模式-模版方法模式

51 阅读6分钟

# 模版方法模式

1.简介

模版方法模式是一种行为设计模式,它在父类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

​ 假设你正在开发一段程序,用户需要向其中输入各种格式的文件(PDF、Word、CSV),程序会试图从这些文件中抽取有意义的数据,并以统一的格式将其返回给用户。

​ 首个版本,你仅支持DOC文件,接下来你支持了CSV,在接下来你实现了PDF。但是你会发现这三个类中包含了许多相似的代码,尽管这些类处理不同数据格式的代码完全不同,但是数据处理和分析的代码却几乎完全一样,这个时候你需要在保持算法结构完整的情况下去除重复代码。

​ 另一方面,你在客户端需要写许多条件判断语句,以根据不同的处理对象类型选择合适的处理过程。如果所有处理数据的类都有相同的接口或基类,那么你就可以去除客户端代码中的条件语句,转而使用多态机制来在处理对象上调用函数。

解决方案:

​ 模版方法模式建议将算法分解为一系列的步骤,然后将这些步骤改造成为方法,最后在模版方法中依次调用这些方法。 ​ 步骤可以是抽象的也可以有一些默认的实现,子类必须实现所有的抽象步骤,如果有必要还可以进行重写一些默认的实现。 ​ 同时还有一种名为钩子函数的步骤,钩子是内容为空的可选步骤,即使不重写钩子,模版方法也能工作,通常放置在算法重要步骤的前后,为子类提供额外的算法扩展点。

2.UML图

模版方法模式1.png

1.抽象类(AbstractClass) 会声明作为算法步骤的方法,以及依次调用他们的实际模版方法。算法步骤可以被声明为抽象类型,也可以提供一些默认实现。 2.**具体类(Concrete)**可以重写所有步骤,但不能重写模版方法自身。

3.代码实现

父类定义:

package com.gs.designmodel.template;

/**
 * @author: Gaos
 * @Date: 2023-06-27 10:32
 **/

import java.util.Date;

/**
 * 在父类中我们已经定义了一个上班算法的骨架,包含以下内容
 * 1、进入公司
 * 2、打开电脑
 * 3、上班情况
 * 4、关闭电脑
 * 5、离开公司
 *
 * 其中我们在父类中已经实现了1、2、4、5方法,子类中仅需要实现work这一个抽象方法
 * 来记录每天的工作情况即可
 */
public abstract class Worker {

    protected String name;

    public Worker(String name) {
        this.name = name;
    }

    /**
     * 记录一天的工作
     */

    public final void workOneDay() {
        System.out.println("-----------work start------------");
        enterCompany();
        computerOn();
        work();
        computerOff();
        exitCompany();
        System.out.println("------------work end------------");
    }

    /**
     * 工作
     */
    public abstract void work();

    private void computerOff(){
        System.out.println(name + "关闭电脑");
    }

    private void computerOn() {
        System.out.println(name + "打开电脑");
    }

    private void enterCompany() {
        System.out.println(name + "进入公司");
    }

    private void exitCompany() {
        if(isNeedPrintDate()) {
            System.out.println(new Date() + "--->");
        }
        System.out.println(name + "离开公司");
    }

    public boolean isNeedPrintDate(){
        return false;
    }
}

在父类中我们已经定义了一个上班算法的骨架,包含以下内容

  • 1、进入公司

  • 2、打开电脑

  • 3、上班情况

  • 4、关闭电脑

  • 5、离开公司

    其中我们在父类中已经实现了1、2、4、5方法,子类中仅需要实现work这一个抽象方法来记录每天的工作情况即可

子类定义:

package com.gs.designmodel.template;

/**
 * @author: Gaos
 * @Date: 2023-06-27 10:37
 **/

/**
 * 程序猿
 */
public class ITWorker extends Worker{



    public ITWorker(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + "写程序-联调-修改bug");
    }

    @Override
    public boolean isNeedPrintDate() {
        return Boolean.TRUE;
    }

}
package com.gs.designmodel.template;

/**
 * @author: Gaos
 * @Date: 2023-06-27 10:42
 **/

/**
 * HR
 */
public class HRWorker extends Worker{
    public HRWorker(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + "筛选简历-打电话");
    }
}
package com.gs.designmodel.template;

/**
 * @author: Gaos
 * @Date: 2023-06-27 10:43
 **/

/**
 * 测试人员
 */
public class TestWorker extends Worker{

    public TestWorker(String name) {
        super(name);
    }

    @Override
    public void work() {
        System.out.println(name + "编写测试用例-测试系统");
    }
}

测试:

package com.gs.designmodel.template;

/**
 * @author: Gaos
 * @Date: 2023-06-27 10:48
 **/
public class Test {
    public static void main(String[] args) {
        Worker it1 = new ITWorker("程序猿1");
        it1.workOneDay();
        Worker it2 = new ITWorker("程序猿2");
        it2.workOneDay();

        Worker hr = new HRWorker("HR");
        hr.workOneDay();

        Worker test = new HRWorker("test");
        test.workOneDay();
    }
}

测试结果:


-----------work start------------
程序猿1进入公司
程序猿1打开电脑
程序猿1写程序-联调-修改bug
程序猿1关闭电脑
Tue Jun 27 10:50:06 CST 2023--->
程序猿1离开公司
------------work end------------
-----------work start------------
程序猿2进入公司
程序猿2打开电脑
程序猿2写程序-联调-修改bug
程序猿2关闭电脑
Tue Jun 27 10:50:06 CST 2023--->
程序猿2离开公司
------------work end------------
-----------work start------------
HR进入公司
HR打开电脑
HR筛选简历-打电话
HR关闭电脑
HR离开公司
------------work end------------
-----------work start------------
test进入公司
test打开电脑
test筛选简历-打电话
test关闭电脑
test离开公司
------------work end------------

进程已结束,退出代码0

其中需要注意一下父类中设置的钩子函数:

 public boolean isNeedPrintDate(){
        return false;
    }

父类中添加了一个isNeedPrintDate()方法,且默认返回false,不打印时间。但在离开公司这个方法中添加了这个钩子。

 private void exitCompany() {
        if(isNeedPrintDate()) {
            System.out.println(new Date() + "--->");
        }
        System.out.println(name + "离开公司");
    }

如果某子类需要走一些自身逻辑调用打印时间,可以重写钩子方法,返回true。比如说上面示例中的程序员。

钩子方法中父类可以提供默认实现,或者空实现,子类也可以重写或者不重写。但是需要注意钩子函数不能去影响算法本身。

4.总结

如果你只希望客户端扩展某个特定算法步骤,而不是整个算法或者结构时,可以使用模版方法模式。

当多个类的算法除了一些细微不同之外完全一样的时候,你可以使用模版方法模式,但是如果算法改变的话你需要修改其继承的所有类。

  1. 分析目标算法, 确定能否将其分解为多个步骤。 从所有子类的角度出发, 考虑哪些步骤能够通用, 哪些步骤各不相同。
  2. 创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。 在模板方法中根据算法结构依次调用相应步骤。 可用 final最终修饰模板方法以防止子类对其进行重写。
  3. 虽然可将所有步骤全都设为抽象类型, 但默认实现可能会给部分步骤带来好处, 因为子类无需实现那些方法。
  4. 可考虑在算法的关键步骤之间添加钩子。
  5. 为每个算法变体新建一个具体子类, 它必须实现所有的抽象步骤, 也可以重写部分可选步骤。

相关参考文章:

refactoringguru.cn/design-patt…

blog.csdn.net/lmj62356579…