面向?qū)ο蟮脑O(shè)計原則

設(shè)計模式相關(guān)電子書,鏈接???????

什么是好的軟件設(shè)計呢?軟件設(shè)計的金科玉律:復(fù)用

面向?qū)ο蟮脑O(shè)計到底有沒有什么原則呢?

變化是復(fù)用最大的天敵!面向?qū)ο蟮淖畲髢?yōu)勢就在于:抵御變化。


重新認識面向?qū)ο螅?/b>

? ? 1.理解隔離變化

?從宏觀層面來看,面向?qū)ο蟮臉?gòu)建方式更能適應(yīng)軟件的變化,能將變化所帶來的影響新奇跡世界最小

? ? 2..各司其職

?從微觀層面來看,面向?qū)ο蟮姆绞礁鼜娬{(diào)各個類的“責(zé)任”

?由于需求變化導(dǎo)致的校報增類型不應(yīng)該影響原來類型的實現(xiàn)——是所謂各負其責(zé)

? ? 3.對象是什么

?從語文實現(xiàn)層面來看,對象封裝國代碼和數(shù)據(jù)

從規(guī)格層面來講,對象是一系列可被使用的公共接口

從概念層面來講,對象是某種擁有責(zé)任的抽象


一、開閉原則(The Open-Closed Principle ,OCP)

? ? ? ? ?對擴展開放,對更改封閉

? ? ? ? 類模塊應(yīng)該是可擴展,但是不可修改


一個軟件實體應(yīng)當對擴展開放,對修改關(guān)閉。即軟件實體應(yīng)盡量在不修改原有代碼的情況下進行擴展。

?軟件實體可以指一個軟件模塊、一個由多個類組成的局部結(jié)構(gòu)或一個獨立的類。

繼續(xù)拿《設(shè)計模式簡介》中的Mainform的例子來講解吧。

如果要增加一個圓形呢?怎么修改代碼呢?

第一種寫法,先增加一個Circle類,接著在MainForm類添加一個成員變量,這個成員變量是存放Circel的vector,還要在OnMouseUp()和OnPaint()的這兩個成員方法做修改,這樣就違反開閉原則。

此外,該設(shè)計邏輯復(fù)雜,總的來說是一個僵化的、脆弱的、具有很高的牢固性的設(shè)計。


在很多面向?qū)ο缶幊陶Z言中都提供了接口、抽象類等機制,可以通過它們定義系統(tǒng)的抽象層,再通過具體類來進行擴展。如果需要修改系統(tǒng)的行為,無須對抽象層進行任何改動,只需要增加新的具體類來實現(xiàn)新的業(yè)務(wù)功能即可,實現(xiàn)在不修改已有代碼的基礎(chǔ)上擴展系統(tǒng)的功能,達到開閉原則的要求。

在本實例中,由于在MainForm類的OnPaint()方法中針對每一個形狀類編程,因此增加新的形狀類不得不修改源代碼。可以通過抽象化的方式對系統(tǒng)進行重構(gòu),使之增加新的形狀類時無須修改源代碼,滿足開閉原則。

用開閉原則重構(gòu)該設(shè)計如下圖:


? ? (1) 增加一個形狀積累類Shape,將各種具體形狀類作為其子類;

? ? ?(2)對于MainForml來說,它管理的容器也發(fā)生了改變,只需要管理Shape的指針,而不再具體管理某一個具體的形狀,實現(xiàn)了一次向上抽象的管理。同時onPaint的方法來說,也不在直接管理繪制的問題,而是調(diào)用了Shape中的虛函數(shù)Draw,通過多態(tài)調(diào)用來實現(xiàn)了MainForm來調(diào)用了自己繪制自己的方法。

此時,在該設(shè)計中,新增一個圖形,只需要實現(xiàn)繼承Shape,實現(xiàn)Draw的接口,滿足對擴展開放;也不需要修改Mairform的onPaint()方法,對修改關(guān)閉。


二、 里氏替換原則(Liskov Substitution Principle ,LSP)

? ? ? ?所有引用基類的地方必須能透明地使用其子類的對象

void MainForm::OnPaint(const PaintEventArgs& e){

? ? //針對所有形狀

? ? for (int i = 0; i < shapeVector.size(); i++){

? ? ? ? shapeVector[i]->Draw(e.Graphics); //多態(tài)調(diào)用,各負其責(zé)

? ? }

? ? //...

? ? Form::OnPaint(e);

}

在軟件中將一個基類對象替換成它的子類對象,程序?qū)⒉粫a(chǎn)生任何錯誤和異常。

在Mainform類的OnPaint()方法中,shapeVecotr存放的是基類Shape的指針,引用了基類Shape,shapeVecotr可以存放Shape的不同派生類實例對象,調(diào)用同一接口Draw,這里不會產(chǎn)生任何錯誤異常。

? 例如有兩個類,一個類為BaseClass,另一個是SubClass類,并且SubClass類是BaseClass類的子類,那么一個方法如果可以接受一個BaseClass類型的基類對象base的話,如:method1(base),那么它必然可以接受一個BaseClass類型的子類對象sub,method1(sub)能夠正常運行。反過來的代換不成立,如一個方法method2接受BaseClass類型的子類對象sub為參數(shù):method2(sub),那么一般而言不可以有method2(base),除非是重載方法。


在使用里氏代換原則時需要注意如下幾個問題:

? ? ? (1)子類的所有方法必須在父類中聲明,或子類必須實現(xiàn)父類中聲明的所有方法。根據(jù)里氏代換原則,為了保證系統(tǒng)的擴展性,在程序中通常使用父類來進行定義,如果一個方法只存在子類中,在父類中不提供相應(yīng)的聲明,則無法在以父類定義的對象中使用該方法。

? ? ? (2) 我們在運用里氏代換原則時,盡量把父類設(shè)計為抽象類或者接口,讓子類繼承父類或?qū)崿F(xiàn)父接口,并實現(xiàn)在父類中聲明的方法,運行時,子類實例替換父類實例,我們可以很方便地擴展系統(tǒng)的功能,同時無須修改原有子類的代碼,增加新的功能可以通過增加一個新的子類來實現(xiàn)。里氏代換原則是開閉原則的具體實現(xiàn)手段之一。

? ? ? ? ?里氏代換原則是實現(xiàn)開閉原則的重要方式之一。在傳遞參數(shù)時使用基類對象,除此以外,在定義成員變量、定義局部變量、確定方法返回類型時都可使用里氏代換原則。針對基類編程,在程序運行時再確定具體子類。



三、 依賴倒置原則(Dependency Inversion Principle ,DIP)

? ? ? ?高層模塊不應(yīng)該依賴于低層模塊,二者都應(yīng)該依賴于抽象

? ? ? ?抽象不應(yīng)該依賴于細節(jié),細節(jié)應(yīng)該依賴于抽象

? ? ? ?針對接口編程,不要針對實現(xiàn)編程。


面向?qū)ο蟪绦蛟O(shè)計相對于面向過程(結(jié)構(gòu)化)程序設(shè)計而言,依賴關(guān)系被倒置了。因為傳統(tǒng)的結(jié)構(gòu)化程序設(shè)計中,高層模塊總是依賴于低層模塊。


問題的提出:

Robert C. Martin氏在原文中給出了“Bad Design”的定義:

系統(tǒng)很難改變,因為每個改變都會影響其他很多部分。

當你對某地方做一修改,系統(tǒng)的看似無關(guān)的其他部分都不工作了。

系統(tǒng)很難被另外一個應(yīng)用重用,因為很難將要重用的部分從系統(tǒng)中分離開來。

導(dǎo)致“Bad Design”的很大原因是“高層模塊”過分依賴“低層模塊”。

一個良好的設(shè)計應(yīng)該是系統(tǒng)的每一部分都是可替換的。如果“高層模塊”過分依賴“低層模塊”,一方面一旦“低層模塊”需要替換或者修改,“高層模塊”將受到影響;另一方面,高層模塊很難可以重用。


問題的解決:

為了解決上述問題,Robert C. Martin氏提出了OO設(shè)計的Dependency Inversion Principle (DIP) 原則。

DIP給出了一個解決方案:在高層模塊與低層模塊之間,引入一個抽象接口層。


抽象接口是對低層模塊的抽象,低層模塊繼承或?qū)崿F(xiàn)該抽象接口。

這樣,高層模塊不直接依賴低層模塊,而是依賴抽象接口層。抽象接口也不依賴低層模塊的實現(xiàn)細節(jié),而是低層模塊依賴(繼承或?qū)崿F(xiàn))抽象接口。

類與類之間都通過抽象接口層來建立關(guān)系。


《設(shè)計模式簡介》中的Mainform的例子中,第一種寫法,Marinform類的OnPaint()方法針對具體類來操作,因此在增加新的形狀時,都不得不修改CustomerDAO的源代碼。

我們可以通過引入抽象類Shape來解決該問題。在引入抽象類Shape之后,Maiform針對抽象類Shape編程,而將具體類實例對象存儲在shapeVector中,符合依賴倒置原則。

根據(jù)里氏代換原則,雖然shapeVector存儲的是Shape類型的指針,但具體類實例對象是可以存儲在該Vecotr的,遍歷shapeVector,調(diào)用了Shape中的虛函數(shù)Draw,通過多態(tài)調(diào)用來真正的具體實例對象的方法,程序不會出現(xiàn)任何問題。

如果需要增加新的形狀類,只要將新的具體類實例對象加入到shapeVector即可,其實MainForm的OnMouseUp()方法可以用工廠模式,MainForm的代碼沒作多少修改,滿足開閉原則。

在Mainform例子中,使用了開閉原則、里氏代換原則和依賴倒轉(zhuǎn)原則。在大多數(shù)情況下,這三個設(shè)計原則會同時出現(xiàn),開閉原則是目標,里氏代換原則是基礎(chǔ),依賴倒轉(zhuǎn)原則是手段,它們相輔相成,相互補充,目標一致,只是分析問題時所站角度不同而已。



四、 迪米特原則(最少知道原則)(Law of Demeter ,LoD)

? ? ? ? ??一個對象應(yīng)當對其他對象有盡可能少的了解,不和陌生人說話。


對于面向OOD來說,又被解釋為下面兩種方式:

1)一個軟件實體應(yīng)當盡可能少地與其他實體發(fā)生相互作用。

2)每一個軟件單位對其他的單位都只有最少的知識,而且局限于那些與本單位密切相關(guān)的軟件單位。


迪米特法則通俗的來講,就是一個類對自己依賴的類知道的越少越好。也就是說,對于被依賴的類來說,無論邏輯多么復(fù)雜,都盡量地的將邏輯封裝在類的內(nèi)部,對外除了提供的public方法,不對外泄漏任何信息。

迪米特法則還有一個更簡單的定義:只與直接的朋友通信。

首先來解釋一下什么是直接的朋友:每個對象都會與其他對象有耦合關(guān)系,只要兩個對象之間有耦合關(guān)系,我們就說這兩個對象之間是朋友關(guān)系。耦合的方式很多,依賴、關(guān)聯(lián)、組合、聚合等。其中,我們稱出現(xiàn)成員變量、方法參數(shù)、方法返回值中的類為直接的朋友,而出現(xiàn)在局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要作為局部變量的形式出現(xiàn)在類的內(nèi)部。

代碼舉例:通過老師要求班長告知班級人數(shù)為例,講解迪米特法則。先來看一下違反迪米特法則的設(shè)計,代碼如下:

public class Student {?

? ? private Integer id;? ?

? ? private String name;? ?

? ? public Student(Integer id, String name)

? ? {? ? ? ?

? ? ? ? this.id = id;? ? ?

? ? ? ? this.name = name;

? ? }

}

public class Teacher {? ?

? ? public void call(Monitor monitor) {

? ? ? ? List<Student> sts = new ArrayList<>();? ? ?

? ? ? ? for (int i = 0; i < 10; i++) {

? ? ? ? ? ? sts.add(new Student(i + 1, "name" + i));

? ? ? ? }

? ? ? ? monitor.getSize(sts);

? ? }

}

public class Monitor {?

? ? public void getSize(List list) {

? ? ? ? System.out.println("班級人數(shù):" + list.size());

? ? }

}

現(xiàn)在這個設(shè)計的主要問題出在 Teacher 中,根據(jù)迪米特法則,只與直接的朋友發(fā)生通信,而 Student 類并不是 Teacher 類的直接朋友(以局部變量出現(xiàn)的耦合不屬于直接朋友),從邏輯上講 Teacher 只與 Monitor 耦合就行了,與 Student 并沒有任何聯(lián)系,這樣設(shè)計顯然是增加了不必要的耦合。按照迪米特法則,應(yīng)該避免類中出現(xiàn)這樣非直接朋友關(guān)系的耦合。修改后的代碼如下:

public class Student {? ?

? ? private Integer id;? ?

private String name;?

? ? public Student(Integer id, String name)? {? ? ? ?

? ? ? ? this.id = id;? ? ? ?

? ? ? ? this.name = name;

}

}

public class Teacher {? ?

? ? public void call(Monitor monitor) {

? ? ? ? monitor.getSize();

? ? }

}

public class Monitor {? ?

? ? public void getSize() {

? ? ? ? List<Student> sts = new ArrayList<>();? ? ?

? ? ? ? for (int i = 0; i < 10; i++) {

? ? ? ? ? ? sts.add(new Student(i + 1, "name" + i));

? ? ? ? }

? ? ? ? System.out.println("班級人數(shù)" + sts.size());

? ? }

}

將Student 從 Teacher 抽掉,也就達到了 Student 和 Teacher 的解耦,從而符合了迪米特原則。

迪米特法則的初衷是降低類之間的耦合,由于每個類都減少了不必要的依賴,因此的確可以降低耦合關(guān)系。但是凡事都有度,雖然可以避免與非直接的類通信,但是要通信,必然會通過一個“中介”來發(fā)生聯(lián)系,例如本例中,老師(Teacher)就是通過班長(Monitor)這個“中介”來與 學(xué)生(Student)發(fā)生聯(lián)系的。過分的使用迪米特原則,會產(chǎn)生大量這樣的中介和傳遞類,導(dǎo)致系統(tǒng)復(fù)雜度變大。所以在采用迪米特法則時要反復(fù)權(quán)衡,既做到結(jié)構(gòu)清晰,又要高內(nèi)聚低耦合。


使用迪米特原則時要考慮的

朋友間也是有距離的

? ??? ??一個類公開的public屬性或方法越多,修改時涉及的面也就越大,變更引起的風(fēng)險擴散也就越大。因此,為了保持朋友類間的距離,在設(shè)計時需要反復(fù)衡量:是否還可以再減少public方法和屬性,是否可以修改為private等。

**注意:**迪米特原則要求類“羞澀”一點,盡量不要對外公布太多的public方法和非靜態(tài)的public變量,盡量內(nèi)斂,多使用private、protected等訪問權(quán)限。

是自己的就是自己的

? ??? ??如果一個方法放在本類中,既不增加類間關(guān)系,也對本類不產(chǎn)生負面影響,就放置在本類中。



五、單一職責(zé)原則

? ? ? ?一個類應(yīng)該僅有一個引起它變化的原因.

? ? ? 變化的方向隱含著類的責(zé)任


換句話說,如果一個類需要改變,改變它的理由永遠只有一個。如果存在多個改變它的理由,就需要重新設(shè)計該類。

單一職責(zé)原則原則的核心含意是:只能讓一個類/接口/方法有且僅有一個職責(zé)。


假如說,類 A 負責(zé)兩個不同的職責(zé),T1 和 T2,當由于職責(zé) T1 需求發(fā)生改變而需要修改類 A 時,有可能會導(dǎo)致原本運行正常的職責(zé) T2 功能發(fā)生改變或出現(xiàn)異常。為什么會出現(xiàn)這種問題呢?代碼耦合度太高,實現(xiàn)復(fù)雜,簡單一句話就是:不夠單一。那么現(xiàn)在提出解決方案:分別建立兩個類 A 和 B ,使 A 完成職責(zé) T1 功能,B 完成職責(zé) T2 功能,這樣在修改 T1 時就不會影響 T2 了,反之亦然。


為什么一個類不能有多于一個以上的職責(zé)?

如果一個類具有一個以上的職責(zé),那么就會有多個不同的原因引起該類變化,而這種變化將影響到該類不同職責(zé)的使用者(不同用戶):

? ??一方面,如果一個職責(zé)使用了外部類庫,則使用另外一個職責(zé)的用戶卻也不得不包含這個未被使用的外部類庫。

? ? ?另一方面,某個用戶由于某個原因需要修改其中一個職責(zé),另外一個職責(zé)的用戶也將受到影響,他將不得不重新編譯和配置。這違反了設(shè)計的開閉原則,也不是我們所期望的。


說到單一職責(zé)原則,很多人都會不屑一顧。因為它太簡單了。稍有經(jīng)驗的程序員即使從來沒有讀過設(shè)計模式、從來沒有聽說過單一職責(zé)原則,在設(shè)計軟件時也會自覺的遵守這一重要原則,因為這是常識。在軟件編程中,誰也不希望因為修改了一個功能導(dǎo)致其他的功能發(fā)生故障。而避免出現(xiàn)這一問題的方法便是遵循單一職責(zé)原則。雖然單一職責(zé)原則如此簡單,并且被認為是常識,但是即便是經(jīng)驗豐富的程序員寫出的程序,也會有違背這一原則的代碼存在。為什么會出現(xiàn)這種現(xiàn)象呢?因為有職責(zé)擴散。所謂職責(zé)擴散,就是因為某種原因,職責(zé) T 被分化為粒度更細的職責(zé) T1 和 T2

結(jié)論:只有邏輯足夠簡單,才可以在代碼級別上違反單一職責(zé)原則;只有類中方法數(shù)量足夠少,才可以在方法級別上違反單一職責(zé)原則。


六、 接口分隔原則(Interface Segregation Principle ,ISP)

? ? ? ? ? ?不應(yīng)該強迫客戶程序依賴它們不用的方法

? ? ? ? ? ? 接口應(yīng)該小而完備



換句話說,使用多個專門的接口比使用單一的總接口總要好。

它包含了2層意思:

? ??接口的設(shè)計原則:接口的設(shè)計應(yīng)該遵循最小接口原則,不要把用戶不使用的方法塞進同一個接口里。如果一個接口的方法沒有被使用到,則說明該接口過胖,應(yīng)該將其分割成幾個功能專一的接口。

? ??接口的依賴(繼承)原則:如果一個接口a繼承另一個接口b,則接口a相當于繼承了接口b的方法,那么繼承了接口b后的接口a也應(yīng)該遵循上述原則:不應(yīng)該包含用戶不使用的方法。 反之,則說明接口a被b給污染了,應(yīng)該重新設(shè)計它們的關(guān)系。

如果用戶被迫依賴他們不使用的接口,當接口發(fā)生改變時,他們也不得不跟著改變。換而言之,一個用戶依賴了未使用但被其他用戶使用的接口,當其他用戶修改該接口時,依賴該接口的所有用戶都將受到影響。這顯然違反了開閉原則,也不是我們所期望的。


代碼示例:

public interface IAnimal {? ?

? ? void eat();? ?

? ? void talk();? ?

? ? void fly();

}

public class BirdAnimal implements IAnimal {?

? ? @Override

? ? public void eat() {

? ? ? ? System.out.println("鳥吃蟲子");

? ? }?


? ? @Override

? ? public void talk() {? ? ? ? //并不是所有的鳥都會說話

? ? }?


? ? @Override

? ? public void fly() {? ? ? ? //并不是所有的鳥都會飛

? ? }

}

public class DogAnimal implements IAnimal {? ?

? ? @Override

? ? public void eat() {

? ? ? ? System.out.println("狗狗吃飯");

? ? }?


? ? @Override

? ? public void talk() {? ? ? ? //狗不會說話

? ? }? ?


? ? @Override

? ? public void fly() {? ? ? ? //狗不會飛

? ? }

}

通過上面的代碼發(fā)現(xiàn):狗實現(xiàn)動物接口,必須實現(xiàn)三個接口,根據(jù)常識我們得知,第二個和第三個接口不一定會有實際意義,換句話說也就是這個方法有可能一直不會被調(diào)用。但是就是這樣我們還必須實現(xiàn)這個方法,盡管方法體可以為空,但是這就違反了接口隔離的定義。我們知道 由于Java類支持實現(xiàn)多個接口,可以很容易的讓類具有多種接口的特征,同時每個類可以選擇性地只實現(xiàn)目標接口,基于此特點我們可以對功能進一步的細化,編寫一個或多個接口,代碼如下:

public interface IEat {? ?

? ? void eat();

}

public interface IFly {? ?

? ? void fly();

}

public interface ITalk {? ?

? ? void talk();

}

public class DogAnimal implements IEat {?

? ? @Override

? ? public void eat() {

? ? ? ? System.out.println("狗狗吃飯");

? ? }


}public class ParrotAnimal implements IEat, IFly, ITalk {?

? ? @Override

? ? public void eat() {

? ? ? ? System.out.println("鸚鵡吃東西");

? ? }? ?


? ? @Override

? ? public void fly() {

? ? ? ? System.out.println("鸚鵡吃飛翔");

? ? }? ?


? ? @Override

? ? public void talk() {

? ? ? ? System.out.println("鸚鵡說話");

? ? }

}

說到這里,很多人會覺的接口隔離原則跟之前的單一職責(zé)原則很相似,其實不然。

其一,單一職責(zé)原則原注重的是職責(zé);而接口隔離原則注重對接口依賴的隔離。

其二,單一職責(zé)原則主要是約束類,其次才是接口和方法,它針對的是程序中的實現(xiàn)和細節(jié);而接口隔離原則主要約束接口接口,主要針對抽象,針對程序整體框架的構(gòu)建

接口隔離原則一定要適度使用,接口設(shè)計的過大或過小都不好,過分的細粒度可能造成接口數(shù)量龐大不易于管理

總而言之,接口分隔原則指導(dǎo)我們:

一個類對一個類的依賴應(yīng)該建立在最小的接口上

建立單一接口,不要建立龐大臃腫的接口

盡量細化接口,接口中的方法盡量少


七、 組合/聚合復(fù)用原則(Composite/Aggregate Reuse Principle ,CARP)CARP)

? ? ???盡量使用組合/聚合,不要使用類繼承

即在一個新的對象里面使用一些已有的對象,使之成為新對象的一部分,新對象通過向這些對象的委派達到復(fù)用已有功能的目的。就是說要盡量的使用合成和聚合,而不是繼承關(guān)系達到復(fù)用的目的。

組合和聚合都是關(guān)聯(lián)的特殊種類。

聚合表示整體和部分的關(guān)系,表示“擁有”。組合則是一種更強的“擁有”,部分和整體的生命周期一樣。

組合的新的對象完全支配其組成部分,包括它們的創(chuàng)建和湮滅等。一個組合關(guān)系的成分對象是不能與另一個組合關(guān)系共享的。

組合是值的聚合(Aggregation by Value),而一般說的聚合是引用的聚合(Aggregation by Reference)。


在面向?qū)ο笤O(shè)計中,有兩種基本的辦法可以實現(xiàn)復(fù)用:第一種是通過組合/聚合,第二種就是通過繼承。


什么時候才應(yīng)該使用繼承

只有當以下的條件全部被滿足時,才應(yīng)當使用繼承關(guān)系:

1)派生類是基類的一個特殊種類,而不是基類的一個角色,也就是區(qū)分"Has-A"和"Is-A"。只有"Is-A"關(guān)系才符合繼承關(guān)系,"Has-A"關(guān)系應(yīng)當用聚合來描述。

2)永遠不會出現(xiàn)需要將派生類換成另外一個類的派生類的情況。如果不能肯定將來是否會變成另外一個派生類的話,就不要使用繼承。

3)派生類具有擴展基類的責(zé)任,而不是具有置換掉(override)或注銷掉(Nullify)基類的責(zé)任。如果一個派生類需要大量的置換掉基類的行為,那么這個類就不應(yīng)該是這個基類的派生類。

4)只有在分類學(xué)角度上有意義時,才可以使用繼承。


總的來說:

如果語義上存在著明確的"Is-A"關(guān)系,并且這種關(guān)系是穩(wěn)定的、不變的,則考慮使用繼承;如果沒有"Is-A"關(guān)系,或者這種關(guān)系是可變的,使用組合。另外一個就是只有兩個類滿足里氏替換原則的時候,才可能是"Is-A" 關(guān)系。也就是說,如果兩個類是"Has-A"關(guān)系,但是設(shè)計成了繼承,那么肯定違反里氏替換原則。


錯誤的使用繼承而不是組合/聚合的一個常見原因是錯誤的把"Has-A"當成了"Is-A" 。"Is-A"代表一個類是另外一個類的一種;"Has-A"代表一個類是另外一個類的一個角色,而不是另外一個類的特殊種類。


看一個例子:

如果我們把“人”當成一個類,然后把“雇員”,“經(jīng)理”,“學(xué)生”當成是“人”的派生類。這個的錯誤在于把 “角色” 的等級結(jié)構(gòu)和 “人” 的等級結(jié)構(gòu)混淆了?!敖?jīng)理”,“雇員”,“學(xué)生”是一個人的角色,一個人可以同時擁有上述角色。如果按繼承來設(shè)計,那么如果一個人是雇員的話,就不可能是學(xué)生,這顯然不合理。

正確的設(shè)計是有個抽象類 “角色”,“人”可以擁有多個“角色”(聚合),“雇員”,“經(jīng)理”,“學(xué)生”是“角色”的派生類。


組合/聚合的優(yōu)缺點:類之間的耦合比較低,一個類的變化對其他類造成的影響比較少,缺點:類的數(shù)量增多實現(xiàn)起來比較麻煩

繼承的優(yōu)點:由于很多方法父類已經(jīng)實現(xiàn),子類的實現(xiàn)會相對比較簡單,缺點:將父類暴露給了子類,一定程度上破壞了封裝性,父類的改變對子類影響比較大

---------------------

作者:X-Programer

來源:CSDN

原文:https://blog.csdn.net/q5707802/article/details/91355587

版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容