`
dongpo
  • 浏览: 5865 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

第10章 Liskov 替换原则

阅读更多

1. 目的
OCP原则背后的主要机制是抽象和多态。支持抽象和多态的关键机制之一是继承。只有良好的继承,才能使设计符合OCP。那么,什么才是良好的继承?LSP原则提供了一个判断标准。

 

2. 什么是LSP
Liskov Substitution Principle。Liskov是提出这个原则的人的名字。

 

LSP的定义是这样的:子类型必须能够替换掉它们的基类型。

 

替换性质是这样的:若对每个类型S的对象o1,都存在一个类型T的对象o2,使得在所有针对T编写的程序P中,用o1替换o2后,程序P行为功能不变,则S是T的子类型。

 

例如:有类B继承类A。
 


对于任意函数f(A &a),以类A的引用为参数。如果以类B引用做参数传给f,f的动作结果仍然与预期一致,则B可以替换A。

 

3. 一个明显的例子
 


 
 
void Draw(const Shape &shape)
{
 switch(shape.m_iType)
 {
 case CIRCLE:
  static_cast<const Circle&>(shape).Draw();
  break;
 case SQUARE:
  static_cast<const Square&>(shape).Draw();
  break;
 default:
  break;
 }
}

    这个继承关系是不符合LSP的。如果增加了新的Shape子类,则不能替换Shape,因为Draw函数中需要判断具体的子类类型,必须添加代码才行。同时,这个继承关系也不符合OCP原则。

    我们可以这样修改:
 

 
4. 一个隐晦的例子
上面的例子是比较明显的,很容易发现。还有比较微妙的违规:
 

 
因为Square的长和宽是相等的,所以SetWidth和SetHeight这样实现:


void Square::SetWidth(int width)
{
 Rectangle::SetWidth(width);
 Rectangle::SetHeight(width);
}

void Square::SetHeight(int height)
{
 Rectangle::SetWidth(height);
 Rectangle::SetHeight(height);
}

 

现在有这样一个函数f:
bool f(Rectangle &rect, int width, int height)
{
 rect.SetWidth(wiedht);
 rect.SetHeight(height);
 int area = rect.GetWidth() * rect.GetHeight();
 if (width * height == area)
 {
  return true;
 }
 else
 {
  return false;
 }
}


    函数f设定了Rectangle的长和宽,并期望area是设定的长和宽之积。如果此处传入一个Square引用做参数,则f可能返回false,这和f作者的期望不一致。所以,Square不能替换Rectangle,违反了LSP。

 

 

 

    如果只是观察Rectangle和Square,它们的继承关系,以及SetWidth和SetHeight的实现都是合理的,但是为什么从函数f的角度看就不合理了呢?

    记住:考察一个设计是否合理,要放到具体的环境中去,单独的考察一个设计是没有意义的。

    这里,Rectangle和Square违反LSP的原因,是因为Square的作者和函数f的作者对于Width和Height能否独立变化有歧义。怎么避免这种歧义?

 

5. 基于契约设计(Design By Contract,DBC)
    使用DBC,类的编写者显示地规定针对该类的契约。客户代码的编写者可以通过该契约获悉可以以来的行为方式。契约是通过为每个方法声明的前置条件和后置条件来指定的。要使用一个方法,前置条件必须为真。方法执行完毕,要保证后置条件为真。

基于契约设计的规则是:派生类的前置条件与基类相等或更弱,后置条件与基类相等或更强。
    更弱的前置条件,保证在能使用基类的地方也能使用派生类;更强的后置条件,保证派生类的运行结果在基类的运行结果范围之内。

    可惜的是,C++和Java并没有支持前置条件和后置条件的机制。

 

6. 在单元测试中指定契约
    可以通过编写单元测试的方式来指定契约。客户查看这些单元测试,这样就知道他们能对这个类做什么样的假设。

 

7. 更强的抽象
    f的作者之所以会假设Rectangle的长和宽可以单独变化,是因为他们看到了Rectangle的SetWidth和SetHeight实现。如果SetWidth和SetHeight是纯虚的,就没有办法对这两个函数做假设了。我们可以做一个更强的抽象:
 

 
8. 结论
    我们经常通过继承和多态实现可扩展性。但是具体是否有可扩展性,需要运用LSP原则去判断。使用基类的地方,如果给它一个派生类,代码是否依然正常运行。如果答案是Yes,那么就是符合LSP的,是可扩展的;否则,就违反了LSP,需要进行重构。

  • 大小: 2.7 KB
  • 大小: 8.9 KB
  • 大小: 9.9 KB
  • 大小: 16 KB
  • 大小: 26.6 KB
  • 大小: 11.2 KB
分享到:
评论

相关推荐

    敏捷软件开发:原则、模式与实践

    第10章 Liskov替换原则(LSP) 第11章 依赖倒置原则(DIP) 第12章 接口隔离原则(ISP) 第III部分 薪水支付案例研究 第13章 COMMAND模式和ACTIVE OBJECT模式 第14章 TEMPLATE METHOD模式和STRATEGY模式:继承...

    敏捷软件开发.pdf

    第10章 Liskov替换原则(LSP) 10.1 Liskov替换原则(LSP) 10.2 一个违反LSP的简单例子 10.3 正方形和矩形,更微妙的违规 10.4 一个实际的例子 10.5 用提取公共部分的方法代替继承 10.6 启发式规则和习惯用法...

    敏捷软件开发:原则、模式与实践.pdf

    第10章 Liskov替换原则(LSP) 第11章 依赖倒置原则(DIP) 第12章 接口隔离原则(ISP) 第三部分 薪水支付案例研究 第13章 COMMAND模式和ACTIVE OBJECT模式 第14章 TEMPLATE METHOD模式和STRATEGY模式:继承与委托 第15...

    敏捷软件开发:原则、模式与实践.pdf

    第十章 Liskov替换原则(LSP) 10.1 Liskov替换原则(LSP) 10.2 一个违反LSP的简单例子 10.3 正方形和矩形,更微妙的违规 10.4 一个实际的例子 10.5 用提取公共部分的方法代替继承 10.6 启发式规则和习惯用法 10.7 ...

    敏捷软件开发:原则、模式与实践.pdf 高清

    第十章 Liskov替换原则(LSP) 10.1 Liskov替换原则(LSP) 10.2 一个违反LSP的简单例子 10.3 正方形和矩形,更微妙的违规 10.4 一个实际的例子 10.5 用提取公共部分的方法代替继承 10.6 启发式规则和习惯用法 10.7 ...

    敏捷软件开发原则、模式与实践 C#版

    LISKOV替换原则 第11章 DIP:依赖倒置原则 第12章 ISP:接口隔离原则 第13章 写给C#程序员的UML概述 第14章 使用UML 第15章 状态图 第16章 对象图 第17章 用例 第18章 顺序图 第19章 类图 第20章 咖啡的启示 第三...

    24个设计模式与6大设计原则

    26.2 里氏替换原则【LISKOV SUBSTITUTION PRINCIPLE】 297 26.3 依赖倒置原则【DEPENDENCE INVERSION PRINCIPLE】 309 26.4 接口隔离原则【INTERFACE SEGREGATION PRINCIPLE】 310 26.5 迪米特法则【LOW ...

    Java面向对象程序设计杨晓燕面向对象基本原则和模式.pptx

    第10页/共30页 Java面向对象程序设计杨晓燕面向对象基本原则和模式全文共30页,当前为第10页。 发现变化,并封装变化 在软件设计之初,需要发现所要开发软件中可能存在或已经存在的"变化",然后利用抽象的方式对...

    亮剑.NET深入体验与实战精要2

    第10章 网络应用开发 383 10.1 Socket基本编程 384 10.1.1 Socket基本知识 384 10.1.2 Socket服务端开发步骤 386 10.1.3 Socket客户端开发步骤 388 10.2 异步Socket通信——实现MSN机器人 390 10.2.1 机器人服务端 ...

    亮剑.NET深入体验与实战精要3

    第10章 网络应用开发 383 10.1 Socket基本编程 384 10.1.1 Socket基本知识 384 10.1.2 Socket服务端开发步骤 386 10.1.3 Socket客户端开发步骤 388 10.2 异步Socket通信——实现MSN机器人 390 10.2.1 机器人服务端 ...

Global site tag (gtag.js) - Google Analytics