一个组件与另一个组件之间的关系可以通过三种方式建立起来:事件、依赖倒置、Bridge。现在我们只考虑单向依赖的关系,即信息提供者和信息消费者。事件是一种松耦合的信息发布方式,事件发布者(信息提供者)不需要关心事件预定者(即信息消费者)的任何信息,但是事件预定者需要依赖事件发布者;依赖倒置则反转了这种关系,在依赖倒置的方式中,信息提供者依赖信息消费者(你也许对这句话觉得奇怪,后面的例子会说明),而信息消费者不需要了解信息提供者的任何信息;所谓Bridge,就是一个中介者模式,在采用这种设计时,信息发布者和信息提供者是完全相互独立的,互不依赖,它们之间通过Bridge桥接起来。
那么在设计组建之间的关系时到底该选择那种方式,则需要依据实际情况而定。假设,组件A是信息提供者,而组件B是信息消费者。可以有以下几种情况:
(1)A发布事件,B包含A的引用,然后B预定A的事件。(事件方式)
(2)B接口中提供接收信息的方法,A包含B的引用,在适当时候A调用B的接收信息的方法。(依赖倒置方式)
(3)A发布事件,B接口中提供接收信息的方法,通过Bridge将A、B联系起来。(Bridge方式)
下面举个实际的例子,在我们的应用中,有个功能服务管理器IServiceManager(信息提供者),功能服务可以在运行的时候变化,这个变化可以被IServiceManager检测到,而显示功能服务名称的IServiceDisplayer(信息消费者)也需要在功能服务变化的时候,将显示作必要的改变。如果按照“事件”方式,应该如下设计:
按照“依赖倒置”方式设计如下:
按照“Bridge”方式设计如下:
public class P
{
public void Use(){};
}
public class C
{
private P _p;
public DoSomething()
{
_p.Use();
}
}
那么在设计组建之间的关系时到底该选择那种方式,则需要依据实际情况而定。假设,组件A是信息提供者,而组件B是信息消费者。可以有以下几种情况:
(1)A发布事件,B包含A的引用,然后B预定A的事件。(事件方式)
(2)B接口中提供接收信息的方法,A包含B的引用,在适当时候A调用B的接收信息的方法。(依赖倒置方式)
(3)A发布事件,B接口中提供接收信息的方法,通过Bridge将A、B联系起来。(Bridge方式)
下面举个实际的例子,在我们的应用中,有个功能服务管理器IServiceManager(信息提供者),功能服务可以在运行的时候变化,这个变化可以被IServiceManager检测到,而显示功能服务名称的IServiceDisplayer(信息消费者)也需要在功能服务变化的时候,将显示作必要的改变。如果按照“事件”方式,应该如下设计:
public interface IServiceManager
{
event CbServiceAdded ServiceAdded ;
}
public delegate void CbServiceAdded(string serviceName) ;
public interface IServiceDisplayer
{
IServiceManager ServiceManager ;
}
{
event CbServiceAdded ServiceAdded ;
}
public delegate void CbServiceAdded(string serviceName) ;
public interface IServiceDisplayer
{
IServiceManager ServiceManager ;
}
按照“依赖倒置”方式设计如下:
public interface IServiceManager
{
IServiceDisplayer ServiceDisplayer{set ;}
}
public interface IServiceDisplayer
{
void AddService(string serviceName) ;
}
{
IServiceDisplayer ServiceDisplayer{set ;}
}
public interface IServiceDisplayer
{
void AddService(string serviceName) ;
}
按照“Bridge”方式设计如下:
public interface IServiceManager
{
event CbServiceAdded ServiceAdded ;
}
public delegate void CbServiceAdded(string serviceName) ;
public interface IServiceDisplayer
{
void AddService(string serviceName) ;
}
public interface IServiceBridge
{
IServiceManager ServiceManager{set ;}
IServiceDisplayer ServiceDisplayer{set ;}
void Initialize();
}
public class ServiceBridge :IServiceBridge
{
private IServiceManager serviceManager = null ;
private IServiceDisplayer serviceDisplayer = null ;
#region IServiceBridge 成员
public void Initialize()
{
this.serviceManager.ServiceAdded += new CbServiceAdded(serviceManager_ServiceAdded);
}
public IServiceManager ServiceManager
{
set
{
this.serviceManager = value ;
}
}
public IServiceDisplayer ServiceDisplayer
{
set
{
this.serviceDisplayer = value ;
}
}
#endregion
private void serviceManager_ServiceAdded(string serviceName)
{
this.serviceDisplayer.AddService(serviceName) ;
}
}
{
event CbServiceAdded ServiceAdded ;
}
public delegate void CbServiceAdded(string serviceName) ;
public interface IServiceDisplayer
{
void AddService(string serviceName) ;
}
public interface IServiceBridge
{
IServiceManager ServiceManager{set ;}
IServiceDisplayer ServiceDisplayer{set ;}
void Initialize();
}
public class ServiceBridge :IServiceBridge
{
private IServiceManager serviceManager = null ;
private IServiceDisplayer serviceDisplayer = null ;
#region IServiceBridge 成员
public void Initialize()
{
this.serviceManager.ServiceAdded += new CbServiceAdded(serviceManager_ServiceAdded);
}
public IServiceManager ServiceManager
{
set
{
this.serviceManager = value ;
}
}
public IServiceDisplayer ServiceDisplayer
{
set
{
this.serviceDisplayer = value ;
}
}
#endregion
private void serviceManager_ServiceAdded(string serviceName)
{
this.serviceDisplayer.AddService(serviceName) ;
}
}
三种方式都是可行的,但是在不同的应用情况下,不同的方式导致应用程序中组件之间不同的依赖复杂度,并对整个系统的结构的清晰度产生深刻的影响。那么,原则是什么?
(1)通常情况下,采用“事件”方式。
(2)如果使用“事件”方式时遇到这样的情况:IServiceDisplayer预定的IServiceManager的那部分事件的预定者只有IServiceDisplayer,而不会有其它组件预定这部分事件,则可以考虑将这些事件从IServiceManager中移除,转而采用“依赖倒置”方式。这样做的好处是,大大减少了IServiceManager需要发布的事件的数量。
(3)如果IServiceDisplayer所需的信息不仅仅来自IServiceManager,还来自许多其它组件,则采用第三种方式。
(4)要谨慎使用“依赖倒置”方式,特别是当IServiceManager不需要从IServiceDisplayer获取任何信息时,第二种方式会导致IServiceManager对IServiceManager的依赖,而这个依赖本来是不必要的。
(5)当一个(或多个)信息接受者需要从众多的信息发布者获取事件信息时,使用第三种方式是推荐的选择。
后续的文章会继续对这三种方式作深刻的剖析,当然这要等我的认识进一步深化之后。“依赖倒置”这个名字是我取的,不知道是否有更正式的名称,望告知:)
Feedback
# re: 组件设计实战--组件之间的关系 (Event、依赖倒置、Bridge) 回复
2005-12-21 08:56 by CavingdeepHi, 把你的文章总结一下的话我想就是大概下面这段:
类型之间的引用关系有以下4种可能:
1、直接引用。
2、间接引用(通过委托)
3、间接引用(通过接口)
4、间接引用(通过第三个类型)
其中耦合度最高的就是直接引用,所以我们要尽可能避免。
大概就是这个样子的总结,对吧。^_^
类型之间的引用关系有以下4种可能:
1、直接引用。
2、间接引用(通过委托)
3、间接引用(通过接口)
4、间接引用(通过第三个类型)
其中耦合度最高的就是直接引用,所以我们要尽可能避免。
大概就是这个样子的总结,对吧。^_^
看了大作,小弟对楼主命名有点看法。
1.楼主说的依赖倒置,这不能不让你联想到IoC(Inversion of Control)。可楼主提到的那种依赖倒置,根本就是直接引用,哪儿倒了?这里用依赖倒是这个词儿,有混淆视听之嫌,呵呵
2.楼主提到的事件方式,反而是IoC的一种实现。不是由事件发生包含事件消费者的引用,而是事件消费者订阅事件。这样反过来才是IoC。
3.楼主说得第三种方式,把事件发生和消费的耦合给提了出来,间接解耦了,绝。但bridge。。。嘿嘿,这一定不是GoF的bridge,GoF中的bridge是将抽象定义和实现分开,而不是把两部分的联系放到bridge中,这两者差的有点远了。。。建议楼主换个自己造的词儿吧。。。
一点愚见,请指教
1.楼主说的依赖倒置,这不能不让你联想到IoC(Inversion of Control)。可楼主提到的那种依赖倒置,根本就是直接引用,哪儿倒了?这里用依赖倒是这个词儿,有混淆视听之嫌,呵呵
2.楼主提到的事件方式,反而是IoC的一种实现。不是由事件发生包含事件消费者的引用,而是事件消费者订阅事件。这样反过来才是IoC。
3.楼主说得第三种方式,把事件发生和消费的耦合给提了出来,间接解耦了,绝。但bridge。。。嘿嘿,这一定不是GoF的bridge,GoF中的bridge是将抽象定义和实现分开,而不是把两部分的联系放到bridge中,这两者差的有点远了。。。建议楼主换个自己造的词儿吧。。。
一点愚见,请指教
# re: 组件设计实战--组件之间的关系 (Event、依赖倒置、Bridge) 回复
2005-12-21 11:42 by zhuwei skyto windwolf:
1.IOC的核心是“依赖注入”,表示组件之间的装配在运行时自动发生,一般通过IOC容器对装配进行管理。这里所说的“依赖倒置”的意思与IOC不一样,“依赖倒置”的意义是,本来正常的是应该让B依赖于A,结果使用“依赖倒置”设计却让A依赖于B,这是依赖方向的填倒,这和IOC的意义在本质上是不同的。“依赖倒置”的设计,仍然可以使用IOC自动来装配A和B之间的依赖关系。
2.这篇文章中我特意没有提到IOC,就是怕读者把两者搞混淆了。毕竟现在在.NET平台上使用spring.net的人还不是很多。
3.我这里的Bridge不是GOF中的Bridge Pattern,这里Bridge的主要意思是起到“桥接”的作用,意如其名。我找不到什么名字比它更贴切了:)
1.IOC的核心是“依赖注入”,表示组件之间的装配在运行时自动发生,一般通过IOC容器对装配进行管理。这里所说的“依赖倒置”的意思与IOC不一样,“依赖倒置”的意义是,本来正常的是应该让B依赖于A,结果使用“依赖倒置”设计却让A依赖于B,这是依赖方向的填倒,这和IOC的意义在本质上是不同的。“依赖倒置”的设计,仍然可以使用IOC自动来装配A和B之间的依赖关系。
2.这篇文章中我特意没有提到IOC,就是怕读者把两者搞混淆了。毕竟现在在.NET平台上使用spring.net的人还不是很多。
3.我这里的Bridge不是GOF中的Bridge Pattern,这里Bridge的主要意思是起到“桥接”的作用,意如其名。我找不到什么名字比它更贴切了:)
看到了楼主的回复,还是有点不同意见。
楼主文中指的“依赖倒置”应该就是那经典的OO设计四大原则里的DIP(Dependency inversion Principle)吧,DIP的目的是解除A和B之间的依赖,从这点来看,DIP绝不仅仅是将原本的A->B变成B->A,而是包括所有使A和B之间的->给切断,准确的说,是把A和B之间的之间联系给切断。
所以IoC是DIP的一个实例,他把A->B变成了A->C和B->C;楼主说的依兰倒置也DIP的一个实例,他将A->B变成了B->A。另外,DIP是将不恰当的A->B装成B->A,而不是相反。
大道理讲完了,我们来看看楼主文中的三个例子:
我用P来代表生产者,C来代表消费者。先产生后消费,生产者不知道超市里的产品会卖给谁,消费者要知道自己要买什么东西,这是我们平常生活中的一个常理。这就是说C依赖P。
楼主文中指的“依赖倒置”应该就是那经典的OO设计四大原则里的DIP(Dependency inversion Principle)吧,DIP的目的是解除A和B之间的依赖,从这点来看,DIP绝不仅仅是将原本的A->B变成B->A,而是包括所有使A和B之间的->给切断,准确的说,是把A和B之间的之间联系给切断。
所以IoC是DIP的一个实例,他把A->B变成了A->C和B->C;楼主说的依兰倒置也DIP的一个实例,他将A->B变成了B->A。另外,DIP是将不恰当的A->B装成B->A,而不是相反。
大道理讲完了,我们来看看楼主文中的三个例子:
我用P来代表生产者,C来代表消费者。先产生后消费,生产者不知道超市里的产品会卖给谁,消费者要知道自己要买什么东西,这是我们平常生活中的一个常理。这就是说C依赖P。













在楼主文中,Manager产生事件,但Manager并不知道事件会被怎么处理,而Displayer订阅事件,Displayer知道要显示Manager产生的事件。这就是第一个例子的模式,Display依赖Manager。
现在来看看第二个例子。很明显,依赖确实反转了,但这是“依赖反转”吗?Manager里有Displayer的引用,也就是说Manager强烈依赖Displayer。这显然是一种退步。曲解了DIP原则的本意。
再看最后的例子,也就是楼主所说的bridge(楼主在回复中也提到了这不是GoF中的bridge,大家别给搞混了)。这个例子中,Manager和Displayer没有直接联系,联系放在了Bridge中,也就是Manager->Bridge, Displayer->Bridge (这就是spring的IoC方法,Bridge相当于IoC容器)(这种方法的一个弊病就是Manager和Displayer需要依赖Bridge等IoC框架)。楼主文中使用了事件来再次依赖反转,Manager<-Bridge, Display<-Bridge,这样使得Manager和Displayer不用依赖Bridge这个对业务无意义的东西了,但这样使Bridge高度依赖Displayer等,难以封装。这个议题没有完美的解决方案。。。
以上分析后发现,楼主文中的(2)为依赖反转前,(1)使用了事件来依赖反转,(3)为IoC容器模式的变体。
现在来看看第二个例子。很明显,依赖确实反转了,但这是“依赖反转”吗?Manager里有Displayer的引用,也就是说Manager强烈依赖Displayer。这显然是一种退步。曲解了DIP原则的本意。
再看最后的例子,也就是楼主所说的bridge(楼主在回复中也提到了这不是GoF中的bridge,大家别给搞混了)。这个例子中,Manager和Displayer没有直接联系,联系放在了Bridge中,也就是Manager->Bridge, Displayer->Bridge (这就是spring的IoC方法,Bridge相当于IoC容器)(这种方法的一个弊病就是Manager和Displayer需要依赖Bridge等IoC框架)。楼主文中使用了事件来再次依赖反转,Manager<-Bridge, Display<-Bridge,这样使得Manager和Displayer不用依赖Bridge这个对业务无意义的东西了,但这样使Bridge高度依赖Displayer等,难以封装。这个议题没有完美的解决方案。。。
以上分析后发现,楼主文中的(2)为依赖反转前,(1)使用了事件来依赖反转,(3)为IoC容器模式的变体。
to windwolf:
我想你误解了“依赖倒置”的意思,依赖倒置是将原本的依赖方向颠倒过来,而不是你说的“反转依赖”,更不是“反转控制”,呵呵。
在复杂的情况下,我之所以推荐使用“Bridge方式”,是因为这样可以使我们的业务类的独立性最大,这对业务类的单元测试和维护都是意义重大的。
正如你所说,这个议题没有完美的解决方案,只有根据实际的应用情况,选择较好的一种方案。
我想你误解了“依赖倒置”的意思,依赖倒置是将原本的依赖方向颠倒过来,而不是你说的“反转依赖”,更不是“反转控制”,呵呵。
在复杂的情况下,我之所以推荐使用“Bridge方式”,是因为这样可以使我们的业务类的独立性最大,这对业务类的单元测试和维护都是意义重大的。
正如你所说,这个议题没有完美的解决方案,只有根据实际的应用情况,选择较好的一种方案。
to zhuweisky
你文中的“依赖倒置”是褒义的,
我回复里的“反转依赖”是中型的,只是说依赖方向改了,但结果如何,改了之后是否使结构更合理,则不得而知。
我回复里没有用“依赖倒置”这个词的用意就在于,我任务楼主文中第二个例子反转了之后反而不好,还不如第一个。
至于第三个方式,我完全同意你的意见,这种bridge是测试的好方法,但不适合用来做框架。因为框架类bridge依赖业务类。
ps:楼主有没有spring.net 源码剖析之类的文章?
你文中的“依赖倒置”是褒义的,
我回复里的“反转依赖”是中型的,只是说依赖方向改了,但结果如何,改了之后是否使结构更合理,则不得而知。
我回复里没有用“依赖倒置”这个词的用意就在于,我任务楼主文中第二个例子反转了之后反而不好,还不如第一个。
至于第三个方式,我完全同意你的意见,这种bridge是测试的好方法,但不适合用来做框架。因为框架类bridge依赖业务类。
ps:楼主有没有spring.net 源码剖析之类的文章?
windwolf说的没错,Ioc模式本质就反映了面向对象设计最经典一个的原则:依赖倒置原则(DIP),显然楼主对“依赖注入”的理解是有误的。
还有非常搞笑的bridge模式,楼主还是实实在在把名称和概念与大众统一一下吧,否则你说的这个bridge和很多说的bridge不是一个概念,讨论开来也没有意义。
还有非常搞笑的bridge模式,楼主还是实实在在把名称和概念与大众统一一下吧,否则你说的这个bridge和很多说的bridge不是一个概念,讨论开来也没有意义。
([url=http://www.sfwre.com]wow gold[/url])
([url=http://www.wowgoldue.com]wow gold[/url])([url=http://zxfan1898.bokee.com]wow gold[/url])
([url=http://www.wowgoldue.com]wow gold[/url])([url=http://zxfan1898.bokee.com]wow gold[/url])