里氏替换原则 里氏替换
临近端午节,各位读者,你们假期行程安排好了吗?“菜鸟”已经做好决定了,谁都不能阻拦(产品经理也不行),“菜鸟”要好好在家休息三天。最近实在是太累了,一直在疯狂的加班。
好了好了言归正传,开始我们的正文。今天由“菜鸟”为大家分享一下设计模式七大原则中的“里氏替换原则”。讲该原则之前我们不得不提一下面向对象对象的三大特性,它们分别是:封装、继承、多态。相信了解面向对象编程的程序员,对这三大特性肯定不会陌生。
那么肯定就有人会产生疑问了,这三大特性跟我们今天的内容有什么关系吗?别慌且听“菜鸟”慢慢诉说,我们今天分享的“里氏替换原则”就是专门为“继承”所准备的。
我们应该都知道使用继承也是存在弊端的,它会给程序带来侵入性,并且降低程序的可移植性,还会增加对象之间的耦合性。当父类需要修改时,需要考虑到所有的子类,盲目修改父类,有可能会造成子类的功能产生异常。
为了减少这些弊端,就产生了“里氏替换原则”,该原则是1988年由麻省理工一位名为Barbara Liskov的女士提出。通过其名称,我们应该能看出来该原则的重点就在“替换”这两个字上。
该原则的核心思想就是在程序当中,如果将一个父类对象替换成它的子类对象后,该程序不会发生异常。这也是该原则希望达到的一种理想状态。
下面就让我们从四个方面来深入了解一下里氏替换原则。
第一点
讲第一点之前我们先思考在“继承”中会面临的这样一个问题:我们的父类定义好的方法,并不会强制要求其子类必须完全遵守该方法的实现规则,它是可以修改继承自父类的任意方法的。
如以下代码所示:
public class Demo {
public static void main(String[] args) {
Superclass superclass = new Superclass();
superclass.add(3, 2);
Son son = new Son();
son.add(3, 2);
}
}
/**
* 父类
*/
class Superclass {
public void add(int num1, int num2) {
int value = num1 + num2;
System.out.println(&34;父类计算出的结果为:&34; + value);
}
}
/**
* 子类
*/
class Son extends Superclass {
@Override
public void add(int num1, int num2) {
int value = num1 - num2;
System.out.println(&34;子类计算出的结果为:&34; + value);
}
}
父类的本意是想要定义一个两数相加的方法,但是子类继承该方法后却修改为减法,并且也成功了。这样操作后,会对整个继承体系造成破坏。当你想把使用父类的地方替换为其子类时,会发现原来的正常的功能现在出现问题了。
为了解决此问题,就有了里氏替换原则的第一点:
子类可以实现父类中的抽象方法,但是不能重写(覆盖)父类的非抽象方法。
如果我们严格遵守该原则就不会产生上面的问题。
第二点
当我们迫不得已需要在子类中添加一个跟父类方法名称相同的方法时,就需要遵守下面这点。
当子类需要重载父类中的方法的时候,子类方法的形参(入参)要比父类方法输入的参数更宽松(范围更广)。。
这句话的意思大家能明白吗?不是特别清楚也不用着急,请看下面的代码。
/**
* 父类
*/
public class Superclass {
public void method(ArrayList arrayList) {
System.out.println(&34;父类方法执行了!&34;);
}
}
/**
* 子类
*/
public class Son extends Superclass {
// 重载了父类的method,并且方法入参比父类的入参范围更广。
public void method(List list) {
System.out.println(&34;子类方法执行了!&34;);
}
}
那么这样做有什么意义那,目的又是为了什么那?大家可以先思考一下。
其实很简单,这样做最直接的一个目的就是防止我们将父类对象替换为子类对象后,造成方法调用混乱的问题。
请看下面测试代码和其执行结果:
public static void main(String[] args) {
ArrayList list = new ArrayList();
Superclass superclass = new Superclass();
Son son = new Son();
System.out.print(&34;使用父类对象调用的结果:&34;);
superclass.method(list);
System.out.print(&34;将父类对象替换成子类对象后的调用结果:&34;);
son.method(list);
}
执行结果如下:
如果没有遵守上面的规则的话就会造成很大的麻烦,请看下列示例代码。
public class Demo2 {
public static void main(String[] args) {
ArrayList list = new ArrayList();
Superclass superclass = new Superclass();
Son son = new Son();
System.out.print(&34;使用父类对象调用的结果:&34;);
superclass.method(list);
System.out.print(&34;将父类对象替换成子类对象后的调用结果:&34;);
son.method(list);
}
}
/**
* 父类
*/
class Superclass {
public void method(List arrayList) {
System.out.println(&34;父类方法执行了!&34;);
}
}
/**
* 子类
*/
class Son extends Superclass {
public void method(ArrayList list) {
System.out.println(&34;子类方法执行了!&34;);
}
}
测试结果如下:
确实发生问题了吧!我们的本意是希望对象替换后还执行原来的方法的,可结果却发生变化了。这样是不符合里氏替换原则的。所以我们要时刻牢记,子类方法的入参要比父类的入参范围更大,这样才不会造成不必要的错误。
第三点
当我们需要重写或者实现父类方法时,需要遵守下面这一点。
重写或者实现父类方法的时候,方法的返回值可以被缩小,但是不能放大。
示例代码:
/**
* 父类
*/
public class Superclass {
public List getList() {
return new ArrayList();
}
}
/**
* 子类
*/
public class Son extends Superclass {
@Override
public ArrayList getList() {
return new ArrayList();
}
}
如果我们试图在子类中放大,重写或实现来自父类方法的返回值时,代码会报错,连基本的编译器都无法通过。
第四点
子类可以拥有自己独特的方法或属性。
这句话就更好理解了,它说明子类不光可以拥有从父类继承来的东西,也可以进行自我扩展,编写属于自己的东西。
这一点相信大家都明白,我就不在编写示例代码了。
总结
通过上面的描述相信大家都对里氏替换原则有了一个基本的概念,其实它就是告诉我们在继承中需要注意什么问题和遵守什么规则。
然而在实际开发中我们在很多时候还是会违背该原则的,虽然表面上没有什么特别大的问题,但是这样做会大大增加代码的出错率。我们编写代码时不光要考虑怎么实现该功能,程序的健壮性和后期的扩展以及移植都是需要考虑到的。只有这样做才可以使我们的程序更加优秀。
好了今天的分享就到这里了,如果感觉“菜鸟”的文章写的还不错的,记得点赞加关注呦!你们的支持就是我坚持下去的动力。