里氏替換原則,OCP作為OO的高層原則,主張使用“抽象(Abstraction)”和“多態(tài)(Polymorphism)”將設(shè)計(jì)中的靜態(tài)結(jié)構(gòu)改為動(dòng)態(tài)結(jié)構(gòu),維持設(shè)計(jì)的封閉性。“抽象”是語(yǔ)言提供的功能。“多態(tài)”由繼承語(yǔ)義實(shí)現(xiàn)。
里氏替換原則包含以下4層含義:
- 子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法,但是不能覆蓋父類(lèi)的非抽象方法。
- 子類(lèi)中可以增加自己特有的方法。
- 當(dāng)子類(lèi)覆蓋或?qū)崿F(xiàn)父類(lèi)的方法時(shí),方法的前置條件(即方法的形參)要比父類(lèi)方法的輸入?yún)?shù)更寬松。
- 當(dāng)子類(lèi)的方法實(shí)現(xiàn)父類(lèi)的抽象方法時(shí),方法的后置條件(即方法的返回值)要比父類(lèi)更嚴(yán)格。
現(xiàn)在我們可以對(duì)以上四層含義進(jìn)行講解。
子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法,但是不能覆蓋父類(lèi)的非抽象方法
在我們做系統(tǒng)設(shè)計(jì)時(shí),經(jīng)常會(huì)設(shè)計(jì)接口或抽象類(lèi),然后由子類(lèi)來(lái)實(shí)現(xiàn)抽象方法,這里使用的其實(shí)就是里氏替換原則。子類(lèi)可以實(shí)現(xiàn)父類(lèi)的抽象方法很好理解,事實(shí)上,子類(lèi)也必須完全實(shí)現(xiàn)父類(lèi)的抽象方法,哪怕寫(xiě)一個(gè)空方法,否則會(huì)編譯報(bào)錯(cuò)。
里氏替換原則的關(guān)鍵點(diǎn)在于不能覆蓋父類(lèi)的非抽象方法。父類(lèi)中凡是已經(jīng)實(shí)現(xiàn)好的方法,實(shí)際上是在設(shè)定一系列的規(guī)范和契約,雖然它不強(qiáng)制要求所有的子類(lèi)必須遵從這些規(guī)范,但是如果子類(lèi)對(duì)這些非抽象方法任意修改,就會(huì)對(duì)整個(gè)繼承體系造成破壞。而里氏替換原則就是表達(dá)了這一層含義。
在面向?qū)ο蟮脑O(shè)計(jì)思想中,繼承這一特性為系統(tǒng)的設(shè)計(jì)帶來(lái)了極大的便利性,但是由之而來(lái)的也潛在著一些風(fēng)險(xiǎn)。下面舉例來(lái)說(shuō)明繼承的風(fēng)險(xiǎn),我們需要完成一個(gè)兩數(shù)相減的功能,由類(lèi)A來(lái)負(fù)責(zé)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class A{ public int func1( 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.func1( 100 , 50 )); System.out.println( "100-80=" +a.func1( 100 , 80 )); } } |
運(yùn)行結(jié)果:
1
2
|
100-50=50 100-80=20 |
后來(lái),我們需要增加一個(gè)新的功能:完成兩數(shù)相加,然后再與100求和,由類(lèi)B來(lái)負(fù)責(zé)。即類(lèi)B需要完成兩個(gè)功能:
兩數(shù)相減。
兩數(shù)相加,然后再加100。
由于類(lèi)A已經(jīng)實(shí)現(xiàn)了第一個(gè)功能,所以類(lèi)B繼承類(lèi)A后,只需要再完成第二個(gè)功能就可以了,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class B extends A{ public int func1( int a, int b){ return a+b; } public int func2( int a, int b){ return func1(a,b)+ 100 ; } } public class Client{ public static void main(String[] args){ B b = new B(); System.out.println( "100-50=" +b.func1( 100 , 50 )); System.out.println( "100-80=" +b.func1( 100 , 80 )); System.out.println( "100+20+100=" +b.func2( 100 , 20 )); } } |
類(lèi)B完成后,運(yùn)行結(jié)果:
1
2
3
|
100-50=150 100-80=180 100+20+100=220 |
我們發(fā)現(xiàn)原本運(yùn)行正常的相減功能發(fā)生了錯(cuò)誤。原因就是類(lèi)B在給方法起名時(shí)無(wú)意中重寫(xiě)了父類(lèi)的方法,造成所有運(yùn)行相減功能的代碼全部調(diào)用了類(lèi)B重寫(xiě)后的方法,造成原本運(yùn)行正常的功能出現(xiàn)了錯(cuò)誤。在本例中,引用基類(lèi)A完成的功能,換成子類(lèi)B之后,發(fā)生了異常。在實(shí)際編程中,我們常常會(huì)通過(guò)重寫(xiě)父類(lèi)的方法來(lái)完成新的功能,這樣寫(xiě)起來(lái)雖然簡(jiǎn)單,但是整個(gè)繼承體系的可復(fù)用性會(huì)比較差,特別是運(yùn)用多態(tài)比較頻繁時(shí),程序運(yùn)行出錯(cuò)的幾率非常大。如果非要重寫(xiě)父類(lèi)的方法,比較通用的做法是:原來(lái)的父類(lèi)和子類(lèi)都繼承一個(gè)更通俗的基類(lèi),原有的繼承關(guān)系去掉,采用依賴(lài)、聚合,組合等關(guān)系代替。