模板方法模式
1.基本概念:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
1、在造房子的时候,地基、走线、水管都一样,只有在建筑的后期才有加壁橱加栅栏等差异。
2、西游记里面菩萨定好的 81 难,这就是一个顶层的逻辑骨架。
钩子方法
提供缺省行为:
在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”
优点:
1、封装不变部分,扩展可变部分。
2、提取公共代码,便于维护。
3、行为由父类控制,子类实现。
缺点:
每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要改一遍
注意事项:
为防止恶意操作,一般模板方法都加上 final 关键词。锁定模板方法
2.锚定源码:
spring refresh()
比如spring的refresh方法,不同的容器调用会有不同的实现,但configureAndRefreshWebApplicationContext这个方法的执行流程是不变的,refresh只是其中一个固定的环节
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
ConfigurableApplicationContext 接口 定义了refresh()
ConfigurableWebApplicationContext接口 继承 ConfigurableApplicationContext
AbstractApplicationContext 实现 ConfigurableApplicationContext接口 重写refresh()方法
3.实战
举个贴近生活的例子吧,就以我吃的午餐为接口,设计使用模板方法设计午餐类
午餐类
package DesignPattern.templatemethod;
/**
* 午餐类
*/
public abstract class Lunch {
//固定流程吃午饭,1米饭+1荤+1素+(汤默认不需要)
protected final void eatLunch() {
eatStapleFood();
eatVegetable();
eatMeat();
if (needSoup()) {
this.Soup();
}
}
//吃主食 米饭是固定的,不需要改变
final void eatStapleFood() {
System.out.println("每天吃米饭");
}
//吃蔬菜 每天吃什么蔬菜是变化的
abstract void eatVegetable();
//吃肉 每天吃什么荤菜是变化的
abstract void eatMeat();
//钩子方法 是否需要汤 默认不需要 每天不一定会喝汤
protected boolean needSoup() {
return false;
}
//是否需要汤 默认不需要
final void Soup() {
System.out.println("今天喝汤");
}
}
周一午餐类
package DesignPattern.templatemethod;
//周一午餐
public class MonLunch extends Lunch{
@Override
void eatVegetable() {
System.out.println("蔬菜吃大白菜");
}
@Override
void eatMeat() {
System.out.println("荤菜吃红烧虾");
}
}
周二午餐
package DesignPattern.templatemethod;
public class TueLunch extends Lunch{
@Override
void eatVegetable() {
System.out.println("蔬菜吃秋葵");
}
@Override
void eatMeat() {
System.out.println("荤菜吃牛肉");
}
//周二想喝汤了
@Override
protected boolean needSoup() {
return true;
}
}
测试类
package DesignPattern.templatemethod;
public class Test {
public static void main(String[] args) {
System.out.println("————周一午餐————");
Lunch mondayLunch = new MonLunch();
mondayLunch.eatLunch();
System.out.println("————周二午餐————");
Lunch tuesday = new TueLunch();
tuesday.eatLunch();
}
}
控制台输出:
4.实战改进
这里的钩子方式的实现方式是在周二午餐类中写死为需要,就是每个周二都必须喝汤,但如果我不想要喝汤怎么办呢?
可以使用构造方法来改进,通过传入true or false 来控制是否需要喝汤
改进后的周二午餐类如下
package DesignPattern.templatemethod;
public class TueLunch extends Lunch {
//是否需要喝汤标志位 默认不需要
private boolean needSoupFlag = false;
public TueLunch(boolean needSoupFlag) {
this.needSoupFlag = needSoupFlag;
}
@Override
void eatVegetable() {
System.out.println("蔬菜吃秋葵");
}
@Override
void eatMeat() {
System.out.println("荤菜吃牛肉");
}
//周二想喝汤了
@Override
protected boolean needSoup() {
return this.needSoupFlag;
}
}
test类
package DesignPattern.templatemethod;
public class Test {
public static void main(String[] args) {
System.out.println("————周一午餐————");
Lunch mondayLunch = new MonLunch();
mondayLunch.eatLunch();
System.out.println("————周二午餐————");
Lunch tuesday = new TueLunch(false);
tuesday.eatLunch();
}
}
控制台输出