里氏替换原则

定义:在面向对象的程序设计中,里氏替换原则(Liskov Substitution principle)是对子类型的特别定义。里氏替换原则的内容可以描述为: “派生类(子类)对象可以在程序中代替其基类(超类)对象。”

原理:在软件开发过程中,若子类重写了父类方法,当用子类代替父类时就会出现逻辑不一致的问题。

问题由来:类A实现了方法a,而其子类A1覆写了该方法,则当子类出现在父类定义的类型时可能会出错(针对继承时),如下:

public class Main {

    public static void main(String[] args) {
        A[] as = new A[]{
                new A(), new A1()
        };
        for (A a : as) {
            a.a();
        }
    }

}

class A {
    public void a() {
        System.out.println("A#a");
    }
}

class A1 extends A {
    @Override
    public void a() {
        System.out.println("A1#a");
    }
}
output
A#a
A1#a

可以看到,数组中的第二个对象是A1的实例,而A1覆写来了方法a,此时虽然定义的类型是A,到那时由于A1是A的子类,其是可以替换类型A的对象的,而由于方法a被覆写,输出内容超出了预想内容。

产生原因:软件开发时类型把控不严格可能会导致此问题

解决办法:

  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  2. 子类可以增加自己特有的方法
  3. 当子类的方法重载父类的方法时,方法的形参要比父类方法的输入参数更宽松
  4. 当子类的方法实现父类的抽象方法时,方法的返回值应比父类更严格

应用场景:

针对继承时

如果继承是为了实现代码重用时,那么共享的父类方法应该保持不变,不能被子类重新定义,为了避免出现此类问题,我们应该在共享的父类方法中加上final字段,防止子类不小心覆写了共享的父类方法。子类只能通过新添加方法来扩展功能,那么上面的代码应该修改为如下形式:

public class Main {

    public static void main(String[] args) {
        A[] as = new A[]{
                new A(), new A1()
        };
        for (A a : as) {
            a.a();
        }
    }

}

class A {
    public final void a() {
        System.out.println("A#a");
    }
}

class A1 extends A {
    public void a1() {
        System.out.println("A1#a1");
    }
}

A#a
A#a

可以看到,此时即便在A类的数组中实例化的时A1,我们调用方法a时的逻辑还是A的逻辑,而A1需要扩展功能我们则新添加一个方法a1,让其实现A1需要扩展的功能。

针对多态时

如果继承的目的时为了多态,而多态的前提是子类覆盖并重新定义父类方法,为了符合里氏替换原则,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类是,父类就不能实例化,所以也不存在可实例化的父类对象在程序里。也就规避了子类替换父类实例时逻辑不一致的问题。如下:

public class Main {

    public static void main(String[] args) {
        A[] as = new A[]{new A1(), new A2()};
        for (A a : as) {
            a.a();
        }
    }

}

abstract class A {
    abstract void a();
}

class A1 extends A {
    @Override
    void a() {
        System.out.println("A1#a");
    }
}

class A2 extends A {
    @Override
    void a() {
        System.out.println("A2#a");
    }
}
A1#a
A2#a

此时A类的定义是为了多态,同时我们将A类设为了抽象类并将方法a设为抽象方法,因此A类不可以被实例化且所有子类都必须实现该方法,所以也就不存在父类逻辑,因此也就规避了子类与父类逻辑不一致的可能。注:若方法较多,且让子类选择实现时可以不设为抽象方法,单父类必须设为抽象类,且需要子类实现的方法必须为空方法。此时需要共享的方法要添加上final关键字,防止子类不小心实现了共享方法。若我们希望某类不能被继承时也需要将类设为final类。

小提示:final 关键字很重要,在必要时不要忘了加上它。添加final关键字时不仅可以提高代码可读性同时也可以增加代码的性能。

你可能感兴趣的:(里氏替换原则)