对象创建完了以后,就是互相协作完成系统的功能。对象的协作方式通常有如下方式:
直接引用,互通有无
这种方式最为自然,最为直接,最为简单,也是通常情况下的首选。不管是传参数,还是直接创建后直接使用对象的方法,都是属于这种情况:
public class ComponentB { public void Run(ComponentA componentA) { componentA.Say(); } }
依靠中介通信
当对象之间的交互复杂起来以后,直接的通信可能耦合度就太高了,这个时候要靠辅助对象来间接的传递信息,很多的设计模式其实都是该思想的衍生物,比如中介者模式,代理模式,外观模式等。
这里看一个UI层的例子:程序的界面上有3个对象,分别是Panel,Timeline,Scrollbar;这些对象之间分属不同的层次结构,但是它们之间需要互相操作,比如Scrollbar动的时候,需要修改Timeline上的一些小控件,而且需要操作Panel的一些控件,操作Timeline上的内容的时候,需要检查是否需要滚动Scrollbar,是否需要操作Panel,总之就是这3个对象的其中一个动的话,另外两个都要有些操作需要进行。
针对这个交互的问题,我大概有3种想法:
第一种:3个对象之间互相引用,当需要的时候就直接调用对方的方法。
比如Panel类的伪代码就可能如下所示:
class Panel { private Timeline m_timeline; private Scrollbar m_scrollbar; public void Action() { DoSomething(); m_timeline.M1(); m_scrollbar.M2(); } }
第二种:能否尝试使用事件来回调自己的方法。
在脑海中就是那么一想,我决定放弃这个想法。
第三种:创建一个中介对象,所有交互的代码都放到这个中介中。
中介者和Panel的伪代码如下:
class Mediator { private Timeline m_timeline; private Scrollbar m_scrollbar; private Panel m_panel; public void Initialize(Timeline timeline, Scrollbar scrollbar, Panel panel) { m_timeline = timeline; m_scrollbar = scrollbar; m_panel = panel; } public void PanelAction() { m_timeline.M1(); m_scrollbar.M2(); } public void ScrollbarAction() { m_timeline.M1(); m_panel.M3(); } public void TimelineAction() { m_scrollbar.M2(); m_panel.M3(); } } class Panel { Mediator m_mediator; public void Action() { DoSomething(); m_mediator.PanelAction(); } }
如果说前两种实现是形成了3个对象之间的网状图的话,那么第三种实现其实是多加了中介者对象,4个对象形成了以中介者为中心的星状图。
我实际采用了第三种实现来简化对象之间的交互关系。 我这里并不想说第三种一定比前两种好,如果没有变化存在,只要是让系统正常工作的代码其实都是好代码。我总是认为, 并不是任何时候状态模式就比分支语句更合适一个对象 。
其实除了依靠进程内对象通信外,依靠程序外的媒介通信也属于这种情况,比如利用共享内存,Socket,文件系统中的文件,第三方消息队列(如MSMQ),分布式中间件等通信。
使用事件通信
当对象之间通信方式同质化严重(工作方式基本类似,交互的方式也类似),特别是1对多的通信,而且交互对象数量较多,类型未知时,事件,或者说是观察者模式,或者说是发布订阅模式就可以使用起来了。
让我再来回忆一下前面使用过的一个例子:
public class Program { static void Main(string[] args) { User user = new User(); View ui = new View(user); user.Name = "Hello"; } } delegate void OnNameChange(string name); class View { public View(User user) { user.onNameChanged += user_onNameChanged; } void user_onNameChanged(string name) { Console.WriteLine(name); } } class User { private string m_name; public string Name { get { return m_name; } set { m_name = value; onNameChanged(m_name); } } public event OnNameChange onNameChanged; }
交互即耦合
其实对象交互的方式,本质上就是描述了对象耦合的方式 。强耦合的关系一旦面临变化,一般会引起较大范围的改动。众多模式的出现都是为了给对象解耦。
对象之间的交互产生了对象之间的耦合,这是自然而有效的,对象之间就是要通过交互而形成流程,进而完成整个业务。除了上面这些描述的耦合方式,其实还有一个耦合方式更加紧密,那就是继承。
看一段简单的代码:
class Base {} class Derived : Base {}
这样简单的代码,其实就是形成了一个相当紧密的耦合:Base的任何修改都会毫无保留的传递给Derived。
同时,给予这样的一个分析,我们可以解释一个常识:为什么基类要尽量的抽象,不完成特别具体的细节。这么做就是为了 尽量维护基类的稳定,让基类尽量不变化 。
总结来说, 对象交互的常用方式就是:"继承+组合(直接引用)+中介+事件",而这些方式的耦合级别也是基本按这个顺序越来越弱 。