十五、模版方法模式


1 基本介绍

模版方法模式(Template Method Pattern),又称为 模板模式,它是一种 行为型 设计模式。该模式 定义了算法中各个细节的执行流程(称为 模版方法),而 将细节的具体实现延迟到子类中(称为 抽象方法),使得子类可以在 不改变算法中细节的执行流程 的情况下 重定义细节的具体实现

2 案例

本案例实现了 统一写各种练习册 的功能,只要继承 Workbook 抽象类并实现其定义的 abstract 方法,就可以使用 .write() 来完成写练习册的操作。

2.1 Workbook 抽象类

public abstract class Workbook { // 练习册
    // protected 表示不想被别的包的类随便调用,abstract 表示强制其子类重写
    protected abstract void open(); // 打开练习册
    protected abstract void solveProblems(); // 完成练习册上的题目
    protected abstract void close(); // 合上练习册

    // public 表示可以被其他类调用,final 表示其子类不能随便修改本方法的实现
    public final void write() { // 写练习册
        open();
        solveProblems();
        close();
    }
}

2.2 EnglishWorkbook 类

public class EnglishWorkbook extends Workbook { // 英语练习册
    @Override
    protected void open() {
        System.out.println("#打开英语练习册");
    }

    @Override
    protected void solveProblems() {
        System.out.println("#写英语题");
    }

    @Override
    protected void close() {
        System.out.println("#合上英语练习册");
    }
}

2.3 MathWorkbook 类

public class MathWorkbook extends Workbook { // 数学练习册
    @Override
    protected void open() {
        System.out.println("$打开数学练习册");
    }

    @Override
    protected void solveProblems() {
        System.out.println("$写数学题");
    }

    @Override
    protected void close() {
        System.out.println("$合上数学练习册");
    }
}

2.4 Client 类

public class Client { // 客户端,测试了 英语练习册 和 数学练习册 的 write() 方法
    public static void main(String[] args) {
        Workbook englishWorkbook = new EnglishWorkbook();
        englishWorkbook.write();

        System.out.println("================");

        Workbook mathWorkbook = new MathWorkbook();
        mathWorkbook.write();
    }
}

2.5 Client 类的运行结果

#打开英语练习册
#写英语题
#合上英语练习册
================
$打开数学练习册
$写数学题
$合上数学练习册

2.6 总结

Workbook 抽象类中的方法分为两种:

  • abstract 修饰的:表示强制子类重写、被 模版方法 调用的方法。这就是基本介绍中提到的 抽象方法,在其子类中将被实现。此外,这种方法还可以使用 public 修饰,代表这个方法实现了一个完整的操作。
  • final 修饰的:表示不能被子类重写、提供给外部调用的方法。这就是基本介绍中提到的 模版方法,它定义了 抽象方法 的执行流程,只要按照这个流程,就能够完成某个完整的操作,在本案例中是 写练习册 这个完整的操作。

如果想要在添加一种练习册,只需要继承 Workbook 抽象类,并实现抽象方法,就可以使用 .write() 方法来完成 写这个练习册 的操作,这就增强了系统的 扩展性

3 各角色之间的关系

3.1 角色

3.1.1 AbstractClass ( 抽象类 )

该角色负责 定义抽象方法实现模版方法,在 模版方法 中调用 抽象方法。本案例中,Workbook 类扮演了该角色。

3.1.2 ConcreteClass ( 具体类 )

该角色负责 实现 AbstractClass 角色中定义的抽象方法。本案例中,EnglishWorkbook, MathWorkbook 类都在扮演该角色。

3.1.3 Client ( 客户端 )

该角色负责 使用 AbstractClass 角色的模版方法完成具体的业务逻辑。本案例中,Client 类扮演了该角色。

3.2 类图

alt text

4 注意事项

  • 明确算法执行流程:在抽象类中定义好算法各个细节的执行流程,确保这个流程是不可变的,通常通过在 模板方法 上使用 final 关键字来防止子类重写。
  • 合理划分步骤:将算法划分为若干个步骤(细节),每个步骤对应一个方法。
  • 最大化代码复用:模版方法模式通过 继承机制 实现了 代码的复用,父类中的 模板方法已实现的具体方法 可以被子类直接使用。
  • 避免不必要的子类:如果算法中的可变步骤很少,或者算法本身并不复杂,那么使用模版方法模式可能会导致过度设计。在这种情况下,应该考虑其他更简单的设计模式或方案。

5 在源码中的使用

在 Spring 中,AbstractApplicationContext 抽象类使用到了模版方法模式,其实现了 refresh() 方法,作为 模版方法

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		StartupStep contextRefresh =
				this.applicationStartup.start("spring.context.refresh");

		prepareRefresh();
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
		prepareBeanFactory(beanFactory);

		try {
			postProcessBeanFactory(beanFactory); // 模版方法

			StartupStep beanPostProcess =
					this.applicationStartup.start("spring.context.beans.post-process");
			invokeBeanFactoryPostProcessors(beanFactory);

			registerBeanPostProcessors(beanFactory);
			beanPostProcess.end();

			initMessageSource();

			initApplicationEventMulticaster();

			onRefresh(); // 模版方法

			registerListeners();

			finishBeanFactoryInitialization(beanFactory);

			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			destroyBeans();

			cancelRefresh(ex);

			throw ex;
		}

		finally {
			resetCommonCaches();
			contextRefresh.end();
		}
	}
}

protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}

protected void onRefresh() throws BeansException {
	// For subclasses: do nothing by default.
}

refresh() 中直接调用的 postProcessBeanFactory(), onRefresh() 方法都是 “抽象”方法(这里换做 钩子方法 比较好。钩子方法 指的是在父类中空实现,在子类中具体实现的方法),交给其子类实现,而 refresh() 方法作为 模版方法 调用它们。注意:refresh() 中还间接调用了 “抽象”方法,为了不使结果更复杂,没有介绍这些方法。

6 优缺点

优点

  • 提高系统的灵活性和拓展性:模版方法模式封装了算法中各个细节的执行流程,将算法中 不变的部分(细节的执行流程)和 可变的部分(细节的具体实现)分离开来,从而使细节的具体实现可以根据需要进行变化,提高了算法的 灵活性拓展性
  • 符合开闭原则:模版方法模式符合 开闭原则,即对扩展开放,对修改关闭。通过添加新的子类来扩展算法,而不需要修改现有的类和接口。

缺点

  • 过度设计:在某些情况下,如果算法本身并不复杂,或者算法细节的变化并不频繁,那么使用模版方法模式可能会导致 过度设计。这会增加系统的复杂性和开发成本。
  • 依赖继承:模版方法模式依赖于 继承机制 来实现算法的扩展。然而,继承机制本身存在一些缺点,如 破坏封装性增加类之间的耦合度 等。如果过度依赖继承,可能会导致系统难以维护和扩展。

7 适用场景

  • 算法有固定步骤但部分实现可变:当一个算法或过程包含多个步骤,且这些步骤中的 大部分 对于不同的实现来说是 相同 的,但 某些 步骤的具体实现可能因情况而 时,可以使用模板方法模式。这样可以将算法的 固定部分可变部分 分离,提高代码的复用性和灵活性。
  • 需要在不同子类中重用相同的算法:如果多个子类共享相同的算法流程,但某些步骤的实现因子类而异,则可以使用模板方法模式。通过将算法的固定部分(即算法细节的执行流程)放在父类中,而将可变部分作为抽象方法留给子类实现,可以避免代码重复,同时保持算法的整体结构不变。

8 总结

模版方法模式 是一种 行为型 设计模式,该模式将算法中各个细节的执行流程固定到 模版方法 中,将这些细节的具体实现交给其子类作为 抽象方法 实现(有些时候也可以是 钩子方法),使得子类可以在 不改变算法中细节的执行流程 的情况下 重定义细节的具体实现,从而提高了系统的 灵活性拓展性。不过,该模式很 依赖继承机制,所以需要遵守 里氏替换原则,从而减少 继承 带来的缺陷。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值