最近看了《Head First Design Patterns》這本書,正如其名,這本書講的是設計模式(Design Patterns),而這本書的第一章,講的是很重要的一些設計原則(Design Principles),
-
Identify the aspects of your application that vary and separate them from what stays the same.(識別應用程式中各個方面的變化,并將它們與保持不變的部分分開,)
-
Program to an interface, not an implementation.(面向介面而不是實作編程,)
-
Favor composition over inheritance.(優先考慮組成而不是繼承,)
其中令我感觸頗深的是,“面向介面而不是實作編程”顛覆了我一直以來的認識,
文章中示例代碼為原書中截圖,C#代碼參照文末提供鏈接,
開始
書中用了一個很形象的示例:模擬鴨子程式(SimUDuck),系統的最初設計使用標準的OO技術,并創建了一個Duck基類,所有其他Duck型別都繼承自該基類,

設計系統時考慮到鴨子都會發出叫聲,而且都會游泳,于是將Quack方法和Swim方法定義到Duck基類中并實作;此外,并不是所有的鴨子都是長得一樣的,那么將Display方法在Duck基類中定義為抽象的,所有繼承自Duck基類的子類撰寫自己的實作,
新的需求產生了!我們需要讓系統中的鴨子可以飛,從面向物件的角度來考慮,如果我們想要代碼重用,只需要在Duck基類中添加方法Fly并實作它——所有的鴨子子類都是繼承自Duck基類的——就實作了讓鴨子飛的功能,我們通過繼承實作了代碼重用,很輕松就解決了問題,
也許我們需要深入考慮一下,所有的鴨子都會飛嗎?玩具橡膠鴨呢?我們把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的行為,優先考慮組合而不是繼承讓我們的系統擁有更多的靈活性,封裝變化,還可以在運行時動態更改類的行為,
示例代碼
示例代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/29322.html
標籤:設計模式
下一篇:創建型模式
