1、什么是里氏替换原则

        该原则的核心思想就是在程序当中,如果将一个父类对象替换成它的子类对象后,该程序不会发生异常。这也是该原则希望达到的一种理想状态。

通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。

里氏代换原则是开闭原则的重要方式之一,由于使用父类对象的地方都可以使用子类对象,因此在程序中尽量使用父类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象。

优点:

1、代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;

里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。2、提高代码的重用性;
3、子类可以形似父类,但又异于父类,“龙生龙,凤生凤,老鼠生来会打洞”是说子拥有父的“种”,4、“世界上没有两片完全相同的叶子”是指明子与父的不同;
5、提高代码的可扩展性,实现父类的方法就可以“为所欲为”了,君不见很多开源框架的扩展接口他都是通过继承父类来完成的;
6、提高产品或项目的开放性。

缺点:

1、继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
2、降低代码的灵活性。子类必须拥有父类的属性和方法,让子类自由的世界中多了些约束;
3、增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的结果————大段的代码需要重构。

2、使用说明

        里氏替换原则通俗来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

        例如:鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期,其类图如图 1 所示。


 代码如下:

package com.example.demo.principle;

public class LSPtest {
    public static void main(String[] args) {
        Bird bird1 = new Swallow();
        Bird bird2 = new BrownKiwi();
        bird1.setSpeed(120);
        bird2.setSpeed(120);
        System.out.println("如果飞行300公里:");
        try {
            System.out.println("燕子将飞行" + bird1.getFlyTime(300) + "小时.");
            System.out.println("几维鸟将飞行" + bird2.getFlyTime(300) + "小时。");
        } catch (Exception err) {
            System.out.println("发生错误了!");
        }
    }
}

//鸟类
class Bird {
    double flySpeed;

    public void setSpeed(double speed) {
        flySpeed = speed;
    }

    public double getFlyTime(double distance) {
        return (distance / flySpeed);
    }
}

//燕子类
class Swallow extends Bird {
}

//几维鸟类
class BrownKiwi extends Bird {
    public void setSpeed(double speed) {
        flySpeed = 0;
    }

 
}

------------------   运行结果   --------------------------

如果飞行300公里:
燕子将飞行2.5小时.
几维鸟将飞行Infinity小时。

Process finished with exit code 0

这个设计存在的问题是:

    1、几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。

    2、燕子和几维鸟都是鸟类,但是父类抽取的共性有问题,几维鸟的的飞行不是正常鸟类的功能,需要特殊处理,应该抽取更加共性的功能。

正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。其类图如图 2 所示。

这样设计虽然增加了一个动物类,但是我的子类仅仅是继承父类,不去重写父类的方法。这样在代码层面,父类若是替换成子类,那么程序仍然能够正常运行。

代码如下:

package com.example.demo.principle;

public class Lsptest2 {
    public static void main(String[] args) {

        Animal animal1 = new Bird();
        Animal animal2 = new BrownKiwi();
        animal1.setRunSpeed(120);
        animal2.setRunSpeed(180);
        System.out.println("如果奔跑300公里:");
        try {
            System.out.println("鸟类将奔跑" + animal1.getRunSpeed(300) + "小时.");
            System.out.println("几维鸟将奔跑" + animal2.getRunSpeed(300) + "小时。");
            Bird bird = new Swallow();
            bird.setFlySpeed(150);
            System.out.println("如果飞行300公里:");
            System.out.println("燕子将飞行" + bird.getFlyTime(300) + "小时.");
        } catch (Exception err) {
            System.out.println("发生错误了!");
        }
    }
}
    /**
     * 动物类,抽象的功能更加具有共性
     */
       class  Animal{
        Double runSpeed;

        public void setRunSpeed(double runSpeed) {
            this.runSpeed = runSpeed;
        }

        public double getRunSpeed(double distince) {
        return distince/runSpeed;
        }
    }

    /**
     * 鸟类继承动物类
     */
    class Bird extends Animal{
           double flySpeed;

        public void setFlySpeed(double flySpeed) {
            this.flySpeed = flySpeed;
        }


        public double getFlyTime(double distince) {
            return distince/flySpeed;
        }
    }


    /**
     * 几维鸟继承动物类
     */
    class  BrownKiwi extends  Animal{

    }

    /**
     * 燕子继承鸟类  飞行属于燕子的特性,
     */
  class Swallow extends  Bird{

  }


---------   运行结果  -----------------
如果奔跑300公里:
鸟类将奔跑2.5小时.
几维鸟将奔跑1.6666666666666667小时。
如果飞行300公里:
燕子将飞行2.0小时.

3、总结

里氏替换原则的主要作用如下。

  1. 里氏替换原则是实现开闭原则的重要方式之一。
  2. 它克服了继承中重写父类造成的可复用性变差的缺点。
  3. 它是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性。
  4. 加强程序的健壮性,同时变更时可以做到非常好的兼容性,提高程序的维护性、可扩展性,降低需求变更时引入的风险。
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐