zoukankan      html  css  js  c++  java
  • 里氏替换原则

    里氏替换原则:

          面向对象有三大特性:封装、继承、多态。所以我们在实际开发过程中,子类在继承父类后,根据多态的特性,可能是图一时方便,经常任意重写父类的方法,那么这种方式会大大增加代码出问题的几率。比如下面场景:类C实现了某项功能F1。现在需要对功能F1作修改扩展,将功能F1扩展为F,其中F由原有的功能F1和新功能F2组成。新功能F由类C的子类C1来完成,则子类C1在完成功能F的同时,有可能会导致类C的原功能F1发生故障。这时候里氏替换原则就闪亮登场了。

    四层含义:

    • 子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
    • 子类中可以增加自己特有的方法。
    • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
    • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

    子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法

      在我们做系统设计时,经常会设计接口或抽象类,然后由子类来实现抽象方法,这里使用的其实就是里氏替换原则。子类可以实现父类的抽象方法很好理解,事实上,子类也必须完全实现父类的抽象方法,哪怕写一个空方法,否则会编译报错。

      里氏替换原则的关键点在于不能覆盖父类的非抽象方法。父类中凡是已经实现好的方法,实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些规范,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

      在面向对象的设计思想中,继承这一特性为系统的设计带来了极大的便利性,但是由之而来的也潜在着一些风险。就像开篇所提到的那一场景一样,对于那种情况最好遵循里氏替换原则,类C1继承类C时,可以添加新方法完成新增功能,尽量不要重写父类C的方法。否则可能带来难以预料的风险,比如下面一个简单的例子还原开篇的场景:

    public class C {
        public int fun1(int a, int b){
            return a+b;
        }
    }
     
    public class C1 extends C{
        @Override
        public int fun1(int a, int b) {
            return a-b;
        }
    }
     
    public class Client{
        public static void main(String[] args) {
            C c = new C1();
            System.out.println("2+1=" + c.fun1(2, 1));
        }
    }
    View Code

    运行结果:2+1=1

      上面的运行结果明显是错误的。类C1继承C,后来需要增加新功能,类C1并没有新写一个方法,而是直接重写了父类C的fun1方法,违背里氏替换原则,引用父类的地方并不能透明的使用子类的对象,导致运行结果出错。

    子类中可以增加自己特有的方法

           在做功能拓展的时候,子类尽量不要重写父类的非抽象方法。我们可以增加新的方法。

    public class C1 extends C{
        public int func2(int a, int b) {
            return a-b;
        }
    }

    当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

    import java.util.HashMap;
    public class Father {
        public void func(HashMap m){
            System.out.println("执行父类...");
        }
    }
     
    import java.util.Map;
    public class Son extends Father{
        public void func(Map m){//方法的形参比父类的更宽松
            System.out.println("执行子类...");
        }
    }
     
    import java.util.HashMap;
    public class Client{
        public static void main(String[] args) {
            Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
            HashMap h = new HashMap();
            f.func(h);
        }
    }
    View Code

    运行结果:执行父类...,然而如果互换了输入参数,如下所示:

    import java.util.HashMap;
    public class Father {
        public void func(Map m){
            System.out.println("执行父类...");
        }
    }
     
    import java.util.Map;
    public class Son extends Father{
        public void func(HashMap m){//方法的形参比父类的更宽松
            System.out.println("执行子类...");
        }
    }
     
    import java.util.HashMap;
    public class Client{
        public static void main(String[] args) {
            Father f = new Son();//引用基类的地方能透明地使用其子类的对象。
            HashMap h = new HashMap();
            f.func(h);
        }
    }
    View Code

    运行结果:执行子类,此时,子类方法被执行了,这会引起业务逻辑混乱,因为在实际开发中,父类一般是抽象类,子类是实现类,传递这样一个实现类,就会歪曲了父类的意图。因此,子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。。

    总结:

          采用里氏替换原则的目的是增强程序的健壮性,版本升级也可以保持好的兼容性。即使增加子类

    转载请注明出处:http://www.cnblogs.com/jiansen/
  • 相关阅读:
    密码学复习
    Kafka Stream 高级应用
    Kafka Stream 流和状态
    Kafka Stream 处理器API
    SSM工作流程与原理详解
    Seata AT和XA模式
    分布式锁结合SpringCache
    使用RabbitMQ最终一致性库存解锁
    使用Springboot+SpringCloud+Seata1.3.0+Nacos1.2.1进行全局事务管理
    在微服务环境下,远程调用feign和异步线程存在请求数据丢失问题
  • 原文地址:https://www.cnblogs.com/jiansen/p/7343893.html
Copyright © 2011-2022 走看看