接口与多态&&依赖注入&&反射机制
Published by Shangyu Liu,
在java中为什么存在接口这个概念,它解决了什么问题。
接口这个词本身是非常形象的,两个工作的部件不是直接对接的,而是通过接口,接口装到了你实现的部件上,你就不需要管接口另一端接的是什么部件了,当另一端的部件功能改变时,你使用的接口不变,你的代码就不用变化。在java中,接口通过类实现,在使用接口的代码中:
接口名 变量名 = new 实现类();
类不可以多继承,但接口可以,接口ab继承了接口a和接口b,然后某个类去实现接口ab。类不可以多继承的原因是如果两个父类有同名方法会冲突,但是接口无所谓因为都没有定义方法的实现,要到实现类中去实现。
接口的出现是为了解耦,代码的协作总是需要耦合,程序员总是希望更松的耦合,耦合涉及到两个方面,消费者和提供商,比如下面的代码:
Class Provider{
public outerFunction(){
...
}
}
Class Consumer{
private Provider pro;
Consumer(){
this.pro = new Provider();
this.pro.outerFunction();
...
}
}
在消费者中,使用了提供商,一旦提供商有变化,比如名字发生了变化,就要将消费者中所有的Provider替换成新的名字,造成了强耦合,代码的维护性变差,如果将提供商的类改为了接口,接口的方法是固定的,当实现类再变化时,就不会影响代码中对于接口的使用:
Interface Provider{
public outerFunction(){
...
}
}
Class ProviderImp{
具体方法重写...
}
Class Consumer{
private Provider pro;
Consumer(){
this.pro = new ProviderImp();//这里是唯一需要修改的地方,其他都与实现类的名字无关了
this.pro.outerFunction();
...
}
}
接口的实现类如果有很多个,this.pro永远是接口的类型,实例化为具体的实现,这些实现类提供完全相同的用法,因而在消费者中屏蔽了具体类的实现,只要consumer了解了接口的形式即可使用。类似的如果是类的继承,可以用ParentClass myclass = new ChildClass(); 这个写法下,在使用中使用的是子类的方法而非父类的。
接口的使用已经尽量降低了耦合,归根结底,接口避免了在new的时候等号左边的代码更改,如何才能在依赖的实现类变化时去掉等号右边的代码变动呢,这就要引入java的反射机制,让所依赖的类是运行代码时动态加载的而不是固定死的,上面的写法会在编译阶段装载指定的类,而反射机制允许我们在运行时根据指定的包名、类名来动态加载、实例化一个类,举个例子,有一个Car类,程序跑起来之后,用户可以在与程序的交互中选择轮胎,轮胎接口指向那种轮胎是无法在编译时确定的,这就需要反射机制:
Class Reflect{
private Interface interface;
String path= 伪代码:接受用户键盘输入,输入类名。
interface = Class.forName(path).newInstance();
}
这样就可以在运行中选择所有实现Interface的类了。然而这种在consumer中实例化类的做法仍然是糟糕的,一方面当依赖类的构造函数参数变化时,consumer的代码仍然需要更改,另一方面在每一个consumer中需要保存一个类的实例副本,对于一些共享服务(单例)是糟糕的。因此很多框架推崇依赖注入的编程思想,在consumer中仅使用provider提供的实例而不负责构建它。构建类的工作全部交给框架去做,而框架之所以能够这么做的基础就是反射机制,至于反射机制,另篇详述
与java依靠反射机制的依赖注入相对比,js的依赖注入又是通过什么实现的呢?再次强调,依赖注入就是不用具体的类名去new,new的过程交给框架,Function.prototype.toString。介绍原型链时已经明确所有的函数对象继承于Function,原型链上有toString方法,
最后总结一下,依赖注入需要我们明白:1、为什么要把依赖的类从consumer中抽出来,2、抽出来之后框架如何仅通过类型就把类实例化了并提供给了consumer