面向对象设计原则—Principles and Patterns读书笔记二

原作者 Robert C. Martin

原文:http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf

引用页:http://www.objectmentor.com/resources/publishedArticles.html

 

二:面向对象设计原则

1 The Open-Close Principle(开闭原则)

A module should be open for extension but closed for modification.

一个模块应该对扩展开放,而对修改封闭。

这是所有OO设计原则中最重要的一条。我们应该构造这样的模块,使其不做修改即可被扩展。即我们可以在不改变模块源码的同时改变模块的行为。

多态是实现开闭原则最基本的方法。例子任何一本讲模式或者OO的书上都能找到,就不多举了。

2 The Liskov Substitution Principle(Liskov替换原则)

Subclasses should be substitutable for their base classes.Subclasses should be substitutable for their base classes.

子类应能够替代它们的基类。

这句话从语法上来说简直是句废话。在面向对象的语言中,对基类引用的场合都能够传入其子类,罕有例外。但在语义上就不一定了。看下面的例子。

class Rectangle

{

public:

void SetWidth(int width);

void SetHeight(int height);

int GetHeight();

int GetWidth();

}

void SquareTest(Rectangle& rect)

{

rect.SetWidth(5);

rect.SetHeight(6);

assert(rect.GetWidth() * rect.GetHeight() == 30);

}

上面这段测试代码无疑是任何时候都应该通过的。

从逻辑上讲,正方形是矩形的一种,是is-a的关系,因此从Rectangle类派生出Squre类似乎是顺理成章的事情。但如果上面SquareTest函数接受的是一个Square对象,assert一定会失败。这就违背了LSP。究其原因,在于Rectangle类隐含着如下假定:宽高可以被分别赋值而互不影响。

这里需要引入一个Design By Contract(按契约设计)来解释这个问题:LSP的本质是派生类必须满足基类所遵循的契约。上例中Rectangle隐含的契约:宽高可以被互不影响地赋值显然是Square做不到的。从契约角度讲,派生类必须做到如下两点:

1)前置条件不能强于基类。

2)后置条件不能弱于基类。

前置条件例:基类某个方法可以接受所有正数,而派生类覆盖了此方法,只接受大于10000的数,那么某个传1000给这个方法的调用者就会失败。

后置条件例:基类某方法返回偶数,而派生类覆盖此方法改为可返回奇数,那么对基类方法返回偶数进行校验的调用者就会失败。上例中的后置条件是设置高度不影响宽度,设置宽度不影响高度。但Square显然做不到这一点。

3 Dependency Inversion Principle (DIP)

控制反转原则

Depend Upon Abstractions. Do not depend on concretions.

如果OCP是OO的目标,那DIP就是主要的方法。DI是依赖于接口、抽象功能或抽象类的策略,而非依赖于具体功能 或类。这种原则是COM,CORBA,EJB等组件设计背后的力量。

过程式设计展现了一种特定的依赖结构。即自底向上构造软件,上层结构高度依赖于底层模块,而底层模块又依赖于更底层的模块。这种依赖关系导致了软件的脆弱。上层模块主要是处理上层策略,对下层如何实现并不关心。那为什么要上层模块直接依赖于底层实现呢?

clip_image001

而面向对象设计则展示了一种与此迥异的依赖关系。主要的依赖指向抽象。而具体实现这些抽象的模块则不再被依赖。相反,这些实现模块自身也依赖于抽象。

clip_image002

依赖抽象。依赖反转的含义很简单,所有的依赖都只应依赖于抽象,接口或抽象类。不应有任何依赖指向具体类。原因也很简单:具体类易变,而抽象类的改变则要少很多。此外,抽象类是占位符,它们代表了功能可以被改变或扩展,而抽象类自身不需更改的地方。

你可能感兴趣的:(Pattern)