模板方法模式(Template method pattern)
模板方法模式是一种行为型设计模式,它定义了一个操作中的算法骨架,将一些步骤的具体实现延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
概念理论:
模板方法模式基于一个抽象类或接口定义算法的骨架,该骨架由一个模板方法和一些抽象方法组成。模板方法中定义了算法的结构,而抽象方法则由具体子类来实现。子类可以通过重写抽象方法来实现特定步骤的具体行为,而不需要修改算法的整体结构。
优点:
- 提供了一种代码复用的方式,将相同的算法结构封装在模板方法中,减少了重复代码的编写。
- 提高了代码的可维护性,由于算法的结构被封装在模板方法中,如果需要修改算法的结构,只需要修改模板方法即可,不需要修改每个子类的实现。
- 提供了一种扩展的方式,通过在子类中重写抽象方法,可以灵活地改变算法的具体实现。
缺点:
- 模板方法模式可能会导致类的个数增加,因为每个具体实现都需要一个子类。
- 算法的结构在父类中固定,子类只能通过重写抽象方法来改变算法的具体实现,有一定的局限性。
适用场景:
- 当存在一些具有相同算法结构但具体实现不同的操作时,可以使用模板方法模式,将相同的算法结构封装在模板方法中,具体实现由子类提供。
- 当需要控制算法的流程,但某些步骤的具体实现可能会变化时,模板方法模式可以提供一种灵活的方式。
案例解析
下面是一个使用模板方法模式的Java示例:
// 抽象类定义算法的骨架
abstract class AbstractClass {
public void templateMethod() {
// 步骤1
step1();
// 步骤2
step2();
// 步骤3
step3();
}
protected abstract void step1();
protected abstract void step2();
protected abstract void step3();
}
// 具体子类实现具体的步骤
class ConcreteClass extends AbstractClass {
@Override
protected void step1() {
System.out.println("执行步骤1");
}
@Override
protected void step2() {
System.out.println("执行步骤2");
}
@Override
protected void step3() {
System.out.println("执行步骤3");
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
在上述代码中,AbstractClass
是抽象类,定义了一个模板方法 templateMethod
和三个抽象方法 step1
、step2
、step3
。templateMethod
方法中按照固定的顺序调用了这三个抽象方法。
ConcreteClass
是具体子类,继承自 AbstractClass
,实现了具体的步骤。
在 Main
类的示例中,我们创建了一个 ConcreteClass
对象,并调用其 templateMethod
方法。可以看到,具体步骤的实现在子类中完成,而算法的结构由父类控制。
模板方法模式适用于存在相同算法结构但具体实现不同的场景,可以提供一种代码复用和扩展的方式。它可以用于控制算法的流程,并在子类中灵活地定义具体的实现。
疑问:templateMethod() 方法使用final 关键字修饰 是不是更合理一些?
在模板方法模式中,将templateMethod()
方法使用final
关键字修饰是一种常见的做法,可以确保算法的结构不被子类修改。使用final
关键字修饰templateMethod()
方法可以防止子类对整个算法的修改,而只允许子类通过实现抽象方法来改变算法的具体实现细节。
使用final
关键字修饰templateMethod()
方法的优点包括:
- 确保算法的结构不被修改:通过使用
final
关键字修饰templateMethod()
方法,父类的算法结构变得不可修改,从而保证了算法的一致性和稳定性。 - 避免子类的误操作:防止子类意外地修改整个算法的流程,避免了潜在的错误和不一致性。
- 提高代码可读性和可维护性:通过使用
final
关键字明确表示算法的结构不可修改,可以使代码更加清晰和易于理解。
然而,需要注意的是,如果你期望子类能够修改整个算法的结构,或者允许在模板方法中添加新的步骤,那么就不应该使用final
关键字修饰templateMethod()
方法。在这种情况下,你可以将templateMethod()
方法定义为非final
,并提供一些钩子方法(hook methods)供子类进行扩展。
综上所述:
- 使用
final
关键字修饰templateMethod()
方法可以确保算法的结构稳定性和一致性 - 但需要根据具体需求进行决策,如果希望允许子类修改整个算法结构或添加新的步骤,就不应该使用
final
关键字修饰。
当我们希望在模板方法模式中允许子类修改整个算法的结构或添加新的步骤时,可以通过提供
钩子方法(hook methods)
来实现这种扩展性。钩子方法是在模板方法中的空方法或默认实现,子类可以选择性地覆盖或扩展这些方法。
以下是一个结合钩子方法的模板方法模式的Java示例:
// 抽象类定义算法的骨架
abstract class AbstractClass {
public final void templateMethod() {
step1();
step2();
// 钩子方法
if (hookMethod()) {
additionalStep();
}
step3();
step4();
}
private void step4(){
System.out.println("任务执行完毕!");
}
protected abstract void step1();
protected abstract void step2();
// 钩子方法
protected boolean hookMethod() {
return true;
}
// 钩子方法的默认实现
protected void additionalStep() {
System.out.println("执行附加步骤");
}
protected abstract void step3();
}
// 具体子类实现具体的步骤
class ConcreteClass extends AbstractClass {
@Override
protected void step1() {
System.out.println("执行步骤1");
}
@Override
protected void step2() {
System.out.println("执行步骤2");
}
// 重写钩子方法
@Override
protected boolean hookMethod() {
return true; // 在子类中禁用附加步骤
}
@Override
protected void step3() {
System.out.println("执行步骤3");
}
@Override
protected void additionalStep() {
System.out.println("我是自定义的扩展");
}
}
public class TemplateMethodPattern {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
在上述代码中,我们在抽象类 AbstractClass
中定义了一个钩子方法 hookMethod()
和一个钩子方法的默认实现 additionalStep()
。在模板方法 templateMethod()
中,通过调用钩子方法来决定是否执行附加步骤。
在具体子类 ConcreteClass
中,我们重写了钩子方法 hookMethod()
,并返回true
开启了附加步骤,并且我们还重写了additionalStep()
,这样,当我们调用 templateMethod()
时,就会执行(自定义/默认)附加步骤。(当然我们也可以重写钩子方法 hookMethod()
,返回 false
来禁用附加步骤)
通过使用钩子方法,子类可以选择性地扩展算法的结构。在这个示例中,我们通过重写钩子方法来禁用附加步骤,但在其他子类中,可以根据需要重写钩子方法,添加自定义的附加步骤。
这种结构允许子类通过覆盖或扩展钩子方法来自定义算法的行为,从而提供了更大的灵活性和扩展性。