zoukankan      html  css  js  c++  java
  • 02.里氏替换原则 (LSP)

    LSP全称

    LSP , Liskov Substitution Principle , 里氏替换原则

    定义

    所有引用基类(父类)的地方必须能透明地使用其子类的对象。通俗讲:子类可以扩展父类的功能,但不能改变父类原有的功能

    优点

    1. 是实现开闭原则的重要方式之一
    2. 克服了继承中重写父类造成的可用性变差的缺点
    3. 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性

    缺点

    1. 继承是侵入性的,只要继承就必须拥有父类的所有属性和方法
    2. 可能造成子类代码冗余,灵活性降低,因为必须拥有父类的属性和方法

    实现

    • 问题由来: 有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
    • 解决方案:
      当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

    里氏替换原则的核心原理是抽象,抽象又依赖于继承这个特性

    1. 子类必须完全实现父类的方法
    2. 子类可以有自己的修改(重写,重载,添加父类中没有的方法)
    3. 子类中override的方法,传入参数类型必须是与父类相同类型,或是子类型
    4. 子类中override的方法,返回类型必须是与父类相同类型,或是子类型

    实例

    举例说明两个数相减,由A类负责

    class A {
        public int func1(int a, int b) {
            return a-b;
        }
    }
    
    public class LSPClient {
        public static void main(String[] args) {
            A a = new A();
            System.out.println("5-3=" + a.func1(5, 3));
            System.out.println("100-10=" + a.func1(100, 10));
        }
    }
    
    输出结果:
    5 - 3 = 2
    100 - 10 = 90
    

    此时需要新加一个功能:两数相加,然后再与100求和,由B类负责

    class A {
        public int func1(int a, int b) {
            return a - b;
        }
    }
    
    class B extends A {
        @Override
        public int func1(int a, int b) {
            return a + b;
        }
    
        public int func2(int a, int b) {
            return func1(a, b) + 100;
        }
    }
    
    public class LSPClient {
        public static void main(String[] args) {
            B b = new B();
            System.out.println("5-3=" + b.func1(5, 3));
            System.out.println("100-10=" + b.func1(100, 10));
            System.out.println("50+20+100=" + b.func2(50, 20));
        }
    }
    
    输出结果:
    5-3=8
    100-10=110
    50+20+100=170
    

    此时发现原本运行正常的相减功能发生了错误,原因是子类B重写了父类A中的func1方法,从两数相减变成了两数相加。引用基类A完成的功能,换成子类B之后,发生了异常。在实际编程中,我们常常会通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的几率非常大。如果非要重写父类的方法,比较通用的做法是:原来的父类和子类都继承一个更通俗的基类,原有的继承关系去掉,采用依赖、聚合,组合等关系代替。

    里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。包含以下4层含义:

    1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
    2. 子类中可以增加自己特有的方法。
    3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
      当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
    4. 看上去很不可思议,因为我们会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?后果就是:你写的代码出问题的几率将会大大增加。
  • 相关阅读:
    C/C++中volatile关键字详解(转)
    Spring中 @Autowired标签与 @Resource标签 的区别(转)
    [转]各种互斥量的总结
    nginx限制ip访问(转)
    HDU 4833 Best Financing (DP)
    HDU 4832 Chess (DP)
    HDU 4831 Scenic Popularity
    POJ 2155 Matrix (二维线段树)
    POJ 2155 Matrix (二维树状数组)
    HDU 4819 Mosaic (二维线段树)
  • 原文地址:https://www.cnblogs.com/lwcode6/p/13954911.html
Copyright © 2011-2022 走看看