设计模式是什么?
设计模式是这些原则在某些特定公共场景下标准化的应用,接下来让我们通过一些样例学习什么是设计模式。
Farhana: 当然,我喜欢样例。
Shubho: 让我们以汽车为例讨论一下。汽车是一个非常复杂的对象,由成千上万的其它对象组成,如发动机,车轮,方向盘,车座,车体等等其它不同的部分或部件。
当装配汽车时,制造商须要集中并装配这些更小的自成汽车子系统的不同部件。而这些不同的小部件同样也是复杂的对象,其它制造商同样要生产并组装它们。在生产汽车时,汽车公司并不会为怎么生产组装这些部件担心(前提是他们要确保这些对象/设备的质量)。当然,汽车制造商更加关心怎么装配这些不同部件以便能生产不同型号的汽车。
Farhana: 汽车制造公司必须有怎样生产不同型号汽车的设计图或蓝图,对吗?
Shubho: 当然,而且这些设计都是良好的,他们花费大量的时间和精力来做这些设计。一旦设计完成,生产汽车就仅仅是照葫芦画瓢了。
Farhana: 嗯。假设事先有一些好的设计,就能在短时间内遵照这些设计生产不同产品,而且制造商在每次生产某一个型号产品时就不须要又一次设计或又一次发明车轮,他们仅仅须要依照已有的设计办事就可以了。
Shubho: 你抓到重点了。如今假设我们是软件生产商,我们使用基于需求而来的不同组件或功能构建各种不同的软件程序。当生产这些不同软件系统时,我们常常须要为一些不同软件系统中存在的同样情况开发代码,对吗?
Farhana: 是的,在开发不同软件程序时常常遇到同样的设计问题。
Shubho: 我们尝试使用面向对象的方式开发软件,并尝试应用OOPD来让代码能易于维护,可复用,可扩展。不管什么时候,当我们遇到这些设计问题时,假设我们有一组经过慎重开发,良好測试的对象以供使用会不会更好呢?
Farhana: 是的,这样能够节省时间,生产出更好的软件,且利于以后维护。
Shubho: 非常好!从设计上来说,它的长处是你不须要开发那些对象。经过多年发展,人们已经遇到过一些相似的设计问题,并已经形成有一些公认的,良好的已标准化的设计方案。我们称之为设计模式。
我们一定好感谢四人组,他们在《设计模式:可复用面向对象软件设计》中总结出了23种主要的设计模式。四人组由Erich Gamma, Richard Helm, Ralph Johnson, 和John Vlissides组成。实际中有非常多面向对象设计模式,但这23种模式被公觉得是全部其它设计模式的基础。
Farhana: 我能发明一个新的模式吗?这可能吗?
Shubho: 当然,亲爱的,为什么不能呢?!设计模式不是由科学家发明创造的。它们是被发现找到的。这意味着不论什么通用问题场景中都有一些好的设计方案在那。假设我们能够指出一个能够解决一个新的设计相关问题的面向对象设计,那么这将会是一个由我们定义的新的设计模式。谁知道呢?!假设我们发现找到一些设计模式,也许将来有一天人们会称我们为二人组,哈哈。
Fahana: :)
我们将怎样学习设计模式?
Shubho: 我一直觉得样例是学习的最好途径。在我们的学习方法中,我们不会先讨论理论后讨论实现。我觉得这是非常糟糕的方式。设计模式不是基于理论的发明。其实,问题场景首先出现,其次是基于这些问题的来龙去脉和需求,然后是一些设计方案的演化,最后当中的一些被标准化为模式。所以对每个我们讨论的设计模式,我们将尝试理解并分析一些现实生活中的样例,然后一步步尝试归纳一个设计,并最后总结一些与某些模式匹配设计。设计模式就是在这些相似过程中发现的。你觉得呢?
Farhana:我想这样的方式对我更实用。假设我能通过分析问题和归纳方案得出设计模式,我就不用死记那些设计模式和定义了。请依照你的方式继续。
一个常见的设计问题和它的解决方式
Shubho: 让我们考虑以下的场景:
我们房间里有些电器(电灯,风扇等)。这些设备依照某些方式布局,并由开关控制。不论什么时候你都能替换或排查一个电器而不用碰到其它东西。比如,你能够换一个电灯而不须要换开关。同样,你能够换一个开关或排查它而不须要碰到或替换对应的电灯或风扇;甚至你能够用把电灯连接到风扇的开关上,把风扇连到电灯的开关上,而不须要碰到开关。
电器:风扇和电灯
风扇和电灯的两种不同开关,一个普通点,还有一个别致点
Farhana: 是的,但就是这样子,对吗?
Shubho: 是的,确实如此,就该如此布局。当不同东西联系在一起时,它们应该依照一定方式联系:改动或替换一个系统时不会影响到还有一个,或者说即便有,也应该最小化。这能够让你的系统易于管理,且成本低。想想一下,假设改一下房间里的灯同一时候须要改开关,你会乐意在你房子上花钱并安装这个系统吗?
Farhana: 当然不会。
Shubho: 如今,让我们思考一下电灯或风扇怎样连接到开关上才干达到改变一个不会影响到还有一个。你觉得该怎样?
Farhana: 用电线!
Shubho: 非常好。把电灯/风扇和开关联系到一起的是电线和电器布局。我们能够它们看做不同系统间相互联系的桥梁。其主要的思想是,一个事物不能和还有一外一个事物直接联系。当然啦,它们应当通过某些桥梁或接口联系在一起。用软件术语来说,这叫“松耦合”。
Farhana: 我知道了。
Shubho: 如今,让我们尝试判断在电灯/风扇和开关样例中的几个关键问题,并尝试判断它们是怎样设计并联系起来的。
Farhana: 好,我们试一下。
样例中我们有开关,可能有几种开关,如普通的开关,美丽的开关,但通常来说它们还是开关,而且每种开关都能够打开和关闭。
所以以下我们会有一个开关基类Switch:
public class Switch { public void On() { //打开开关 } public void Off() { //关闭开关 } }
接下来我们能够有一些详细的开关,比如一个美丽开关,一个普通开关等等,当然,我们会让类FancySwitch和
NormalSwitch
nd继承类Switch:
public class NormalSwitch : Switch { } public class FancySwitch : Switch { }
这里的两个详细类有自己的特征和行为,仅仅是此时此刻,我们简单化以下。
Shubho: 非常棒,接下来电灯和风扇怎么办?
Farhana: 我试试. 依据OODP的开放闭合原则,我们知道仅仅要可能,就应该尝试抽象,对吗?
Shubho: 对
Farhana: 跟开关不一样,风扇和电灯等是两种不同的事物。对于开关,我们能够使用一个开关基类Switch
,但风扇和电灯是两个不同的事物,相比定义一个基类,接口可能更合适。一般来说,他们都是电器。所以我们能够定义一个接口,如IElectricalEquipment,作为对电灯和风扇的抽象,能够吗?
Shubho: 能够
Farhana: 好,每种电器都有些同样的功能。他们能够打开和关闭。所以接口可能例如以下:
public interface IElectricalEquipment { void PowerOn(); //每种电器都能打开 void PowerOff(); //每种电器都能关闭 }
Shubho: 太好了,你非常善于抽象东西。如今我们须要一座桥梁。在现实中,电线是桥梁。在我们对象设计中,开关知道怎样打开和关闭电器,电器以某种方式联系到开关。这里我们沒有电线,让电器连接到开关的唯一方式是封装。
Farhana: 是的,但开关不能直接知道风扇或电灯。开关应当知道一个电器IElectricalEquipment能够打开或关闭。这意味着,
ISwitch
应该有一个IElectricalEquipment实例,对吗?
Shubho: 对,对风扇或电灯的封装的实例是一个桥梁。所以让我们改动Switch类以便封装一个电器:
public class Switch { public IElectricalEquipment equipment { get; set; } public void On() { //开关打开 } public void Off() { //开关关闭 } }
Farhana: 明确。让我们定义真实的电器:风扇和电灯。如我所见,一般来说它们都是电器,所以它们都简单实现了IElectricalEquipment
接口。
以下是风扇类:
public class Fan : IElectricalEquipment { public void PowerOn() { Console.WriteLine("风扇打开"); } public void PowerOff() { Console.WriteLine("风扇关闭"); } }
以下是电灯类:
public class Light : IElectricalEquipment { public void PowerOn() { Console.WriteLine("电灯打开"); } public void PowerOff() { Console.WriteLine("电灯关闭"); } }
Shubho:太好了。如今让开关工作。当开关打开关闭的时候它应当能够打开关闭电器(它连接到的) 。
这里的关键点是:
- 当开关按下开时,连接的电器也应该打开。
- 当开关按下关时,连接的电器也应该关闭。
大致的代码例如以下:
static void Main(string[] args) { //构造电器设备:风扇,开关 IElectricalEquipment fan = new Fan(); IElectricalEquipment light = new Light(); //构造开关 Switch fancySwitch = new FancySwitch(); Switch normalSwitch = new NormalSwitch(); //把风扇连接到开关 fancySwitch.equipment = fan; //开关连接到电器,那么当开关打开或关闭时电器应该打开/关闭 fancySwitch.On(); fancySwitch.Off(); //把电灯连接到开关 fancySwitch.equipment = light; fancySwitch.On(); //打开电灯 fancySwitch.Off(); //关闭电灯 }
Farhana: 明确。开关的On()方法应当内部调用电器的TurnOn()方法,Off()方法应当内部调用TurnOff()方法,所以开关类Switch应例如以下:
public class Switch { public IElectricalEquipment equipment { get; set; } public void On() { Console.WriteLine("开关打开"); equipment.PowerOn(); } public void Off() { Console.WriteLine("开关关闭"); equipment.PowerOff(); } }
Shubho: 非常好。这自然同意你把风扇从一个开关接到还有一个上。只是你看,反过来也能够。这意味着你能够改变风扇或电灯的开关而不须要碰到风扇或电灯。比如,你能够非常轻松的把点灯的开关从FancySwitch换到NormalSwitch上,例如以下:
normalSwitch.equipment = light; normalSwitch.On(); //打开电灯 normalSwitch.Off(); //关闭电灯
你看,连接一个抽象电器到一个开关(通过封装)能够让你改变开关和电器而不会对对方产生影响。这个设计是优雅的,良好的。四人组为该模式取名为:桥接模式。
Farhana: 太棒了。我想我明确这个了。从根本上说,两个系统不应当直接联系或依赖与对方。 当然,他们应该联系或依赖于抽象(如依赖倒置原则和开放闭合原则所讲),所以他们是松耦合的,因此我们能够在须要时改变我们的实现而不会对系统其它部分产生过多影响。
Shubho: 你理解了,亲爱的.我们看下桥接模式的定义:
"将抽象部分与实现部分分离,使它们都能够独立的变化"
你看我们的实现完美遵循该定义。假设你有一个类设计器(如Visual Studio或其它支持该功能的IDE环境),你会看到相似的例如以下类图:
在这里, Abstraction 是开关基类Switch。
RefinedAbstraction 是详细开关类 (FancySwitch
,NormalSwitch
等等。)。 Implementor 是电器接口IElectricalEquipment
。ConcreteImplementorA 和ConcreteImplementorB 是电灯类Light和风扇类Fan。
Farhana: 问你个问题,仅仅是好奇啊。如你所说有非常多其它的设计模式,为什么你以桥接模式開始呢?有重要原因吗?
Shubho: 这个问题非常好。是的,我以桥接模式而不以其它開始是由于一个理由。我觉得桥接模式是全部面向对象模式的基础。理由例如以下:
- 它教导怎样思考抽象,这是面向对象设计模式的关键概念。
- 它实现了主要的OOD原则。
- 它easy理解。
- 假设正确理解该模式,学习其它模式会非常easy。
Farhana: 你觉得我理解的对吗?
Shubho: 我觉得你理解的非常正确。
Farhana: 那么接下来是什么?
Shubho: 通过理解桥接模式,我们仅仅是開始理解设计模式的思想。在我们接下的对话中,我们将会学习其它的设计模式,我希望你不会觉得它们无聊。
Farhana:不会的,相信我。