一、LSP简介(LSP--Liskov Substitution Principle):
定义:如果对于类型S的每一个对象o1,都有一个类型T的对象o2,使对于任意用类型T定义的程序P,将o2替换为o1,P的行为保持不变,则称S为T的一个子类型。
子类型必须能够替换它的基类型。LSP又称里氏替换原则。
对于这个原则,通俗一些的理解就是,父类的方法都要在子类中实现或者重写。
 
二、举例说明:
对于依赖倒置原则,说的是父类不能依赖子类,它们都要依赖抽象类。这种依赖是我们实现代码扩展和运行期内绑定(多态)的基础。因为一旦类的使用者依赖某个具体的类,那么对该依赖的扩展就无从谈起;而依赖某个抽象类,则只要实现了该抽象类的子类,都可以被类的使用者使用,从而实现了系统的扩展。
但是,光有依赖倒置原则,并不一定就使我们的代码真正具有良好的扩展性和运行期内绑定。请看下面的代码:
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这样的语句,扩展性良好,也有了运行期内绑定的优点。
三、LSP优点:
1、保证系统或子系统有良好的扩展性。只有子类能够完全替换父类,才能保证系统或子系统在运行期内识别子类就可以了,因而使得系统或子系统有了良好的扩展性。
2、实现运行期内绑定,即保证了面向对象多态性的顺利进行。这节省了大量的代码重复或冗余。避免了类似instanceof这样的语句,或者getClass()这样的语句,这些语句是面向对象所忌讳的。
3、有利于实现契约式编程。契约式编程有利于系统的分析和设计,指我们在分析和设计的时候,定义好系统的接口,然后再编码的时候实现这些接口即可。在父类里定义好子类需要实现的功能,而子类只要实现这些功能即可。
 
四、使用LSP注意点:
1、此原则和OCP的作用有点类似,其实这些面向对象的基本原则就2条:1:面向接口编程,而不是面向实现;2:用组合而不主张用继承
2、LSP是保证OCP的重要原则
3、这些基本的原则在实现方法上也有个共同层次,就是使用中间接口层,以此来达到类对象的低偶合,也就是抽象偶合!
4、派生类的退化函数:派生类的某些函数退化(变得没有用处),Base的使用者不知道不能调用f,会导致替换违规。在派生类中存在退化函数并不总是表示违反了LSP,但是当存在这种情况时,应该引起注意。 
5、从派生类抛出异常:如果在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。
 
 
 
 

设计模式学习--面向对象的5条设计原则之Liskov替换原则--LSP的更多相关文章

  1. 设计模式学习--面向对象的5条设计原则之开放封闭原则--OCP

    一.OCP简介(OCP--Open-Closed Principle):Software entities(classes,modules,functions,etc.) should be open ...

  2. 设计模式学习--面向对象的5条设计原则之单一职责原则--SRP

    一.SRP简介(SRP--Single-Responsibility Principle): 就一个类而言,应该只专注于做一件事和仅有一个引起它变化的原因.   所谓职责,我们可以理解他为功能,就是设 ...

  3. 设计模式学习--面向对象的5条设计原则之接口隔离原则--ISP

    一.ISP简介(ISP--Interface Segregation Principle): 使用多个专门的接口比使用单一的总接口要好.一个类对另外一个类的依赖性应当是建立在最小的接口上的.一个接口代 ...

  4. 设计模式学习--面向对象的5条设计原则之依赖倒置原则--DIP

    一.DIP简介(DIP--Dependency Inversion Principle): 1.高层模块不应该依赖于低层模块,二者都应该依赖于抽象.2.抽象不应该依赖于细节,细节应该依赖于抽象.   ...

  5. 设计模式学习总结(一)——设计原则与UML统一建模语言

    一.概要 设计模式(Design Pattern)是一套被反复使用.多数人知晓的.经过分类的.代码设计经验的总结. 使用设计模式的目的:为了代码可重用性.让代码更容易被他人理解.保证代码可靠性. 设计 ...

  6. 面向对象世界里转转七(Liskov替换原则)

    前言:Liskov替换原则是关于继承机制的应用原则,是实现开放封闭原则的具体规范,违反了Liskov原则必然意味着违反了开放封闭原则.因此,有必要对面向对象的继承机制及其基本原则做以探索,来进一步了解 ...

  7. 软件设计----LisKov替换原则(LSP)

    LisKov替换原则的定义:一个软件实体如果使用的是一个基类的话,一定适用于其子类,而且根本不能觉察出基类对象和子类对象的区别. 1)怎么理解上面的概念?就是我们程序设计的子类型能够完全替换父类型,而 ...

  8. 敏捷软件开发:原则、模式与实践——第10章 LSP:Liskov替换原则

    第10章 LSP:Liskov替换原则    Liskov替换原则:子类型(subtype)必须能够替换掉它们的基类型(base type). 10.1 违反LSP的情形 10.1.1 简单例子 对L ...

  9. 编码最佳实践——Liskov替换原则

    Liskov替换原则(Liskov Substitution Principle)是一组用于创建继承层次结构的指导原则.按照Liskov替换原则创建的继承层次结构中,客户端代码能够放心的使用它的任意类 ...

随机推荐

  1. WebAPI Token 验证

    WebAPI Token 验证 登录端 //HttpContext.Current.Session.Timeout = 10; ////生成Ticket //FormsAuthenticationTi ...

  2. 记录.NET Core通过Docker部署到Linux

    1.现在CentOS安装Docker环境(参考地址:https://docs.docker-cn.com/engine/installation/linux/docker-ce/centos/) 我这 ...

  3. MySQL事务一致性理解

    一致性是指数据处于一种语义上的有意义且正确的状态.一致性是对数据可见性的约束,保证在一个事务中的多次操作的数据中间状态对其他事务不可见的.因为这些中间状态,是一个过渡状态,与事务的开始状态和事务的结束 ...

  4. 使用JQuery插件Jcrop进行图片截取

    Jcrop插件本身并不含有图片截取功能,它仅仅是在前端层面构建一套截取动画效果并产生4个坐标点,插件使用者将这4个坐标点传回至服务器接口上进行截取操作.其优点是具有较高的通用性.浏览器兼容性(IE6+ ...

  5. mysql数据库binlog日志的异地备份

    MySQL数据库的二进制日志binlog记录了对数据库的全量DDL和DML操作,对数据库的point to point灾难恢复起着无法替代的关键作用.因此,基于此类考虑,需要对生产环境产生的binlo ...

  6. HDU 1710Binary Tree Traversals(已知前序中序,求后序的二叉树遍历)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1710 解题思路:可以由先序和中序的性质得到 : 先序的第一个借点肯定是当前子树的根结点, 那么在 中序 ...

  7. 五:MyBatis学习总结(五)——实现关联表查询

    一.一对一关联 1.1.提出需求 根据班级id查询班级信息(带老师的信息) 1.2.创建表和数据 创建一张教师表和班级表,这里我们假设一个老师只负责教一个班,那么老师和班级之间的关系就是一种一对一的关 ...

  8. KMP-字符串模式匹配(c++/python实现)

    KMP算法可以在O(n+m)的时间数量级上完成模式匹配,其做法在于:没当一次匹配过程中出现字符比较不等时,不需要回溯指针,而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远的一段距离后,继 ...

  9. Postman安装及简介

    Postman简介 不管web自动化测试还是APP自动化端,测试过程中都会涉及到接口测试.接口测试分为服务器端测试和客户端测试.今天给大家介绍一个测试服务器端的小工具--Postman.它可以构造各类 ...

  10. C/C++ -- Gui编程 -- Qt库的使用 -- HelloWorld

    1.纯代码写对话框HelloWorld 创建空Qt工程,添加C++源文件main.cpp 需要设置编码以支持中文 -----源代码main.cpp----- #include <QApplica ...