定義:在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,里氏替換原則(Liskov Substitution principle)是對(duì)子類型的特別定義儒搭。里氏替換原則的內(nèi)容可以描述為: “派生類(子類)對(duì)象可以在程序中代替其基類(超類)對(duì)象贾费±缰椋”
原理:在軟件開發(fā)過程中牺六,若子類重寫了父類方法键闺,當(dāng)用子類代替父類時(shí)就會(huì)出現(xiàn)邏輯不一致的問題。
問題由來:若類A
實(shí)現(xiàn)了方法a
虽抄,而其子類A1
覆寫了該方法走搁,則當(dāng)子類出現(xiàn)在父類定義的類型時(shí)可能會(huì)出錯(cuò)(針對(duì)繼承時(shí)),如下:
public class Main {
public static void main(String[] args) {
A[] as = new A[]{
new A(), new A1()
};
for (A a : as) {
a.a();
}
}
}
class A {
public void a() {
System.out.println("A#a");
}
}
class A1 extends A {
@Override
public void a() {
System.out.println("A1#a");
}
}
output
A#a
A1#a
可以看到迈窟,數(shù)組中的第二個(gè)對(duì)象是A1
的實(shí)例私植,而A1
覆寫來了方法a
,此時(shí)雖然定義的類型是A
车酣,到那時(shí)由于A1
是A的子類曲稼,其是可以替換類型A
的對(duì)象的索绪,而由于方法a
被覆寫,輸出內(nèi)容超出了預(yù)想內(nèi)容躯肌。
產(chǎn)生原因:軟件開發(fā)時(shí)類型把控不嚴(yán)格可能會(huì)導(dǎo)致此問題
解決辦法:
- 子類可以實(shí)現(xiàn)父類的抽象方法者春,但不能覆蓋父類的非抽象方法
- 子類可以增加自己特有的方法
- 當(dāng)子類的方法重載父類的方法時(shí),方法的形參要比父類方法的輸入?yún)?shù)更寬松
- 當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí)清女,方法的返回值應(yīng)比父類更嚴(yán)格
應(yīng)用場(chǎng)景:
針對(duì)繼承時(shí)
如果繼承是為了實(shí)現(xiàn)代碼重用時(shí)钱烟,那么共享的父類方法應(yīng)該保持不變,不能被子類重新定義嫡丙,為了避免出現(xiàn)此類問題拴袭,我們應(yīng)該在共享的父類方法中加上final
字段,防止子類不小心覆寫了共享的父類方法曙博。子類只能通過新添加方法來擴(kuò)展功能拥刻,那么上面的代碼應(yīng)該修改為如下形式:
public class Main {
public static void main(String[] args) {
A[] as = new A[]{
new A(), new A1()
};
for (A a : as) {
a.a();
}
}
}
class A {
public final void a() {
System.out.println("A#a");
}
}
class A1 extends A {
public void a1() {
System.out.println("A1#a1");
}
}
A#a
A#a
可以看到,此時(shí)即便在A
類的數(shù)組中實(shí)例化的時(shí)A1
父泳,我們調(diào)用方法a
時(shí)的邏輯還是A
的邏輯般哼,而A1
需要擴(kuò)展功能我們則新添加一個(gè)方法a1
,讓其實(shí)現(xiàn)A1
需要擴(kuò)展的功能惠窄。
針對(duì)多態(tài)時(shí)
如果繼承的目的時(shí)為了多態(tài)蒸眠,而多態(tài)的前提是子類覆蓋并重新定義父類方法,為了符合里氏替換原則
杆融,我們應(yīng)該將父類定義為抽象類楞卡,并定義抽象方法,讓子類重新定義這些方法脾歇,當(dāng)父類是抽象類是蒋腮,父類就不能實(shí)例化,所以也不存在可實(shí)例化的父類對(duì)象在程序里藕各。也就規(guī)避了子類替換父類實(shí)例時(shí)邏輯不一致的問題池摧。如下:
public class Main {
public static void main(String[] args) {
A[] as = new A[]{new A1(), new A2()};
for (A a : as) {
a.a();
}
}
}
abstract class A {
abstract void a();
}
class A1 extends A {
@Override
void a() {
System.out.println("A1#a");
}
}
class A2 extends A {
@Override
void a() {
System.out.println("A2#a");
}
}
A1#a
A2#a
此時(shí)A
類的定義是為了多態(tài),同時(shí)我們將A
類設(shè)為了抽象類并將方法a
設(shè)為抽象方法激况,因此A
類不可以被實(shí)例化且所有子類都必須實(shí)現(xiàn)該方法险绘,所以也就不存在父類邏輯,因此也就規(guī)避了子類與父類邏輯不一致的可能誉碴。注:若方法較多,且讓子類選擇實(shí)現(xiàn)時(shí)可以不設(shè)為抽象方法瓣距,單父類必須設(shè)為抽象類黔帕,且需要子類實(shí)現(xiàn)的方法必須為空方法。此時(shí)需要共享的方法要添加上final
關(guān)鍵字蹈丸,防止子類不小心實(shí)現(xiàn)了共享方法成黄。若我們希望某類不能被繼承時(shí)也需要將類設(shè)為final
類呐芥。
小提示:final
關(guān)鍵字很重要,在必要時(shí)不要忘了加上它奋岁。添加final
關(guān)鍵字時(shí)不僅可以提高代碼可讀性同時(shí)也可以增加代碼的性能思瘟。