您的位置:首页 > 其它

胡扯IoC(Inversion of Control)

2014-02-10 20:30 821 查看
看一篇关于Redis的博客(http://www.hoterran.info/redis_eventlibrary),讲网络模块时出现这个词汇:IoC(依赖翻转)。觉得很厉害的样子,不懂,便抽空学习了一下。

讲IoC最好的两篇文章是:

http://en.wikipedia.org/wiki/Inversion_of_control
http://www.objectmentor.com/resources/articles/dip.pdf
第一个要明确的:IoC是一个概念,而不是一项具体的技术。有多种技术可以实现这个概念。这个概念讲了这么一件事:how to make reusable code controls the execution of problem-specific code. 这句话解释了为什么要用Inversion of
Control这几个字。用形象的话说:“Inversion of control is sometimes facetiously referred to as the "Hollywood Principle: Don't call us, we'll call you", because program logic runs against abstractions such as callbacks.”

第二个要明确的是:实现IoC的技术很多,每一项技术并不是为IoC而生,而只是正好能用于实现IoC而已。最核心的还是IoC这个概念本身,它是魂,领会了这一点,IoC就清晰了一半。

下面通过实现IoC的具体技术来进一步理解什么是IoC。

首先来一个代码(来自wiki),这段Java代码写得很水,因为它需要关注大量细节,导致的结果是ServerFacade对象高度依赖DAO对象的实现。

public class ServerFacade {
public <K, V> V respondToRequest(K request) {
if (businessLayer.validateRequest(request)) {
DAO.getData(request);
return Aspect.convertData(request);
}
return null;
}
}


正确的写法是让DAO对象本身来关注如何生成一个K对象需要的数据。这样一来,K和DAO就可以根据接口各自灵活实现了。底层业务无需跟着改变。

public class ServerFacade {
public <K, V> V respondToRequest(K request, DAO dao) {
return dao.getData(request);
}
}


有两个词汇经常跟IoC交替使用,可以把他们理解成IoC的具体实现。

1. Dependency Injection

依赖注入,简单说就是调用者来确定具体类,把这些具体类作为参数来调用函数,被调用函数内用接口类实现逻辑,返回结果。

参考:http://en.wikipedia.org/wiki/Dependency_injection

2. Dependency Inversion

看一段很水的代码,它需要根据各种情况来处理各种逻辑。

enum OutputDevice {printer, disk};
void Copy(outputDevice dev)
{
int c;
while ((c = ReadKeyboard()) != EOF)
if (dev == printer)
WritePrinter(c);
else
WriteDisk(c);
}


这里的Dependency是Copy的实现逻辑依赖于dev的类型。那么,Inversion指的是:让dev相关的对象依赖于Copy逻辑。这个时候,我们可以把Copy逻辑实现得非常简单,dev相关的对象真正依赖的是Copy逻辑中定义的接口。所以,我把Dependency Inversion改为一个更清晰的说法:Logic Dependency Invert to Interface Dependency。逻辑依赖是复杂的,接口依赖是简单的(这里简单并不一定是代码量小,而是修改简单,移植简单,重构影响面小)。

class Reader : public inputDev
{
virtual int read() = 0;
}
class Writer : public outputDev
{
virtual void write(int c) = 0;
}
void Copy(inputDev in, outputDev out)
{
int c;
while ((c = in.read()) != EOF) out.write(c);
}


为什么要用Inversion这个词呢?其作者说:“One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design, tend to create software structures in which high level modules depend upon
low level modules, and in which abstractions depend upon details.”我想说的是,这是很不负责任的命名方法!我还想说,理解了作者的这段话(我们大部分都是这样写代码的,不是么?所以,我相信这句话你肯定能理解^o^),也就明白了Dependency Inversion要解决一个什么问题:上层逻辑不应该依赖于下层实现。反过来说,下层逻辑应该依赖上层实现?也不是。谁直接依赖谁都不是什么好事。所以,依赖第三方吧!这个第三方是什么呢?接口(Interface),或者换个更形象的单词:contracts。

在大一的C语言教科书上我们学会了复用,复用什么呢?子函数(sub-routine)。这种复用可以极大的降低我们的工作量,通常是50%以上---因为我们的代码总行数一般也不会超过100行。如果我们的代码行数是10万行呢?岂不是可以减少5w行?No,恐怕随着时间推移,不仅不会减少,反而会增加1w行!为什么呢?我们来举例仔细分析一下子函数的弊端:

上层逻辑用到了一个子函数,又一个上层逻辑用到了子函数,再来一个逻辑,它还是仿佛可以用到子函数,不过它又发现这个子函数总体上是合适的,但内部有一个变量的类型不太匹配,它通常会采用一个傻大粗的做法:把这个函数复制一份,改个名字,分开调用(比如,func_v2,别笑,大家就是这么干!)。有了v2就会有v3,v4,然后,你懂的。如果你学习过模板,你会说,犯不着这样,用一个模板就行了。嗯,很好,这里的确可以用模板来解决问题。但是对于这样一种情况,模板就无能为力了:又一个上层函数用到了该子函数,但子函数内部有一段简单的判断逻辑对于这个上层逻辑来说是不能存在的。。。哈哈,无语了。你说你们这些“上层逻辑,怎么这么事儿呢!”

此乃子函数之过?此乃上层函数之过也!因为说不改的也是上层函数,说要改的还是上层函数,你让我等底层函数如何是好?正确的设计手段是:上层函数不应该对底层函数有任何形式的依赖,上层自身对自身负责。

再多一句嘴,谁最需要被“复用”?对于大学生来说,答案是函数和库;对于从业者来说,如果还这么回答就应该被打板子。最应该被复用的是“高层模块”,因为在一个大型系统中,他们是变数最大的部分。如果高层模块对具体底层模块有很强的依赖,那么这些高层模块就被底层所绑架,导致天上飞的就没办法在地上跑,地上跑的没办法在水里游。其实,一个优良设计的运输工具,应该是换一下引擎就能上天,再换一下引擎就能入地,而在坐在工具里的人来说,他看到的总是一个前进后退的开关,和富丽堂皇高科技的控制室。

为什么C语言的库函数、STL等这么多年了都还依然被广泛使用呢?这是因为:当人们发现这个库函数解决不了问题的时候就跑去自己另搞一套了。你把源码开放给他们试试,绝对给你改得面目全非七零八落,10年后,神仙都看不懂。

参考:http://www.objectmentor.com/resources/articles/dip.pdf

OceanBase中,有把IoC用得非常好的代码,也有根本不知IoC为何物的代码,前者的确很容易维护,甚至都感觉不怎么需要维护,后者根本没法改!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: