里氏替换原则LSP

里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计原则的一种,也叫里氏代换原则。里氏替换原则是关于继承的一个原则,遵循里氏替换原则能够更好地发挥继承的作用,里氏替换原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的。

里氏替换原则

里氏替换原则问题由来

有一功能 P1,由类 A 完成。现需要将功能 P1 进行扩展,扩展后的功能为 P,其中P由原有功能 P1 与新功能 P2 组成。新功能 P 由类 A 的子类 B 来完成,则子类 B 在完成新功能 P2 的同时,有可能会导致原有功能 P1 发生故障。

里氏替换原则是什么

里氏替换原则的官方定义

  • 如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型

  • 所有引用基类的地方必须能透明地使用其子类的对象

里氏替换原则解读

下面我们来解读下里氏替换原则的定义。

里氏替换原则强调的是设计和实现要依赖于抽象而非具体;子类只能去扩展基类,而不是隐藏或者覆盖基类,它包含以下4层含义

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

子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法,父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏

举例

public class C {
    public int func(int a, int b){
        return a+b;
    }
}

public class C1 extends C{
    @Override
    public int func(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.func(2, 1));
    }
}

运行结果:2+1=1

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

子类可以有自己的个性

在继承父类属性和方法的同时,每个子类也都可以有自己的个性,在父类的基础上扩展自己的功能。前面其实已经提到,当功能扩展时,子类尽量不要重写父类的方法,而是另写一个方法,所以对上面的代码加以更改,使其符合里氏替换原则,代码如下:

public class C {
    public int func(int a, int b){
        return a+b;
    }
}

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

public class Client{
    public static void main(String[] args) {
        C1 c = new C1();
        System.out.println("2-1=" + c.func2(2, 1));
    }
}

运行结果:2-1=1

覆盖或实现父类的方法时输入参数可以被放大

当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松

通过代码来讲解一下

public class ParentClazz {
    public void say(CharSequence str) {
        System.out.println("parent execute say " + str);
    }
}

public class ChildClazz extends ParentClazz {
    public void say(String str) {
        System.out.println("child execute say " + str);
    }
}

/**
 * 测试
 */
public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");

    }
}

执行结果:
parent execute say hello
child execute say hello

以上代码中我们并没有重写父类的方法,只是重载了同名方法,具体的区别是:子类的参数 String 实现了父类的参数 CharSequence。此时执行了子类方法,在实际开发中,通常这不是我们希望的,父类一般是抽象类,子类才是具体的实现类,如果在方法调用时传递一个实现的子类可能就会产生非预期的结果,引起逻辑错误,根据里氏替换的子类的输入参数要宽于或者等于父类的输入参数,我们可以修改父类参数为String,子类采用更宽松的 CharSequence,如果你想让子类的方法运行,就必须覆写父类的方法。代码如下:

public class ParentClazz {
    public void say(String str) {
        System.out.println("parent execute say " + str);
    }
}

public class ChildClazz extends ParentClazz {
    public void say(CharSequence str) {
        System.out.println("child execute say " + str);
    }
}

public class Main {
    public static void main(String[] args) {
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");
    }
}

执行结果:
parent execute say hello
parent execute say hello

覆写或实现父类的方法时输出结果可以被缩小

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

代码实现案例如下

public abstract class Father {
    public abstract Map hello();
}

public class Son extends Father {
    @Override
    public Map hello() {
        HashMap map = new HashMap();
        System.out.println("son execute");
        return map;
    }
}

public class Main {
    public static void main(String[] args) {
        Father father = new Son();
        father.hello();
    }
}

执行结果:
son execute

里氏替换原则优点

保证了父类的复用性,同时也能够降低系统出错误的故障,防止误操作,同时也不会破坏继承的机制,这样继承才显得更有意义。

增强程序的健壮性,版本升级是也可以保持非常好的兼容性.即使增加子类,原有的子类还可以继续运行.在实际项目中,每个子类对应不同的业务含义,使用父类作为参数,传递不同的子类完成不同的业务逻辑,完美!

里氏替换原则总结

继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

里氏替换原则的目的就是增强程序健壮性,版本升级时也可以保持非常好的兼容性。

有人会说我们在日常工作中,会发现在自己编程中常常会违反里氏替换原则,程序照样跑的好好的。所以大家都会产生这样的疑问,假如我非要不遵循里氏替换原则会有什么后果?后果就是:你写的代码出问题的几率将会大大增加。

极客教程为大家详细介绍了面向对象的7大设计原则,可以参考

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程