我给媳妇解释设计模式:第一部分
英文原文:How I explained Design Patterns to my wife: Part 1
引子我跟媳妇曾经就面向对象设计这个话题做过有趣的探讨。当我把它们发表在社区之后,得到了一些很不错的反馈,也大大鼓舞了我。所以,我很高兴能把我们后面的一次谈话继续分享出来,那是关于面向对象的设计模式的,大家往下看吧。 |
|
什么是设计模式丈夫: 我想你现在对面向对象的设计原则有了一些基本概念了吧。我们那次关于OOD原则(SOLID原则)的有趣谈话被我发表在社区上了,你不会介意吧?网址在这里: 我怎么向妻子解释OOD。 设计模式则是这些原则在某些特定和常用条件下的应用,并且做了一些标准化。我们还是来一些例子吧。 媳妇: 好极了,我喜欢例子。 丈夫: 以我们的车为例吧。它是一个对象,不过有点复杂,是由几千个其它对象组成的,包括引擎、车轮、转向装置、座位、车身,等等。
一辆车的各种零件。 |
|
这辆车在制造的时候,制造商收集所有的零件,把它们组装起来。这些零件本身也是复杂的对象,是由其它的制造商组装的。但汽车公司并不关心这些零件是怎么造出来的(当然,他们需要确信这些零件的质量是过硬的)。他们只会关心如何通过不同的方式将不同的零件组装起来,以便生产出不同型号的汽车。
由不同零件根据不同设计组装成的不同型号的车。 媳妇: 每种型号的汽车应该都有各自的设计和蓝图什么的,是吧? 丈夫: 非常正确。而且,这些设计是经过深思熟虑的,花了很长的时间和很大的努力才得以诞生。完成设计之后,汽车的生产就只剩下遵循设计这么简单的事了。 |
媳妇: 嗯……很不错的办法,先想出一些优秀的设计,然后遵照这些设计,就可以在很短的时间里造出不同的东西。如果制造商想要开发某种型号的产品,不需要从头进行设计,或者说重新造轮子,只要遵循那些设计就可以了。
用于不同型号产品(车)的不同设计。 丈夫: 你说到点子上了。现在,回到现实里,我们是软件厂商,我们需要根据需求,用不同的组件来创造出不同的软件。在这个过程中,一定会碰到一些情形,是在许多不同的软件里都有的,对不对? 媳妇: 对啊。而且,我们还常常在不同的软件里碰到相同的设计难题呢。 |
丈夫: 我们尝试着用一种面向对象的方式来开发我们的软件,利用OOD原则来让我们的代码更容易管理、重用和扩展。就像你上面提到的那些相同的问题,如果我们预先就有一些良好的设计,那是不是很棒呢? 媳妇: 是啊,那可以省下大把的时间,而且这样打造的软件质量更好,更容易管理。 丈夫: 没错。还有个好消息,我们并不需要自己造轮子。这么多年以来,遭遇同样问题的人们早已发现了许多很棒的解决方案,而且把它们标准化过了。我们管这些方案叫设计模式。 我们要感谢四人帮(GoF),他们在设计模式: 可重用面向对象软件的基本元素这本书里归纳了23个最基本的设计模式。想知道这四个牛人是谁吗?Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides。面向对象的设计模式很多,但大家认为这23个模式是其它模式的基础。 |
媳妇:我能创建一个新模式吗?有可能吗? 丈夫:当然可以,亲爱的,为什么不行呢?设计模式并不是被科学家发明和创造的东西。他们只是被“发现”而已。也就是说,对任何一个普通的问题场景,肯定会有一些好的设计方案。如果我们能识别出一个能解决某个新问题的面向对象设计,那我们就定义了一个新的设计模式。谁知道呢?如果我们发现一些设计模式,没准儿大家会叫我们“二人帮”呢...哈哈。 媳妇::) |
媳妇:对啊,这看起来很自然,不是吗? 丈夫:是的,非常自然,同时也应该这样安排。当不同的事物联系到一起时,他们应该在一个可以变更或者可以替换的系统中以便不相互影响,或者影响尽可能的小。这样让你更为方便、成本最小地去管理你的系统。可以想象,如果你要换一个你房间里的灯泡得要求你把开关也换了,你会考虑在你房子里使用这样的一个系统吗? 媳妇:当然不会。 丈夫:现在,让我们想一下电灯或者电风扇是怎样和开关联系起来以便更换其中一个而不会影响到其他的。你想到什么了? 媳妇:当然是电线啦。 |
丈夫:正确,是电线以及其他的电工手段把电灯/电风扇与开关连接起来。我们可以把这概括为沟通不同系统的桥梁。基本思想是,一个事物不能直接连接另一个事物。当然,他们能够通过一些桥梁或接口连接起来。在软件世界里,我们称之为“松耦合”。 媳妇:嗯,我明白这点。 丈夫:现在,我们来尝试理解一些类似电灯/电风扇与开关类似的关键问题,同时尝试理解是怎样设计和关联它们的。 媳妇:好的,我们开始吧。 在我们的列子里,有一些开关,这些类似普通的开关、有不同的花式开关可能有不同的种类,但是,一般情况下,他们就是开关。同时,每个开关都能开和关。 |
这样的话,我们就会得到如下的Switch基类:
同时,我们可能也 需要一些特定类型的开关,譬如正常的开关、不同花式的开关等等。同样的我们扩展Switch类来实现FancySwitch和NormalSwitch:
这两个特定的开关类可能用于它们自己特有的行为和特征,但是到目前为止,我们还是保持它们现在的简单形式。 丈夫:棒极了。现在,如何处理风扇和灯呢? 媳妇:让我试试。按照面向对象设计原则中的封闭原则,我认为我们需要试着在任何可能的地方做抽象处理,对吗? 丈夫:对。 |
媳妇: 电扇和电灯情况有点不一样,它们两个不是同一种东西。对于不同的开关,我们可以用同一个基本的Switch类,但对于电扇和电灯就不大合适了,感觉用接口会更合适一点。因为,从大体上讲,它们都算是电器,那么我们可以就定义一个接口: IElectricalEquipment,用它来抽象电扇和电灯,对不对? 丈夫: 很对。 媳妇: 那么,所有电器都有一些共性,可以被打开和关闭。那么这个接口就可以是:
|
媳妇: 没错,开关并不知道电扇和电灯的存在。它只知道它可以打开或关闭某个电器IElectricalEquipment。那么,也就是说每个Switch应该拥有一个IElectricalEquipment实例,是吧? 丈夫: 很对。这里,被封装的实例,也就是IElectricalEquipment,就是这座桥。好,我们来修改一下Switch类,让它把电器封装进去:
|
电扇类:
电灯类:
也就是说:
|
我们想要的功能基本上是这个样子:
|
丈夫: 干得好。这个电扇显示是可以换开关的。而且,反过来也是可以换的,可以不修改电扇和电灯,直接更换开关,例如,我们可以把电灯的开关从FancySwitch换成NormalSwitch:
|
在我们的例子里,Abstraction是基础的Switch类,RefinedAbstraction是某个具体的开关类(FancySwitch和NormalSwitch),Implementor是IElectricalEquipment接口,ConcreteImplementorA和ConcreteImplementorB分别是Fan和Light类。 媳妇: 我有点好奇。你不是说有很多模式么,干嘛先说桥梁模式呢?有什么特别重要的原因吗? 丈夫: 问得好。我从桥梁模式开始,而不是其它模式,只有一个原因。我觉得它是所有面向对象设计模式的基础。因为:
|
|