【設計模式】設計原則--面向接口編程你理解的對嗎?

最近看了《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

Duck Class Diagram

設計系統時考慮到鴨子都會發出叫聲,並且都會游泳,因而將Quack方法和Swim方法定義到Duck基類中並實現;此外,並非全部的鴨子都是長得同樣的,那麼將Display方法在Duck基類中定義爲抽象的,全部繼承自Duck基類的子類編寫本身的實現。code

新的需求產生了!咱們須要讓系統中的鴨子能夠飛。從面向對象的角度來考慮,若是咱們想要代碼重用,只須要在Duck基類中添加方法Fly並實現它——全部的鴨子子類都是繼承自Duck基類的——就實現了讓鴨子飛的功能。咱們經過繼承實現了代碼重用,很輕鬆就解決了問題。orm

也許咱們須要深刻考慮一下,全部的鴨子都會飛嗎?玩具橡膠鴨呢?咱們把Fly方法的定義及實現放到了Duck基類中,全部繼承自它的子類都繼承到了Fly方法,其中也包括了不該繼承Fly方法的子類。若是按照上面的方案,那咱們只能在RubberDuck橡膠鴨子類中重寫父類的Fly方法讓RubberDuck執行Fly的時候什麼都不作。對象

再深刻一些,若是咱們的系統中除了橡膠鴨外,還有其餘各類鴨子,好比木頭鴨子呢?這時DecoyDuck木頭鴨子繼承來的Quack方法出現了問題——木頭鴨子不會叫!咱們只好再把DecoyDuck中的Quack方法重寫了......

若是咱們改用接口會怎麼樣呢?把QuackFly方法從基類中拿出來,分別在IQuackableIFlyable接口中定義,而後咱們不一樣的子類根據須要來繼承接口,並實現QuackFly方法。

Interface Class Diagram

當咱們有不少個子類鴨子的時候,就要分別爲每一個繼承了IQuackableIFlyable接口的子類來編寫QuackFly的實現方法,這徹底破壞了代碼重用!值得注意的是,雖然咱們在這裏使用了接口,但這並非面向接口編程。

封裝變化

這裏引入第一條設計原則: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的不一樣子類中,QuackFly的行爲是會發生變化的,那麼咱們將QuackFly方法從Duck基類中拿出來,併爲QuackFly方法分別建立一些類,來實現各類不一樣的行爲。

Encapsulation Varies

面向接口而不是實現編程

設計原則:Program to an interface, not an implementation.(面向接口而不是實現編程。)

在這裏,面向接口而不是實現編程,和封裝變化是相輔相成的。值得注意的是,這裏所說的接口,並非咱們代碼層面上的interface,"面向接口編程(Program to an interface)所表達的意思其實是面向基類編程(Program to a supertype),核心思想是利用面向對象編程的多態性。在代碼的具體實現上,咱們既能夠用Interface來做爲咱們所面向的接口,也能夠用一個抽象的基類來做爲咱們面向的接口。遵循面向接口編程,對模擬鴨子程序的FlyQuack行爲進行設計,咱們能夠定義接口IFlyBehaviorIQuackBehavior來表明行爲FlyQuack,接口的實現則是行爲具體的表現形式。咱們能夠將接口的不一樣實現類,來賦值給Duck的不一樣子類,從而利用繼承多態來實現面向接口編程。類圖以下:

Program To Interface

FlyBehavior是一個全部不一樣的Fly類都要繼承的接口或基類,其中定義了Fly方法。不一樣的Fly類有不一樣的Fly方法實現。QuackBehavior相似。

接下來咱們對Duck類進行更改,將FlyQuack委託出去,再也不經過Duck類或其子類的方法來實現。

  1. 首先咱們在Duck類中定義兩個表明FlyBehaviorQuackBehavior的變量。這兩個變量的值是不一樣的Duck所須要的特定FlyBehaviorQuackBehavior的子類:Instance Variables

  2. 而後實現PerformQuack方法:Implement PerformQuack

  3. FlyBehaviorQuackBehavior賦值:Assignment

至此咱們就實現了面向接口編程。

咱們還能夠動態設置Duck的行爲,只須要爲Duck類的FlyBehaviorQuackBehavior提供Set方法(在C#中,使用自動屬性便可)。

優先考慮組成而不是繼承

Favor composition over inheritance.(優先考慮組成而不是繼承。)

HAS-A(有一個)比IS-A(是一個)要好。HAS-A在咱們的Duck系統中能夠描述爲:每個DuckHAS-A有一個FlyBehavior,還HAS-A有一個QuackBehaviorDuck委託它們來處理FlyQuack的行爲。優先考慮組合而不是繼承讓咱們的系統擁有更多的靈活性,封裝變化,還能夠在運行時動態更改類的行爲。

示例代碼

示例代碼

相關文章
相關標籤/搜索