( 十六 )、设计模式 之 模板模式(TemplateMethod)
( 十六 )、设计模式 之 模板模式(TemplateMethod)
1、简介
定义: 定义一个操作中的算法骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。在标准的模板方法模式实现中,主要是使用继承的方式,来让父类在运行期间可以调用到子类的方法。
变与不变(封装公共、抽象变化)
程序设计的一个很重要的思考点就是“变与不变”也就是分析程序中哪些功能是可变的,哪些功能是不变的,然后把不变的部分抽象出来,进行公共的实现,把变化的部分分离出去,用接口来封装隔离,或者是用抽象类来约束子类行为。模板方法模式很好地体现了这一点。模板类实现的就是不变的方法和算法的骨架,而需要变化的地方,都通过抽象方法,把具体实现延迟到子类中了,而且还通过父类的定义来约束了子类的行为,从而使系统能有更好的复用性和扩展性。
2、模板方法模式的结构说明
- 抽象类(AbstractClass): 用来定义算法骨架和抽象方法,具体的子类通过实现这些抽象方法操作来实现一个算法的各个步骤。在这个类里面,还可以提供算法中通用的实现。
- 具体实现类(ConcreteClass): 用来实现算法骨架中的某些步骤,完成与特定子类相关的功能。
模板方法模式的写法
在实现模板的时候,到底哪些方法实现在模板上呢? 模板能不能全部实现了,也就是模板不提供抽象方法呢? 当然,就算没有抽象方法,模板一样可以定义成为抽象类。
通常在模板里面包含以下操作类型:
1、模板方法: 就是定义算法骨架的方法。
2、具体的操作: 在模板中直接实现某些步骤的方法。通常这些步骤的实现算法是固定的,而且是不怎么变化的,因此可以将其当作公共功能实现在模板中。如果不需为子类提供访问这些方法的话,还可以是 private 的。这样一来,子类的实现就相对简单些。如果是子类需要访问,可以把这些方法定义为 protected final的,因为通常情况下,这些实现不能够被子类覆盖和改变了。
3、原语操作: 就是在模板中定义的抽象操作,通常是模板方法需要调用的操作,是必须的操作,而且在父类中还没有办法确定下来如何实现,需要子类来真正实现的方法。
4、钩子操作: 在模板中定义,并提供默认实现的操作。这些方法通常被视为可扩展的点,但不是必须的,子类可以有选择地覆盖这些方法,以提供新的实现来扩展功能。也就是说钩子操作是可以被扩展的点,但不是必须的。
5、Factory Method: 在模板方法中,如果需要得到某些对象实例的话,可以考虑通过工厂方法模式来获取,把具体的构建对象的实现延迟到子类中去。
3、实现
3.1、模板方法
/**
* @Author dw
* @ClassName AbstractClass
* @Description 定义模板方法、抽象操作、模板公共方法的抽象类
* @Date 2023/8/18 18:17
* @Version 1.0
*/
public abstract class AbstractTemplate {
/**
* 模板方法, 定义算法的骨架
*/
public final void templateMethod () {
// 第一步
this.operation1();
// 第二步
this.operation2();
// 第三步
this.commonOperation();
// 第四步
this.doOperation1();
// 第五步
this.doOperation2();
}
/**
* 具体操作1,算法中的固定操作,子类不需要访问。
*/
private void operation1() {
// 具体实现
}
/**
* 具体操作2,算法中的固定操作,子类可能需要访问。但是不能被子类覆盖, 所以定义成final
*/
protected final void operation2() {
// 具体实现
}
/**
* 子类的公共操作,父类提供默认的实现,可由子类覆盖具体实现
*/
protected void commonOperation () {
// 提供默认的实现
}
/**
* 抽象方法操作1, 由子类提供具体的实现
*/
public abstract void doOperation1();
/**
* 抽象方法操作2, 由子类提供具体的实现
*/
public abstract void doOperation2();
}
3.2、算法的具体实现
/**
* @Author dw
* @ClassName ConCreteClass
* @Description 算法骨架的具体实现类
* @Date 2023/8/18 18:24
* @Version 1.0
*/
public class ConCreteClass extends AbstractTemplate {
/**
* 算法操作1
*/
@Override
public void doOperation1() {
// 具体的实现
}
/**
* 算法操作2
*/
@Override
public void doOperation2() {
// 具体的实现
}
/**
* 子类的公共操作,父类提供默认的实现,可由子类覆盖具体实现
*/
@Override
protected void commonOperation() {
super.commonOperation();
}
}
4、模板方法模式的优缺点
优点:
- 封装不变部分,扩展可变部分。提取公共代码,便于维护。 行为由父类控制,子类实现。
- 实现代码复用。 模板方法模式是一种实现代码复用的很好的手段。通过把子类的公共功能提炼和抽取,把公共部分放到模板中去实现。
缺点:
算法骨架不容易升级。 模板方法模式最基本的功能就是通过模板的制定,把算法骨架完全固定下来。事实上模板和子类是非常耦合的,如果要对模板中的算法骨架进行变更,可能就会要求所有相关的子类进行相应的变化。所以抽取算法骨架的时候要特别小心,尽量确保是不会变化的部分才放到模板中。
5、何时选用模板方法模式?
建议在以下情况中选用模板方法模式。
1、需要固定定义算法骨架,实现一个算法的不变的部分,并把可变的行为留给子类来实现的情况。
2、各个子类中具有公共行为,应该抽取出来,集中在一个公共类中去实现,从而避免代码重复。
3、需要控制子类扩展的情况。模板方法模式会在特定的点来调用子类的方法,这样只允许在这些点进行扩展。
6、模板方法模式与工厂方法模式的区别
模板方法模式与工厂方法模式很相似,不同之处在于侧重点不同,工厂方法模式注重于对象的创建延迟到子类执行。而模板方法模式则是定义一个操作中的算法骨架,
而将一些步骤延迟到子类中