模板方法与回调
很久没有写文章了,今天讲一下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
至此,你应该已经掌握了回调的使用方法。
希望对你有帮助。
原文地址:https://zhuanlan.zhihu.com/p/27909708