在软件开发中,我们经常会遇到需要定义一组相似操作的场景。这些操作可能在整体上有着相同的结构,但在细节上有所差异。如果每次都重复编写这些操作的通用结构,会导致代码的冗余性,同时也增加了后期维护的难度。为了解决这个问题,模板方法模式应运而生。
使用场景
模板方法模式适用于以下场景:
- 当存在一组相似的操作,它们具有相同的算法结构,但实现细节各不相同时。
- 当希望在不改变算法的整体结构的情况下,允许子类自由扩展或修改某些步骤时。
- 当希望将算法的实现细节封装起来,只暴露出高层接口供调用者使用时。
JUC 下的 AQS 就使用到了模板方法模式,其中 acquire() 是模板方法。tryAcquire() 方法的具体实现去交给子类完成。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
实现方式
结构说明
模板方法模式由抽象类和具体子类组成。抽象类定义了算法的框架,其中包含了一个或多个抽象方法,用于由具体子类实现。具体子类继承抽象类,并根据需要重写其中的抽象方法,从而实现具体的细节。
在模板方法模式中,通常涉及以下几个角色:
- 抽象类(Abstract Class):抽象类定义了算法的框架,包括一个或多个抽象方法和具体方法。其中的抽象方法由子类实现,具体方法可以被子类直接继承或重写。
- 具体子类(Concrete Subclass):具体子类继承抽象类,并根据需要实现其中的抽象方法。具体子类提供了算法的具体实现细节。
示例代码
以下是一个简单的代码示例:
// 抽象类,定义模板方法和抽象步骤方法
public abstract class AbstractClass {
// 模板方法,定义算法的整体结构
public final void templateMethod() {
step1();
step2();
step3();
}
// 模板公共方法
protected final void step1(){
System.out.println("ConcreteClass: Step 1");
}
// 抽象步骤方法,由子类实现具体的步骤逻辑
protected abstract void step2();
// 抽象步骤方法,由子类实现具体的步骤逻辑
protected abstract void step3();
}
// 具体子类,实现抽象步骤方法
public class ConcreteClass extends AbstractClass {
protected void step2() {
System.out.println("ConcreteClass: Step 2");
}
protected void step3() {
System.out.println("ConcreteClass: Step 3");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
在上述代码中,我们首先定义了一个抽象类 AbstractClass,其中包含了模板方法和抽象方法。然后,我们创建了具体子类 ConcreteClass,根据需要实现了抽象方法。
在客户端代码 Client 中,我们创建了具体子类的对象,并调用了模板方法 templateMethod(),从而执行了定义好的算法。
运行该代码将输出以下结果:
ConcreteClass: Step 1
ConcreteClass: Step 2
ConcreteClass: Step 3
注意:
- 一般模板方法都加上 final 关键字, 防止子类重写模板方法。
- 抽象模板中的基本方法尽量设计为 protected 类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为 protected 类型。实现类若非必要,尽量不要扩大父类中的访问权限。
钩子方法
钩子方法(Hook Method)是模板方法模式中的一种特殊方法,用于在抽象类中提供一个默认的实现,但允许具体子类选择性地进行重写或扩展。钩子方法允许子类在不改变算法骨架的情况下,对算法的某些步骤进行定制。
以下是一个包含钩子方法的 Java 示例代码:
// 抽象类,定义模板方法和钩子方法
public abstract class AbstractClass {
// 模板方法,定义算法的整体结构
public final void templateMethod() {
step1();
step2();
// 钩子方法的调用
if (hookMethod()) {
step3();
}
}
protected abstract void step1();
protected abstract void step2();
// 钩子方法,默认返回true,子类可以选择性地重写
protected boolean hookMethod() {
return true;
}
protected abstract void step3();
}
// 具体子类1
public class ConcreteClass1 extends AbstractClass {
protected void step1() {
System.out.println("ConcreteClass1: Step 1");
}
protected void step2() {
System.out.println("ConcreteClass1: Step 2");
}
protected void step3() {
System.out.println("ConcreteClass1: Step 3");
}
}
// 具体子类2
public class ConcreteClass2 extends AbstractClass {
protected void step1() {
System.out.println("ConcreteClass2: Step 1");
}
protected void step2() {
System.out.println("ConcreteClass2: Step 2");
}
protected boolean hookMethod() {
return false; // 重写钩子方法,返回false
}
protected void step3() {
System.out.println("ConcreteClass2: Step 3");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
class1.templateMethod();
System.out.println("------------------");
AbstractClass class2 = new ConcreteClass2();
class2.templateMethod();
}
}
在上述代码中,我们定义了一个抽象类 AbstractClass,其中包含模板方法 templateMethod() 和钩子方法 hookMethod()。在模板方法中,我们先执行了step1() 和 step2() 两个基本操作方法,然后通过调用钩子方法决定是否执行 step3()。
具体子类 ConcreteClass1 和 ConcreteClass2 继承了抽象类,并实现了基本操作方法 step1()、step2() 和钩子方法 hookMethod()、step3()。
在客户端代码 Client 中,我们分别创建了具体子类的对象,并调用其模板方法,从而执行了定义好的算法。
运行该示例代码将输出以下结果:
ConcreteClass1: Step 1
ConcreteClass1: Step 2
ConcreteClass1: Step 3
------------------
ConcreteClass2: Step 1
ConcreteClass2: Step 2
通过重写钩子方法,具体子类可以选择性地对算法进行定制化。这就展示了钩子方法在模板方法模式中的应用。
优缺点
优点
- 代码复用:模板方法模式通过将算法的通用结构定义在抽象类中,可以使子类直接继承这些通用部分,从而达到代码复用的目的。
- 扩展性:模板方法模式允许子类根据需要重写父类的某些步骤,从而实现对算法的自由扩展和修改,同时保持整体结构的稳定性。
- 封装性:模板方法模式将算法的实现细节封装在抽象类中,对调用者屏蔽了具体的实现细节,只暴露出高层接口。
缺点
- 模板方法模式将算法的执行流程固定在抽象类中,可能会导致代码的可读性降低,增加理解和维护的难度。
- 模板方法中的步骤越多, 其维护工作就可能会越困难。
- 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。
总结
模板方法是一种简单但非常实用的设计模式,它通过定义一个算法的框架,并将具体实现延迟到子类中,实现了代码复用和扩展的目的。在具体实现步骤相对固定、但又存在差异性的情况下,模板方法模式能够很好地解决代码重复和维护难度的问题。