设计模式学习--面向对象的5条设计原则之Liskov替换原则--LSP

简介: 一、LSP简介(LSP--Liskov Substitution Principle): 定义:如果对于类型S的每一个对象o1,都有一个类型T的对象o2,使对于任意用类型T定义的程序P,将o2替换为o1,P的行为保持不变,则称S为T的一个子类型。
一、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、从派生类抛出异常:如果在派生类的方法中添加了其基类不会抛出的异常。如果基类的使用者不期望这些异常,那么把他们添加到派生类的方法中就可以能会导致不可替换性。
 
 
 
 
目录
相关文章
|
设计模式 数据库连接 PHP
PHP编程中的面向对象与设计模式
在PHP编程世界中,掌握面向对象编程(OOP)和设计模式是提升代码质量和开发效率的关键。本文将深入浅出地介绍如何在PHP中应用OOP原则和设计模式,以及这些实践如何影响项目架构和维护性。通过实际案例,我们将探索如何利用这些概念来构建更健壮、可扩展的应用程序。
|
9月前
|
设计模式 架构师 Java
设计模式觉醒系列(01)设计模式的基石 | 六大原则的核心是什么?
本文介绍了设计模式的六大原则,包括单一职责原则(SRP)、开闭原则(OCP)、里氏替换原则(LSP)、接口隔离原则(ISP)、依赖倒置原则(DIP)和迪米特法则。通过具体案例分析了每个原则的应用场景及优势,强调了这些原则在提升代码可维护性、可复用性、可扩展性和降低耦合度方面的重要作用。文章指出,设计模式的核心在于确保系统模块间的低耦合高内聚,并为后续深入探讨23个经典设计模式打下基础。
|
10月前
|
设计模式 存储 关系型数据库
「全网最细 + 实战源码案例」设计模式——六大设计原则
本文介绍了面向对象设计中的六大原则,旨在提高软件系统的可维护性、可复用性和可拓展性。这些原则包括:开闭原则(OCP)、里氏代换原则(LSP)、依赖倒转原则(DIP)、接口隔离原则(ISP)、迪米特法则(LoD)和合成复用原则(CARP)。每项原则通过具体示例展示了如何通过抽象、多态、组合等方式降低耦合度,增强系统的灵活性与稳定性,从而提升开发效率并降低成本。
269 10
|
11月前
|
设计模式 Java 程序员
【23种设计模式·全精解析 | 概述篇】设计模式概述、UML图、软件设计原则
本系列文章聚焦于面向对象软件设计中的设计模式,旨在帮助开发人员掌握23种经典设计模式及其应用。内容分为三大部分:第一部分介绍设计模式的概念、UML图和软件设计原则;第二部分详细讲解创建型、结构型和行为型模式,并配以代码示例;第三部分通过自定义Spring的IOC功能综合案例,展示如何将常用设计模式应用于实际项目中。通过学习这些内容,读者可以提升编程能力,提高代码的可维护性和复用性。
2412 1
【23种设计模式·全精解析 | 概述篇】设计模式概述、UML图、软件设计原则
|
设计模式 Java 关系型数据库
设计模式——设计模式简介和七大原则
设计模式的目的和核心原则、单一职责原则、接口隔离原则、依赖倒转原则、里氏替换原则、开闭原则、迪米特法则、合成复用原则
设计模式——设计模式简介和七大原则
|
设计模式 Java 测试技术
Java设计模式-UML与设计原则(1)
Java设计模式-UML与设计原则(1)
134 1
|
6月前
|
设计模式 Java 数据库连接
【设计模式】【创建型模式】工厂方法模式(Factory Methods)
一、入门 什么是工厂方法模式? 工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类决定实例化哪个类。工厂方法模式使类的实例化延迟
200 16
|
6月前
|
设计模式 负载均衡 监控
并发设计模式实战系列(2):领导者/追随者模式
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发设计模式实战系列,第二章领导者/追随者(Leader/Followers)模式,废话不多说直接开始~
207 0
|
6月前
|
设计模式 监控 Java
并发设计模式实战系列(1):半同步/半异步模式
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发设计模式实战系列,第一章半同步/半异步(Half-Sync/Half-Async)模式,废话不多说直接开始~
192 0
|
6月前
|
设计模式 安全 Java
并发设计模式实战系列(12):不变模式(Immutable Object)
🌟 大家好,我是摘星!🌟今天为大家带来的是并发设计模式实战系列,第十二章,废话不多说直接开始~
163 0