[OOD]违反里氏替换原则的解决方案

简介: 关于OOD中的里氏替换原则,大家耳熟能祥了,不再展开,可以参考设计模式的六大设计原则之里氏替换原则。

关于OOD中的里氏替换原则,大家耳熟能祥了,不再展开,可以参考设计模式的六大设计原则之里氏替换原则。这里尝试讨论常常违反的两种形式和解决方案。

违反里氏替换原则的根源是对子类及父类关系不明确。我们在设计继承关系常常受一些主观认识的左右,比如Robert C. Martin提到的线段与线的关系,以及被大家说到烂的正方形与矩形。从以前的经验我们认为它们符合继承关系,比如线段是线的较短形式,正方形是矩形的一个特例。但事实上它们并不能完全的包容和替代。

以集合的形式表示,左图是里氏替换的目标,子类可以完全包容了父类的特性集合。右图则是说两者存在不兼容的特性集合:
LRP

对应的解决方案就是进一步抽象,将它们之前的关系从语言的角度重新定义,也许果真是is-a, 也许是has-a,也许它们只是兄弟。
基本的思路如下:

1. 找到更高层次的抽象

up_abstraction
以Robert C. Martin举的线与线段为例, 初始实现Line是LineSegment的基类:

// Line代表经过两点(P1,P2)的一条的直线
class Line{
 public:
   double GetSlope() const;
   Point GetP1() const;
   Point GetP2() const;
   virtual bool IsOn(const Point&) const;

 private:
  Point itsP1;
  Point itsP2;
}

// LineSegment则是由两点(P1,P2)连接的线段。
class LineSegment : public Line {
 public:
  virtual bool IsOn(const Point&) const;
}

其中IsOn函数用于计算某个点在不在直线或线段上。对于直线而言,一个点在不在其上仅仅取决于这个点相对于直线的两个点的关系。而对于线段而言,还是它是否在线程起止边界内。两者对于这个接口函数的判断条件并不相同,所以LineSegment无法直接代替父类,违反了里氏替换原则。

解决方案是将这个不一致的接口排除掉,剩下的公共接口做为直线和线段的基类,即定义一个LinearObject做为Line及LineSegment的基类:

class Line{
 public:
   double GetSlope() const;
   Point GetP1() const;
   Point GetP2() const;
   // 纯虚函数的意义在于,确保使用基类的客户代码不会使用这个接口函数
   virtual bool IsOn(const Point&) const = 0;

 private:
  Point itsP1;
  Point itsP2;
}

2. 改为has-a关系

另一种解决方案,是针对继承关系太过牵强的情况,比如所谓的is-implemented-in-terms-of (由谁实现)的情况,不如转化为组合模式,如下面的关系:
compositor
Scott Meyers在Effective C++ 3e, Item 38提到一个案例。比如准备基于std::list实现一个Set。初步想法是期望保持与list相同的接口,于是定义为:

template<typename T>
class Set : public std::List<T> {...}

但Set与List在行为有一个巨大的差异是Set不允许重复的元素,所以也违反了里氏替换原则。
解决方案就是,使用std::list实现,就是一个has-a关系,可以定义为:

template<typename T>
class Set {
 public:
  void insert(const T& item);
  void remove(const T& item);
  ...

 private:
  std::list<T> rep;
}
目录
相关文章
|
设计模式 Java 数据库
Java设计模式七大原则-依赖倒转原则
Java设计模式七大原则-依赖倒转原则
101 0
|
8月前
|
设计模式 算法 Java
深入理解面向对象设计的深层原则与思维
软件设计原则是指在软件开发过程中,通过一系列指导性的原则来指导设计决策和编码实践。这些原则旨在提高软件系统的质量,使其具有可维护性、可扩展性、可重用性和可测试性。几个重要性:可维护性、可扩展性、可重用性、可测试性和降低系统复杂度。软件设计原则是提高软件系统质量和可维护性的基石。遵循这些原则可以使得代码更加清晰、灵活和可靠,提高开发效率和软件质量,减少后期维护成本。同时,它们也为团队合作和团队成员共同理解代码提供了共同的规范和指导。
164 2
深入理解面向对象设计的深层原则与思维
|
8月前
|
设计模式 Java 开发者
Java设计模式七大原则之里氏替换原则
Java设计模式七大原则之里氏替换原则
68 0
|
8月前
|
设计模式 存储 自然语言处理
Java面向对象设计七大原则
Java面向对象设计七大原则
125 0
软件设计原则-迪米特原则讲解以及代码示例
迪米特原则(Law of Demeter,简称LoD)也被称为最少知识原则(Least Knowledge Principle,LKP),是面向对象设计的一种重要原则。迪米特原则的核心思想是尽量减少对象之间的交互,使得系统中的模块能够相对独立地变化。
145 0
|
设计模式 算法
原则的重要性(单一职责原则-开放封闭原则)一
原则的重要性(单一职责原则-开放封闭原则)一
102 0
|
程序员 测试技术
面向对象设计五个基本原则
只有聪明人才能看见的简介~( ̄▽ ̄~)~
114 0
|
关系型数据库
面向对象的设计(OOD)原则了解一下
面向对象的设计(OOD)原则了解一下
223 0
|
设计模式
设计模式七大原则——迪米特原则
设计模式七大原则——迪米特原则
设计模式七大原则——迪米特原则
面向对象基本原则(2)- 里式代换原则与依赖倒置原则
面向对象基本原则(1)- 单一职责原则与接口隔离原则 面向对象基本原则(2)- 里式代换原则与依赖倒置原则 面向对象基本原则(3)- 最少知道原则与开闭原则

热门文章

最新文章

下一篇
开通oss服务