在编程过程中啊,很多时候我们是用面向对象的思想编程。这是我们在编程过程中很自然就会去应用的思想。比如我们来模拟一下英雄联盟这个游戏的应用场景。
现在呢,我们有一个需求:当用户输入一个英雄的名称后,释放英雄的一个技能。
面对这个需求,首先我们很容易想到把英雄这个类抽象出来,创建一个对象。
1 | public class Diana { |
创建好了对象,我们就应该去使用它。大多数情况下,我们是去调用它的方法。
1 | public class Main { |
代码中的第6行,当我们需要使用到Diana这个对象的时候,我们就会去创建它。但是仔细一想,这样直接new对象真的好么?实现需求肯定是能实现的,但这不是我们追求的目标,我们的目标是不仅实现需求,还追求代码的可维护性。试想,如果有一天Diana这个字符串对应的不再是Diana这个对象了,那么我们是不是需要去修改第6行的代码,去创建别的对象?在代码量少,业务简单的情况下,你可能觉得还好,改了就改了呗。但是一旦这个类被多个类所调用呢?是不是就得在每一个使用了Diana对象的类中进行修改?这样是不是很难维护呢?
所以,我们提出一个概念:不要面向具体的对象编程,要面向抽象编程。那么问题来了,有什么办法可以让我们更好的去面向抽象编程呢?
- interface、abstract
- 工厂模式
- IOC / DI
我们先来看第一种:使用interface
先定义一个Hexo接口,所有的英雄类实现这个接口。
1 | public interface Hero { |
在使用了接口之后,我们还是需要new出每个对象,而且也没有优化掉switch..case..,那么,我们为什么还要抽象出接口呢?这里给出结论:单纯地使用interface能统一方法的调用,但无法统一对象的实例化。相比较第一版的代码,原本在switch..case..中的方法调用优化了,但是switch..case..没有优化掉。仔细分析,这里的switch..case..正是执行了对象的实例化,正好印证了结论。
1 | public static void main(String[] args) { |
那么,只是简化了方法的调用,意义大吗?
当然了,如果有很多个英雄呢?那岂不是要调用很多次R()吗?所以,统一方法的调用还是很有意义的。
从上面的分析,interface可以解决的是方法统一调用的问题,其实,抽象的难点在于new对象的优化。只有一段代码中没有new的出现,才能保持代码的相对稳定,才能逐步实现OCP(开闭原则)。而对象的实例化是不可能消除的,只能将它实例化的过程转移到其他的代码片段中。接下来,我们就利用简单工厂模式来转移对象的实例化代码。
首先,我们创建一个工厂,该工厂用来产生Hero的对象
1 | public class BeanFactory { |
有了工厂之后,我们不再需要在main方法中使用switch..case..了。这段代码利用interface+工厂模式,已经将对象的实例化分离到工厂中,对main方法来说,它就是稳定的一段代码了吗?不一定,稳定是相对的。这段代码虽然分离了对象的实例化代码,但是它是巧妙地利用了BeanFactory的静态方法。假如BeanFactory不是静态的了,那么调用getHero()就需要变化。从另外一个角度来说,我们实现了与具体对象(通过new出来的对象)的低耦合,但是我们耦合了工厂对象。
1 | public static void main(String[] args) { |
在使用了interface+简单工厂模式后,main方法已经是相对稳定的了。但是代码中总是会存在不稳定,我们要做的是隔离这些不稳定的代码,使其他代码相对稳定。假如这个BeanFactory能生产出任何的对象,那我们岂不是就不用修改main方法里的BeanFactory了吗,因为无论什么样的对象,工厂都能生产出来。其实,这就类似于Spring中ApplicationContext。
在这里停下来想一想,到底是什么导致了代码的不稳定呢?主要是两个方面:
1.用户的输入信息变化了,这和本例是一致
2.用户的需求变化了
假如说用户输入的不再是英雄的名字(字符串),而是能直接输入一个对象的话,那我们就消除了new对象,从而消除了switch..case…其实这在java中是可以利用反射实现的,只不过要求用户输入的必须是带上包名的完整的类的名称。
1 | public class BeanFactory { |
我们利用了反射消除了switch..case这种根据用户输入字符串new对象的方式。但是要注意的是,我们需要对用户输入的字符串进行处理,处理成完整的类的名称。当用户输入字符串后,就相当于我们拿到了对应的对象。这里就类似于Spring中利用配置文件来反射对象。
上面的代码,我们利用了静态的BeanFactory,实际上已经实现了OCP原则。但唯一遗憾的是,如果BeanFactory变动了(比如原来的getHero方法改变了),那我们还是需要修改main方法的。那有什么办法再把BeanFactory分离出去,更好地实现OCP原则吗?方法是有的,那就是Spring的IOC。在之前的代码中,我们都是主动地去要,主动地调用BeanFactory的方法。一旦改变,就会违反OCP。而使用Spring的IOC,我们是被动地接收,不再主动地去要。这样就分离了BeanFactory,也就使得OCP更加稳定,这就是所谓的控制反转。反转的是资源(不仅仅只是包括对象)的获取途径,从主动获取反转为被动接收。
新的问题又出现了?怎么被动接收呢?通过方法传参?
这就涉及到了另外一个概念———依赖注入(DI),通常我们是在类的层面注入。就比如这里的main方法,虽然是在main方法中使用,但是通常是在main方法这个类中注入BeanFactory,这样main方法也可以去调用。
常用的两种依赖注入的方法:
1.属性注入
2.构造器注入