从模板模式到同步回调

175 阅读4分钟

一、简述

模板模式和回调模式都有利于代码的复用和拓展,使用在逻辑相似的业务块中可大大介绍重复代码量,但两者有何异同,让我们通过对比来进行学习,需求案例为B站尚硅谷设计模式——模板模式中提出的打豆浆:

不同类型豆浆的做法在流程上大差不差,说白了只有选材这一步需要异化;

二、模板模式的实现

  1. 抽象类定义好做豆浆的步骤,并且直接实现重复的部分;
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("开始打碎");
    }
}
  1. 子类重写抽象类中没有实现的部分
public class BlackBeanSoyMilk extends SoyMilk {

    @Override
    protected void select() {
        System.out.println("备好白糖、黑豆、黄豆");
    }
    
}
  1. 调用者创建好黑豆豆浆子类,使其调用父类模板方法
public class TemplateClient {

    public static void main(String[] args) {
        SoyMilk blackBeanSoyMilk = new BlackBeanSoyMilk();
        blackBeanSoyMilk.make();
    }
}

由此便实现了一个最基本的模板模式,代码复用体现在抽象类中直接实现的不变部分,而便于拓展则是因为红豆豆浆、黄豆豆浆等仅需重写父类的select方法即可拥有完整的业务流程;

但是,模板模式也有其笨重的地方:当子类的业务不再与抽象类定义的模板方法那么吻合,有部分抽象方法其实是没必要实现的,但碍于抽象类基本性值,即便是空方法也得 @Override 出来,此时就需要同步回调出场了;

三、同步回调——更灵活的模板模式

为了解决模板方式过于"笨重"的问题,我们先来看下同步回调是怎么实现的:

  1. 创建接口类,设置好打豆浆的基本流程
public interface SoyMilk {

    /**
     * 设置制作豆浆的步骤
     */
    void make();

    /**
     * 选材
     */
    void select();

    /**
     * 其他重复步骤
     */
    void other();
}

  1. 整一个实现类,把全部方法实现了,当然,需要差异化的方法可以直接空实现
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("开始打碎");
    }
}
  1. 业务类来了,对标模板模式中的"子类",只不过一个基于继承,一个基于组合,组合中的,组合进来之后直接调用模板接口的模板(主)方法;
public class RedBeanSoyMilk {

    public void select(SoyMilk soyMilk) {
        soyMilk.make();
    }
}
  1. 调用者在调用业务类的方法是,通过在参数中传入内部类并重写差异化方法的形式注入"回调函数",因为在同步回调过程中,回调函数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)可知,模板模式将个性化实现放在子类中,而同步回调则将其放在调用类中;不愧是"回调",用的时候才进行定义,业务类现学现用,从调用类中拿过刚注册好的方案搭配着模板接口的实现类使用;

最后打个比方来帮助理解:

模板方法就像个稳扎稳打的大师兄,从师傅那里学到了本事,还能加入自己的想法,所以对敌之前就已经有自己成套且详细的打法;

而回调模式则像个天资聪颖的小师弟,虽然也学到了师傅的本领,但更信奉"看招拆招",在对敌之前并没有自己完整的打法,而是在对敌过程中进行完善;