模板方法与回调

Java基础

浏览数:566

2019-3-4

   很久没有写文章了,今天讲一下23种设计模式中的模板方法,以及非常常用的回调。

   之所以想统一地讲这2者,是因为我感觉它们有相似的地方,需求都是,我需要在一个固定的流程中,完成一个由其他类实现的功能。比如,把大象放进冰箱要几步?,三步:

   1,打开冰箱门

  2,把大象装进去

  3,关上冰箱门

  其中,1,3,我作为一个冰箱厂商,可以立刻就实现,但第二步,我希望由各个子工厂自己完成,所以,我可以这么写:

public abstract class Parent {
    //模板方法,被外部调用
    public final void template(){
        //第一步
        System.out.println("打开冰箱门");
        //第二步
        stepTwo();
        //第三步
        System.out.println("关闭冰箱门");
    }
    //让子类去实现,被模板方法调用
    protected abstract void stepTwo();
}

   一个模板方法,被外部使用,他按照一定的步骤执行,但第二步移交给子类去实现。模板方法用final修饰,因为不允许被修改,而需要子类实现的方法用abstract修饰。

   接下来写一个子类:

public class Son1 extends Parent {
    @Override
    protected void stepTwo() {
        System.out.println("我选择Son1把大象装进去");
    }
}

   子类继承父类,然后实现这个方法。

   使用示例:

Parent elephant = new Son1();
elephant.template();

   输出结果:

打开冰箱门 我选择这样把大象装进去 关闭冰箱门

   至此,你就完成了模版方法的使用。

   很明显,这是使用了多态的特性来完成的。那如果想在2个没有继承关系的类达到这个效果,该怎么做呢?

   回调!

   比如,冰箱子工厂说,我也不知道该怎么把大象装进去,大象爱怎么进去怎么进去,就让大象来决定的。

   而且显然的,冰箱和大象没有共同的父类,不能简单地使用多态特性了。

   我们可以用接口+匿名内部类来实现,这是组成回调所需要的重要元素。

   按照如下方法改造Son1类,这时,原本的stepTwo()方法,就成了原本的模版——按照一定的顺序执行业务逻辑,但其中一步交给别人来实现。

   实现方法是:

   在类里声明一个接口,接口里有一个方法;类里声明一个类型为该接口的类变量;一个设值函数(外部调用这个方法,新建一个匿名内部类,完成业务逻辑);然后在你的业务流程中,通过这个类变量,调用接口里的方法。

public class Son1 extends Parent {
    //回调的接口
    public interface Elephant{
        void getIn();
    }
    
    //实现接口的元素
    private Elephant elephant;
    //设值方法
    public void setElephant(Elephant elephant){
        this.elephant = elephant;
    }
    
    @Override
    protected void stepTwo() {
        System.out.println("我选择让大象把自己装进去");
        elephant.getIn();
    }
    
}

   ( 我强烈建议你,一个接口里只声明一个方法。)

   这就完成了接口的书写,接下来是匿名内部类。不过别急,先回想一下模版方法的初衷——

   A:我想按照一定顺序把大象放在冰箱里,但具体怎么放,交给子厂商决定。

   B:而子厂商:我知道具体该怎么放,但大象要怎么迈开步子,我想让大象自己决定。

   A,B两个例子,有一定共通性,相信各位一定可以理解。区别只是,A中的2个主体有继承关系,可以使用多态;B中2个主体没有继承关系,所以要使用接口与匿名内部类来实现。

   我们接下来看一下匿名内部类要怎么用:

Son1 son1 = new Son1();
son1.setElephant(new Son1.Elephant() {
    @Override
    public void getIn() {
        System.out.println("我是一头大象,我在匿名内部类里自由地跳舞");
    }
});
son1.stepTwo();

   我们暂时把代码改成了这样,为了方便理解,抛去了Parent类。

   一共三个语句

   1,声明一个Son1类并定义。

   2,调用setXXX设值方法,这个方法可以访问外部类的元素,等下我们会做详细的说明。

   3,调用“模版方法”

   输出如下;

我选择让大象把自己装进去 我是一头大象,我在匿名内部类里自由地跳舞

   其实以上就是回调的精髓。但我们还可以进一步学习,如果冰箱子厂商说,我的冰箱有限高,高于这个高度的大象要自己反省一下。

   该怎么做?

   怎么在“模版”类里,告知外部的实现类,我有一些数据要给你?

   只需要修改接口里对方法的声明,通过方法参数来实现:

//回调的接口
public interface Elephant{
    //通过方法形参,传递参数
    void getIn(int height);
}
private int height = 100;
@Override
protected void stepTwo() {
    System.out.println("我选择让大象把自己装进去");
    //通过方法形参,传递参数
    elephant.getIn(height);
}

   那么原本大象的方法,就要改为:

public static void main(String[] args) {
    Son1 son1 = new Son1();
    int myHeight = 120; //外部参数
    son1.setElephant(new Son1.Elephant() {
        @Override
        public void getIn(int height) {
            System.out.println("我是一头大象,我在匿名内部类里自由地跳舞");
            System.out.println("我的高度是 " + myHeight); // 如果这个参数在外部方法的参数里,要声明为final
            System.out.println("系统要求的高度是 " + height ); // 访问传进来的参数
        }
    });
    son1.stepTwo();
}

   通过方法参数来获取传参,同时也可以获取外部类的成员,注意,外部的参数需要声明为final,或者达到final的效果,你不能在匿名内部类里修改这个myHeight的值。

   执行结果为:

我是Son1,我要让高于100的大象自我反省一下! 我是一头大象,我在匿名内部类里自由地跳舞 我的高度是 120 系统要求的高度是 100

   至此,你应该已经掌握了回调的使用方法。

    希望对你有帮助。