自上篇翻译<如何向妻子解释OOD>后收到了很好的反应。故特继续翻译作者的<How I explained Design Patterns to my wife: Part 1>一文,以飨读者。在此文中,作者依旧通过与妻子浅显易懂的对话,向读者解释了什么是设计模式。
Shubho:通过我们关于面向对象设计原则(OODP,即SOLID原则)的对话,我想你已经对面向对象设计原则(OODP)有了基本的认识。希望你不要介意我把对话分享到博客上。你可以在这找到它:<如何向妻子解释OOD>.
设计模式是这些原则在某些特定公共场景下标准化的应用,接下来让我们通过一些例子学习什么是设计模式。
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和NormalSwitchnd继承类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(); //每种电器都能关闭
}
Farhana: 是的,但开关不能直接知道风扇或电灯。开关应当知道一个电器IElectricalEquipment能够打开或关闭。这意味着,ISwitch应该有一个IElectricalEquipment实例,对吗?
Shubho: 对,对风扇或电灯的封装的实例是一个桥梁。所以让我们修改Switch类以便封装一个电器:
public class Switch {
public IElectricalEquipment equipment {
get;
set;
}
public void On() {
//开关打开
}
public void Off() {
//开关关闭
}
}
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: 这个问题很好。是的,我以桥接模式而不以其他开始是因为一个理由。我认为桥接模式是所有面向对象模式的基础。理由如下:
Farhana: 你认为我理解的对吗?
Shubho: 我认为你理解的非常正确。
Farhana: 那么接下来是什么?
Shubho: 通过理解桥接模式,我们仅仅是开始理解设计模式的思想。在我们接下的对话中,我们将会学习其他的设计模式,我希望你不会觉得它们无聊。
Farhana:不会的,相信我。
此文译自CodeProject上<How I explained OOD to my wife>一文,该文章在Top Articles上排名第3,读了之后觉得非常好,就翻译出来,供不想读英文的同学参考学习。
作者(Shubho)的妻子(Farhana)打算重新做一名软件工程师(她本来是,后来因为他们孩子出生放弃了),于是作者就试图根据自己在软件开发设计方面的经验帮助她学习面向对象设计(OOD)。
自作者从事软件开发开始,作者常常注意到不管技术问题看起来多复杂,如果从现实生活的角度解释并以对答的方式讨论,那么它将变得更简单。现在他们把在OOD方面有些富有成效的对话分享出来,你可能会发现那是一种学习OOD很有意思的方式。
下面就是他们的对话:
Shubho:亲爱的,让我们开始学习OOD吧。你了解面向对象原则吗?
Farhana:你是说封装,继承,多态对吗?我知道的。
Shubho:好,我希望你已了解如何使用类和对象。今天我们学习OOD。
Farhana:等一下。面向对象原则对面向对象编程(OOP)来说不够吗?我的意思是我会定义类,并封装属性和方法。我也能根据类的关系定义它们之间的层次。如果是,那么还有什么?
Shubho:问得好。面向对象原则和OOD实际上是两个不同的方面。让我给你举个实际生活中的例子帮你弄明白。
再你小时候你首先学会字母表,对吗?
Farhana:嗯
Shubho:好。你也学了单词,并学会如何根据字母表造词。后来你学会了一些造句的语法。例如时态,介词,连词和其他一些让你能造出语法正确的句子。例如:
"I" (代词) "want" (动词) "to" (介词) "learn" (动词) "OOD"(名词)。
看,你按照某些规则组合了单词,并且你选择了有某些意义的正确的单词结束了句子。
Farhana:OK,这意味着什么呢?
Shubho:面向对象原则与这类似。OOP指的是面向对象编程的基本原则和核心思路。在这里,OOP可以比作英语基础语法,这些语法教你如何用单词构造有意义且正确的句子,OOP教你在代 码中构造类,并在类里封装属性和方法,同时构造他们之间的层次关系。
Farhana:嗯..我有点感觉了,这里有OOD吗?
Shubho:马上就有答案。现在假定你需要就某些主题写几篇文章或随笔。你也希望就几个你擅长主体写几本书。对写好文章/随笔或书来说,知道如何造句是不够的,对吗?为了使读者能更轻 松的明白你讲的内容,你需要写更多的内容,学习以更好的方式解释它。
Farhana:看起来有点意思...继续。
Shubho:现在,如果你想就某个主题写一本书,如学习OOD,你知道如何把一个主题分为几个子主题。你需要为这些题目写几章内容,也需要在这些章节中写前言,简介,例子和其他段落。 你需要为写个整体框架,并学习一些很好的写作技巧以便读者能更容易明白你要说的内容。这就是整体规划。
在软件开发中,OOD是整体思路。在某种程度上,设计软件时,你的类和代码需能达到模块化,可复用,且灵活,这些很不错的指导原则不用你重新发明创造。确实有些原则你已经在你的类和对象中已经用到了,对吗?
Farhana:嗯...有个大概的印象了,但需要继续深入。
Shubho:别担心,你马上就会学到。我们继续讨论下去。
Shubho:这是一个非常重要的问题。当我们能很快地设计一些类,完成开发并发布时,为什么我们需要关心OOD?那样子还不够吗?
Farhana:嗯,我早先并不知道OOD,我一直就是开发并发布项目。那么关键是什么?
Shubho:好的,我先给你一句名言:
走在结冰的河边不会湿鞋,开发需求不变的项目畅通无阻(Walking on water and developing software from a specification are easy if both are frozen)
-Edward V. Berard
Farhana:你的意思是软件开发说明书会不断变化?
Shubho:非常正确!软件开发唯一的真理是“软件一定会变化”。为什么?
因为你的软件解决的是现实生活中的业务问题,而现实生活中得业务流程总是在不停的变化。
假设你的软件在今天工作的很好。但它能灵活的支持“变化”吗?如果不能,那么你就没有一个设计敏捷的软件。
Farhana:好,那么请解释一下“设计敏捷的软件”。
Shubho:"一个设计敏捷的软件能轻松应对变化,能被扩展,并且能被复用。"
并且应用好"面向对象设计"是做到敏捷设计的关键。那么,你什么时候能说你在代码中很好的应用了OOD?
Farhana:这正是我的问题。
Shubho:如果你代码能做到以下几点,那么你就正在OOD:
Farhana:还有?
Shubho:我们并不是孤立的。很多人在这个问题上思考了很多,也花费了很大努力,他们试图做好OOD,并为OOD指出几条基本的原则(那些灵感你能用之于你的OOD)。他们最终也确实总结出了一些通用的设计模式(基于基本的原则)。
Farhana:你能说几个吗?
Shubho:当然。这里有很多涉及原则,但最基本的是叫做SOLID的5原则(感谢Uncle Bob,伟大OOD导师)。
S = 单一职责原则 Single Responsibility Principle
O = 开放闭合原则 Opened Closed Principle
L = Liscov替换原则 Liscov Substitution Principle
I = 接口隔离原则 Interface Segregation Principle
D = 依赖倒置原则 Dependency Inversion Principle
接下去,我们会仔细探讨每一个原则。
Shubho:我先给你展示一张海报。我们应当谢谢做这张海报的人,它非常有意思。

单一职责原则海报
它说:"并不是因为你能,你就应该做"。为什么?因为长远来看它会带来很多管理问题。
从面向对象角度解释为:"引起类变化的因素永远不要多于一个。"
或者说"一个类有且只有一个职责"。
Farhana:能解释一下吗?
Shubho:当然,这个原则是说,如果你的类有多于一个原因会导致它变化(或者多于一个职责),你需要一句它们的职责把这个类拆分为多个类。
Farhana:嗯...这是不是意味着在一个类里不能有多个方法?
Shubho:不。你当然可以在一个类中包含多个方法。问题是,他们都是为了一个目的。如今为什么拆分是重要的?
那是因为:
Farhana:能给我一个例子吗?
Shubho:当然,看一下下面的类层次。当然这个例子是从Uncle Bob那里得来,再谢谢他。

违反单一职责原则的类结构图
这里,Rectangle类做了下面两件事:
并且,有两个应用使用了Rectangle类:
这违反了SRP(单一职责原则);
Farhana:如何违反的?
Shubho:你看,Rectangle类做了两件事。在一个方法里它计算了面积,在另外一个方法了它返回一个表示矩形的GUI。这会带来一些有趣的问题:
在计算几何应用程序中我们必须包含GUI。也就是在开发几何应用时,我们必须引用GUI库;
图形应用中Rectangle类的变化可能导致计算几何应用变化,编译和测试,反之亦然;
Farhana:有点意思。那么我猜我们应该依据职责拆分这个类,对吗?
Shubho:非常对,你猜我们应该做些什么?
Farhana:当然,我试试。下面是我们可能要做的:
拆分职责到两个不同的类中,如:
Shubho:非常好。在这里,Rectangle类被计算几何应用使用,而RectangleUI被图形应用使用。我们甚至可以分离这些类到两个独立的DLL中,那会允许我们在变化时不需要关心另一个就可以实现它。
Farhana:谢谢,我想我明白SRP了。SRP看起来是把事物分离成分子部分,以便于能被复用和集中管理。我们也不能把SRP用到方法级别吗?我的意思是,我们可以写一些方法,它们包含做很多事的代码。这些方法可能违反SRP,对吗?
Shubho:你理解了。你应当分解你的方法,让每个方法只做某一项工作。那样允许你复用方法,并且一旦出现变化,你能购以修改最少的代码满足变化。
开放闭合原则
Shubho:这里是开放闭合原则的海报

开放闭合原则海报
从面向对象设计角度看,它可以这么说:"软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。"
通俗来讲,它意味着你应当能在不修改类的前提下扩展一个类的行为。就好像我不需要改变我的身体而可以穿上衣服。
Farhana:有趣。你能够按照你意愿穿上不同的衣服来改变面貌,而从不用改造身体。你对扩展开放了,对不?
Shubho:是的。在OOD里,对扩展开发意味着类或模块的行为能够改变,在需求变化时我们能以新的,不同的方式让模块改变,或者在新的应用中满足需求。
Farhana:并且你的身体对修改是闭合的。我喜欢这个例子。当需要变化时,核心类或模块的源代码不应当改动。你能用些例子解释一下吗?
Shubho:当然,看下面这个例子。它不支持"开放闭合"原则。

违反开发闭合原则的类结构
你看,客户端和服务段都耦合在一起。那么,只要出现任何变化,服务端变化了,客户端一样需要改变。
Farhana:理解。如果一个浏览器以紧耦合的方式按照指定的服务器(比如IIS)实现,那么如果服务器因为某些原因被其他服务器(如Apache)替换了,那么浏览器也需要修改或替换。这确实很可怕!
Shubho:对的。下面是正确的设计。

遵循开放闭合原则的类结构
在这个例子中,添加了一个抽象的服务器类,客户端包含一个抽象类的引用,具体的服务类实现了抽象服务类。那么,因任何原因引起服务实现发生变化时,客户端都不需要任何改变。
这里抽象服务类对修改是闭合的,实体类的实现对扩展是开放的。
Farhana:我明白了,抽象是关键,对吗?
Shubho:是的,基本上,你抽象的东西是你系统的核心内容,如果你抽象的好,很可能在扩展功能时它不需要任何修改(就像服务是一个抽象概念)。如果在实现里定义了抽象的东西(比如IIS服务器实现的服务),代码要尽可能以抽象(服务)为依据。这会允许你扩展抽象事物,定义一个新的实现(如Apache服务器)而不需要修改任何客户端代码。
Shubho:"Liskov's替换原则(LSP)"听起来很难,却是很有用的基本概念。看下这幅有趣的海报:

Liskov替换原则海报
这个原则意思是:"子类型必须能够替换它们基类型。"
或者换个说法:"使用基类引用的函数必须能使用继承类的对象而不必知道它。"
Farhana:不好意思,听起来有点困惑。我认为这个OOP的基本原则之一。也就是多态,对吗?为什么一个面向对象原则需要这么说呢?
Shubho:问的好。这就是你的答案:
在基本的面向对象原则里,"继承"通常是"is a"的关系。如果"Developer" 是一个"SoftwareProfessional",那么"Developer"类应当继承"SoftwareProfessional"类。在类设计中"Is a"关系非常重要,但它容易冲昏头脑,结果使用错误的继承造成错误设计。
"Liskov替换原则"正是保证继承能够被正确使用的方法。
Farhana:我明白了。有意思。
Shubho:是的,亲爱的,确实。我们看个例子:

Liskov替换原则类结构图
这里,KingFisher类扩展了Bird基类,并继承了Fly()方法,这看起来没问题。
现在看下面的例子:

违反Liskov替换原则类结构图
Ostrich(鸵鸟)是一种鸟(显然是),并从Bird类继承。它能飞吗?不能,这个设计就违反了LSP。
所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类,Ostrich应该继承与它。
Farhana:好,明白了。那么让我来试着指出为什么LSP这么重要:
对吗?
Shubho:非常正确。你能设计对象,使用LSP做为一个检查工作来测试继承是否正确。
Shubho:今天我们学习"接口分离原则",这是海报:

接口分离原则海报
Farhana:这是什么意思?
Shubho:它的意思是:"客户端不应该被迫依赖于它们不用的接口。"
Farhana:请解释一下。
Shubho:当然,这是解释:
假设你想买个电视机,你有两个选择。一个有很多开关和按钮,它们看起来很混乱,且好像对你来说没必要。另一个只有几个开关和按钮,它们很友好,且适合你使用。假定两个电视机提供同样的功能,你会选哪一个?
Farhana:当然是只有几个开关和按钮的第二个。
Shubho:对,但为什么?
Farhana:因为我不需要那些看起来混乱又对我没用的开关和按钮。
Shubho:以便外部能够知道这些类有哪些可用的功能,客户端代码也能根据接口来设计.现在,如果接口太大,包含很多暴露的方法,在外界看来会很混乱.接口包含太多的方法也使其可用性降低,像这种包含了无用方法的"胖接口"会增加类之间的耦合.你通过接口暴露类的功能,对.同样地,假设你有一些类,
这也引起了其他问题.如果一个类想实现该接口,
接口隔离原则确保实现的接口有他们共同的职责,它们是明确的,
Shubho:非常正确.一起看个例子.

注意到IBird接口包含很多鸟类的行为,包括Fly()行为.
Farhana:确实如此。那么这个接口必须拆分了?
Shubho:是的。这个"胖接口"应该拆分未两个不同的接口,

这里如果一种鸟不会飞(如Ostrich),
Farhana:
Shubho:对的。
Farhana:如果他们确实需要复用方案,
Shubho:你理解了。
Shubho:这是SOLID原则里最后一个原则。这是海报
它的意思是:高层模块不应该依赖底层模块,两者都应该依赖其抽象
Shubho:考虑一个现实中的例子。你的汽车是由很多如引擎,
Farhana:是的
Shubho:好,它们没有一个是严格的构建在一个单一单元里;
在替换时,你仅需要确保引擎或车轮符合汽车的设计(
当然,
现在,如果你的汽车的零部件不具备可插拔性会有什么不同?
Farhana:那会很可怕!因为如果汽车的引擎出故障了,
Shubho:是的,那么该如何做到"可插拔性"呢?
Farhana:这里抽象是关键,对吗?
Shubho:是的,在现实中,汽车是高级模块或实体,
相比直接依赖于引擎或车轮,
一起看下面的类图
Shubho:注意到上面Car类有两个属性,
Farhana:所以,如果代码中不用依赖倒置,
使用低级类会破环高级代码;
当低级类变化时需要很多时间和代价来修改高级代码;
产生低复用的代码;
Shubho:你完全掌握了,亲爱的!
Shubho:除SOLID原则外还有很多其它的面向对象原则。
"组合替代继承":这是说相对于继承,要更倾向于使用组合;
"笛米特法则":这是说"你的类对其它类知道的越少越好";
"共同封闭原则":这是说"相关类应该打包在一起";
"稳定抽象原则":这是说"类越稳定,越应该由抽象类组成";
Farhana:我应该学习那些原则吗?
Shubho:当然可以。你可以从整个网上学习。
Farhana:在那些设计原则之上我听说过很多设计模式。
Shubho:对的。
Farhana:那么接下去我将学习设计模式吗?
Shubho:是的,亲爱的。
Farhana:那会很有意思,对吗?
Shubho:是,那确实令人兴奋。
-------------------------------------------------------------------------------------------------------------------
声明:
本文版权归作者共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文出处,连接,作者,否则保留追究法律责任的权利。
最近有人向我询问如何学习,工作的问题,思前想后,觉得习惯最重要,于是就想到整理一下自己的学习工作习惯。本人工作比较随便,谈不上章法,就随便列举一些。
个人觉得这点最重要,所以就放在了第一项。也许是年龄增大的缘故,现在根本熬夜不得。现在每天光顾博客园,发现有些人很晚的时候还在发贴,回帖,仿佛不知疲倦。在此衷心劝告一句:莫要透支身体!养成良好的作息规律是人生一大事,毕竟身体健康最重要,其他诸如事业,名利都是浮云!不是说人生最大的悲剧不是没钱花,而是有钱没命花么?!
本人现在每天晚上10:00(以往12:00)左右休息,早上7:30(以往8:30)起床,加班之事少有。
在公司总是事情很多,大事小事每天有十几件很正常,可惜记性不太好,如果不记下来,总会忘这忘那。所以除非遇到的是马上要处理的事,否则必然记下来,方式随便:碎纸片,记事本,札记等等,但要放在显眼的地方。
学习的方法很多,其中最好的方法之一莫过于带着问题去学习,但问题解决了要记得把他记下来。初学者,或者比较笨拙的人(比如我自己)尤其应该这么做,目的是为了温故而知新。这样子可以在以后有时间的时候拿出来翻翻,由于是自己不会的东西,所以看起来很快;也正因为是自己不会的东西,看过之后就避免在犯同样的错误或问同样的问题。个人能力也正是在这点滴见积累起来的。个人在工作的时候,尤其是项目发布初期,总是会在一个地方记下该项目自发布运行时期遇到的所有问题,一则避免再犯,二则可以完善思路。
很多新人刚开始总是问很多问题,有时候没人搭理自己就会觉得很委屈,其实是自己懒,经常问同样的问题才让人烦,所以要谨记这点。好记性不如赖笔头正是如此。
上面说了几项,总结为一句话就是要记东西,这里就以此举例——使用好OutLook。个人觉得工作中有OutLook就足够了,完全用不着别的软件。OutLook提供了邮件,便条,任务,日历等多种功能,即实用又方便,何乐而不为呢?!本人目前OutLook里邮件自动分了N类,任务列表天天有亮有灰,每天早上上班,从来不用为今天干嘛而发愁,因为9:30有自动提醒。
当然,如果你用别的办公软件也行,总之公司有的要用起来,没有的就弄个D版的用起来。
也许你看到上面的名字觉得有点逗,其实很实在。本人电脑任务栏上必然有这些工具,随点随用,不亦乐乎。相信工作几年的人应该有此体会,刚入行的以后慢慢就知道了。
以画图举例,说到截图,也许你会说Snagit,它是强大,但点来点去麻烦的很,还倒不如直接PrintScreen+Ctrl+V方便——平时又不需要去演示,谁需要截的那么细致啊。
还有个记事本,就强大了,有心情的时候完全可以用记事本写代码假装高人,Word是很强大,但能写代码么?嘿嘿。平时灵光一闪有个绝好的点子,用记事本记下就记下来,不会丢掉;换做其他,恐怕工具还没打开,点子没了,郁闷就来了。
向他人学习,很多人都能做的到,实际未必。事实上如果你面前有个牛人,好学的你自然会向他请教学习,这只能算是好学的一小部分。因为跟你差不多的和比你差的总是占大部分。
俗话说:“三个臭皮匠,顶个诸葛亮”,并不是说三个臭皮匠加起来就真的比诸葛亮厉害,而是说每个人都有可取之处,世上没有十全十美的人。就算你是公司的首席架构师又如何,总有你不会的东西。所以遇到跟你差不多的人,尤其是有些微妙的竞争关系的人,不要不服,一定虚心向人学习,这样子有可能你学到了他会的,而他没有学到你会的,结果你就真的超过了他。
通常帮助别人时你一定在某一方面比别人强,这时也许你感觉很舒服,那人也很感激你。这时你要注意多些赠品,比如帮他检视代码啊,指出不足啊,最后一定发掘他的优点,顺便看看自己有没有。这样子既帮了别人,又帮了自己,只有自己知道是双赢并窃喜,结束时还可以故作大方地说“不客气”或者“不用找零了”!
上次有个新人说他现在要学习HTML+CSS+JavaScript+Asp.NET+C#+SQLServer,感觉学不过来,问我该怎么办。我说丢掉几个,他说学了一些,丢掉觉得可惜,又说有人给他的工作就需要这些,我还说丢掉大部分,主学一个,辅学另一个做调节,其他当兴趣;如果有人催,就说自己不会。最后他又说他问了好几个工作过的人,答案都这样。我说这就对了,贪多嚼不烂,听我们的,没有错。
很多人经常会接触的很多东西,好奇心促使他什么都想学,他就同时什么都学,结果什么也学不会。人的精力毕竟有限,所以同时只能学习一门技术,能力好的可以学两门。其实,以我自己的经验来看,如果学精了一门,与之类似的技术,就算你根本都没接触过,你也可以声称自己精通它。这不是吹牛,所谓万变不离其踪。对于一门基础,只知万变顶多算入门;知其踪就可算精通了。然后遇到大同小异的东西,知其根难道还怕不知其表么?!
举个例子,C#与Java之争由来已久,每次都口水漫天,这只能说大多数人都很水。真正懂C#或Java的人不会互喷(因为他都懂了,虽然他可能没用过C#或Java)——你见过C#或Java之父相互口水的么?!
好了,今天就到这里。如果你觉得有什么不妥的地方,请你多多指正。