java solid设计原则_六大设计原则之里氏替换原则(LSP)
一、SOLID
設(shè)計(jì)模式的六大原則有:
Single Responsibility Principle:單一職責(zé)原則
Open Closed Principle:開閉原則
Liskov Substitution Principle:里氏替換原則
Law of Demeter:迪米特法則
Interface Segregation Principle:接口隔離原則
Dependence Inversion Principle:依賴倒置原則
把這六個(gè)原則的首字母聯(lián)合起來(兩個(gè) L 算做一個(gè))就是 SOLID (solid,穩(wěn)定的),其代表的含義就是這六個(gè)原則結(jié)合使用的好處:建立穩(wěn)定、靈活、健壯的設(shè)計(jì)。下面我們來看一下里氏替換原則。
設(shè)計(jì)模式六大原則(SOLID)
二、里氏替換原則定義
【所有引用基類的地方必須能透明地使用其子類的對(duì)象】
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
三、里氏替換原則彌補(bǔ)繼承的缺陷
氏替換原則的意思是,所有基類在的地方,都可以換成子類,程序還可以正常運(yùn)行。這個(gè)原則是與面向?qū)ο笳Z言的繼承特性密切相關(guān)的。
在學(xué)習(xí)java類的繼承時(shí),我們知道繼承有一些優(yōu)點(diǎn):
子類擁有父類的所有方法和屬性,從而可以減少創(chuàng)建類的工作量。
提高了代碼的重用性。
提高了代碼的擴(kuò)展性,子類不但擁有了父類的所有功能,還可以添加自己的功能。
但有優(yōu)點(diǎn)也同樣存在缺點(diǎn):
繼承是侵入性的。只要繼承,就必須擁有父類的所有屬性和方法。
降低了代碼的靈活性。因?yàn)槔^承時(shí),父類會(huì)對(duì)子類有一種約束。
增強(qiáng)了耦合性。當(dāng)需要對(duì)父類的代碼進(jìn)行修改時(shí),必須考慮到對(duì)子類產(chǎn)生的影響。
如何揚(yáng)長避短呢?方法是引入里氏替換原則。
四、里氏替換原則對(duì)繼承進(jìn)行了規(guī)則上的約束
里氏替換原則對(duì)繼承進(jìn)行了規(guī)則上的約束,這種約束主要體現(xiàn)在四個(gè)方面:
子類必須實(shí)現(xiàn)父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實(shí)現(xiàn))方法。
子類中可以增加自己特有的方法。
當(dāng)子類覆蓋或?qū)崿F(xiàn)父類的方法時(shí),方法的前置條件(即方法的形參)要比- 父類方法的輸入?yún)?shù)更寬松。(即只能重載不能重寫)
當(dāng)子類的方法實(shí)現(xiàn)父類的抽象方法時(shí),方法的后置條件(即方法的返回值)要比父類更嚴(yán)格。
下面對(duì)以上四個(gè)含義進(jìn)行詳細(xì)的闡述
4.1、子類必須實(shí)現(xiàn)父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實(shí)現(xiàn))方法
在我們做系統(tǒng)設(shè)計(jì)時(shí),經(jīng)常會(huì)設(shè)計(jì)接口或抽象類,然后由子類來實(shí)現(xiàn)抽象方法,這里使用的其實(shí)就是里氏替換原則。若子類不完全對(duì)父類的方法進(jìn)行實(shí)例化,那么子類就不能被實(shí)例化,那么這個(gè)接口或抽象類就毫無存在的意義了。
里氏替換原則規(guī)定,子類不能覆寫父類已實(shí)現(xiàn)的方法。父類中已實(shí)現(xiàn)的方法其實(shí)是一種已定好的規(guī)范和契約,如果我們隨意的修改了它,那么可能會(huì)帶來意想不到的錯(cuò)誤。下面舉例說明一下子類覆寫了父類方法帶來的后果。
public classFather {public void fun(int a, intb) {
System.out.println(a+ "+" + b + "=" + (a +b));
}
}public class Sun extendsFather {
@Overridepublic void fun(int a, intb) {
System.out.println(a+ "-" + b + "=" + (a -b));
}
}public classClient {public static voidmain(String[] args) {
Father father= newFather();
father.fun(1, 2);//父類存在的地方,可以用子類替代//子類Sun替代父類Father
System.out.println("子類替代父類后的運(yùn)行結(jié)果");
Sun sun= newSun();
sun.fun(1, 2);
}
}
運(yùn)行結(jié)果:
1+2=3子類替代父類后的運(yùn)行結(jié)果1-2=-1
我們想要的結(jié)果是“1+2=3”。可以看到,方法重寫后結(jié)果就不是了我們想要的結(jié)果了,也就是這個(gè)程序中子類B不能替代父類A。這違反了里氏替換原則,從而給程序造成了錯(cuò)誤。
我們可以給父類的非抽象(已實(shí)現(xiàn))方法加final修飾,這樣就在語法層面控制了父類非抽象方法被子類重寫而違反里氏替換原則。
有時(shí)候父類有多個(gè)子類(Sun1、Sun2),但在這些子類中有一個(gè)特例(Sun2)。要想滿足里氏替換原則,又想滿足這個(gè)子類的功能時(shí),有的伙伴可能會(huì)修改父類(Father)的方法。但是,修改了父類的方法又會(huì)對(duì)其他的子類造成影響,產(chǎn)生更多的錯(cuò)誤。這是怎么辦呢?我們可以為這個(gè)特例(Sun2)創(chuàng)建一個(gè)新的父類(Father2),這個(gè)新的父類擁有原父類的部分功能(Father2并不繼承Father,而是持有Father的一個(gè)引用,組合Father,調(diào)用Father里的功能),又有不同的功能。這樣既滿足了里氏替換原則,又滿足了這個(gè)特例的需求。
4.2、子類中可以增加自己特有的方法
這個(gè)很容易理解,子類繼承了父類,擁有了父類和方法,同時(shí)還可以定義自己有,而父類沒有的方法。這是在繼承父類方法的基礎(chǔ)上進(jìn)行功能的擴(kuò)展,符合里氏替換原則。
4.3 、當(dāng)子類覆蓋或?qū)崿F(xiàn)父類的方法時(shí),方法的前置條件(即方法的形參)要比父類方法的輸入?yún)?shù)更寬松
先看一段代碼:
public classFather {public voidfun(HashMap map){
System.out.println("父類被執(zhí)行...");
}
}public class Sun extendsFather {public voidfun(Map map){
System.out.println("子類被執(zhí)行...");
}
}public classClient {public static voidmain(String[] args) {
System.out.print("父類的運(yùn)行結(jié)果:");
Father father=newFather();
HashMap map=newHashMap();
father.fun(map);//父類存在的地方,可以用子類替代//子類B替代父類A
System.out.print("子類替代父類后的運(yùn)行結(jié)果:");
Sun sun=newSun();
sun.fun(map);
}
}
運(yùn)行結(jié)果:
父類的運(yùn)行結(jié)果:父類被執(zhí)行...
子類替代父類后的運(yùn)行結(jié)果:父類被執(zhí)行...
我們應(yīng)當(dāng)主意,子類并非重寫了父類的方法,而是重載了父類的方法。因?yàn)樽宇惡透割惖姆椒ǖ妮斎雲(yún)?shù)是不同的。子類方法的參數(shù)Map比父類方法的參數(shù)HashMap的范圍要大,所以當(dāng)參數(shù)輸入為HashMap類型時(shí),只會(huì)執(zhí)行父類的方法,不會(huì)執(zhí)行子類的重載方法。這符合里氏替換原則。
但如果我將子類方法的參數(shù)范圍縮小會(huì)怎樣?看代碼:
public classFather {public voidfun(Map map){
System.out.println("父類被執(zhí)行...");
}
}public class Sun extendsFather {public voidfun(HashMap map){
System.out.println("子類被執(zhí)行...");
}
}public classClient {public static voidmain(String[] args) {
System.out.print("父類的運(yùn)行結(jié)果:");
Father father=newFather();
HashMap map=newHashMap();
father.fun(map);//父類存在的地方,可以用子類替代//子類B替代父類A
System.out.print("子類替代父類后的運(yùn)行結(jié)果:");
Sun sun=newSun();
sun.fun(map);
}
}
運(yùn)行結(jié)果:
父類的運(yùn)行結(jié)果:父類被執(zhí)行...
子類替代父類后的運(yùn)行結(jié)果:子類被執(zhí)行...
在父類方法沒有被重寫的情況下,子方法被執(zhí)行了,這樣就引起了程序邏輯的混亂。所以子類中方法的前置條件必須與父類中被覆寫的方法的前置條件相同或者更寬松。
4.4、當(dāng)子類的方法實(shí)現(xiàn)父類的(抽象)方法時(shí),方法的后置條件(即方法的返回值)要比父類更嚴(yán)格
public abstract classFather {public abstractMap fun();
}public class Sun extendsFather {
@OverridepublicHashMap fun() {
System.out.println("子類被執(zhí)行...");return null;
}
}public classClient {public static voidmain(String[] args) {
Father father=newSun();
father.fun();
}
}
運(yùn)行結(jié)果:
子類被執(zhí)行...
注意:是實(shí)現(xiàn)父類的抽象方法,而不是父類的非抽象(已實(shí)現(xiàn))方法,不然就違法了第一條。
若在繼承時(shí),子類的方法返回值類型范圍比父類的方法返回值類型范圍大,在子類重寫該方法時(shí)編譯器會(huì)報(bào)錯(cuò)。(Java語法)
總結(jié)
以上是生活随笔為你收集整理的java solid设计原则_六大设计原则之里氏替换原则(LSP)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 110配线架详解
- 下一篇: cl 编译器环境配置问题