乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      小例子背后的大道理:Adapter模式詳解

       瞻云軒 2015-05-08

      前文說到一位用戶拿著業(yè)界標準開關(guān)(一個標準的StandardSwitcher,它依賴IStandardSwitchable接口才能工作,然而目前我們的燈并不支持這個接口)出現(xiàn)在我面前,叫囂著他的“標準開關(guān)”應(yīng)該能打開我們的燈。好吧,這個需求是合理的,的確應(yīng)該支持。但是該死的是,為什么沒有早一點兒知道這個標準的存在呢?這樣就不需要花費時間和人力定義這個接口,現(xiàn)在也不會這么糾結(jié)。和上次一樣,先講故事、演進方案,再分析背后的思想。

      這回主要講解Adapter模式,GoF講解了這個模式是什么,怎么用,用在什么地方。我想來解釋一下Adapter模式的要點是什么,對Adapter模式的延展,以及對Adapter模式的誤用。順便得瑟一下我對面向?qū)ο笤O(shè)計的理解。

      兩個方案

      現(xiàn)在有兩個選擇。

      1. 讓我們的燈直接支持標準開關(guān)。也就是讓燈實現(xiàn)IStandardSwitchable接口。

        • 好處:成本低,實現(xiàn)方式優(yōu)雅。

        • 壞處:相當(dāng)于放棄了已經(jīng)買了我們的燈,又想用標準開關(guān)的用戶。

      2. 不改變現(xiàn)在的燈,讓標準開關(guān)能打開我們的燈。標準接口我們改不了,燈也不能改。好在計算機界有句話,叫“加一層可以解決一切問題”。這讓我想到了買外國電器附贈的那個電源接口轉(zhuǎn)換器?,F(xiàn)在,我們的燈需要個類似的玩意兒。

        • 好處:支持所有的燈。

        • 壞處:這東西都是要附贈的,會降低我們的利潤。

      第一個方案很簡單,就是讓Light多實現(xiàn)個接口就OK了。圖就不給了。

      現(xiàn)在分析第二個方案,標準接口依賴IStandardSwitchable接口,那我們必須有一個類來實現(xiàn)它,并完成所需要的功能——操作燈。咱也是學(xué)過設(shè)計模式的人,這個問題很明顯可以用Adapter模式來解釋。

      相關(guān)類圖很容易就可以畫出來。

      clip_image002

      圖1 讓燈支持IStandardSwitchable接口的方案 

      其對應(yīng)的代碼會是這個樣子:

      1. public interface IStandardSwitchable  
      2. {  
      3.     void TurnOn();  
      4.     void TurnOff();  
      5. }  
      6.  
      7.  
      8. public class SwitcherAdapter : IStandardSwitchable  
      9. {  
      10.     public Light Switchee { get; set; }  
      11.  
      12.  
      13.     public void TurnOn()  
      14.     {  
      15.         Switchee.TurnOn();  
      16.     }  
      17.  
      18.  
      19.     public void TurnOff()  
      20.     {  
      21.         Switchee.TurnOff();  
      22.     }  

      代碼1

      Job Done。Light通過SwitcherAdapter支持了新的接口,這簡直就是應(yīng)用適配器模式的典范啊。(嗯,這句的確是反話,不過你猜出來為什么這個Adapter不屬于適配器模式嗎?)

      “上回真是白跟你說了那么多,平時沒覺得你這么不開竅啊。你自己好好想想吧!”背后看著我畫UML圖的設(shè)計Guru好像有點兒生氣。

      上回?我冷靜下來回想上回的內(nèi)容和現(xiàn)在的問題。上回講的DIP,講不要依賴實現(xiàn),要依賴抽象。再想想目前的需求,我們有燈,有收音機,如果用戶說要用標準開關(guān)開收音機,難道還要實現(xiàn)一個RadioAdapter不成?這顯然違反了OCP。

      需求是要“通過加一層讓燈支持標準開關(guān)”,但是并不是說這一層就要使用燈,為了讓這個Adapter更加通用,應(yīng)該讓Adapter依賴ISwitchable接口。像下面這個樣子。

      clip_image004

      圖2 Adapter模式 

      與代碼1的差別,僅僅是SwitcherAdapter里的Switchee屬性的類型改成ISwitchable而已。代碼就不再貼了。其所體現(xiàn)的原則就是上一篇講的DIP。

      這個事兒其實任何人靜靜地想想都能想到。但我繞這個彎子,其實是想順便表達這樣一個意思:一個緊急需求來了的時候,人們更容易傾向于把完成工作放在第一位,從而一時忽視了設(shè)計的嚴謹度,事后又忘了重構(gòu),于是Bad Smell就這樣產(chǎn)生了。當(dāng)然,這些大家也都知道。

      面向?qū)ο蟮脑O(shè)計并不是對現(xiàn)實的模擬

      (這一節(jié)算是一個插曲吧,因為這個論點太大,寫出來都覺得不自量力,不寫又覺得對不起自己愛得瑟的作風(fēng)。一點拙見,大家多多批評。覺得偏題太遠的話可以直接看一下節(jié)。)

      但是(重點來了),為什么緊張時做出的直觀設(shè)計更可能是錯誤的呢?因為人一緊張就容易憑感覺,而使用直覺做設(shè)計時,大都會以現(xiàn)實世界為原本,但是良好的面向?qū)ο笤O(shè)計,是絕對不能僅僅依靠現(xiàn)實世界的。其實圖1 的設(shè)計從直覺上來講是符合需求,也很符合人們對這個世界的認知的。但是它并不是一個良好的面向?qū)ο笤O(shè)計。圖2是相對良好的設(shè)計,但是圖2顯然又沒有圖1 那么直觀,那么好理解,那么符合這個世界的真實狀態(tài)。

      圖2和圖1 的差別僅僅在于Adapter要依賴誰上,Adapter要依賴于ISwitchable接口這個事兒,并不是為了更真實地模擬這個世界,而純粹地是為了解耦合而出現(xiàn)(或者說,為了依賴抽象)。但是在現(xiàn)實世界中,是不存在解不解耦合的概念的。解耦合是為了保證設(shè)計上的靈活性引入的概念。

      現(xiàn)實中事物間的依賴都是具體的,是為了復(fù)用、靈活性等才引入的抽象,客觀現(xiàn)實是不存在抽象的。抽象是要取決于你是如何看待客觀事物的。舉個例子,在動物學(xué)家看來,人與動物間有IS-A關(guān)系;但是如果你是要開發(fā)一款MMORPG游戲,人(NPC和Avatar)和動物(一般會是怪物)應(yīng)該是不會有IS-A關(guān)系的。觀察的角度不同,就會得出不同的設(shè)計;這些設(shè)計沒有對錯之分,只有是否滿足需求之別。

      所以,有些地方,把面向?qū)ο蟮脑O(shè)計過程解釋為對現(xiàn)實世界的模擬是很片面的。如果僅僅以現(xiàn)實世界的樣子對系統(tǒng)進行設(shè)計,得出的設(shè)計很可能是僵化的,就像圖1那樣。(有人可能想說我曲解了人家的意思,但是我想說,你寫成那個樣子明明就是故意給人誤解的,至少是很容易引導(dǎo)人誤解。容易被誤解,就是有問題。沒什么好狡辯的。)

      但是,這并不意味著做設(shè)計就要全面地抽象,模擬現(xiàn)實世界的好處是代碼容易理解,但是如果全部抽象成圖2那樣,所有都抽象出個接口,所有都依賴抽象,那代碼的可讀性顯然會下降。所以,好的面向?qū)ο笤O(shè)計,會是真實地模擬現(xiàn)實與抽象現(xiàn)實間的取舍的過程。如果你看過一些功能相似、但實現(xiàn)不同的開源框架,會發(fā)現(xiàn)有些好理解,有些不好理解,其根本原因就是其抽象的層次或者說抽象的程度不一樣。抽象度過高,靈活性也許上去了,但是并不見得就是好事兒。過度設(shè)計,就是因為對現(xiàn)實的抽象度太高,造成可讀性差,不好維護,還沒解決問題,就先被問題解決掉了。

      上面的例子可能依然沒有什么說服力。我再舉一個。上篇文章有人回復(fù)說,

      “開關(guān)里面還包含一個開關(guān)接口 ,很奇怪的方式。 

      在我看來應(yīng)該是燈光有開關(guān)”。

      我想感謝一下這位朋友,因為他提出的這個思路,我一開始就潛意識地?zé)o視掉了。經(jīng)他一提,我才意識這也是設(shè)計過程中一類常見的問題。這個設(shè)計是一個很真實地反應(yīng)現(xiàn)實的設(shè)計。但是并不是一個可行的類設(shè)計。如果你按這個方案寫代碼,就會發(fā)現(xiàn)很多問題。原因我已經(jīng)回復(fù)了。

      總結(jié)一下,做面向?qū)ο笤O(shè)計的時候,請記得自己要做的是什么?不要讓現(xiàn)實世界的“真實”的樣子混淆了視聽。面向?qū)ο笤O(shè)計,是以可復(fù)用地、靈活地實現(xiàn)需求為目標的,對現(xiàn)實的抽象,而不是對現(xiàn)實的模擬;抽象的結(jié)果很可能在現(xiàn)實中并不存在。

      Adapter模式的關(guān)鍵

      Adapter模式最關(guān)鍵的要求是:Adapter是對兩個功能相近的接口間的適配。如果被適配的對象是個具體類,那么多數(shù)情況下,Adapter非但不會帶來好處,反而是僅僅增加了維護成本,就像前面說的,有一個新的具體類出現(xiàn),就要同時添加一個Adapter。

      (如果你非說你見過很多 “適配”具體類的,你是對的,但是那叫Proxy,不叫Adapter,解決的也不是同一種問題,而且多數(shù)情況下,Proxy是可以自動生成的,所以不需要擔(dān)心加一個類,就要自己實現(xiàn)一個對應(yīng)的Proxy的問題??梢杂孟旅孢@個圖對比一下,來自《敏捷軟件開發(fā)》)

      clip_image006

      圖3 Proxy模式 

      這不是在死摳Adapter模式的含義。因為只有理解Adapter的目標、適用范圍之后,才不會誤用這個模式。見過不少人理解力很好或是英文很好,看到 Adapter這個詞是個模式就想當(dāng)然地覺得自己“知道”了這個模式的用法(畢竟這個模式也的確不復(fù)雜),并“用”了起來。比如圖1的那個例子,就是最常見的誤用之一。

      這也不是在死摳名詞。給模式命名的好處之一就是讓兩個都懂模式的人溝通起來更順暢。模式名所表達的,不是一個簡單的類關(guān)系圖,而是對要解決問題的類型的定位和解決問題的策略。

      Adapter,表示遇到的問題是接口不匹配。

      Proxy,表示遇到的問題是主體邏輯與附加邏輯(持久化、網(wǎng)絡(luò)傳輸?shù)龋┘m纏。

      名詞用錯了,就可能會帶來不必要的誤會。

      如果你就是覺得沒必要死摳概念,下面的“廣義Adapter模式”可能會比較適合你。

      廣義Adapter模式

      這年頭好像什么東西都非要搞出個狹義和廣義之分。我個人比較反感這一點,因為狹廣之分的存在,本身就是一種對概念的模糊。這導(dǎo)致人們在溝通時,如果遇到問題,常常要想一下對方說的是廣義的還是狹義的,而不是把焦點放在問題本身。這像是給自己和對方找借口或是后路?;蛟S是因為大家都想給自己留個后路,這東西才會這么流行。附經(jīng)典對白一則:

      “嗯?不對吧,不應(yīng)該是XXXX嗎?”

      “呃,我說的是一種廣義上的XXXX。”

      “哦。(Shit!)”

      每個人們學(xué)習(xí)模式,總會有自己的理解,自己的抽象。當(dāng)理解的角度不同的時候,就會把Adapter模式的內(nèi)涵延展到不同的地方。這就導(dǎo)致了不同人對廣義Adapter的定義是不同的。

      比如《敏捷軟件開發(fā)》,從邏輯關(guān)系出發(fā),把Adapter的概念延展為:使用一個特定的類,實現(xiàn)對方法調(diào)用的定向派發(fā)(我自己總結(jié)的,原文沒這話)。從這個概念上講,Adapter模式可以用于對具體類的適配。因為這個延展的概念實際上已經(jīng)超出了原有的GoF的定義。這顯然不能說是錯誤的,你甚至?xí)X得這個人水平真高,能對設(shè)計模式進行再抽象,再擴展。

      但是問題是,不同人對同一概念的延展方向是不同的。你覺得Adapter和Delegate/Event有什么相似之處嗎?我相信更多人會覺得Observer模式與Delegate/Event的相似之處更多些。因為無數(shù)的人和書都說過C#的Delegate /Event機制就是Observer模式的一種具體實現(xiàn)。如果你面試的時候說,Delegation就是一種Adapter,你的面試可能就直接 Pass了。這事兒也的確真實地發(fā)生過。

      但是如果去看《Pro Objective-C Design Pattern for iOS》第112頁,對Adapter的描述真的是這樣的。

      “The Delegation pattern was once one of the inspirations for cataloging the Adapter

      pattern in the “Gang of Four” book.

      如果你怕我斷章取義,可以自己去看。

      這個人是從類與類之間的關(guān)系出發(fā),把具有相似結(jié)構(gòu)、交互方式的類的組合都定義為Adapter。你說他的理解錯了嗎?我只能說:“狹義來講,是錯的,廣義來講,是對的?!钡@是這個世界上最操蛋的答案之一。

      像上面鏈接的博客里描述的那個面試者,顯然就成了廣義與狹義之分的犧牲品——他說的是廣義的Adapter,但是面試官想聽到的是狹義的Adapter。(不過從后面的敘述來看,那個面試官也是半瓶子醋,問Delegate的時候居然會順便問異步,讓我不得不懷疑他是不是認為事件是異步觸發(fā)的。)

      對Adapter有獨特的理解很好,能把Adapter, Observer, Delegation, Proxy全統(tǒng)一起來理解更是NB。但是,其實在多數(shù)情況下,越是獨到的見解,越可能會給面對面的溝通帶來障礙。這些獨到的見解在個人頓悟模式的過程中很有用,寫到書里也很好,畢竟讀者可以細細體味,幫助讀者從不同的角度思考問題;但想在面試之類的當(dāng)面溝通的場合上裝逼,然后自己的口才又不咋地。怕只會畫虎不成反類犬。

      對Adapter模式的誤用

      學(xué)歷史的時候,常常見到“左派”、“右派”這樣的詞,意思是他們走的路線不對。這個詞用得很形象,都是走極端。 模式的誤用,常見的誤用之一也是走極端。

      圖2 的Adapter模式,成功的把標準的開關(guān)接口適配到了我們的接口上。于是便有了一個順理成章的思路,ISwitchable和 IStandardSwitchable接口都是對開關(guān)的定義,我們通過Adapter模式,讓支持IStandardSwitchable的開關(guān)能夠開我們的燈。

      那么我們之前的這個設(shè)計:

      clip_image008

      圖4. 第一回中提出的開關(guān)開燈方案(Abstract Server)

       

      是不是應(yīng)該改成這樣?

      clip_image010

      圖5. 試圖把Adapter模式用于實現(xiàn)DIP 

      這個設(shè)計相比原來的設(shè)計方案,抽象度更高、耦合性更低,Light甚至不需要依賴ISwitchable接口就可以工作,這樣我們可以很有信心地說,我們可以讓一切類都支持ISwitchable接口!

      這個想法很豐滿,但是現(xiàn)實很骨感。如果你認真看過了前面的內(nèi)容,應(yīng)該已經(jīng)知道這個方案其實很爛的原因了。

      這個世界很微妙,《敏捷軟件開發(fā)》(P370)的確就把圖5稱為Adapter模式,不過你應(yīng)該懂的,他說的是廣義的Adapter模式。并不是說對具體類的Adapter就一定是誤用,如果沒有違反OCP就不是誤用,如果那個Light是個Utility類,就不算是誤用。

      (如果你想噴Adapter模式本來就有兩種,一種是基于類的,一種是基于對象的,你最好先去把Adapter概念回個爐,我們說的根本不是一碼事兒。)

      誤用的原因

      我自己總結(jié)了一下出現(xiàn)這種誤用的原因有三(這些原因會讓人出現(xiàn)各種形式的誤用,而不針對Adapter模式):

      1. 想當(dāng)然地類推。像上面那樣,從適配IStandardSwitchable可行,直接推出適配ISwitchable也可以,畢竟這是同樣功能的接口啊。但是,不能這樣類推。

      2. 妄圖用同一個方式解決所有問題的想法或創(chuàng)造出一個work for everything的東西的想法。我直覺上就想用熱力學(xué)第二定律來反駁這想法(和work forever差不多意思),不過“no silver bullet”可能更合適些。但是有些人,尤其是Level越高的,就越容易陷入這個泥潭??赡芩麄冇X得不創(chuàng)造些NB的東西出來,就太對不起大家了。當(dāng)然,這個想法是很好的,但是也要講求方法,拿著錘子就看什么都是釘子的做法是要不得的。 參考十條不錯的編程觀點。第一條就是,獨立思考,妄圖通過學(xué)習(xí)各種模式就可以應(yīng)對一切設(shè)計問題的想法就是要不得的。還有一條讓我印象很深的就是關(guān)于Google的使用,推薦大家也去看看。

      3. 對設(shè)計原則和設(shè)計模式的理解不透徹。如果真正理解了Adapter模式的意圖、適用范圍。是不會犯這樣的錯誤的。但是很可惜,這個世界上的誘惑太多了,哪怕Wikipedia這樣看似很權(quán)威的地方都在誤導(dǎo)著別人(所以,自己思考,自己判斷)。Wikipedia上對DIP的解釋是這樣的:“Applying the dependency inversion principle can also be seen as applying the Adapter pattern, i.e.”直譯過來就是“遵循依賴倒置原則可被視同于應(yīng)用適配器模式”。Oops…用了適配器模式,那的確是DIP了,但是適配器并不用來達到DIP這個目標的,適配器模式雖然DIP,但是如果用來現(xiàn)實DIP,效果卻很糟糕,帶來了更多 的問題。我猜作者的本意只是想表達:適配器模式本身是符合DIP原則的。這沒錯。但是我相信有一票人看到這里就去研究適配器模式并計劃用它來實現(xiàn)DIP 了。(有人嫌我啰嗦,我只是想把問題說清楚,讓更多的人無可誤解。)

        這里說的缺乏經(jīng)驗可能并不是工作年限不足的問題,更可能的是態(tài)度的問題,要么是對Adapter模式想當(dāng)然、覺得自己在字面上的理解就差不多,要么是想對Adapter模式進行所謂的“活用”,結(jié)果犯了激進冒險主義錯誤。

      下回預(yù)告

      我們的燈賣得好,用戶就多了起來,需求也多了起來。這樣一下子來了兩個用戶,一個要求,我要用兩個開關(guān)控制同一個燈(床頭一個,走廊一個,看來這用戶晚上常起夜);另一個要求,我想用一個開關(guān)控制屋子里所有的燈(看來這用戶不差錢)。

      那么,我們又需要做出怎樣的設(shè)計來應(yīng)對這些需求呢

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多