1. 程式人生 > >淺談Bridge模式的意圖與心機~~

淺談Bridge模式的意圖與心機~~

說實話,第一次聽到這個官方定義我的內心是崩潰的,好不容易初探端詳的面向物件的思想直接在這一句話中毀滅殆盡,好吧,我承認,我花了很久才吃透了這個模式

Bridge模式即大名鼎鼎的橋接模式,在寶典《Gof》中的意圖是如下定義的:將抽象與實現解耦,使它們可以獨立地變化~~

在具體地聊這個問題之前,我們再來聊2個基本的概念:

解耦:即讓各種事物相互獨立地行事,或者至少明確地宣告之間的關係

抽象:是指不同事物之間概念上的聯絡方式

對於抽象與實現,大多數人談及此問題時之所以會迷糊是因為他們認為實現即抽象的構建,因此實現與抽象來說應當是相輔相成的關係,及抽象是實現的佔位符,
何談耦合與解耦~~~

實際則是我想錯了,“這裡”的實現所指即抽象類及其派生類用來實現自己所用的物件~~,(在沒有理解Bridge之前即使這句話你絕壁還是蒙逼的)

不過如果你已經意識到了上面這句話與之前那句話之間的區別,別擔心,其實你已經較之前跨出了歷史性的一步。

橋接模式在《設計模式》解析中給出了極高的評價,被稱作是最難理解的模式,但卻也是一個極其符合設計模式2大原則的例子:“找出變化並封裝之”和“優先使用物件聚集而不是類繼承”。

應用設計模式並不是直接拿模板去拼裝程式碼,最重要的是理解模式的意圖與目的,知道模式所欲解決的問題本身是什麼?這才是學習解決問題方法的正確姿勢~~~

對於在Bridge模式所遇到的問題主要有如下2點:概念的抽象有變化;這些概念的實現方式存在變化。

如果舉出一個例子的話相信會好理解很多~~:現在有2個列印工具程式,同時現在有N種圖形可能將要被列印~~,你需要編寫一個程式去完成使用這2個程式之一去列印圖形。對於列印某一個圖形的時候選擇哪一種方式去列印該圖形則無需該程式考慮,呼叫者可以自己協調~~

以Cricle為例:我們的程式既然可以列印圖形Cricle,那麼這個程式必然需要設定變數接受所有有關該圖形基礎資訊,將這些所有資訊包裹在一個數據型別中,很多人喜歡稱這種行為作封裝,的確根據c++的定義的確是這樣,但是目前這樣做並沒有體現出封裝行為在設計模式中真正的深層含義,我暫且將其稱作“包裹”,除了這些基礎資訊之外,這個程式還將具有列印該圖形的功能,這個功能需要用到一些列印工具的部分功能組合協助完成。對於列印這個功能,我們習慣稱作方法。

UML如下所示:


現在我們很明顯有可能遇到以下2個問題,假設當前使用的是列印工具1,圖形為Cricle,當繪製圖形發生變化時我們呢該如何處理呢?

貼上複製然後加上一個分支?這很明顯是個錯誤的決定,這個方法直接違反了開放封閉原則,順便說一句:很多bug都是源於修改,你經常很難把握修改一段邏輯對你的程式的整體的改變及影響,又有時這就是一個火藥桶。

既然學習了面向物件的方法,我們呢自然要有一些面向物件的意識~,我們考慮一下如下這種設計;


我們可以設計這樣一個抽象類Shape,他用來代表所有的圖形,然後所有的具體的圖形例如Cricle之流都作為Shape的子類繼承,這樣做的話其實就是相當於我們封裝了圖形的資訊,我們可以通過Shape來引用到任意一個具體的圖形,而程式本身則完全無需關注具體圖形的形狀和細節,只需要在特定的時期直接使用即可,這就體現了封裝的價值:隱藏,c++中將這個行為稱作對資料的隱藏,但是其實封裝隱藏的不僅僅是資料,準確的說應當是隱藏一切,任何可以實際表達或衡量的事物其實都可以被隱藏~~,(舉個栗子:方法的隱藏就是c++中虛擬函式的使用)


同樣的我們也設定一個類似的抽象類Draw用來表示具體的列印的方法工具;然後將每一種具體的繪製工具類用這個類作為佔位符代替,可能很多人會說到:這2套繪製工具是本就存在而非我們設計的,介面,功能的實現方法都不同,最重要的是他們極有可能並不是共生與一個父類,而是2個獨立的類,對於這種認知上功能上價值上相同但是實際細節不同的2個類,我們可以採用介面卡模式,為他們適配一套共有的介面,這在日常程式碼生產中是極為常見和有效的處理方式~~。

現在我們便得到了如下2個組合:


這個時候就很顯然的出現了問題~,我們該如何去組合這2個內容呢?
很明顯;我們的封裝出來的列印方法是在這個程式中是不能獨立存在的,我們的列印方法沒有方法去主動獲取圖形的資訊,同時在認知上來說一個繪列印工具也的確沒有責任去獲取圖形的資訊,他只是一個被呼叫的根據具體引數實時做出對應行為的工具,我們於是產生了這樣一個結構,讓我們的圖形類繼承自工具類,這樣所有的圖形就都具有了列印的功能,所有的圖形都可以完成自己的列印,對於一個圖形包含自身列印或其他這一類對自身操作的方法,從責任上來說也的確是合適的,每個物件都只負責與自己有關的thing。
實際的UML如下:

相信一些敏銳的人已經發現這在這其中所蘊含的一些重大危機:

1:具體圖形類與列印方法類之間的耦合度過高(每一種能夠提出的新的組合都需要建立一個新類,每一種基礎圖形或列印方法的新增都將帶來大量的類爆炸)

2:這類結構設計有可能會存在過多的冗餘程式碼(由於介面的統一,實現方式經常會出現相似或完全相同,這種設計下會出現大量相同程式碼)

很明顯這不是一種很好的設計!

這個時候就讓我們試一試另一種設計吧:


這樣也算是解除了列印版本1與列印版本2之間的程式碼冗餘,不過不得不說的類爆炸的問題和Cricle和Rentangle之間的耦合仍然在~~~,和原來相比半斤八兩~

肯定是會有更好的設計~

這裡不得不說到軟體設計的又一大原則,儘量使用類聚集而不是類繼承,類繼承所帶來的類與類之間的關係是一種絕對的強耦合。

在這裡我們不由地想到列印工具和圖形本身都是可抽象的事物,現在耦合度過高的問題在於他們都有可能同時發生變化,如果有辦法可以分離這2種變化就完美了。

接下來就將向你們展示最終版本的結構設計

將列印工具作為圖形類的一部分內嵌入圖形類之中,在這種設計之下,圖形的變化與列印方式的變化便被分離了開來,無論新增多少種不同的圖形或者是列印方式,都不會產生多種實現組合的建立問題,只需要關注其變化的部分~~,有沒有感覺瞬間問題都變得清爽了起來~~

此刻對於開始的那句抽象以及它所使用的物件的實現的耦合的分離是否有所理解,在這裡就是對於列印方法的變化和圖形本身變化的分離,使得介面卡的可擴充套件性大大提高~~

下面是源於一段來自《設計模式解析》的一段分析:

Bridge模式:

意圖:將一組實現與另一組使用它們的物件分離

問題:一個抽象類蘊含多個實現,但是不能出現類數量爆炸的增長

解決方案:為所有實現定義一個介面,供抽象類的所有派生類使用

效果:實現與使用實現的物件的解耦合,提供了可擴充套件性,客戶物件無需操心實現問題。

實現:1:將實現封裝在一個抽象類中

            2:在要實現的抽象的基類中包含一個實現的控制代碼。(介面)