一、定义
我们平时说的继承,它有另一层含义
- 父类中已实现的方法实际上是在设定规范,虽然不要所有子类都遵守这些规范,但是子类如果对这些已实现的方法任意修改,就会对整个继承体系造成破坏。
- 继承有优点,也有缺点。如果一个类被其它类所继承,当这个类需要修改时,不仅要考虑所有的子类,且在修改后相关子类的功能可能会出现问题。
里氏替换原则定义
- 定义一 : 现在有类
Class1
的对象obj1
,类Class2
的对象obj2
,将某个程序中所有的obj1
替换成obj2
后,这个程序的行为没有发生变化,说明Class2
是Class1
的子类。 - 定义二 : 所有引用基类(父类)的地方必须能透明地使用其子类对象。
二、应用举例
我们使用一个加减法的例子来说明
2.1 代码一
创建 A类
,使其实现减法功能
class A {
public int fun1(int a, int b) {
return a - b;
}
}
public class Client {
public static void main(String[] args) {
A a = new A();
System.out.println("100-50 = " + a.fun1(100, 50));
}
}
运行结果为 : 100 - 50 = 50
2.2 代码二
现在添加一个新功能,使两数相加后再与100相加,且这个功能由 B类
实现,所以这里我们可以使 B类
直接继承 A类
后再增加新功能就可以了。
class A {
public int fun1(int a, int b) {
return a - b;
}
}
class B extends A {
public int fun1(int a, int b) {
return a + b;
}
public int fun2(int a, int b) {
return fun1(a, b) + 100;
}
}
public class Client {
public static void main(String[] args) {
B b = new B();
System.out.println("100-50 = " + b.fun1(100, 50));
// 结果50
}
}
运行结果为: 100-50 = 150
可以看到,我们在 B类
中给方法起名时,无意间重写了父类的方法,因为 A、B类
是继承关系,调用者因为想使用减法功能而调用了 B类
中重写后的方法 func1()
时,就会造成功能异常。
2.3 代码三
我们可以创建一个更通俗的基类 Base
,使 A、B类
都继承这个基类,这样 A、B类
就不存在继承关系了,调用者也不会像之前那样调用了重写的方法。
// 基类
class Base {
}
class A extends Base {
public int func1(int a, int b) {return a - b;}
}
class B extends Base {
private A a = new A();// 组合关系
// 两数相加
public int func1(int a, int b) {
return a + b;
}
// 两数相加后再加100
public int func2(int a, int b) {
return this.func1(a, b) + 100;
}
// 使用A类中的减法
public int func3(int a, int b) {
return this.a.func1(a, b);
}
}
public class Client {
public static void main(String[] args) {
B b = new B();
System.out.println(b.func1(10, 20)); // 两数相加 =>30
System.out.println(b.func2(20, 30)); // 两数相加+100 =>150
System.out.println(b.func3(100, 10)); // 两数相减 =>90
}
}
由于B类不再继承A类,因此调用者不会再以为 func1()
是减法运算了。
三、注意事项和细节
在实际编程中,通过重写父类方法的方法实现新功能确实很简单,但是会降低整个体系的可复用性(特别是频繁使用多态时),所以我们可以对其进行改造 :
- 使原来的父类和子类都继承一个更通俗的基类;
- 去掉原来的继承方式,采用依赖、聚合、组合等方式来替代;
- 里氏替换原则通俗地讲就是:子类可以拓展父类的功能,但是不能改变父类原有的功能。
个人觉得,
子类不能改变父类原有的功能
,指的是不能改变父类方法的用途,但其细节可以改变。
例如:类A有一个排序方法sort()
,用途是排序,细节上使用的是冒泡排序;子类B继承类A,重写方法sort()
,用途不变还是排序,但细节上采用了快排方式。