依赖和耦合关系.docx
- 文档编号:30199260
- 上传时间:2023-08-07
- 格式:DOCX
- 页数:15
- 大小:361.18KB
依赖和耦合关系.docx
《依赖和耦合关系.docx》由会员分享,可在线阅读,更多相关《依赖和耦合关系.docx(15页珍藏版)》请在冰豆网上搜索。
依赖和耦合关系
依赖和耦合关系
1、依赖和耦合(DependencyandCoupling)
(1)什么是依赖
Rose的帮助文档上是这样定义“依赖”关系的:
“依赖描述了两个模型元素之间的关系,如果被依赖的模型元素发生变化就会影响到另一个模型元素。
典型的,在类图上,依赖关系表明客户类的操作会调用服务器类的操作。
”
(2)什么是耦合
MartinFowler在《ReducingCoupling》一文中这样描述耦合:
“如果改变程序的一个模块要求另一个模块同时发生变化,就认为这两个模块发生了耦合。
”[Fowler2001]
注意:
从上面的定义可以看出:
如果模块A调用模块B提供的方法,或访问模块B中的某些数据成员(当然,在面向对象开发中一般不提倡这样做),我们就认为模块A依赖于模块B,模块A和模块B之间发生了耦合。
耦合是依赖的同义词,被定义为“两个元素之间的一种关系,其中一个元素变化,导致另一个元素变化”。
抽象耦合被定义为“若类A维护一个指向抽象类B的引用,则称类A抽象耦合于B”。
2、依赖是不可避免的
(1)“分而治之”的问题处理方法
对于复杂的系统,我们常常采用它划分成多个模块,这样将能够有效地控制模块的复杂度,使每个模块都易于理解和维护。
(2)“分而治之”的结果是产生依赖关系
●一旦我们采用“分而治之”的处理方法后,模块之间就必须以某种方式交换信息,也就是必然要发生某种耦合关系。
●如果某个模块和其它模块没有任何关联(哪怕只是潜在的或隐含的依赖关系),我们就几乎可以断定,该模块不属于此软件系统,应该从系统中剔除。
●如果所有模块之间都没有任何耦合关系,其结果必然是:
整个软件不过是多个互不相干的系统的简单堆积,对每个系统而言,所有功能还是要在一个模块中实现,这等于没有做任何模块的分解。
(3)依赖是不可避免的
因此,模块之间必定会有这样或那样的依赖关系,我们永远也不要幻想消除所有的依赖(耦合关系)。
我们在类的设计时,应该首先考虑的是该类应该尽可能依赖不经常变化的“目标”-----比如,系统的API库或者框架中的组件-----试图令具体的、易变的模块依赖于抽象的、稳定的模块的基本原则。
在Java中,表示字符串的是具体内String。
该类是稳定的,也就是说,它不太会改变。
因此,直接依赖于它不会造成损害。
既然变化不可避免,我们所能做的就是处理好依赖关系,将变化造成的影响的波及范围尽量减小。
(4)我们所追求的是尽可能降低过强的耦合关系
“依赖”是和“变化”紧密联系在一起的概念。
由于依赖关系的存在,变化在某处发生时,影响会波及开去,造成很多修改工作,这就是依赖的危害。
●因为,过强的耦合关系(如一个模块的变化会造成一个或多个其他模块也同时发生变化的依赖关系)会对软件系统的质量造成很大的危害。
●特别是当需求发生变化时,代码的维护成本将非常高。
所以,我们必须想尽办法来控制和消解不必要的耦合,特别是那种会导致其它模块发生不可控变化的依赖关系。
(5)如何达到-----采用什么的策略或者方法
依赖倒置、控制反转、依赖注入等原则。
3、依赖倒置(DIP----DependencyInversionPrinciple)
(1)什么是依赖倒置----将依赖关系倒置为依赖接口
依赖倒置原则就是建立在抽象接口的基础上的。
RobertMartin这样描述依赖倒置原则[Martin1996]:
●上层模块不应该依赖于下层模块,它们共同依赖于一个抽象。
●抽象不能依赖于具体,具体依赖于抽象-----也就是“系统的核心逻辑”不依赖于“具体的实现细节”。
依赖性倒置原则形式化了抽象耦合的概念,明确表述了应该“依赖于抽象类,不要依赖于具体类”。
(2)如何达到依赖倒置的效果
为了消解两个模块间的依赖关系,应该在两个模块之间定义一个抽象接口,上层模块调用抽象接口定义的方法,下层模块实现该接口。
针对接口编程遵守上述原则,从而在很大程度上阻止了变化波及范围的扩大,有效地隔离了变化,有助于增强系统的可重用性和可扩展性。
(3)为什么要依赖接口
●因为抽象是相对稳定的或者是相对变化不频繁的,而具体是易变的
因此,要使我们的设计具有很大的灵活性,就需要做到大家都依赖于抽象的东西,而利用抽象隔离具体类之间的关系,这样使得“具体”的任何改动都可以在局部范围内实现,而不影响其它的结构。
比如,一个IO系统它必然就有READ/WRITE这两个抽象,至于具体是磁盘还是键盘,那是下面的实现不同了,通过这种构架,能保持软件的弹性与可维护性。
●依赖抽象是我们实现代码扩展和运行期内绑定(多态)的基础
因为一旦类的使用者依赖某个具体的类,那么对该依赖的扩展就无从谈起;而依赖某个抽象类,则只要实现了该抽象类的子类,都可以被类的使用者使用,从而实现了系统的扩展。
(4)示例(摘录网上资料)
这里举个比较好笑的例子:
一个即将做领导的儿子问曾经做领导的父亲怎么才能平步青云,父亲说你不能说假话,因为老百姓会不答应,也不能说实话,因为领导会对你有意见,儿子思索良久问:
那我该说什么?
父亲意味深长的说:
空话
笑话不但好笑,还能反映一定的道理,为啥空话这么有用,因为他是抽象的,而抽象不涉及到一些具体的数据或者事务,因此他是稳定的,是不容易变化的,同时基本上也是正确的。
4、依据“依赖倒置原则”进行编程开发
(1)体现倒置原则的UML类图
以前传统的过程设计中是从上到下的一条依赖线
现在是平的一条到接口,然后是一条从下往上的的关联线。
(2)其主要的优点
●由于在依赖倒置中,通过抽象接口达到隔离了“服务的提供者”和“服务的请求者”,使它们之间没有直接的耦合关系,两者可以独立地扩展或重用。
●依赖倒置原则是许多面向对象技术的优点的根本。
合理地应用它对于建立可复用的框架是必要的。
对于构建富有变化弹力的代码也是相当重要的。
而且,由于抽象和具体相互完全分离,代码的维护就容易很多了。
依赖倒置原则一般应用于类与类之间的代码级的依赖关系。
(3)应用依赖倒置原则的代码示例
●任何实例对象变量都不应该持有一个指向具体类的引用,而应该为接口类型的对象声明
classSomeClass{
privateSomeInterfaceobj=null;
}
●任何类都不应该从具体类派生,而应该实现某个接口---面向接口编程,而不要面向实现编程
classSomeClassimplementsSomeInterface{//extendsSuperClass
}
5、依赖倒置原则示例
(1)持久层中的各个组件常规的设计和实现方案
在很多Web应用系统中,都需要对后台的数据库系统进行访问以实现数据存储的目的。
而一个常见的解决手段是使用分层的设计思想和方法,将Web应用系统中的持久层中的各个组件分为数据访问操作组件(DAO,DataAccessObject)和数据连接组件,下面的图中所示为这种常规的设计和实现方案的类图。
(2)常规设计和实现方案中所反映出的问题
从上面的图中的所示的数据访问操作组件和数据连接组件之间的关系来看,两者产生了紧密的藕合关系。
一旦数据连接组件有了任何变化,数据访问操作组件都有可能会受其影响而需要被改动。
当然,如果只是针对这两个类本身而言的话,这种依赖关系根本算不上什么问题。
然而,在一个实际的应用系统中,数据访问操作组件和数据连接组件都会由不只一个类而构成的,并都有一定的技术实现上的复杂度,这时它们两者之间的这种依赖关系就会增加系统的复杂性;而且随着应用系统中的各个程序模块越来越多、功能实现也越来越复杂时,这种由于两者之间的紧密藕合所带来的系统的复杂度会越来越高。
因此,如果不限制它们之间的紧密藕合联系,那模块间的依赖、程序的复杂度和开发维护的难度都会成指数上升。
(3)对设计进行优化——在两者之间添加一个抽象的接口
在两者之间添加一个数据连接组件的接口,并在其中包含数据访问操作组件中所需要的各种数据连接的服务方法的定义,而某个具体的数据连接组件则实现这个接口。
此时数据访问操作组件则只需要调用数据连接组件接口中的服务来实现数据库的各种连接功能,两者都只依赖于数据连接组件的接口。
请见下面的图中所示的优化设计结果的类图。
这样,数据访问操作组件到数据连接组件的依赖关系被数据连接组件的接口所隔离开。
因为在接口中只包含了所需要的各种连接的服务功能的定义,而不包括任何连接的服务功能的具体实现,所以数据连接组件的接口的内容在具体设计时也会很简单。
在数据连接组件接口不变的情况下,数据访问操作组件和某个具体的数据连接组件这两个模块都可以自由地进行修改而不影响到对方(比如添加另一个数据连接组件),依赖关系也变得简单和可管理。
(4)进一步改进本设计和实现方案——核心逻辑不依赖于具体的实现细节
如果从系统架构的角度来看上面的图中所示的设计和实现方案还存在一定的问题,因为数据访问操作组件所提供的功能是整个应用系统的数据访问的核心部分,而数据连接组件中的各个功能方法则可以看成是具体实现的的细节。
因此,从这个角度来看图中优化设计后的结果时,该设计是“核心逻辑依赖于具体的实现细节”。
当然,也就没有遵守依赖倒置原则中的“抽象不能依赖于具体,具体依赖于抽象”的要求。
因为,当细节变化(数据连接中的数据源或者连接方式发生变化)时,数据访问操作组件中的核心逻辑(数据访问的实现逻辑)也会受到一定的影响。
因为当应用系统中的数据存储从某一种形式的数据库系统改变另一种形式的数据库系统(比如从微软的MSSQLServer2000改变为Oracle10G数据库系统)时,此时也将必然会影响到数据访问操作组件中有关的的具体实现方法(因为对这两种不同的数据库系统在具体进行数据访问操作实现时的SQL语句或者涉及对存储过程的调用时,是不一样的!
)。
为了能够达到当系统中的数据库类型发生变化时,不至于影响到对数据库访问组件的使用者(业务层组件)的代码,有必要对系统设计进一步完善!
下面的图所示为这样的应用场景下的各个类的设计要求的图示。
(5)利用数据库访问操作的抽象接口进行隔离
因此,有必要对上面的图中所体现出的设计结果进一步地改进和完善。
主要的思路是将由于数据库连接方式的不同而造成的数据库访问操作的不同隔离开,在数据库访问操作这一层次同样也设计出一个数据库访问操作的抽象接口,并为该数据库访问操作的抽象接口提供不同的具体数据库访问操作的实现类。
这样的设计能够避免改动对数据库访问操作的使用类(一般为上层的业务层组件类)的代码,下面的图为进一步完善后的设计结果的类图。
控制反转(Ioc----InversionofControl)
1、消解框架和我们的应用系统类之间的依赖关系
前面依赖倒置原则描述的是类与类之间的代码级的依赖关系。
如果我们开发的系统是基于某种框架系统来开发的,此时我们的类对目标框架的依赖关系就会更强烈一点。
那么,该如何消解框架和我们的应用系统类之间的依赖关系呢?
利用控制反转。
2、控制反转
(1)什么是控制反转
“好莱坞原则(不要调用我们,让我们调用你)”。
(2)面向框架和面向系统类库开发的不同点
●框架和类库最重要的区别是:
框架是一个‘半成品’的应用程序,而类库只包含一系列可被应用程序调用的类。
●类库给用户提供了一系列可复用的类,这些类的设计都符合面向对象原则和模式。
用户使用时,可以创建这些类的实例,或从这些类中继承出新的派生类,然后调用类中相应的功能。
在这一过程中,类库总是被动地响应用户的调用请求。
●框架则会为某一特定目的实现一个基本的、可执行的架构。
框架中已经包含了应用程序从启动到运行的主要流程,流程中那些无法预先确定的步骤留给用户来实现。
程序运行时,框架系统自动调用用户实现的功能组件。
这时,框架系统的行为是主动的。
(3)应用系统和框架系统之间的依赖关系有以下特点
●应用系统和框架系统之间实际上是双向调用,双向依赖的关系。
●通过依赖倒置原则可以减弱应用系统到框架系统之间的依赖关系。
●而利用“控制反转”及具体的模板方法模式可以消解框架到应用系统之间的依赖关系,这也是所有框架系统的基础。
●框架系统通过“控制反转”,最后能够达到可以独立地被重用。
(4)“控制反转”的体现
传统的面向类库的做法,我们在自己的程序中调用一个个类库去完成一个个功能,类库是我们的执行体,但是逻辑算法等是自己实现的,这就是自己的控制。
但由于了框架后则不一样,逻辑算法都是它来实现,我们只要提供它几个需要的接口或者执行体就可。
这就是反转,控制在框架这边了----由框架来对我们的代码进行调用。
其目的主要是简化工作量,对于一些常见的业务场景不需要自己去重复实现。
3、应用框架是如何实现控制反转
(1)利用模板方法模式
为上层的应用系统提供“模板方法模式”,因为在面向对象领域,“回调函数”(一般是面向系统类库开发时采用的手段)的替代物就是“模板方法模式”(一般是面向应用框架开发时采用的手段)。
(2)什么是模板方法模式
“模板方法模式”,也就是“好莱坞原则(不要调用我们,让我们调用你)”。
模板方法模式是框架系统的基础,任何框架系统都离不开模板方法模式
如Struts框架中的插件技术
publicfinalclassUserDatabasePlugInimplementsPlugIn
{
publicvoiddestroy()
{
}
publicvoidinit(ActionServletservlet,ModuleConfigconfig)throwsServletException
{
}
}
(3)Spring框架中的TransactionTemplate模板类的代码
【例11-34】TransactionTemplate模板类的部分代码示例
packageorg.springframework.transaction.support;
//import语句,在此加以省略
publicclassTransactionTemplateextendsDefaultTransactionDefinitionimplementsInitializingBean
{
publicObjectexecute(TransactionCallbackaction)throwsTransactionException
{
if(this.transactionManagerinstanceof
CallbackPreferringPlatformTransactionManager)
{
return((CallbackPreferringPlatformTransactionManager)
this.transactionManager).execute(this,action);
}
else
{
TransactionStatusstatus=this.transactionManager.getTransaction(this);
Objectresult=null;
try
{//对用户的实现类中的doInTransaction方法进行回调
result=action.doInTransaction(status);
}
catch(RuntimeExceptionex)
{//用户的回调方法执行失败时将自动地进行事务回滚
rollbackOnException(status,ex);
throwex;
}
catch(Errorerr)
{//用户的回调方法执行失败时将自动地进行事务回滚
rollbackOnException(status,err);
throwerr;
}//用户的回调方法执行成功将自动地进行事务提交
this.transactionMmit(status);
returnresult;
}
}
//其它的方法定义,在此加以省略
}
在下面的【例11-35】中所示的是TransactionCallback接口的代码定义示例,该接口只有一个doInTransaction方法的声明。
该方法也就是前面的TransactionTemplate模板类中的“回调”方法(或者是“钩子”方法)。
【例11-35】TransactionCallback接口的定义的代码示例
packageorg.springframework.transaction.support;
importorg.springframework.transaction.TransactionStatus;
publicinterfaceTransactionCallback
{
ObjectdoInTransaction(TransactionStatusstatus);
}
4、“控制反转”和“依赖倒置”的不同点
虽然“依赖倒置”和“控制反转”在设计层面上都是消解模块耦合的有效方法,也都是试图令具体的、易变的模块依赖于抽象的、稳定的模块的基本原则,但二者在使用语境和关注点上存在差异。
(1)“倒置”的目标不同
“依赖倒置”强调的是对于传统的、源于面向过程设计思想的层次概念的“倒置”,而“控制反转”强调的是对程序流程控制权的反转;
(2)应用的范围不同
“依赖倒置”的使用范围更为宽泛,既可用于对程序流程的描述(如流程的主从和层次关系),也可用于描述其他拥有概念层次的设计模型(如服务组件与客户组件、核心模块与外围应用等)。
而“控制反转”则仅适用于描述流程控制权的场合(如算法流程或业务流程的控制权)。
我们也可以把“控制反转”看作是“依赖倒置”的一个特例。
例如,用模板方法模式实现的“控制反转”机制其实就是在框架系统和应用系统之间抽象出了一个描述所有算法步骤原型的接口类,框架系统依赖于该接口类定义并实现程序流程,而应用系统依赖于该接口类提供具体算法步骤的实现,应用系统对框架系统的依赖被“倒置”为二者对抽象接口的依赖。
5、GoF设计模式倡导利用抽象接口消解耦合
“依赖倒置”和“控制反转”原则从不同的角度描述了利用抽象接口消解耦合的动机,GoF的设计模式正是这一动机的完美体现。
依赖注入(DependencyInjection)
1、何谓依赖注入
所谓依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中。
2、依赖注入的几种实现方式
(1)属性注入(SetterInjection)
(2)构造注入(ConstructorInjection)
3、依赖注入和依赖倒置的不同点
(1)依赖注入可以消除对象创建中的依赖性
●遵守依赖倒置中的抽象接口原则,能够隔离使用者和实现者之间的依赖关系,但创建具体实现类的类实例对象仍会造成对于具体实现的依赖。
而采用依赖注入可以消除这种创建依赖性。
●因为,使用依赖注入后,某些类完全是基于抽象接口编写而成的,这可以最大限度地适应需求的变化------借助于“依赖注入”模式可以把具体类的创建过程集中到合适的位置,因为这一动机和GoF中的创建型模式有相似之处-----代替工厂模式。
(2)可以通过*.xml文件来实现依赖注入
目前基于框架的应用系统多数是通过“XML配置”来达到更大的灵活性----由于.NET/JAVA等引入了反射的机制,可以在系统运行期间动态的去创建类的对象实例。
我们就可以把类名放在“XML配置”中,而框架系统则通过反射去加载我们的不同类,并将类名动态地注入到我们的应用系统中----这也是很多框架的最基本的做法。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 依赖 耦合 关系
![提示](https://static.bdocx.com/images/bang_tan.gif)