设计模式(基础篇)-------软件设计七大设计原则(续一)
开放闭合原则------------- OCP (Open-Closed Principle)
你有没有想过Microsoft是怎么维护操作系统的?为什么我们的系统只要有漏洞,Microsoft总是为我们下载补丁包安装就可以呢?这是优良操作系统必须的性质。不然我们每次系统出现问题,总是要修改代码,重新编译。我想这样的系统,打死你也不会买的。这就是开闭原则。从面向对象设计角度看,它可以这么说:"软件实体(类,模块,函数等等)应当对扩展开放,对修改闭合。"通俗的讲就是你应该在一个类在保证系统稳定性且不修改的情前提下,去扩展一个类。这是面向对象设计的基石(Base),也是最重要的原则。
来看一个违反开闭原则的类结构图:
客户端直接与服务器的连接,达到很高的耦合度,只要我们的服务器一般发生一个微小的变化,每一个客户端都应该做出相应的变化。甚至加入我们原来的服务器是IIS,如今我想完全移植到Apache上,难道我要对每一个客户端做相应的变化?显而易见,这样的系统设计是很糟糕的。
如果我这样设计,你是否会眼前一亮:
在这个例子中,添加了一个抽象的服务器类,客户端包含一个抽象类的引用,具体的服务类实现了抽象服务类。那么,因任何原因引起服务实现发生变化时,客户端都不需要任何改变。
这里抽象服务类对修改是闭合的,实体类的实现对扩展是开放的。如果熟悉数据库原理,应该对数据库的三级模式两级映像深有感触吧?
抽象的东西是你系统的核心内容,如果你抽象的好,很可能在扩展功能时它不需要任何修改(就像服务是一个抽象概念)。如果在实现里定义了抽象的东西(比如IIS服务器实现的服务),代码要尽可能以抽象(服务)为依据。这会允许你扩展抽象事物,定义一个新的实现(如Apache服务器)而不需要修改任何客户端代码。
里氏代换原则--------------LSP (Liskov Substitution Principle)
由Barbar Liskov(芭芭拉。里氏)提出,是继承复用的基石。Barbara Liskov,2008年度美国计算机学会(ACM)图灵奖(Turing Award)获得者,是计算机界少有的女性杰出人物。
里氏代换原则意思是:"子类型必须能够替换它们的基类型。" 即:使用基类引用的函数必须能使用继承类的对象而不必知道它。"这是OOP最基本的原则之一,也就是多态。在基本的面向对象原则里,"继承"通常是"is a"的关系。如果"Developer" 是一个"SoftwareProfessional",那么"Developer"类应当继承"SoftwareProfessional"类。在类设计中"Is a"关系非常重要,但它容易冲昏头脑,结果使用错误的继承造成错误设计。里氏代换原则正是保证正确代换的方法。
来看一个小故事来自于<墨子。小取>: "白马,马也; 乘白马,乘马也。骊马(黑马),马也;乘骊马,乘马也。" 讲的是,白马、黑马都是马匹的品种,然而乘白马、黑马都是一样的。这里是因为白马、黑马都是马匹的子类,继承马的全部特性,也就是说马所具有的特性(如:四条腿,尾巴,会跑等等),白马、黑马都有。因此满足里氏代换。
西方有一个很经典的例子就是正方形是否可以继承长方形,作为它的子类。关于这个问题我们以探究便知:正方形是否可以完成继承父类长方形的所有?当然不能。因为这和椭圆与圆的关系是类似的,组成条件满足条件不由包含完全关系。也就是说不是Is a 的关系。
再来看下面一个小例子:
这里,KingFisher类扩展了Bird基类,并继承了Fly()方法,这看起来没问题。
现在看下面的例子:
违反里氏替换原则类结构图
Ostrich(鸵鸟)是一种鸟(显然是),并从Bird类继承。它能飞吗?不能,这个设计就违反了LSP。
所以,即使在现实中看起来没问题,在类设计中,Ostrich不应该从Bird类继承,这里应该从Bird中分离一个不会飞的类,Ostrich应该继承与它。
为什么LSP这么重要:
- 如果没有LSP,类继承就会混乱;如果子类作为一个参数传递给方法,将会出现未知行为;
- 如果没有LSP,适用与基类的单元测试将不能成功用于测试子类(测试的用例设计很有问题);