依赖倒置之我见
2017-03-15 21:36 没有波澜的天空 阅读(290) 评论(0) 编辑 收藏 举报.net 程序员对面向对象设计原则以及设计模式的重视似乎不如Java,包括许多有经验.net的程序员,也并没有将面向对象的思想渗透进项目中。我本身就是这样一个例子。C#和Java都是面向对象的语言,设计模式对两者是通用的,今天就来谈一谈我对面向对象设计原则之一—依赖倒置原则的理解,之所以选择这个原则,因为之前对这个原则很重要(个人认为是最重要的),而且我之前对这个原则的了解相当模糊。
首先,依赖倒置的标准定义:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
这个定义听得人云里雾里,不知所云。究其原因,是几个概念不清楚。
1、依赖,什么叫依赖?
这个问题我探索了好久。面向对象的设计中,依赖是程序模块(普通类、抽象类、接口)的一种关系(关系还包括,关联,聚合,组合,继承等),
依赖主要包括以下几种情况。
a、ClassA中某个方法的参数类型是ClassB; 这种情况成为耦合;
b、ClassA中某个方法的参数类型是ClassB的一个属性; 这种情况成为紧耦合;
c、ClassA中某个方法的实现实例化ClassB;
d、ClassA中某个方法的返回值的类型是ClassB;
但是,用这个“依赖”来解释依赖倒置的定义是解释不通的,因为从这来看,“依赖”和继承是两个概念。但在依赖倒置的概念中,低层模块“依赖”抽象,细节“依赖”抽象,这里的“依赖”中,继承以及实现接口,也是一种“依赖”,所以不能用这里的“依赖”来代替依赖倒置中的“依赖”。
那么依赖倒置中的依赖到底是什么含义呢,目前没有找到让我满意的答案,我姑且谈一下自己理解。我认为如果A模块(普通类,抽象类,接口)依赖B模块,那么如果B发生了变化(包括内部变化以及用别的模块来替代B)则可能要修改A,不然要出现错误(编译或功能),那么就认为A依赖B。这个理解很绕,但我目前实在想不出更好的词来解释“依赖”这个词了。
2、倒置,为什么叫倒置?
倒置是相对于我们的一般思路而言的。一般情况下,高层模块是依赖低层模块的。比如A是高层模块类,B是低层模块类,如果A调用B,那就要再A中实例化一个B的对象。这样,A就会依赖B。而依赖倒置做的就是要解除这种依赖,用的方法就是两者都依赖于抽象。
依赖倒置要求软件设计中“面向接口编程”,其基本的原理是接口比实现稳定。
依赖倒置的具体实现网上有太多例子,但总感觉不够实用。因为总举什么人吃东西,车能跑之类的白痴例子,形象是形象,就是离实际的项目相差太远。相信这些人也没有人真正用过依赖倒置。
我来举个实际的项目的例子。
假如有一张表叫Member,存储在Sql Server中。要实现对这个表的访问(增删改查),按照正常的逻辑,应该有有两个类MemberBll,MemberDal。MemberBll中创建MemberDal调用MemberDal中的方法。
程序结构如下:
public class MemberDal
{
void Add()
{
......
}
void Delete()
{
......
}
}
public class MemberBll
{
void Add()
{
var memberDal=new MemberDal();
memberDal.Add();
}
void Delete()
{
var memberDal=new MemberDal();
memberDal.Delete();
}
}
这样考虑清晰明了,但很明显,这违反了依赖倒置原则,因为高层模块依赖低层模块,一旦低层模块发生改变,必然要修改上层模块。可能有人会问,低层模块不是很稳定吗?怎么会随便变?这个可不一定,现实项目中,修改底层的情况也是很常见的。而对于我们这个真实的项目,底层确实变了,怎么变呢,底层要切换成MySQL数据库。这个时候原来的设计就显得比较脆弱了。那么怎么才能算是一个好的设计呢。根据依赖倒置原则,高层模块不应该依赖低层,他们应该共同依赖抽象。所以应该针对低层模块抽象出一个接口,高层模块和低层模块都依赖这个接口。具体的实现如下:
public interface IMemberDal
{
void Add();
void Delete();
}
public class SQLServerMemberDal:IMemberDal
{
void Add()
{
......
}
void Delete()
{
......
}
}
public class MySqlMemberDal:IMemberDal
{
void Add()
{
......
}
void Delete()
{
......
}
}
public class MemberBll
{
IMemberDal _iMemberDal;
public MemberBll(IMemberDal iMemberDal)
{
_iMemberDal=iMemberDal;
}
void Add()
{
_iMemberDal.Add();
}
void Delete()
{
_iMemberDal.Delete();
}
}
通过以上方式,便取消了底层和上层的依赖,即使再换用其他数据库(如Oralcle,Mongo),也不用再改上层代码了。以上采用了构造函数的形式进行了依赖注入,客户端只要在构造MemberBll时将数据访问层的依赖注入即可。除了这种方式还有其他几种,这个以后再说。
谁也无法预知项目中哪些模块会放生变化,我们能做的就是多考虑一些,防患于未然,同时,也一定得把握好考虑的尺度,过度设计和没有设计一样可怕。