五、里氏代换原则(LSP--Liskov Substitution Principle)

1. 定义

a). 如果对每一个类型为S的对象o1,都有类型为T的对象o2,

使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有变化,

那么类型S是类型T的子类型。

b). 子类型必须能够替换它的基类型。LSP又称里氏替换原则。

对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写。 。

2.分析

a). 在软件中如果能够使用基类对象,那么一定能够使用其子类对象。

把基类都替换成它的子类,程序将不会产生任何错误和异常,

反过来则不成立,如果一个软件实体使用的是一个子类的话,那么它不一定能够使用基类。

b). 里氏代换原则是实现开闭原则的重要方式之一,

由于使用基类对象的地方都可以使用子类对象,因此,

在程序定义中,尽量使用基类类型来对对象进行定义,

在程序运行时,再确定其子类类型,用子类对象来替换父类对象。

3.      实例一

某系统需要实现对重要数据(如用户密码)的加密处理,

在数据操作类(DataOperator)中需要调用加密类中定义的加密算法,

系统提供了两个不同的加密类,CipherA和CipherB,它们实现不同的加密方法,

在DataOperator中可以选择其中的一个实现加密操作。

如图所示:

里氏代换原则-图-1

里氏代换原则-图-2

图(一)和图(二)分析:

(图一)中,

如果需要更换一个加密算法类或者增加并使用一个新的加密算法类,

如将CipherA改为CipherAA, 增加一个CipherC,则需要修改客户类Client和数据操作类DataOperator的源代码,违背了开闭原则。

现使用里氏代换原则对其进行重构,使得系统可以灵活扩展,符合开闭原则。

(图二)中,

在程序定义时:

子类CipherB继承了基类CipherA,

并在DataOperator类中定义了基类对象cipherA

在程序运行时,

客户端类Client使用了配置文件config.xml,配置文件指明了运行时使用的子类加密算法CipherB

4. 实现二

对于依赖倒置原则,说的是父类不能依赖子类,它们都要依赖抽象类。

这种依赖是我们实现代码扩展和运行期内绑定(多态)的基础。

因为一旦类的使用者依赖某个具体的类,那么对该依赖的扩展就无从谈起;

而依赖某个抽象类,则只要实现了该抽象类的子类,都可以被类的使用者使用,从而实现了系统的扩展。

但是,光有依赖倒置原则,并不一定就使我们的代码真正具有良好的扩展性和运行期内绑定。

请看下面的代码:

public class Animal{

private string name;

public Animal(string name){

this.name = name;

}

public void Description(){

Console.WriteLine("This is a(an) " + name);

}

}

//下面是它的子类猫类:

public class Cat : Animal{

public Cat(string name){

}

public void Mew(){

Console.WriteLine("The cat is saying like 'mew'");

}

}

//下面是它的子类狗类:

public class Dog : Animal{

public Dog(string name){

}

public void Bark(){

Console.WriteLine("The dog is saying like 'bark'");

}

}

//最后,我们来看客户端的调用:

public void DecriptionTheAnimal(Animal animal){

if (typeof(animal) is Cat){

Cat cat = (Cat)animal;

Cat.Decription();

Cat.Mew();

}

else if (typeof(animal) is Dog){

Dog dog = (Dog)animal;

Dog.Decription();

Dog.Bark();

}

}

通过上面的代码,我们可以看到虽然客户端的依赖是对抽象的依赖,

但依然这个设计的扩展性不好,运行期绑定没有实现。

是什么原因呢?

其实就是因为不满足里氏替换原则:

子类如Cat有Mew()方法父类根本没有,Dog类有Bark()方法父类也没有,

两个子类都不能替换父类。

这样导致了系统的扩展性不好和没有实现运行期内绑定。

现在看来,一个系统或子系统要拥有良好的扩展性和实现运行期内绑定,有两个必要条件:

第一是依赖倒置原则;

第二是里氏替换原则。

这两个原则缺一不可。

我们知道,在我们的大多数的模式中,我们都有一个共同的接口,然后子类和扩展类都去实现该接口。

下面是一段原始代码:

if(action.Equals(“add”)){

//do add action

}

else if(action.Equals(“view”)){

//do view action

}

else if(action.Equals(“delete”)){

//do delete action

}

else if(action.Equals(“modify”)){

//do modify action

}

我们首先想到的是把这些动作分离出来,就可能写出如下的代码:

public class AddAction{

public void add(){

//do add action

}

}

public class ViewAction{

public void view(){

//do view action

}

}

public class deleteAction{

public void delete(){

//do delete action

}

}

public class ModifyAction{

public void modify(){

//do modify action

}

}

我们可以看到,这样代码将各个行为独立出来,满足了单一职责原则,

但这远远不够,因为它不满足依赖颠倒原则和里氏替换原则。

下面我们来看看命令模式对该问题的解决方法:

public interface Action{

public void doAction();

}

//然后是各个实现:

public class AddAction : Action{

public void doAction(){

//do add action

}

}

public class ViewAction : Action{

public void doAction(){

//do view action

}

}

public class deleteAction : Action{

public void doAction(){

//do delete action

}

}

public class ModifyAction : Action{

public void doAction(){

//do modify action

}

}

//这样,客户端的调用大概如下:

public void execute(Action action){

action.doAction();

}

看,上面的客户端代码再也没有出现过typeof这样的语句,扩展性良好,也有了运行期内绑定的优点。

5. 总结

子类型必须能够替换掉它们的父类。

LSP优点:

1)、保证系统或子系统有良好的扩展性。

只有子类能够完全替换父类,才能保证系统或子系统在运行期内识别子类就可以了,

因而使得系统或子系统有了良好的扩展性。

2)、实现运行期内绑定,即保证了面向对象多态性的顺利进行。

这节省了大量的代码重复或冗余。

避免了类似instanceof这样的语句,或者getClass()这样的语句,这些语句是面向对象所忌讳的。

3)、有利于实现契约式编程。

契约式编程有利于系统的分析和设计,指我们在分析和设计的时候,定义好系统的接口,然后再编码的时候实现这些接口即可。

在父类里定义好子类需要实现的功能,而子类只要实现这些功能即可。

使用LSP注意点:

1)、此原则和OCP的作用有点类似,其实这些面向对象的基本原则就2条:

A:面向接口编程,而不是面向实现;

B:用组合而不主张用继承

2)、LSP是保证OCP的重要原则

3)、这些基本的原则在实现方法上也有个共同层次,就是使用中间接口层,以此来达到类对象的低偶合,也就是抽象偶合!

4)、派生类的退化函数:

派生类的某些函数退化(变得没有用处),Base的使用者不知道不能调用f,会导致替换违规。

在派生类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,应该引起注意。

5)、从派生类抛出异常:

如果在派生类的方法中添加了其基类不会抛出的异常。

如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。

面向对象设计原则实践:之四.里氏代换原则相关推荐

  1. 从零开始学习Java设计模式 | 软件设计原则篇:里氏代换原则

    在本讲中,我来为大家介绍一下软件设计原则里面的第二个原则,即里氏代换原则. 概述 首先,大家应该知道,里氏代换原则是面向对象设计的基本原则之一.那什么是里氏代换原则呢?里氏代换原则是指任何基类可以出现 ...

  2. 带你认识六种设计原则(开闭原则、里氏代换原则、依赖倒转原则....)

    前言 1. 设计原则 1.1. 开-闭原则 1.2. 里氏代换原则 1.3. 依赖倒转原则 1.4. 接口隔离原则 1.5. 合成/聚合原则 1.6. 迪米特法则 前言 学习设计模式之前先要了解其中的 ...

  3. 里氏代换原则C#详解

    看了一大堆的资料讲解关于里氏代换原则,在这里我想分享给大家. 下面这段话来自百度百科,是这么解释里氏代换原则的: 里氏代换原则(Liskov Substitution Principle LSP)是面 ...

  4. 里氏代换原则 举例 分析

    里氏代换原则 定义:里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一. 目的:里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现.L ...

  5. 面向对象五大设计原则-里氏代换原则

    1.里氏代换原则 里氏代换原则(Liskov Substitution Principle)是指,任何基类出现的地方,子类一定可以出现.反之则不一定成立. 通俗的讲便是:"老鼠儿子会打洞&q ...

  6. 面向对象设计原则之里氏代换原则

    里氏代换原则由2008年图灵奖得主.美国第一位计算机科学女博士Barbara Liskov教授和卡内基·梅隆大学Jeannette Wing教授于1994年提出.其严格表述如下:如果对每一个类型为S的 ...

  7. 面向对象设计原则之三:里氏替换原则

    里氏替换原则(Liskov Substitution Principle LSP)   里氏替换原则是面向对象设计的基本原则之一.任何基类可以出现的地方,子类一定可以出现.LSP是继承复用的基石,只有 ...

  8. 软件设计原则——里氏代换原则

    里氏代换原则 里氏代换原则是面向对象设计的基本原则之一. 里氏代换原则:任何基类可以出现的地方,子类一定可以出现. 通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能. 换句话说,子类继承父类 ...

  9. 面向对象——依赖倒转原则和里氏代换原则

    什么是依赖倒转原则 下面三个就是 高层模块不依赖低层模块 抽象不依赖细节 细节依赖抽象 为什么需要这个原则? 假设现在需要开发一个软件,其中某个功能需要和数据库进行操作 那么,马上能想到的方法是不是就 ...

最新文章

  1. 使用 ASP+ DataGrid 控件来创建主视图/详细资料视图
  2. linux下collada-dom编译,COLLADA DOM Tutorial
  3. jenkins 安装配置
  4. 计算机考博哪个学校好考,管理学博士哪个学校好考
  5. python亲密度_Python OpenCV 图像2D直方图,取经之旅第 25 天
  6. 不仅是人类的shooow
  7. 仿IOS圆角可自定义扩展对话框
  8. Python项目--飞机作战完整版(附带图片素材)
  9. ThreeJS 跨域
  10. php swfupload,ThinkPHP+swfupload多图上传实例 经典实用的php多图上传
  11. cityscapes数据集上的类别统计分布
  12. Android下WPS打开Excel2007版也有问题
  13. 计算机桌面图标有哪两类,计算机基础100题
  14. python的十句名言_程序员的二十句励志名言,看看你最喜欢哪句?
  15. R语言使用cph函数和rcs函数构建限制性立方样条cox回归模型、使用rms包的Predict函数计算指定连续变量在不同分组变量下和风险比HR值的关系、使用ggplot2可视化变量与风险值HR的关系
  16. 软件技术基础(一):绪论
  17. 3X技术点亮发布会 雄迈十年产业融合再出发
  18. keras基于CNN和序列标注的对联机器人
  19. 【LadyBug】thirdScriptError:api request success callback function,Cannot read property undefine参数
  20. java list高效去重_简述Java List去重五种方法

热门文章

  1. c++最近公共祖先LCA(倍增算法和tarjan)
  2. 假设检验基础 R语言
  3. iPadPro看电影之MKV转MP4视频格式教程
  4. 今日头条新闻采集爬虫分享
  5. php 连接数据库有很多notice,PHP Notice: undefined index 完美解决方法
  6. Sherman-Morrison-Woodbury,SMW恒等式
  7. python中dic.get用法
  8. MathRound修约
  9. 微信小程序的websocket使用stomp协议--简单实用的npm包
  10. Samba服务和FTP服务