🔥 核心
在父类中定义一个算法的骨架,子类实现骨架中的方法,但不能改变骨架的结构。
🙁 问题场景
你是一名房屋建筑设计师,现在你需要给一片别墅区设计房屋。
这片别墅区计划建造200座别墅,而且,每一座别墅不能相同,必须各具特色——毕竟,你是在设计别墅区,而不是千篇一律的居民楼。
你花了三天时间从头到尾设计了一座别墅,又花了三天时间从头到尾设计了一座别墅,又花了三天时间从头到尾设计了一座别墅…
还有197座别墅没有设计啊!!!你绝望的哭出了声 >_<
🙂 解决方案
你放下手头的设计图纸,静下心来思考了一会。
你有了关键的发现!这么多座别墅,虽然形态各异,但是真的是完全不一样吗?是不是有着共同之处呢?
很快,你抽取出了这200座别墅的建造「模板」:
1)打造地基
2)建造墙壁
3)建造屋顶
4)建造窗户
5)粉刷墙壁
有了模板,你就不需要每次都从头开始设计房屋,而是在这个模板的基础上,实现每一个具体的步骤;而对于每一个具体步骤的实现,当然可以是各具特色的!
🌈 有趣的例子
游戏中各种各样的 角色类型(Character)
,都是根据模板做出来的哦~
下面我们就使用游戏角色模板,设计出两个具体的角色及其各自的行为(人类(Human)
、恶龙(Dragon)
)。
游戏角色(抽象类)
abstract class Character {
// 模板方法
// 这是一个算法骨架
public final void behavior() {
eat();
fight();
sleep();
}
// 留给子类实现的扩展点
public abstract void eat();
public abstract void fight();
public abstract void sleep();
}
人类
class Human extends Character {
@Override
public void eat() {
System.out.println("什么都吃 ^_^");
}
@Override
public void fight() {
System.out.println("使用铁剑进行战斗 ^_^");
}
@Override
public void sleep() {
System.out.println("在床上呼呼大睡 ^_^");
}
}
恶龙
class Dragon extends Character {
@Override
public void eat() {
System.out.println("吃森林里的浆果 >_<");
}
@Override
public void fight() {
System.out.println("喷出火焰进行战斗 >_<");
}
@Override
public void sleep() {
System.out.println("睡在火上顶的巢穴里 >_<");
}
}
public class TemplatePatternDemo {
public static void main(String[] args) {
// 创建一个人类
Character human = new Human();
// 调用人类的模板方法
human.behavior();
// 创建一个恶龙
Character dragon = new Dragon();
// 调用恶龙的模板方法
dragon.behavior();
}
}
什么都吃 ^_^
使用铁剑进行战斗 ^_^
在床上呼呼大睡 ^_^
吃森林里的浆果 >_<
喷出火焰进行战斗 >_<
睡在火上顶的巢穴里 >_<
☘️ 使用场景
◾️当你只希望客户端扩展某个特定算法步骤,而不是整个算法或其结构时,可使用模板方法模式。
模板方法将整个算法转换为一系列独立的步骤,以便子类能对其进行扩展,同时还可让超类中所定义的结构保持完整。
◾️当多个类的算法除一些细微不同之外几乎完全一样时,你可使用该模式。但其后果就是,只要算法发生变化,你就可能需要修改所有的类。
在将算法转换为模板方法时,你可将相似的实现步骤提取到超类中以去除重复代码。子类间各不同的代码可继续保留在子类中。
🧊 实现方式
(1)分析目标算法,确定能否将其分解为多个步骤。从所有子类的角度出发,考虑哪些步骤能够通用,哪些步骤各不相同。
(2)创建抽象基类并声明一个模板方法和代表算法步骤的一系列抽象方法。在模板方法中根据算法结构依次调用相应步骤。可用 final
最终修饰模板方法以防止子类对其进行重写。
(3)虽然可将所有步骤全都设为抽象类型,但默认实现可能会给部分步骤带来好处,因为子类无需实现那些方法。
(4)可考虑在算法的关键步骤之间添加钩子。
(5)为每个算法变体新建一个具体子类,它必须实现所有的抽象步骤,也可以重写部分可选步骤。
🎲 优缺点
➕ 你可将重复代码提取到一个超类中。
➕ 你可仅允许客户端重写一个大型算法中的特定部分,使得算法其他部分修改对其所造成的影响减小。
➖ 部分客户端可能会受到算法框架的限制。
➖ 通过子类抑制默认步骤实现可能会导致违反里氏替换原则。
➖ 模板方法中的步骤越多,其维护工作就可能会越困难。
🌸 补充
模板方法的核心原理是什么呢?是我们熟悉的继承。