在面向对象的程序设计中,里氏替换原则(Liskov Substitution principle)是对子类型的特别定义。它由芭芭拉·利斯科夫(Barbara Liskov)在1987年在一次会议上名为“数据的抽象与层次”的演说中首先提出。
里氏替换原则的内容可以描述为: “派生类(子类)对象可以在程序中代替其基类(超类)对象。” 以上内容并非利斯科夫的原文,而是译自罗伯特·马丁(Robert Martin)对原文的解读。
芭芭拉·利斯科夫与周以真(Jeannette Wing)在1994年发表论文并提出以上的Liskov代换原则。
简单来说,子类可以扩展父类的功能,但不能改变父类原有的功能,也就是说,在子类继承父类时,除添加新的方法且完成新增功能外,尽量不要重写父类的方法。
灵珠和魔丸本是一体,被元始天尊提炼了出来,灵珠将为人民服务,魔丸则会危害世界,所以元始天尊将这个艰巨的任务交给太乙真人,让灵珠投胎到“根正苗红”的李靖家,魔丸动用天劫咒,三年后摧毁。
可是阴差阳错,灵珠被调包,本来可以为民除害的哪吒成了魔,而魔族敖丙出身却成了服务人民的苗子。
package com.guor.principle.animals;
public class Nezha {
/**
* 名号
*/
public void nickName(){
System.out.println("八臂哪吒");
}
/**
* 师傅
*/
public void master(){
System.out.println("太乙真人");
}
/**
* 混元珠
*/
public void MixedYuanzhu(){
System.out.println("魔丸");
}
}
既然功能相同,敖丙继承哪吒,就可以了。
package com.guor.principle.animals;
public class Aobing extends Nezha{
/**
* 名号
*/
public void nickName(){
System.out.println("三太子");
}
/**
* 师傅
*/
public void master(){
System.out.println("申公豹");
}
/**
* 混元珠
*/
public void MixedYuanzhu(){
System.out.println("灵珠");
}
}
敖丙继承哪吒之后,进行方法重写。
这种继承父类的优点是复用了父类的核心功能逻辑,但是也破坏了原有的方法,此时也破坏了原有的方法。此时继承父类实现的敖丙并不满足里氏替换原则,也就是说,此时的子类不能承担原父类的功能,直接给哪吒用,因为称号、师傅、混元珠也都也不一样嘛。
哪吒和敖丙都有称号、师傅、混元珠,在《封神榜》中也有很多共同特点和经历,实现这样的类的最好方式是提取出一个抽象类,由抽象类定义所有人物的共同核心属性、逻辑。
在抽象类银行卡类中,提供了人物的基本信息,包括id、姓名,姓名、武力值以及三个基本的方法。接下来继承这个实现类,实现哪吒和敖丙的技能逻辑。
package com.guor.principle;
public abstract class Hero {
// 编号
private String id;
// 姓名
private String name;
// 武力值
private Integer forceValue;
/**
* 名号
*/
public void nickName(){
System.out.println("英雄名号");
}
/**
* 师傅
*/
public void master(){
System.out.println("师傅");
}
/**
* 混元珠
*/
public void MixedYuanzhu(){
System.out.println("混元珠");
}
}
哪吒类继承了英雄父类Hero,实现的核心功能包括名号、师傅、混元珠,拓展了新的方法重大事件。
package com.guor.principle;
public class Nezha extends Hero {
public Nezha(String id, String name, Integer forceValue) {
super(id, name, forceValue);
}
/**
* 名号
*/
public void nickName(){
System.out.println("八臂哪吒");
}
/**
* 师傅
*/
public void master(){
System.out.println("太乙真人");
}
/**
* 混元珠
*/
public void MixedYuanzhu(){
System.out.println("魔丸");
}
/**
* 重大事件
*/
public void event(){
System.out.println("哪吒闹海");
}
}
敖丙类继承了英雄父类Hero,实现的核心功能包括名号、师傅、混元珠,拓展了新的方法虾兵蟹将。
package com.guor.principle;
public class Aobing extends Hero {
public Aobing(String id, String name, Integer forceValue) {
super(id, name, forceValue);
}
/**
* 名号
*/
public void nickName(){
System.out.println("三太子");
}
/**
* 师傅
*/
public void master(){
System.out.println("申公豹");
}
/**
* 混元珠
*/
public void MixedYuanzhu(){
System.out.println("灵珠");
}
/**
* 手下
*/
public void subordinate(){
System.out.println("虾兵蟹将");
}
}
以上的实现方法都是在遵循里氏替换原则下完成的,子类随时可以替换英雄类。
继承作为面向对象的重要特性,虽然给程序开发带来了非常大的便利,但也引入了一些弊端。继承的开发方式会给代码带来侵入性,可移植能力降低,类之间的耦合度较高。当对父类修改时,就要考虑一整套子类的实现是否有风险,测试成本较高。
里氏替换原则的目的是使用约定的方式,让使用继承后的代码具备良好的扩展性和兼容性。
在日常开发中使用继承的地方并不多,在有些公司的代码规范中也不会允许许多层继承,尤其是一些核心服务的扩展。而继承多数使用在系统架构初期定义好的逻辑上或抽象出的核心功能里。如果使用了继承,就一定要遵从里氏替换原则,否则会让代码出现问题的概率变大。