最近看了《Head First Design Patterns》這本書。正如其名,這本書講的是設計模式(Design Patterns),而這本書的第一章,講的是很重要的一些設計原則(Design Principles)。git
Identify the aspects of your application that vary and separate them from what stays the same.(識別應用程序中各個方面的變化,並將它們與保持不變的部分分開。)github
Program to an interface, not an implementation.(面向接口而不是實現編程。)編程
Favor composition over inheritance.(優先考慮組成而不是繼承。)設計模式
其中令我感觸頗深的是,「面向接口而不是實現編程」顛覆了我一直以來的認識。app
文章中示例代碼爲原書中截圖,C#代碼參照文末提供連接。設計
書中用了一個很形象的示例:模擬鴨子程序(SimUDuck)。系統的最初設計使用標準的OO技術,並建立了一個Duck基類,全部其餘Duck類型都繼承自該基類。3d
設計系統時考慮到鴨子都會發出叫聲,並且都會游泳,因而將Quack
方法和Swim
方法定義到Duck
基類中並實現;此外,並非全部的鴨子都是長得同樣的,那麼將Display
方法在Duck
基類中定義爲抽象的,全部繼承自Duck
基類的子類編寫本身的實現。code
新的需求產生了!咱們須要讓系統中的鴨子能夠飛。從面向對象的角度來考慮,若是咱們想要代碼重用,只須要在Duck
基類中添加方法Fly
並實現它——全部的鴨子子類都是繼承自Duck
基類的——就實現了讓鴨子飛的功能。咱們經過繼承實現了代碼重用,很輕鬆就解決了問題。orm
也許咱們須要深刻考慮一下,全部的鴨子都會飛嗎?玩具橡膠鴨呢?咱們把Fly
方法的定義及實現放到了Duck
基類中,全部繼承自它的子類都繼承到了Fly
方法,其中也包括了不該繼承Fly
方法的子類。若是按照上面的方案,那咱們只能在RubberDuck
橡膠鴨子類中重寫父類的Fly
方法讓RubberDuck
執行Fly
的時候什麼都不作。對象
再深刻一些,若是咱們的系統中除了橡膠鴨外,還有其餘各類鴨子,好比木頭鴨子呢?這時DecoyDuck
木頭鴨子繼承來的Quack
方法出現了問題——木頭鴨子不會叫!咱們只好再把DecoyDuck
中的Quack
方法重寫了......
若是咱們改用接口會怎麼樣呢?把Quack
和Fly
方法從基類中拿出來,分別在IQuackable
和IFlyable
接口中定義,而後咱們不一樣的子類根據須要來繼承接口,並實現Quack
或Fly
方法。
當咱們有不少個子類鴨子的時候,就要分別爲每一個繼承了IQuackable
或IFlyable
接口的子類來編寫Quack
或Fly
的實現方法,這徹底破壞了代碼重用!值得注意的是,雖然咱們在這裏使用了接口,但這並非面向接口編程。
這裏引入第一條設計原則:Identify the aspects of your application that vary and separate them from what stays the same.(識別應用程序中各個方面的變化,並將它們與保持不變的部分分開。)
換言之:take the parts that vary and encapsulate them, so that later you can alter or extend the parts that vary without affecting those that don’t.(將變化的部分封裝起來,以便之後能夠更改或擴展變化的部分而不會影響那些不變的部分。)
這樣帶來的好處是,咱們能夠進行更少的代碼更改來實現需求功能,減小因代碼更改而帶來的意想不到的影響,而且提升了系統靈活性。
咱們知道Duck
的不一樣子類中,Quack
和Fly
的行爲是會發生變化的,那麼咱們將Quack
和Fly
方法從Duck
基類中拿出來,併爲Quack
和Fly
方法分別建立一些類,來實現各類不一樣的行爲。
設計原則:Program to an interface, not an implementation.(面向接口而不是實現編程。)
在這裏,面向接口而不是實現編程,和封裝變化是相輔相成的。值得注意的是,這裏所說的接口,並非咱們代碼層面上的interface
,"面向接口編程(Program to an interface)所表達的意思其實是面向基類編程(Program to a supertype),核心思想是利用面向對象編程的多態性。在代碼的具體實現上,咱們既能夠用Interface
來做爲咱們所面向的接口,也能夠用一個抽象的基類來做爲咱們面向的接口。遵循面向接口編程,對模擬鴨子程序的Fly
和Quack
行爲進行設計,咱們能夠定義接口IFlyBehavior
、IQuackBehavior
來表明行爲Fly
和Quack
,接口的實現則是行爲具體的表現形式。咱們能夠將接口的不一樣實現類,來賦值給Duck
的不一樣子類,從而利用繼承及多態來實現面向接口編程。類圖以下:
FlyBehavior
是一個全部不一樣的Fly
類都要繼承的接口或基類,其中定義了Fly
方法。不一樣的Fly
類有不一樣的Fly
方法實現。QuackBehavior
相似。
接下來咱們對Duck
類進行更改,將Fly
和Quack
委託出去,再也不經過Duck
類或其子類的方法來實現。
首先咱們在Duck
類中定義兩個表明FlyBehavior
和QuackBehavior
的變量。這兩個變量的值是不一樣的Duck
所須要的特定FlyBehavior
、QuackBehavior
的子類:
而後實現PerformQuack
方法:
爲FlyBehavior
和QuackBehavior
賦值:
至此咱們就實現了面向接口編程。
咱們還能夠動態設置Duck
的行爲,只須要爲Duck
類的FlyBehavior
、QuackBehavior
提供Set
方法(在C#中,使用自動屬性便可)。
Favor composition over inheritance.(優先考慮組成而不是繼承。)
HAS-A(有一個)比IS-A(是一個)要好。HAS-A在咱們的Duck
系統中能夠描述爲:每個Duck
都HAS-A有一個FlyBehavior
,還HAS-A有一個QuackBehavior
,Duck
委託它們來處理Fly
和Quack
的行爲。優先考慮組合而不是繼承讓咱們的系統擁有更多的靈活性,封裝變化,還能夠在運行時動態更改類的行爲。