模板方法模式是什么?
定义一个方法,这个方法不能被子类复写 但是可以被子类调用。在这个方法里规定了算法流程或者方法调用顺序。按照这种想法实现的就叫模板方法模式。
案例展示
说总是模糊,做给你看更清晰!
假设咱们来总结一下贝爷吃昆虫的节目,然后用模板方法模式做个小案例。
我们来分析一下贝爷,吃昆虫的流程:
- 抓虫子
- 去头
- 去内脏
- 一口吞
- 点评一波
下边在咱们就按照这个模板来做个模板方法小案例
昆虫——父类、模板方法所在类
package 模板方法模式;
public abstract class 昆虫 {
/**
* 模板方法,吃昆虫,模板方法做成final,不让子类复写
*/
final void eatInsect() {
grab();
decapitation();
gutted();
eat();
evaluation();
}
/**
* 抓虫子
* 抽象方法,具体由子类实现(抓什么虫子)
*/
abstract void grab();
/**
* 去头
*/
void decapitation() {
System.out.println("去头");
}
/**
* 去除内脏
*/
void gutted() {
System.out.println("挤出内脏");
}
void eat() {
System.out.println("一口吞下");
}
/**
* 吃完虫子后的评价——抽象方法
*/
abstract void evaluation();
}
然后写具体的子类
蚂蚱
package 模板方法模式;
public class 蚂蚱 extends 昆虫{
@Override
void grab() {
System.out.println("贝爷,抓住了一个蚂蚱!");
}
@Override
void evaluation() {
System.out.println("味道像鸡肉,还不错!");
}
}
天花幼虫
package 模板方法模式;
public class 天牛幼虫 extends 昆虫{
@Override
void grab() {
System.out.println("贝爷,捉到一只天牛幼虫!");
}
@Override
void evaluation() {
System.out.println("这条虫,是我吃过最难吃的东西之一");
}
}
接下来开始重点戏:贝爷开始吃虫
贝爷
package 模板方法模式;
public class 贝爷 {
public static void main(String[] args) {
System.out.println("荒野求生节目,现在开始!");
System.out.println("----------");
昆虫 grasshopper = new 蚂蚱();
grasshopper.eatInsect();
System.out.println("----------");
昆虫 beetleLarvae = new 天牛幼虫();
beetleLarvae.eatInsect();
}
}
运行结果:
荒野求生节目,现在开始!
----------
贝爷,抓住了一个蚂蚱!
去头
挤出内脏
一口吞下
味道像鸡肉,还不错!
----------
贝爷,捉到一只天牛幼虫!
去头
挤出内脏
一口吞下
这条虫,是我吃过最难吃的东西之一
优点分析:
我们可以看到上边定义了吃虫子方法的模板后,具体到某个虫子,只需要再写一点点代码,然后调用模板方法就可以了。
缺点分析:
他的优势也是他的弱势,当子类具体方法各不相同时,写这个模板不仅没用还麻烦。
代码问题:
经常看荒野求生的人应该知道,贝爷吃过的虫子还有蚯蚓!!没有去头!没有挤出内脏,那怎么办呢?
使用钩子函数,其实就是调用方法时加一个** if **的事,true就条用,false就不调用。
下面咱们改善一下代码。
改善版:
昆虫:
package 模板方法模式.improve;
public abstract class 昆虫 {
/**
* 模板方法,吃昆虫,模板方法做成final,不让子类复写
*/
final void eatInsect() {
grab();
if(needDecapitation()) {
decapitation();
}
if(needGutted()) {
gutted();
}
eat();
evaluation();
}
/**
* 钩子函数,确定是否需要去头
* @return
*/
public boolean needDecapitation() {
return true;
}
/**
* 钩子函数,决定是否需要去内脏
* @return
*/
public boolean needGutted() {
return true;
}
/**
* 抓虫子
* 抽象方法,具体由子类实现(抓什么虫子)
*/
abstract void grab();
/**
* 去头
*/
void decapitation() {
System.out.println("去头");
}
/**
* 去除内脏
*/
void gutted() {
System.out.println("挤出内脏");
}
void eat() {
System.out.println("一口吞下");
}
/**
* 吃完虫子后的评价——抽象方法
*/
abstract void evaluation();
}
因为钩子函数默认返回的是true,所以在蚂蚱、天牛幼虫实现时,不用做任何改变。而蚯蚓就需要复写一下钩子函数的代码了。
蚯蚓
package 模板方法模式.improve;
public abstract class 昆虫 {
/**
* 模板方法,吃昆虫,模板方法做成final,不让子类复写
*/
final void eatInsect() {
grab();
if(needDecapitation()) {
decapitation();
}
if(needGutted()) {
gutted();
}
eat();
evaluation();
}
/**
* 钩子函数,确定是否需要去头
* @return
*/
public boolean needDecapitation() {
return true;
}
/**
* 钩子函数,决定是否需要去内脏
* @return
*/
public boolean needGutted() {
return true;
}
/**
* 抓虫子
* 抽象方法,具体由子类实现(抓什么虫子)
*/
abstract void grab();
/**
* 去头
*/
void decapitation() {
System.out.println("去头");
}
/**
* 去除内脏
*/
void gutted() {
System.out.println("挤出内脏");
}
void eat() {
System.out.println("一口吞下");
}
/**
* 吃完虫子后的评价——抽象方法
*/
abstract void evaluation();
}
再让贝爷吃一波看看
贝爷
package 模板方法模式.improve;
public class 贝爷 {
public static void main(String[] args) {
System.out.println("荒野求生节目,现在开始!");
System.out.println("----------");
昆虫 grasshopper = new 蚂蚱();
grasshopper.eatInsect();
System.out.println("----------");
昆虫 beetleLarvae = new 天牛幼虫();
beetleLarvae.eatInsect();
System.out.println("----------");
昆虫 earthworm = new 蚯蚓();
earthworm.eatInsect();
}
}
运行结果:
荒野求生节目,现在开始!
----------
贝爷,抓住了一个蚂蚱!
去头
挤出内脏
一口吞下
味道像鸡肉,还不错!
----------
贝爷,捉到一只天牛幼虫!
去头
挤出内脏
一口吞下
这条虫,是我吃过最难吃的东西之一
----------
贝爷,抓住了一条蚯蚓!
一口吞下
这玩意,味道出乎意料,有点像意大利面
优点:
通过钩子函数我们可以控制一些方法是否调用。增加了可用性。
缺点:
当需要变化的太多时,如果每个都需要加钩子函数,那就是去了模板的意义。