项目需求
豆浆制作问题
- 制作豆浆的流程:选材–添加配料–浸泡–放到豆浆机打碎
- 通过添加不同的配料,可以制作出不同口味的豆浆
- 选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的
普通模板模式
基本介绍
- 在一个抽象类公开定义了执行它的方法的模板,它的子类可以按照需要重写方法实现,但是调用将以抽象类中定义的方式进行
- 简单点说:模板模式定义一个操作中的算法的骨架,而把一些步骤延迟到子类中,使的子类可以不改变一个算法的结构,就可以重定义该算法的某些特定步骤
代码示例
(1)豆浆类(抽象类)
- 先写好制作豆浆流程中的几个方法,1-选材料、2-添加配料、3-浸泡、4-打磨。其中123方法对于不同口味的豆浆都是一样的,不需要重写,但是4-添加配料是不一样的,需要写成抽象方法,延迟到子类里去实现
- 然后把上述的四个方法封装到一个整体的方法make里,这样调用make就能完成整个流程
- 模板方法可以做成final,不让子类去覆盖
public abstract class SoyaMilk {
//模板方法,模板方法可以做成final,不让子类去覆盖
final void make(){
select();
addCondiments();
soak();
beat();
}
//1-选材料
void select(){
System.out.println("第一步:选择好的新鲜的黄豆");
}
//2-添加不同的配料,抽象方法,子类具体实现
abstract void addCondiments();
//3-浸泡
void soak(){
System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
}
//4-打磨
void beat(){
System.out.println("第四步:黄豆和配料放到豆浆机去打磨");
}
}
(2)子类红豆豆浆
- 重写添加配料的方法
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("第二步:选择上好的红豆");
}
}
(3)子类花生豆浆
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("第二步:加入进口的花生");
}
}
(4)测试类
子类继承了父类的方法,对必要的方法进行了重写,还有make方法,可以子类对象直接调用
public class Client {
public static void main(String[] args) {
System.out.println("------制作红豆豆浆------");
SoyaMilk redBeanSoya = new RedBeanSoyaMilk();
redBeanSoya.make();
System.out.println("------制作花生豆浆------");
SoyaMilk PeanutSoya = new PeanutSoyaMilk();
PeanutSoya.make();
}
}
升级模板模式
如果不想加入配料,只想喝原味豆浆,怎么办呢?
代码示例
(1)豆浆父类
- 方法里加入了一个钩子方法,判断是都需要加入配料,返回一个boolean值
- 在make方法里对加入配料这个方法进行判断,如果上面的boolean为false就不执行加入配料这个方法,就可以得到原味豆浆了
public abstract class SoyaMilk {
//模板方法,模板方法可以做成final,不让子类去覆盖
final void make(){
select();
//如果返回为真,那就添加配料
if(customerWantCondiments()){
addCondiments();
}
addCondiments();
soak();
beat();
}
//1-选材料
void select(){
System.out.println("第一步:选择好的新鲜的黄豆");
}
//2-添加不同的配料,抽象方法,子类具体实现
abstract void addCondiments();
//3-浸泡
void soak(){
System.out.println("第三步:黄豆和配料开始浸泡,需要3小时");
}
//4-打磨
void beat(){
System.out.println("第四步:黄豆和配料放到豆浆机去打磨");
}
//升级部分:制作纯豆浆怎么办?增加一个钩子方法,判断是否加入配料
boolean customerWantCondiments(){
return true;
}
}
(2)子类红豆豆浆
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("第二步:选择上好的红豆");
}
}
(3)子类花生豆浆
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("第二步:加入进口的花生");
}
}
(4)子类原味豆浆
- 重写添加配料的方法,方法里什么都不写,表示不加入配料
- 重写判断方法customerWantCondiments,返回false,表示不加入配料
public class PureSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
//方法一:如果你什么都不写,即可实现纯豆浆
}
//方法二:重写父类的方法
boolean customerWantCondiments(){
return false;
}
}
(5)测试类
public class Client {
public static void main(String[] args) {
System.out.println("------制作红豆豆浆------");
SoyaMilk redBeanSoya = new RedBeanSoyaMilk();
redBeanSoya.make();
System.out.println("------制作花生豆浆------");
SoyaMilk PeanutSoya = new PeanutSoyaMilk();
PeanutSoya.make();
System.out.println("------制作原味豆浆------");
SoyaMilk PureSoya = new PureSoyaMilk();
PureSoya.make();
}
}
UML图
使用模板模式的注意事项和细节
- 算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改
- 实现了最大化代码复用,父类的模板方法和已实现的某些步骤会被子类继承而直接使用
- 既统一了算法,也提供了很大的灵活性,父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现
- 不足之处:每一个不同的实现都需要一个子类实现,导致类的个数增加,是的系统更加庞大
- 一般模板方法都加上final关键字,防止子类重写模板方法
- 模板模式使用场景:当要完成在某个过程,该过程要执行一系列步骤,这一系列的步骤基本相同,但是其个别步骤在实现时可能会有不同,通常考虑用模板方法模式来处理