浅谈(从面向抽象编程再到IOC控制反转)

1.WHAT-面向抽象编程的例子 1.1 面向对象
一般来说,面向对象编程是我们比较常见的,即将一个实际的项目分成多个类(对象),赋予这些对象属性和方法,从而实现编程。
比如,我们要编写一个校园管理平台,分别管理老师和学生,非常直观地我们就能把老师类和同学类给归纳出来,老师有教学的职能(方法)有性别年龄(属性),学生则有念书的职能(方法),也有性别年龄(属性)。
1.2 面向对象进阶到面向抽象
在我看来,面向对象其实就是:归纳总结。我们程序猿将产品经理提出的直观需求进行归纳总结,可以得到归纳的类。如果我们能把归纳的类再进一步地归纳(或者说抽象),我们就得到了一个抽象类。
同一个例子,除了老师类,学生类,我们还可以归纳一个“人”类,这个“人”类有着一个人应具备的属性(如性别,年龄,身高,体重)和能力(如吃饭,睡觉)。
(P.S:当然,归纳出一个抽象类只是面向抽象的一部分,并不是面向抽象的全部。)
2.WHY-为什么要面向抽象编程 2.1 代码编写
在代码编写层面,有过编程经验的我相信都会或多或少经历过:将公用的部分抽离出来,这样就不用重复编写代码。例如组件、包,我们不需要去编写重复性极强的代码。
2.2 代码维护
在代码编写层面,更多还是面向对象的思维在起作用。针对一个项目,我们正常的思维往往是自上而下的,因此先归纳出一个抽象类,再得到具体类,这是很正常的思维逻辑,这样写出来的代码,层次分明,但还是远远不够。
一个大型项目,往往难的不是它的开发,而是它的维护。大型项目里,即使我们用抽象类的方式将类进行了归纳总结,但类与类之间仍然不可避免地有着非常强的依赖关系。
举个栗子:我们A类抽象成IA类,但我们仍然要去实例化类A,而实例化类A的启动类B,依然引用了A,启动类B和A的依赖性仍然存在。(java的多态)
2.2.1 开闭原则(OCP) 首先,要想更好地实现代码维护,我们就要遵循开闭原则:允许拓展,禁止修改。
浅谈(从面向抽象编程再到IOC控制反转)
文章图片

见上图,上图的四个齿轮是我们的四个类,它们互相依赖从而推动程序的运行。但产品经理有一天突然提出了一个新需求,这个需求可能我们直接修改类A就实现了,然而类A一旦发生变动,我们的类B,类D也得跟着动,然后连带着C也可能要跟着动,这样的变动是灾难性的。
由此可见,要遵循开闭原则,并不是一件容易的事情,这必须是在设计代码框架时就得思考清楚。那么到底怎么才能做到开闭原则呢?
2.2.2 面向抽象编程 我们需要明白:变化(产品经理)是固然存在的,是不可磨灭的。代码想要做到只拓展,完全不修改是不可能的。因此,遵循OCP原则,我们应该做到的是:
1.将代码的变化隔离至一处。
2.降低类与类之间的关联性,让每个类可以独立变化而不影响到其他类的使用。
而面向抽象编程正是将这些变化抽象,将类与类关联性降低。它有三个方面:
1.用变量抽象化“值”的变化;
2.用Interface接口抽象化“类”的变化;
3.用一个容器来封装类的实例化,再用反射机制来抽象化类的实例化。
2.2.3 工厂模式 面向抽象编程的前两个方面在日常编程中其实是非常常用的,像写配置文件,此处就不再多做赘述。主要还是第三点:用容器封装类的实例化。类的实例化是类与类之间相互依赖的关键所在,正是因为一个类直接在另一个类中进行了实例化,两个类才产生耦合。
【浅谈(从面向抽象编程再到IOC控制反转)】浅谈(从面向抽象编程再到IOC控制反转)
文章图片

见上图,还是四个齿轮,但我这次在四个齿轮之间加入了一个大齿轮,今后不再是小齿轮互相推动运转,而是大齿轮带动四个小齿轮进行运转。显而易见,小齿轮之间的关联性已经被降低非常多了,之后不管是修改小齿轮的内部,或者是要更换小齿轮,都比原来的风险要小的多。
回到代码上来,这个大齿轮,其实就是一个容器,用这个容器实现类与类之间的关联。而要实现这个容器,我们可以采用工厂模式+反射机制
用具体的代码来实现一下工厂模式+反射机制:
假设我们需要做一个英雄联盟的选英雄,并释放R技能的简单demo。
单纯的工厂模式:

// 主控类 public class Main { public static void main(String[] args) throws Exception { String name = Main.getPlayerInput(); Hero hero = HeroFactory.getHero(name); hero.r(); }private static String getPlayerInput(){ System.out.println("Enter a Hero's Name"); Scanner scanner = new Scanner(System.in); String name = scanner.nextLine(); return name; } } // 工厂类 public class HeroFactory { public static Hero getHero(String name) throws Exception { Hero hero; switch (name){ case "Diana": hero = new Diana(); break; case "Irelia": hero = new Irelia(); break; case "Camile": hero = new Camile(); break; default: throw new Exception(); } return hero; } }

乍看上去,好像我们通过抽象所有英雄有了一个Hero类,同时用工厂来生成它,已经很好地封装了,但是,我们每一次添加英雄,都需要去变动这个工厂,这个工厂作为我们的“大齿轮”,显然还不够资格,它连结了各个英雄类,直接修改它依然有影响连结的风险。
(感觉这个例子可能说服力不太强,因为各个英雄类之间关联性本身就不大,看起来修改工厂也问题不大,我只能直接从齿轮直观来解释了,不知道大家有无更好的例子- -。)。
工厂模式+反射机制:
public static Hero getHero(String name) throws Exception { String classStr = "reflect.hero." + name; Class cla = Class.forName(classStr); Object obj = cla.newInstance(); return (Hero) obj; }

单纯的工厂模式,每一次都需要的变动是因为实例化过程还未被抽象,若是加上了java的反射机制,那就不同了,类的实例化完全取决于用户输入的英雄名(或者说配置文件中的英雄名)。无论我们新增任何英雄还是要调整任何英雄,该工厂都无需发生变化,
显而易见,这个工厂基本实现了我们图中的“大齿轮”,同时,我们可以看到,这样的一个大齿轮,很好地把我们控制代码运行的类和实现业务逻辑的类实现了分离————控制代码:MAIN+FACTORY;业务代码:各个英雄类DIANA+IRELIA+CAMILE。
因此,我们能将OCP原则换一种方式理解:尽量不要修改负责控制的代码,只修改业务逻辑的代码
3.IOC控制反转
假设有一个A类需要引用B类,那么一般来说,我们会在A类中实例化B类;而引入一个容器,将A类,B类都事先引入容器中,再由容器把B类注入到A类中,这就实现了IOC控制反转。
从这个概念来看,工厂模式+反射机制,这种模式已经基本实现了IOC控制反转(将英雄类注入到控制类之中)。但是实际情况中,容器注入某个类并非上面的例子那么简单,它可能需要根据某些特定的条件来判断哪一个情况需要注入哪一个类,然而如此繁琐的业务逻辑,自然不能放在工厂之中,因此要用到java的Springboot框架。
此处不过多做展开,待下次再记录一下对Springboot框架的相关理解。
自己的疑问:
1.工厂模式+反射已经解决了控制类不发生变动了,那么Spring框架相对于它的优势到底在哪呢?因为Spring框架还需要先将类生成bean加入到IOC容器之中,还多了这么一步,所以感觉较之工厂模式+反射来说还更加繁琐。

    推荐阅读