解耦的故事(转tmfc blog)

来源:互联网 发布:xls文件解密软件 编辑:程序博客网 时间:2024/06/04 04:52

前2天在网上看到个非常有意思的故事,我觉得对于初接触这些概念非常有帮助.故事非常形象描述了这些概念.

开关的诞生

     话说在一个紧耦合的世界,有一个名为tmfc的工匠,一天,他发明了一个叫做开关的的设备。他琢磨了老半天,决定把开关装在自己的床头,这样他就不用在睡前起床去拔电灯的电线了(这可是个紧耦合的世界啊),tmfc对自己的发明非常满意。

class Switch{
     Light light;
    public void Switch(Light l){
         light = l;
     }
    public void TurnOn(){
         light.On();
     }
    public void TurnOff(){
         light.Off();
     }
}

     tmfc发明了开关的事情很快在他的朋友圈中传了开来,大家都对这个好东西非常感兴趣,一个朋友的朋友的朋友,电风扇厂的老板,听说了这个消息之后兴奋的马上开着他的BMW(传说中的“别摸我”)来到tmfc的住处表示愿意用此车来交换一个能装在他们的风扇上的开关。
     tmfc虽然对这位朋友没什么好感,但是名车的诱惑还是难挡,于是他爽快的答应了下来,过了两天(这两天中,tmfc花了一天半开车到处去兜风^_^),老板就拿到了非常好用的风扇开关。

class FanSwitch{
     Fan fan;
    public void FanSwitch(Fan f){
         fan = f;
     }
    public void TurnOn(){
         fan.On();
     }
    public void TurnOff(){
         fan.Off();
     }
}

基板和接口

     电风扇厂的技师们没费多大力气就把开关装在了风扇上,风扇推出后及其的受欢迎,老板朋友很快买了新款的Benz,而tmfc也由于发明开关而变得名声大噪,来自全国各地的合作请求络绎不绝,tmfc很快便厌倦了重复的生产(已经不能称这种行为为设计了)不同开关的生活,作为一个聪明人(同时也是一个懒人),他很自然的想到了“为什么我不设计一种通用的开关呢?”。没过多久(当然,他是聪明人嘛!)tmfc有了这样的设计:

abtract class AbstractSwitch{
    abstract public void TurnOn(); 
    abstract public void TurnOff();
}

     他对所有的客户展示了他那优雅而简洁的设计,他声称只要客户的产品从简单的使用AbstractSwitch基板作为他们产品的基板,就可以非常容易的拥有开关的功能,而不必在这里等待他一个个的完成不同的开关产品。“我们的产品已经有基板了,而我们使用的流水线不支持多基板!!”,“我们的产品非常小,无法使用这么大的基板!!”,“使用基板我们的成本提高了一倍!!”,对于这个方案,客户们显然十分不满。面对这个结果,tmfc显得有些意外,但是作为一个优秀工匠(也许我们应该现在应该称他为工程师了),他很快就拿出了更好的解决方案。

Interface ISwitchable{
     On();
     Off();
}

class InterfaceSwitch{
     ISwitchable target;
    public void InterfaceSwitch(ISwitchable t){
         target = t;
     }
    public void TurnOn(){
         t.On();
     }
    public void TurnOff(){
         t.Off();
     }
}

     这次,不需要使用基板,只要增加一个额外的和开关之间的接口就可以在设备上使用开关了,“这下大家满意了吧!”tmfc边咕哝着边给客户们演示了他的新设计。不出所料,大部分的客户感到很满意,虽然需要额外的接口,但还在接口还算便宜,体积也非常的小巧。大部分的客户都愉快的拿着开关和接口走了(剩下那些不是很满意的为了不惹恼它们这个世界上唯一能给他们开关的人,也不得不拿着接口和开关回去想办法说服老板接受这个方案),tmfc也重新开始了开着BMW兜风的日子。

什么?!更改接口?

     随着时间的流逝,市面上开始布满了使用tmfc的开关的产品,看着自己的产品受到大家如此热烈的欢迎,tmfc感到无比的满足。但是他还是发现有些产品没有使用他的开关,他感到纳闷,“为什么你们不在这个台灯上装开关呢?”他指着装有老式插口(可以把两根电线的其中一根更换插槽来实现不同功能的控制装置,在开关发明之前统治着这个紧耦合的世界)的台灯向厂家的促销员问道。“您有所不知啊!说起这个开关我还一肚子气呢!!我派人花重金到tmfc那里买了他那个该死的接口和开关,本想立上一大功的,没想到拿去向老板邀功时才发现根本用不上。因为我们的产品的特点就是有高亮和低亮两档可以调整,但是他那个要命的接口只有开和关两种,如果我们生产只能开关的灯,那根本就没有特色。您知道没有特色就没有竞争力。自从那之后我就被公司派到这个小地方来作促销员了。真是可恶,什么接口嘛,根本没用!!”那可怜的促销员咒骂着。tmfc暗自庆幸他没有亲自来买开关,不然现在被认出来肯定没好果子吃。他强作镇定,点点头快步走开了。

     回到自己的工作室,tmfc反复想着这件事(其实在路上他也一直在想),当然,第一个念头就是“改接口”,这样的接口肯定可以满足他们的要求。

Interface IPowerSwitchable{
     HighLight();
     LowLight();
     Off();
}

只要给他们这个接口和对应的开关就可以了嘛?没错,他们是可以满足,但是其他的需求呢,说不准什么时候就会出现可以调整高中低的台灯了。看来改接口不是好办法,tmfc想着其他的解决方案。

终极解决方案?

     经过两个星期的苦思冥想,tmfc终于找到了终极的解决方案(其中的经过略去五万字)。他做出了一个可以拼接的开关,就像乐高玩具一样,用来把多个开关组合在一起。其中最重要的部分就是简化了接口,现在的接口看上去就像这样:

Interface ISwitchAction{
     Execute();
}

而开关就变成了这样

class Switch{ 
     ISwitchAction Action;
    public void Switch(ISwitchAction action){ 
         Action = action; 
     }
    public void OnPress(){ 
         Action.Execute(); 
     } 
}

      “这样就可以满足所有的要求了!”,tmfc自言自语道。没错,只需要三个继承自ISwitchAction的类(HighLight,LowLight,OffLight),并分别使用他们来初始化三个开关,就可以满足台灯厂的要求了(虽然成本上会有一些小小的提高,但不会太多,因为开关和接口都做了简化)。

进一步的改进

     “看上去还不错,但是能不能再做一点改进呢?”tmfc问自己。他拿着接口和开关反复翻看着,总觉得哪里还有些不对劲。“能不能把接口放在客户那里呢?不行不行,这明显是违反了DIP嘛!为什么非得用接口呢?把接口去掉能不能行得通呢?这样不是能够节省成本吗?”找到方向的tmfc顾不得两个星期奋战的疲惫,继续挑灯夜战。

     又过了三个不眠之夜(大家可不要学tmfc,这对身体可是大大的有害啊),tmfc终于拿出了完善后的开关,这次没有接口了,但是多了一位新朋友:委托(delegate)。让我们先来认识一下这位朋友:

class delegate{
     object Target;
     MethodInfo Method;

     delegate(object target,MethodInfo method){
         Target = target;
         Method = method;
     }
     Invoke(){
          Method.CallOn(Target);
     }
}

     等等,好像又出现一个生面孔,MethodInfo,这是什么?由于tmfc不肯透露其中的奥秘(他声称这是技术机密),我们知道的只有这么多:只要调用MethodInfo.CallOn()方法,就可以在指定的对象(如台灯)上执行指定的命令(如HighLight)。(tmfc暗自偷笑:“其实没什么神秘的,就是记下了命令的位置,并在指定对象的相应位置加上电而已,一些简单的电路就能搞定。弄这个东西纯粹就是为了搞混大家,让我的发明显得高深莫测,哈哈。”)

松耦合的时代

     tmfc在开关问世一周年的时候召开了全球开关用户大会,在大会上tmfc隆重的推出了他的改进型的开关,包括了各种通用的,专用的开关,以及各个合作厂商退出的诸如遥控器,键盘之类的相关产品,在全球引发了轰动。一个月后,tmfc的自传出版了,全世界的有志青年纷纷以这位年轻的传奇人物为榜样。若干年之后,各种新奇的发明(当然,都是受tmfc的开关的影响的)像插头插座,都极大的改变了人们的生活。从此,一个松耦合的时代来临了,而tmfc,也作为松耦合的创始人永远被这个世界的人们纪念着,在tmfc离开这个世界的那天,世界各国首脑商定将每年的12月4日(tmfc的生日)定为国际松耦合日。

故事背后的故事:

  1. 风扇厂老板拿到的显然是最紧偶合的设计,但是我们不能说tmfc不聪明,没有作出更好的设计,因为没有需求上的改变,当时他只看到了这一个需求,并且不需要更改,如果当风扇厂老板找到他是就给出最后的接口的设计,那我们可以说这是一种过度设计.
  2. 关于基板(也就是我们所谓的基类),这个设计比其风扇厂老板的开关是松散了一点,但是无法满足他所面临的需求(这次的需求变化是相当的大),所以,聪明的tmfc同学很快的适应了需求的变化,改变了设计,使用了接口.
  3. 最后的接口设计上我们可以看到,由于加入了组合(这里使用了策略模式),这种方案比基板更加松散,但是这种方法并不是最松偶合的关系,这并不是因为tmfc不聪明,这恰恰是他聪明的地方,为什么这样说,且听下回分解.
  4. 是不是看着很眼熟,没错,这里使用了命令(Command)模式,ISwitchAction就是传统的ICommand接口,从其中派生的类就是一个个的命令(Command)。
  5. 委托和命令模式是不是有些相似?没错,委托其实比命令模式高明的有限。使用委托对于用户来说确实非常的方便(不需要自己继承命令接口,声明命令对象了)但是这个组合是有代价的。
  6. 委托的优势来源。委托将继承改成了组合,当然,这一步改进并不是那么容易的,在灵活性背后付出了性能的代价,当然这个性能代价对于不同的委托实现来说是不同的,像C++的函数指针就没有性能问题,而C#的委托则复杂的多(相应的灵活性也比函数指针要好,当然性能要差很多)
  7. 什么?按下一个开关打开所有的灯?当然可以,命令模式+组合模式都可以实现的东西怎么难得倒委托呢(丝毫没有看不起这两个模式的地方,事实上我非常的欣赏这种模式的组合)?只要扩展一下委托,在里面放一个MethodInfo的列表,在加上两个函数往列表里添加删除东西,Invoke的时候依次调用列表里的所有方法就OK了。
  8. 别被EventHandler搞混了。EventHandler也是委托,所以在.NET框架程序设计里讲事件的时候是没有用EventHandler关键字,而用了delegate关键字。倒是event关键字在幕后做了一些事情。