面向对象基本原则
?
面向對象設計的原則是面向對象思想的提煉,它比面向對象思想的核心要素更具可操作性,但與設計模式相比,卻又更加的抽象,是設計精神要義的抽象概括。形象地將,面向對象思想像法理的精神,設計原則則相對于基本憲法,而設計模式就好比各式各樣的具體法律條文了。
面向對象設計原則有6個:開放封閉原則,單一職責原則,依賴倒置原則,Liskov替換原則,迪米特法則和接口隔離原則或合成/聚合復用原則(不同資料略有不同,這里對7個都做了整理)。
?
1單一職責原則(Single?Responsibility?Principle?SRP)
??There?should?never?be?more?than?one?reason?for?a?class?to?change.?什么意思呢?
??所謂單一職責原則就是一個類只負責一個職責,只有一個引起變化的原因。
??如果一個類承擔的職責過多,就等于把這些職責耦合在一起,一個職責的變化會削弱或抑制這個類完成其他職責的能力,這個耦合會導致脆弱的設計。
軟件設計真正要做的許多內容,就是發現職責并把這些職責相互分離;如果能夠想到多于一個動機去改變一個類,那么這個類就具有多于一個職責,就應該考慮類的分離。
?
以調制解調器為例如下圖:
?
從上述類圖里面我們發現有四個方法Dial(撥通電話),Hangup(掛電話),Receive(收到信息),Send(發送信息),經過分析不難判斷出,實際上Dial(撥通電話)和Hangup(掛電話)是屬于連接的范疇,而Receive(收到信息)和Send(發送信息)是屬于數據傳送的范疇。這里類包括兩個職責,顯然違反了SRP。
這樣做有潛在的隱患,如果要改變連接的方式,勢必要修改Modem,而修改Modem類的結果導致凡事依賴Modem類可能都需要修改,這樣就需要重新編譯和部署,不管數據傳輸這部分是否需要修改。
因此要重構Modem類,從中抽象出兩個接口,一個專門負責連接,另一個專門負責數據傳送。依賴Modem類的元素要做相應的細化,根據職責的不同分別依賴不同的接口。如下圖:
?
這樣以來,無論單獨修改連接部分還是單獨修改數據傳送部分,都彼此互不影響。
?
總結單一職責優點:
降低類的復雜性,
提高可維護性
提高可讀性。
降低需求變化帶來的風險。需求變化是不可避免的,如果單一職責做的好,一個接口修改只對相應的實現類有影響,對其它的接口無影響,這對系統的擴展性和維護性都有很大的幫助。
2里氏替換原則(Liskov?Substitution?Principle?LSP)
?
里氏替換原則是面向對象設計的基本原則之一。任何基類可以出現的地方,子類一定可以出現。LSP是繼承復用的基石,只有當子類可以替換基類,軟件單位的功能不受影響時,基類才能真正的被復用,而子類也可以在基類的基礎上增加新的行為。
Liskov提出了關于繼承的原則:Inheritance?should?ensure?that?any?property?proved?about?supertype?objects?also?holds?for?subtype?objects.----繼承必須確保超類中所擁有的性質在子類中仍然成立。2002年,軟件工程大師Robert?C.?Martin出版了一本《Agile?Software?DevelopmentPrinciples?Patterns?and?Practices》,在文中他把里氏代換原則最終簡化為一句話:“Subtypes?must?be?substitutable?for?their?base?types”也就是說子類必須能夠替換成他們的基類。
里氏替換原則講的是基類和子類的關系,只有這種關系存在的時候里氏替換原則才能成立。里氏替換原則是實現開放封閉原則的具體規范。這是因為:實現開放封閉原則的關鍵是抽象,而繼承關系又是抽象的一種具體實現。
?
我們大家都打過CS的游戲,用槍射擊殺人,如下類圖:
?
槍的主要職責是射擊,如何射擊在各個具體的子類中定義。注意在類中調用其他類時務必調用父類或接口,如果不能掉話父類或接口,說明類的射擊已經違反了LSP原則。
如果我們有一個玩具手?槍,該如何定義呢?我們先在類圖2-1上增加一個類ToyGun,然后繼承于AbstractGun類,修改后的類圖如下:
?
玩具槍是不能用來射擊的,殺不死人的,這個不應該寫shoot方法,在這種情況下業務的調用類就會出現問題。為了解決這個問題,ToyGun可以脫離繼承,建立一個獨立的父類,為了做到代碼可以服用,可以與AbstractGun建立關聯委托關系,如下圖:
?
因此,如果子類不能完整地實現父類的方法,那么建議斷開父子繼承關系,采用依賴,聚合,組合等關系代替繼承。
子類可以有自己的屬性或方法。
覆蓋或實現父類的方法時輸入的參數可以放大。
覆蓋或實現父類的方法時輸出結果可以被縮小。這是什么意思呢,父類的方法返回值是一個類型T,子類相同的方法(覆寫)的返回值為類型S,那么根據里氏替換原則就要求S必須小于等于T,也就是說要么S和T是同一個類型,要么S是T的子類型。
采用里氏替換原則的目的就是增加程序的健壯性,需求變更時也可以保持良好的兼容性和穩定性,即使增加子類,原有的子類可以繼續運行。在實際項目中,每個子類對應不同的業務含義,使用父類作為參數,傳遞不同的子類完成不同業務邏輯。
3依賴倒置原則(Dependence?Inversion?Principle?DIP?)
?所謂依賴倒置原則就是要依賴于抽象,不要依賴于具體。簡單的說就是對抽象進行編程,不要對實現進行編程,這樣就降低了客戶與實現模塊間的耦合。
面向過程的開發,上層調用下層,上層依賴于下層,當下層劇烈變化時,上層也要跟著變化,這就會導致模塊的復用性降低而且大大提高了開發的成本。
面向對象的開發很好的解決了這個問題,一般的情況下抽象的變化概率很小,讓用戶程序依賴于抽象,實現的細節也依賴于抽象。即使實現細節不斷變化,只要抽象不變,客戶程序就不需要變化。這大大降低了客戶程序域實現細節的耦合度。
比如一個合資汽車公司現在要求開發一個自動駕駛系統,只要汽車上安裝上這個系統,就可以實現無人駕駛,該系統可以在福特車系列和本田車系列上使用。面向過程的結構圖:
?
實現代碼如下:
public?class?HondaCar
????{
????????public?void?Run()?{?Console.WriteLine("本田車啟動了!");?}
????????public?void?Turn()?{?Console.WriteLine("本田車拐彎了!");?}
????????public?void?Stop()?{?Console.WriteLine("本田車停止了!");?}
????}
????public?class?FordCar?
????{
????????public?void?Run()?{?Console.WriteLine("福特車啟動了!");?}
????????public?void?Turn()?{?Console.WriteLine("福特車拐彎了!");?}
????????public?void?Stop()?{?Console.WriteLine("福特車停止了!");?}
????}
????public?class?AutoSystem
????{
????????public?enum?CarType{?Ford,Fonda}
????????private?HondaCar?hondcar=new?HondaCar();
????????private?FordCar?fordcar=new?FordCar();
????????private?CarType?type;
????????public?AutoSystem(CarType?carType)
????????{
????????????this.type?=?carType;
????????}
????????public?void?RunCar()
????????{
????????????if?(this.type?==?CarType.Fonda)
????????????{
????????????????hondcar.Run();
????????????}
????????????else?if?(this.type?==?CarType.Ford)
????????????{
????????????????fordcar.Run();
????????????}
????????}
????????public?void?StopCar()
????????{
????????????if?(this.type?==?CarType.Fonda)
????????????{
????????????????hondcar.Stop();
????????????}
????????????else?if?(this.type?==?CarType.Ford)
????????????{
????????????????fordcar.Stop();
????????????}
????????}
????????public?void?TurnCar()
????????{
????????????if?(this.type?==?CarType.Fonda)
????????????{
????????????????hondcar.Turn();
????????????}
????????????else?if?(this.type?==?CarType.Ford)
????????????{
????????????????fordcar.Turn();
????????????}
????????}
????}
顯然這個實現代碼也可滿足現在的需求。
但是如何現在公司業務規模擴大了,該自動駕駛系統還要把吉普車也兼容了。這些就需要修改AutoSystem類如下:
public?class?AutoSystem
????{
????????public?enum?CarType{?Ford,Fonda,Jeep}
????????private?HondaCar?hondcar=new?HondaCar();
????????private?FordCar?fordcar=new?FordCar();
????????private?Jeep?jeep?=?new?Jeep();
????????private?CarType?type;
????????public?AutoSystem(CarType?carType)
????????{
????????????this.type?=?carType;
????????}
????????public?void?RunCar()
????????{
????????????if?(this.type?==?CarType.Fonda)
????????????{
????????????????hondcar.Run();
????????????}
????????????else?if?(this.type?==?CarType.Ford)
????????????{
????????????????fordcar.Run();
????????????}
????????????else?if?(this.type?==?CarType.Jeep)
????????????{
????????????????jeep.Run();
????????????}
????????}
????????public?void?StopCar()
????????{
????????????if?(this.type?==?CarType.Fonda)
????????????{
????????????????hondcar.Stop();
????????????}
????????????else?if?(this.type?==?CarType.Ford)
????????????{
????????????????fordcar.Stop();
????????????}
????????????else?if?(this.type?==?CarType.Jeep)
????????????{
????????????????jeep.Stop();
????????????}
????????}
????????public?void?TurnCar()
????????{
????????????if?(this.type?==?CarType.Fonda)
????????????{
????????????????hondcar.Turn();
????????????}
????????????else?if?(this.type?==?CarType.Ford)
????????????{
????????????????fordcar.Turn();
????????????}
????????????else?if?(this.type?==?CarType.Jeep)
????????????{
????????????????jeep.Turn();
????????????}
????????}
????}
通過代碼分析得知,上述代碼也確實滿足了需求,但是軟件是不斷變化的,軟件的需求也是變化的,如果將來業務又擴大了,該自動駕駛系統還有能實現通用、三菱、大眾汽車,這樣我們不得不又要修改AutoSystem類了。這樣會導致系統越來越臃腫,越來越大,而且依賴越來越多低層模塊,只有低層模塊變動,AutoSystem類就不得不跟著變動,導致系統設計變得非常脆弱和僵硬。
?
導致上面所述問題一個原因是,含有高層策略的模塊,如AutoSystem模塊,依賴于它所控制的低層的具體細節的模塊(如FordCar和HondaCar)。如果能使AutoSystem模塊獨立于它所控制的具體細節,而是依賴抽象,那么我們就可以服用它了。這就是面向對象中的“依賴倒置”機制。如下類圖:
?
實現代碼如下:
public?interface?ICar
????{
????????void?Run();
????????void?Stop();
????????void?Turn();
????}
????public?class?HondaCar:ICar
????{
????????public?void?Run()?{?Console.WriteLine("本田車啟動了!");?}
????????public?void?Turn()?{?Console.WriteLine("本田車拐彎了!");?}
????????public?void?Stop()?{?Console.WriteLine("本田車停止了!");?}
????}
????public?class?FordCar?:ICar
????{
????????public?void?Run()?{?Console.WriteLine("福特車啟動了!");?}
????????public?void?Turn()?{?Console.WriteLine("福特車拐彎了!");?}
????????public?void?Stop()?{?Console.WriteLine("福特車停止了!");?}
????}
????public?class?Jeep:ICar
????{
????????public?void?Run()?{?Console.WriteLine("福特車啟動了!");?}
????????public?void?Turn()?{?Console.WriteLine("福特車拐彎了!");?}
????????public?void?Stop()?{?Console.WriteLine("福特車停止了!");?}
????}
????public?class?AutoSystem
????{
????????private?ICar?car;
????????public?AutoSystem(ICar?car)
????????{
????????????this.car?=?car;
????????}
????????public?void?RunCar()
????????{
????????????this.car.Run();
????????}
????????public?void?StopCar()
????????{
????????????this.car.Stop();
????????}
????????public?void?TurnCar()
????????{
????????????this.car.Turn();
????????}
????}
現在Autosystem系統依賴于ICar這個抽象,而與具體的實現細節HondaCar:和FordCar無關,所以實現細節的變化不會影響AutoSystem.對于實現細節只要實現ICar即可。即實現細節依賴于ICar抽象。
綜上所述:一個應用中的重要策略決定及業務?正是在這些高層的模塊中。也正是這些模塊包含這應用的特性。但是,當這些模塊依賴于低層模塊時,低層模塊的修改比較將直接影響到他們,迫使它們也改變。這種情況是荒謬的。
??應該是處于高層的模塊去迫使那些低層的模塊發生改變。處于高層的模塊應優先于低層的模塊。無論如何高層模塊也不應該依賴于低層模塊。而且我們想能夠復用的是高層的模塊,只有高層模塊獨立于低層模塊時,復用才有可能。
??總之,高層次的模塊不應該依賴于低層次的模塊,它們都應該依賴于抽象。抽象不應該依賴于具體,具體應該依賴于抽象。
?
?
4迪米特法則
迪米特法則(Law?of?Demeter)又叫最少知識原則(Least?Knowledge?Principle?LKP),就是說一個對象應當對其他對象有盡可能少的了解,不和陌生人說話。
????對面向對象來說,一個軟件實體應當盡可能的少的與其他實體發生相互作用。每一個軟件單位對其他的單位都只有最少的知識,而其局限于那些與本單位密切相關的軟件單位。
迪米特法則的目的在于降低類之間的耦合。由于每個類盡量減少對其他類的依賴,因此,很容易使得系統的功能模塊相互獨立,相互之間不存在依賴關系。應用迪米特法則有可能造成的一個后果就是,系統中存在的大量的中介類,這些類只所以存在完全是為了傳遞類之間的相互調用關系---這在一定程度上增加系統的復雜度。
設計模式中的門面模式(Facade)和中介模式(Mediator)都是迪米特法則的應用的例子。
狹義的迪米特法則的缺點:
在系統里面造出大量的小方法,這些方法僅僅是傳遞間接的調用,與系統的商業邏輯無關。
遵循類之間的迪米特法則會使一個系統的局部設計簡化,因為每一個局部都不會和遠距離的對象有之間的關聯。但是,這也會造成系統的不同模塊之間的通信效率降低,也會使系統的不同模塊之間不容易協調。
廣義的迪米特法則在類的設計上的體現:
優先考慮將一個類設置成不變類.
盡量降低一個類的訪問權限。
盡量降低成員的訪問權限。
?
下面的代碼在方法體內部依賴了其他類,這嚴重違反迪米特法則
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public?class?Teacher?{? ?? ????public?void?commond(GroupLeader?groupLeader)?{? ????????List<Girl>?listGirls?=?new?ArrayList<Girl>();? ?? ????????for?(int?i?=?0;?i?<?20;?i++)?{? ????????????listGirls.add(new?Girl());? ????????}? ?? ????????groupLeader.countGirls(listGirls);? ????}? ?? } |
?
方法是類的一個行為,類竟然不知道自己的行為與其他類產生了依賴關系,這是不允許的。正確的做法是:
?
| 1 2 3 4 5 6 7 | public?class?Teacher?{? ?? ????public?void?commond(GroupLeader?groupLeader)?{? ????????groupLeader.countGirls();? ????}? ?? } |
?
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public?class?GroupLeader?{? ?? ????private?List<Girl>?listGirls;? ?? ????public?GroupLeader(List<Girl>?_listGirls)?{? ????????this.listGirls?=?_listGirls;? ????}? ?? ????public?void?countGirls()?{? ????????System.out.println("女生數量是:"?+?listGirls.size());? ????}? ?? } |
?
?
5開放封閉原則(Open-Closed?Principle?OCP)
Software?entities(classes,modules,functions?etc)?should?open?for?extension?,but?close?for?modification.?
???什么意思呢?
???所謂開放封閉原則就是軟件實體應該對擴展開發,而對修改封閉。開放封閉原則是所有面向對象原則的核心。軟件設計本身所追求的目標就是封裝變化,降低耦合,而開放封閉原則正是對這一目標的最直接體現。
???開放封閉原則主要體現在兩個方面:
???對擴展開放,意味著有新的需求或變化時,可以對現有代碼進行擴展,以適應新的情況。
???對修改封閉,意味著類一旦設計完成,就可以獨立其工作,而不要對類盡任何修改。
?
為什么要用到開放封閉原則呢?
軟件需求總是變化的,世界上沒有一個軟件的是不變的,因此對軟件設計人員來說,必須在不需要對原有系統進行修改的情況下,實現靈活的系統擴展。
?
如何做到對擴展開放,對修改封閉呢?
實現開放封閉的核心思想就是對抽象編程,而不對具體編程,因為抽象相對穩定。讓類依賴于固定的抽象,所以對修改就是封閉的;而通過面向對象的繼承和多態機制,可以實現對抽象體的繼承,通過覆寫其方法來改變固有行為,實現新的擴展方法,所以對于擴展就是開放的。
對于違反這一原則的類,必須通過重構來進行改善。常用于實現的設計模式主要有Template?Method模式和Strategy?模式。而封裝變化,是實現這一原則的重要手段,將經常變化的狀態封裝為一個類。
以銀行業務員為例
沒有實現OCP的設計:
?
public?class?BankProcess
????{
????????//存款
????????public?void?Deposite()
????????{
????????}
????????//取款
????????public?void?Withdraw()
????????{
????????}
????????//轉賬
????????public?void?Transfer()
????????{
????????}
????}
?
????public?class?BankStaff
????{
????????private?BankProcess?bankpro?=?new?BankProcess();
????????public?void?BankHandle(Client?client)
????????{
????????????switch?(client.Type)
????????????{
????????????????????//存款
????????????????case?"deposite":
????????????????????bankpro.Deposite();
????????????????????break;
????????????????????//取款
????????????????case?"withdraw":
????????????????????bankpro.Withdraw();
????????????????????break;
????????????????????//轉賬
????????????????case?"transfer":
????????????????????bankpro.Transfer();
????????????????????break;
????????????}
????????}
????}
這種設計顯然是存在問題的,目前設計中就只有存款,取款和轉賬三個功能,將來如果業務增加了,比如增加申購基金功能,理財功能等,就必須要修改BankProcess業務類。我們分析上述設計就不能發現把不能業務封裝在一個類里面,違反單一職責原則,而有新的需求發生,必須修改現有代碼則違反了開放封閉原則。
從開放封閉的角度來分析,在銀行系統中最可能擴展的就是業務功能的增加或變更。對業務流程應該作為擴展的部分來實現。當有新的功能時,不需要再對現有業務進行重新梳理,然后再對系統做大的修改。
如何才能實現耦合度和靈活性兼得呢?
那就是抽象,將業務功能抽象為接口,當業務員依賴于固定的抽象時,對修改就是封閉的,而通過繼承和多態繼承,從抽象體中擴展出新的實現,就是對擴展的開放。
以下是符合OCP的設計:
?
首先聲明一個業務處理接口
public??interface?IBankProcess
????{
?????????void?Process();
????}
public?class?DepositProcess?:?IBankProcess
????{
????????public?void?Process()
????????{
????????????//辦理存款業務
????????????Console.WriteLine("Process?Deposit");
????????}
}
public?class?WithDrawProcess?:?IBankProcess
????{
????????public?void?Process()
????????{
????????????//辦理取款業務
????????????Console.WriteLine("Process?WithDraw");
????????}
}
?
public?class?TransferProcess?:?IBankProcess
????{
????????public?void?Process()
????????{
????????????//辦理轉賬業務
????????????Console.WriteLine("Process?Transfer");
????????}
????}
?
public?class?BankStaff
????{
????????private?IBankProcess?bankpro?=?null;
????????public?void?BankHandle(Client?client)
????????{
????????????switch?(client.Type)
????????????{
????????????????????//存款
????????????????case?"Deposit":
????????????????????bankpro?=?new?DepositUser();
????????????????????break;
????????????????????//轉賬
????????????????case?"Transfer":
????????????????????bankpro?=?new?TransferUser();
????????????????????break;
????????????????????//取款
????????????????case?"WithDraw":
????????????????????bankpro?=?new?WithDrawUser();
????????????????????break;
????????????}
?
?
?
?
????????????bankpro.Process();
????????}
????}
?
這樣當業務變更時,只需要修改對應的業務實現類就可以,其他不相干的業務就不必修改。當業務增加,只需要增加業務的實現就可以了。
?
設計建議:
開放封閉原則,是最為重要的設計原則,Liskov替換原則和合成/聚合復用原則為開放封閉原則提供保證。
可以通過Template?Method模式和Strategy模式進行重構,實現對修改封閉,對擴展開放的設計思路。
封裝變化,是實現開放封閉原則的重要手段,對于經常發生變化的狀態,一般將其封裝為一個抽象,例如銀行業務中IBankProcess接口。
拒絕濫用抽象,只將經常變化的部分進行抽象。
?
6接口隔離原則(ISP)
接口隔離原則?認為:"使用多個專門的接口比使用單一的總接口要好"。因為接口如果能夠保持粒度夠小,就能保證它足夠穩定,正如單一職責原則所標榜的那樣。多個專門的接口就好比采用活字制版,可以隨時拼版拆版,既利于修改,又利于文字的重用。而單一的總接口就是雕版印刷,顯得笨重,實現殊為不易;一旦發現錯字別字,就很難修改,往往需要整塊雕版重新雕刻。
例一:
參考下圖的設計,在這個設計里,取款、存款、轉帳都使用一個通用界面接口,也就是說,每一個類都被強迫依賴了另兩個類的接口方法,那么每個類有可能因為另外兩個類的方法(跟自己無關)而被影響。拿取款來說,它根本不關心“存款操作”和“轉帳操作”,可是它卻要受到這兩個方法的變化的影響。
?
那么我們該如何解決這個問題呢?參考下圖的設計,為每個類都單獨設計專門的操作接口,使得它們只依賴于它們關系的方法,這樣就不會互相影了!
?
?
?
例二:
使用多個專門的接口還能夠體現對象的層次,因為我們可以通過接口的繼承,實現對總接口的定義。例如,.NET框架中IList接口的定義。
1.?public?interface?IEnumerable??
2.?{??
3.?????IEnumerator?GetEnumerator();??
4.?}??
5.?public?interface?ICollection?:?IEnumerable??
6.?{??
7.?????void?CopyTo(Array?array,?int?index);??
8.??
9.?????//?其余成員略??
10.?}??
11.?public?interface?IList?:?ICollection,?IEnumerable??
12.?{??
13.?????int?Add(object?value);??
14.?????void?Clear();??
15.?????bool?Contains(object?value);??
16.?????int?IndexOf(object?value);??
17.?????void?Insert(int?index,?object?value);??
18.?????void?Remove(object?value);??
19.?????void?RemoveAt(int?index);??
20.??
21.?????//?其余成員略??
22.?}?
如果不采用這樣的接口繼承方式,而是定義一個總的接口包含上述成員,就無法實現IEnumerable接口、ICollection接口與IList接口成員之間的隔離。假如這個總接口名為IGeneralList,它抹平了IEnumerable接口、ICollection接口與IList接口之間的差別,包含了它們的所有方法。現在,如果我們需要定義一個Hashtable類。根據數據結構的特性,它將無法實現IGeneralList接口。因為Hashtable包含的Add()方法,需要提供鍵與值,而之前針對ArrayList的Add()方法,則只需要值即可。這意味著兩者的接口存在差異。我們需要專門為Hashtable定義一個接口,例如IDictionary,但它卻與IGeneralList接口不存在任何關系。正是因為一個總接口的引入,使得我們在可枚舉與集合層面上丟失了共同的抽象意義。雖然Hashtable與ArrayList都是可枚舉的,也都具備集合特征,它們卻不可互換。
如果遵循接口隔離原則,將各自的集合操作功能分解為不同的接口,那么站在ICollection以及IEnumerable的抽象層面上,可以認為ArrayList和Hashtable是相同的對象。在這一抽象層面上,二者是可替換的,如圖2-9所示。這樣的設計保證了一定程度的重用性與可擴展性。從某種程度來講,接口隔離原則可以看做是接口層的單一職責原則。
| ? ? |
| 圖2-9??遵循接口隔離原則 |
倘若一個類實現了所有的專門接口,從實現上看,它與實現一個總接口的方式并無區別;但站在調用者的角度,不同的接口代表了不同的關注點、不同的職責,甚至是不同的角色。因此,面對需求不同的調用者,這樣的類就可以提供一個對應的細粒度接口去匹配。此外,一個龐大的接口不利于我們對其進行測試,因為在為該接口實現Mock或Fake對象?時,需要實現太多的方法。
概括地講,面向對象設計原則仍然是面向對象思想的體現。例如,單一職責原則與接口隔離原則體現了封裝的思想,開放封閉原則體現了對象的封裝與多態,而Liskov替換原則是對對象繼承的規范,至于依賴倒置原則,則是多態與抽象思想的體現。在充分理解面向對象思想的基礎上,掌握基本的設計原則,并能夠在項目設計中靈活運用這些原則,就能夠改善我們的設計,尤其能夠保證可重用性、可維護性與可擴展性等系統的質量屬性。這些核心要素與設計原則,就是我們設計的對象法則,它們是理解和掌握設計模式的必備知識。
7組合/聚集復用原則
組合/聚合復用原則(Composite/Aggregate?Reuse?Principle?CARP).組合和聚合都是對象建模中關聯(Association)關系的一種.聚合表示整體與部分的關系,表示“含有”,整體由部分組合而成,部分可以脫離整體作為一個獨立的個體存在。組合則是一種更強的聚合,部分組成整體,而且不可分割,部分不能脫離整體而單獨存在。在合成關系中,部分和整體的生命周期一樣,組合的新的對象完全支配其組成部分,包括他們的創建和銷毀。一個合成關系中成分對象是不能與另外一個合成關系共享。
組合/聚合和繼承是實現復用的兩個基本途徑。合成復用原則是指盡量使用合成/聚合,而不是使用繼承。
只有當以下的條件全部被滿足時,才應當使用繼承關系。
1?子類是超類的一個特殊種類,而不是超類的一個角色,也就是區分“Has-A”和“Is-A”.只有“Is-A”關系才符合繼承關系,“Has-A”關系應當使用聚合來描述。
2?永遠不會出現需要將子類換成另外一個類的
子類的情況。如果不能肯定將來是否會變成另外一個子類的話,就不要使用繼承。
3?子類具有擴展超類的責任,而不是具有置換掉或注銷掉超類的責任。如果一個子類需要大量的置換掉超類的行為,那么這個類就不應該是這個超類的子類。
錯誤的使用繼承而不是合成/聚合的一個常見原因是錯誤地把“Has-A”當成了“Is-A”.”Is-A”代表一個類是另外一個類的一種;而“Has-A”代表一個類是另外一個類的一個角色,而不是另外一個類的特殊種類。
我們需要辦理一張銀行卡,如果銀行卡默認都擁有了存款、取款和透支的功能,那么我們辦理的卡都將具有這個功能,此時使用了繼承關系:
?
為了靈活地擁有各種功能,此時可以分別設立儲蓄卡和信用卡兩種,并有銀行卡來對它們進行聚合使用。此時采用了合成復用原則
?
?
轉載于:https://www.cnblogs.com/sh91/archive/2012/03/25/2416624.html
總結
- 上一篇: 数字图像处理之边缘检测,图像分割
- 下一篇: bootloader学习笔记