SOLID设计原则

一、里氏替换原则

里氏替换原则主要阐述了有关继承的一些原则,也就是什么时候应该使用继承,什么时候不应该使用继承,以及其中蕴含的原理

1.1 作用

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

1.2 实现方法

通俗理解:只要有父类出现的地方,都可以用子类来替代,里氏替换原则对继承进行了规则上的约束,有以下4个方面

  1. 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法
  2. 子类中可以增加自己特有的方法
  3. 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松
  4. 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格

1.3 应用实例

子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法

⚠️ 如果子类覆写了父类中的非抽象方法会带来什么后果?

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 {
    @Override
    public void setSpeed(double speed) {
        flySpeed = 0;
    }
}

️ 上述实例代码中,BrownKiwi没有翅膀不会飞,导致程序执行发生错误,违背了历史替换原则,正确做法应该是定义BirdBrownKiwi的更一般的类animalBird extends AnimalBrownKiwi extends AnimalSwallow extends Bird,并重新定义方法(例如动物都有runTime、runSpeed)。

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

当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松,这样才满足里氏替换原则

正确示例

public class A {
    public void fun(HashMap map) {
        System.out.println("父类被执行...");
    }
}

class B extends A {
    public void fun(Map map) {
        System.out.println("子类被执行...");
    }
}

// 子类的方法参数Map比父类的HashMap参数更宽松,满足里氏替换原则,只有父类的fun方法会被执行
class demo {
    public static void main(String[] args) {
        A a = new A();
        HashMap<Integer, Integer> map = new HashMap();
        a.fun(map); // 父类被执行...
        B b = new B(); //父类存在的地方,可以用子类替代,子类B替代父类A
        b.fun(map); // 父类被执行...
    }
}

❓ 此时可能会有一个疑问,上述子类B代码中fun(Map map)函数什么时候调用呢?

A a = new A();
Map<Integer, Integer> map = new HashMap();
B b = new B();
b.fun(map); // 子类被执行...
// 此时如果调用a.fun(map); 编译器会报错

错误示例

public class A {
    public void fun(Map map) {
        System.out.println("父类被执行...");
    }
}

class B extends A {
    public void fun(HashMap map) {
        System.out.println("子类被执行...");
    }
}

// 子类的方法参数Map比父类的HashMap参数更宽松,不满足里氏替换原则
// 两次调用fun(),分别执行了父类和子类的函数,引起了程序逻辑的混乱
class demo {
    public static void main(String[] args) {
        A a = new A();
        HashMap<Integer, Integer> map = new HashMap();
        a.fun(map); // 父类被执行...
        B b = new B(); //父类存在的地方,可以用子类替代,子类B替代父类A
        b.fun(map); // 子类被执行...
    }
}

当子类的方法实现父类的(抽象)方法时,方法的后置条件(即方法的返回值)要与父类一致或更严格

public abstract class A {
    public abstract Map fun();
}
 
class B extends A{
    @Override
    public HashMap fun(){
        HashMap b=new HashMap();
        b.put("b","子类被执行...");
        return b;
    }
}

class demo {
    public static void main(String[] args){
        A a=new B();
        System.out.println(a.fun());
    }
}

️ 里氏替换原则不是不能override,而是要按照父类所期望的那样去override。这告诉我们,继承也是可以使用的,不过要熟悉相关说明。

原文地址:https://www.cnblogs.com/ffopen/p/15550168.html