一、简述
模板模式和回调模式都有利于代码的复用和拓展,使用在逻辑相似的业务块中可大大介绍重复代码量,但两者有何异同,让我们通过对比来进行学习,需求案例为B站尚硅谷设计模式——模板模式中提出的打豆浆:
不同类型豆浆的做法在流程上大差不差,说白了只有选材这一步需要异化;
二、模板模式的实现
- 抽象类定义好做豆浆的步骤,并且直接实现重复的部分;
public abstract class SoyMilk {
/**
* 固定制作豆浆的步骤
*/
public void make() {
select();
other();
}
/**
* 选材
*/
protected abstract void select();
public void other() {
System.out.println("加入选好的原料");
System.out.println("开始浸泡");
System.out.println("开始打碎");
}
}
- 子类重写抽象类中没有实现的部分
public class BlackBeanSoyMilk extends SoyMilk {
@Override
protected void select() {
System.out.println("备好白糖、黑豆、黄豆");
}
}
- 调用者创建好黑豆豆浆子类,使其调用父类模板方法
public class TemplateClient {
public static void main(String[] args) {
SoyMilk blackBeanSoyMilk = new BlackBeanSoyMilk();
blackBeanSoyMilk.make();
}
}
由此便实现了一个最基本的模板模式,代码复用体现在抽象类中直接实现的不变部分,而便于拓展则是因为红豆豆浆、黄豆豆浆等仅需重写父类的select方法即可拥有完整的业务流程;
但是,模板模式也有其笨重的地方:当子类的业务不再与抽象类定义的模板方法那么吻合,有部分抽象方法其实是没必要实现的,但碍于抽象类基本性值,即便是空方法也得 @Override 出来,此时就需要同步回调出场了;
三、同步回调——更灵活的模板模式
为了解决模板方式过于"笨重"的问题,我们先来看下同步回调是怎么实现的:
- 创建接口类,设置好打豆浆的基本流程
public interface SoyMilk {
/**
* 设置制作豆浆的步骤
*/
void make();
/**
* 选材
*/
void select();
/**
* 其他重复步骤
*/
void other();
}
- 整一个实现类,把全部方法实现了,当然,需要差异化的方法可以直接空实现
public class SoyMilkImpl implements SoyMilk {
@Override
public void make() {
select();
other();
}
@Override
public void select() {
}
@Override
public void other() {
System.out.println("加入选好的原料");
System.out.println("开始浸泡");
System.out.println("开始打碎");
}
}
- 业务类来了,对标模板模式中的"子类",只不过一个基于继承,一个基于组合,组合中的,组合进来之后直接调用模板接口的模板(主)方法;
public class RedBeanSoyMilk {
public void select(SoyMilk soyMilk) {
soyMilk.make();
}
}
- 调用者在调用业务类的方法是,通过在参数中传入内部类并重写差异化方法的形式注入"回调函数",因为在同步回调过程中,回调函数
SoyMilkImpl().select()
会在函数RedBeanSoyMilk.select()
返回之前执行,所以即便我们在一开始为SoyMilkImpl
实现了个别空方法,但此时SoyMilkImpl
所有的方法其实都已经得到了实现;
public class TemplateClient {
public static void main(String[] args) {
RedBeanSoyMilk redBeanSoyMilk = new RedBeanSoyMilk();
redBeanSoyMilk.select(new SoyMilkImpl() {
@Override
public void select() {
System.out.println("备好白糖、红豆、黄豆、红糖");
}
});
}
}
不难看出同步回调解决了"模板模式中子类必须实现所有抽象方法"的问题;
四、总结
模板模式与同步回调的对比:
(1).两者都有利于代码的复用与拓展;
(2).模板模式基于继承;同步回调基于实现+组合;
(3).模板模式的实现必须借助抽象类,子类必须实现父类所有抽象方法; 同步回调基于回调方法,无需实现接口所有方法;
(4).模板模式一共涉及三种类: ①.抽象类/父类:负责定义模板,并实现可复用部分; ②.子类:负责实现不可复用的业务拓展方法,并继承父类模板方法; ③.调用类:执行子类的模板方法;
同步回调一共涉及四种类:
①.模板接口:负责定义模板;
②.实现类:实现接口的所有模板方法,非复用方法可以为空实现;
③.业务类:组合接口,在自己的方法中调用模板接口的(模板/主)方法;
④.调用类:创建并组合业务类与实现类,并用同步回调的形式重写实现类的空实现部分;
(5).由(4)可知,模板模式将个性化实现放在子类中,而同步回调则将其放在调用类中;不愧是"回调",用的时候才进行定义,业务类现学现用,从调用类中拿过刚注册好的方案搭配着模板接口的实现类使用;
最后打个比方来帮助理解:
模板方法就像个稳扎稳打的大师兄,从师傅那里学到了本事,还能加入自己的想法,所以对敌之前就已经有自己成套且详细的打法;
而回调模式则像个天资聪颖的小师弟,虽然也学到了师傅的本领,但更信奉"看招拆招",在对敌之前并没有自己完整的打法,而是在对敌过程中进行完善;