在学习里氏替换原则之前,首先应该理解继承的概念与好处,我们知道在java程序设计语言
中继承是通过extends关键字实现的,那么继承到底有哪些优点呢?
1、实现了代码共享
2、提高了代码的重用性
3、提高了代码的可扩展性
不过有利必有弊,继承虽然带来了很大的好处,但同时也有一定的弊病:
1、继承是侵入性的,只要继承,就必须拥有父类的所有属性和方法。
3、增强了耦合性,但父类的属性被修改的时候,必须考虑到对子类的影响,使得代码
需要重构。
再来看看里氏替换原则的定义吧!
里氏替换原则有两种定义:
第一种定义,也是正宗的定义:如果对每一个类型为T1的对象o1,都有类型为T2的
对象o2 使得在程序中所有未o1的对象都替换为o2的时候,程序的行为没有发生变换,则
T2是T1的子类型。
第二种定义:所有可以使用父类的地方,都可以透明的使用其子类.通俗的讲就是父类
出现的地方,都可以用子类替换,程序不会出错,且程序的行为与替换之前一样!
如何理解里氏替换原则呢?首先举个经典的例子吧!
1、"正方形不是长方形".
我们知道在数学中正方形就是长方形,因此上述说法貌似不正确,不过真是
这样吗?看一段代码先吧。
长方形类
package com.kiritor; /** * @author Kiritor * 长方形 */ public class Rectangle { private double length; private double width; public double getLength() { return length; } public void setLength(double length) { this.length = length; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } }正方形类:
package com.kiritor; /** * @author Kiritor 正方形 */ public class Square extends Rectangle { public void setWidth(double width) { super.setLength(width); super.setWidth(width);//设置正方形的宽 } public void setLength(double length) { super.setLength(length); super.setWidth(length);//设置正方形的长 } }好了,这个继承关系写好了,考虑 到这样一种情况:在客户端需要模拟长方形的宽
逐渐增大的需求的情况
package com.kiritor; public class Test { public void resize(Rectangle objRect) { while(objRect.getWidth() <= objRect.getLength() ) { objRect.setWidth( objRect.getWidth () + 1 ); } } }好了,按照里氏替换原则表明长方形对象是可以使用子类对象替换的也就是正方形
对象,但是具体如何呢?程序的行为产生了变化,陷入了死循环中?因此不符合里氏替换
原则。他们的继承关系不成立!
这也就照成了"正方形即是长方形也不是长方形"的悖论!不过造成这种混乱的到底
是什么原因呢?
●对类继承的关系的定义不明确。
在面向对象的程序设计中,我们关注的是对象 的"行为",使用行为对类进行
分类,也就是"行为"上的"is-a"关系!
●具体的设计要依赖用户需求和实际环境。
如果客户没有"长方形宽逐渐增大"的需求,而仅仅是对其面积等做一下计算
的话,就不会造成"正方形不是长方形"的悖论了,也就不会违反里氏替换原则。
上述两点也是正确运用里氏替换原则所要注意的地方!
说了这么多,到底历史替换原则有什么优点呢?
里氏替换原则增强了程序的健壮性,使程序传递不同的子类完成不同的逻辑
而不产生错误.(这点是很关键的!)
其次里可以看出里氏替换原则其实也就是对继承的一种规范,实现抽象化
的一种规范。这也是实现开闭原则的关键,至于什么事开闭原则,后续学习。
里氏替换原则规定了:若是一个继承类的对象可能会在基类的对象出现的地方
运行错误,则需要重新审视该继承关系,重新设计他们的关系。
符合里氏替换原则的类扩展后不会给已有的系统带来错误