架構師修煉 III - 掌握設計原則

關於軟件的設計原則有不少,對於設計原則的掌握、理解、實踐及昇華是架構師的一項極爲之必要的修煉。 記得在12年前第一次閱讀《敏捷開發》時,五大基本設計原則就深深地植入到個人腦海中一直影響至今,我也由此獲益良多。設計原則固然不止只有五種,最主要的面向對象的設計原則有如下這些:
 
  • 單一職責原則 (SRP) - 就一個類而言,應該僅有一個引發它變化的緣由
  • 開-閉原則 (OCP)- 軟件實體(類,模塊,函數等)應該是能夠擴展的,可是不能夠修改
  • 里氏替換原則 (LSP)- 子類必須可以替換它們的基類型
  • 依賴倒置原則 (DIP)- 抽象不該該依賴於細節。細節應該依賴於抽象。
  • 接口隔離原則 (ISP)- 不該該強迫客戶依賴於它們不用的方法。接口屬於客戶,不屬於它所在的類層次結構。
  • 重用發佈等階原則 (REP)- 重用的粒度就是發佈的粒度。
  • 共同封閉原則 (CCP)- 包中的全部類對於同一類性質的變化應該是共同封閉的。一個變化若對一個包產生影響,則將對該包中的全部類產生影響,而對於其餘的包不形成影響。
  • 共同重用原則(CRP)-  一個包中全部類應該是共同重用的。若是重用了包中的一個類,那麼就要重用包中的全部類。
  • 無環依賴原則(ADP)- 在包的依賴關係圖中不容許存在環。
  • 穩定依賴原則 (SDP)- 朝着穩定的方向進行依賴。
  • 穩定抽象原則(SAP)- 包的抽象程度應該和其穩定程度一致。
  • 合成/聚合 複用原則(CARP)- 要儘可能使用合成/聚合 ,儘可能不要使用繼承
  •  …..
 
固然面向對象的設計原則遠遠不止這些,設計原則是伴隨着開發語言的發展應用和軟件開發經驗的累加總結得出的經驗彙總,隨着語言的演變、開發方法的進步還會不斷地衍生和進化出更多的的設計原則。應用設計原則能夠避開不少的設計中的陷阱與誤區,但在應用設計原則的同時須要緊記一點:設計原則本質上是一些經驗條框,是設計的導盲杖而不要讓它們成爲束縛設計思想的牢籠。
 
每一個架構師在經歷長期的實踐後也會慢慢創建屬於本身的設計原則。多年來我也總結出了一些設計原則,並將上面這些這種可用於代碼設計的原則概括爲:「代碼設計原則」,另一些應用於意識與設計過程當中的原則稱爲「意識-行爲原則」。如下我將會分別講述我對這些設計原則的理解與運用的經驗。
 

意識 - 行爲原則

 
  意識決定行爲,不少的設計失誤並不單純源自於對設計原則的把握不足,而更多可能源自於架構師在乎識指導上的錯誤, 因此在開始設計以前應該先創建正確的思想與意識引導。如下的這些意識-行爲原則是我從不少次的跌倒中總結出的一些心得,將期做爲原則是爲了時刻引導本身不會在相似問題中犯錯。

堅持創新原則

  首先談談模板式設計,我相信模板對於每一位開發人員和設計人員來講都是很是好的東西,由於它能夠「快速」構建出「成熟」的代碼、結構或UI。「拿來主義」在業界盛極不衰,對於架構師而言模板也有這種功效,在設計的過程當中咱們會常常遇到不少必須而不重要的「雞肋」模塊,沒有它們系統會變得不完整,而它們的存在並不能爲系統增長任何的「特點功能」,如:用戶管理、角色管理或系統設置等。常見作法是,直接採用第三方模塊或是從已有的其它項目中複用相似的模塊,你是這樣的嗎 ?至少我是常常這樣作的,由於咱們的中國式項目一般是「驗收驅動」,能經過驗收、成熟可用就好。若是整個項目都只是由各種模板化的模塊所構成,那麼這個項目其實不須要架構師,由於不存在任何設計,全部的工做只是一種「融合」(Fusion)。可能這樣說會有不少人會吐槽說這是一種「資源整合」能力,從「趕項目」的角度來講這無可口非,但從技術含量與本質上說確實不存在任何設計成分,這類拼裝性或是「複製」性的項目只須要項目經理配備幾個高級程序員就能完成了。html

我曾在「表達思惟與駕馭方法論」一文中提到與銷售的溝通方法,其中就有一條:「至少說出系統的三個特點」,這個表述對銷售具備市場意義之外 , 其實對於架構師是起到一個重要的提醒做用同時也是在創建一種設計原則:程序員

  架構設計中模板的拼裝是不可避免的,重要的是必須加入屬於你的特點設計數據庫

很難有人記得住整個軟件的設計師,而卻很容易記住某項極具特點功能的設計者。「特點」 是架構師在軟件中所留下的一種重要的印記,也是在團隊中配備架構師的意義所在。設計出徹底可被模板化重用的設計是一功力,而當中小型企業內出現這樣的設計之日就是架構師離開企業之時,或許這也是當下中國架構師之殤。保持特點保住飯碗,你懂的。編程

 

固守本質原則 

  惟一不變的就是變化自己  —  Jerry Marktos《人月神話》
  不變只是願望,變化纔是永恆  —  Swift
 
看到這兩句經典是否是猜到我想就「變化」二字來一次老生常談 ?其實否則,這兩個字在業內估計也討論了20多年了,也說爛了。我之因此引用這兩位大師的名言只是想無時無刻提醒本身要了解身邊的每個變化,對他們的源頭產生興趣,從而深刻了解。世界上不會有平白無故的愛,也沒有平白無故的恨一切皆有根源,那是 「本質」。咱們來將 「本質」 與 「變化」 這兩個哲學性的問題應用到軟件開發的範疇內來看一個軟件產品的迭代:
  • 用戶的需求在變 - 他們須要增長更多的功能,要求更高質量的用戶體驗。
  • 代碼在變 - 不斷的重構、測試,持續集成,讓代碼變得容讀,穩定。
  • 老闆的想法在變 - 由於市場需求在變,須要爲軟件加入更多的特點知足市場。
  • 架構在變 - 採用更新式的技術體系,得到更高效的生產力,更爲穩定、安全的運行環境。
 
而惟一不變的是:軟件的核心。正如:Windows 變了N個版本最後仍是操做平臺,Office 衍生了多代後若然在處理文檔文件 。
 
  變化是表像,不穩定且可定製的;本質是核心,必須穩定,可擴展而不可修改;被固定的變化則可歸入核心。
 
  架構應從本質入手,一切複雜的事物都應可被分解爲簡單的原理和構成,本質以外的內容皆可變化。咱們來舉例說明,設計一個電子商務網站,其核心就可被分解爲 「購物車」 與 「訂單狀態跟蹤」這是不可變的除非大衆的總體購物行爲發生了本質上的改變,爲了增長用戶體驗咱們選用美觀溫馨的界面套件如BootStrap,若是進一步提高用戶體驗則能夠採用SPA的架構讓客戶在Web上得到Native式的使用體驗;爲了讓用戶使用不一樣的支付方式,咱們就須要定義支付網關接口(引入變化)支持已有的支付平臺,也爲未來「可能」出現的支付平臺留有擴展。爲了加強網站對用戶的粘性,咱們就須要增長社區模塊,並採用雲存儲或是其它的BigData技術以支撐大數據量的運轉;....  最後,一切的本質仍然不變,電商網站,變的是擴展性、易用性、伸縮性等等。架構師能夠向其中添加的功能太多太多,但必須固守本質才能讓整個產品不會成爲一個由高技術打造出來的怪物,在增長新功能時參考 「代碼商人」原則的指引。
 

「代碼商人」 原則

永遠不要投資將來,毫不設計沒有回報的功能設計模式

不知道你是否擁有相似的經歷:安全

  • 在與客戶的交流中,你的老闆和經理在不斷地向客戶描繪「將來」圖景,而在其中包含了不少幾乎是客戶沒有須要的特點 ?
  • 在你設計總體架構時,有一種衝動讓你很想將某項由靈感觸發對於系統「未來」的擴展須要頗有用的功能或模塊加入其中呢 ?
  • 在你的代碼裏面有多少個方法或類是能夠被刪除,但你認爲他們能夠用於「之後」擴展而「手下留碼」的呢 ?
  • 你是否曾與經理或項目組長爲了是否增長某個頗有可能成爲特點且被你實現出來的功能爭論不休呢 ?

  衡量標準的尺子掌握在架構師手中,若是設計中出現林林總總的這些「將來功能」您會如何來對待呢 ?是直接砍掉仍是將其包裝成爲「特點」呢 ?此時架構師不僅僅是須要做爲一名技術人員的角度考慮這個功能是否在未來可用,而更多的是須要考慮「成本」。每一個功能甚至每行代碼都須要付出「人-月」成本,一旦成本失控,軟件就會化身「人狼」吞掉你的項目,而最後也只能後悔沒有找到「銀彈」。每一個「將來」功能如何不能對現有項目帶來即時性的回報,必須砍掉!即便這個功能有如何的美妙、高深或是在未來具備非凡的意義,仍是將它放入「研究室」成爲其它項目的技術儲備吧。站在商人的立場:每一分錢的成本投入,都須要有足夠的利益回報架構

  將來永遠是美好的、豐滿的同時也是浮雲,而現實卻每每是充滿骨感。在架構或代碼中透支將來極少數可得到回報,由於這些「投資」都具備不可預見性只是一些嘗試,在產品中除了「市場策略」須要外的這類過度投資就得有陷入「維護將來」的心理覺悟。新的功能、將來的特點更應該收集起來,做爲一下版本中可選項,經過詳細的市場研究再考慮加入到產品中。固然,對於大型軟件企業這個原則基本上是多餘的,由於不少成熟的軟件企業對需求的控制極其嚴格與規範。但若是你所在的企業尚未這樣的管理意識,或具備超脫性的設計自由,那麼這條原則是很是重要的,咱們是用代碼換錢的人,更少的代碼換更多的錢纔是咱們最基本的生存須要。異步

 

重構優先原則

在沒有代碼的時候就應該重構,重構是寫出優雅代碼的方法而不單純是修改代碼的理論。async

駱駝與賬篷的故事ide

  在風沙彌漫的大沙漠,駱駝在四處尋找溫暖的家。後來它終於找到一頂賬篷,但是,賬篷是別人的(也許你的處境跟它同樣)! 

  最初,駱駝哀求說,主人,個人頭都凍僵了,讓我把頭伸進來緩和暖和吧!主人可憐它,答應了。過了一陣子,駱駝又說,主人,個人肩膀都凍麻了,讓我再進來一點吧!主人可憐它,又答應了。接着,駱駝不斷的提出要求,想把整個身體都放進來。 

  主人有點猶豫,一方面,他懼怕駱駝粗大的鼻孔;另外一方面,外面的風沙那麼大,他好像也須要這樣一位夥伴,和他共同抵禦風寒和危險。因而,他有些無奈地背轉身去,給駱駝騰出更多的位子。等到駱駝徹底精神並能夠掌握賬篷的控制權的時候,它很不耐煩地說,主人,這頂賬篷是如此狹小以至連我轉身都很困難,你就給我出去吧

  這是一個頗有寓意故事,若是將其比喻爲開發過程也頗有意思。對於「發臭」甚至「腐爛」代碼咱們會立刻說「重構」,但重構是否能解決一切問題 ?你是否試太重構失敗呢 ?重構在什麼狀況下是不可用的呢 ?若是這些問題在你心中是沒有準確答案的話, 我建議能夠從新去閱讀一次《代碼重構》一書。我認爲重構不單純是一種開發期與代碼回顧期所使用的方法,而是一種設計與編碼的思想指導!在設計期就應運用重構中的原則,那是否就能夠「防腐」呢 ?答案顯然是肯定的。重構的每每不單純是代碼,而是開發人員、設計人員的思想,不執行甚至沒有代碼規範、隨意命名、隨意複製/粘貼、隨意調用這些都必須被杜絕。我並非指在設計重構就不須要重構,只是這樣作的意義能夠大量減小因爲發現「臭」代碼而去重構的成本 。

  這也能夠說是一個團隊性的開發原則,在項目之始就得有統一的編碼規範(直接使用官方規範),並將重構中的基本代碼重構方法也歸入規範中,在開發過程當中強制執行規範,對任何可能「腐化」的代碼絕對的「零」容忍,痛苦只是一時,但好處倒是長久的。

 

代碼設計原則

 

開放-封閉原則 

開放封閉原則又稱 開-閉原則 Open-Closed Principle (OCP) 

軟件實體(如類,模塊,函數等)應該是能夠擴展的,可是不能夠修改。

  OCP是一個極爲之出名的設計原則,簡單的一句話就歸納了可時該「開放」可時該「封閉」。這句話看起來很簡單,一看彷佛也會以爲本身領悟了什麼,仔細咀嚼卻以爲內中深意無限,到底應怎樣理解這句話且將其應用於設計中呢 ? 我參考了很多國內的資料對此原則的總結,感受就是霧裏看花,沒有辦法找到最爲貼切的解釋。

我想分幾個方面來詮釋這個原則:

從類設計的角度

  在類設計的應用中開-閉原則是一種對類的「多態」控制原則。開閉原則在基類或超類的設計中由爲重要, 能夠簡單地理爲對 成員對象的做用域 和 可「重載」成員 的控制指引原則。按 「里氏替換原則」 基類成員一般對於子類都應該可見,也就是說基類成員的做用域的最小做用範圍應該是 protect , 若是出現大量的 private 成員時就應該考慮將private 成員們分離成其它的類,由於些成員都不適用於其子代而違反了「替換原則」,而更適用「合成/聚合原則「。

  在運用 virtual 關鍵字時需甚重考慮,除了針對某些特殊的設計模式如 」裝飾「模式須要大量 virtual 的支持之外,在沒有必要的狀況下儘可能避免。定義可重寫的成員爲子類預留了」改變行爲「的餘地,但同時也是爲子類違反」替換原則「埋下了地雷。當子類中出現大量重寫成員的時候就得考慮該子類是否還應該繼承於此類族,由於子類在大量地違反」替換原則「時就意味着它知足了被分離出類族的條件。同理,在C#內一但須要在子類內部實現基類接口時也須要做出一樣的考慮。

 

 注:里氏替換原則是開-閉原則的一種重要補充,在類設計中通常是同時使用。

 

從模塊設計的角度

  模塊設計的「開-閉原則」是側重於對接口的控制。而這個在整個架構中也尤其重要,由於模塊間的「開-閉」是直接影響系統級的耦合度。模塊間的開閉須要「衡量成本」,並非將全部的細節都開放使用模塊具備極強的可擴展性就會有很高的重用度。首先要看了解幾點:

開放性與維護成本成正比關係

  接口的開放必須帶有使用說明,這會增長團隊開放的溝通成本同時一但接口發生改變將可能帶來額外的「說明性重構」成本。在某些狀況下咱們很容易被「高擴展性」所引誘將不少「可能」被複用的功能經過擴展接口暴露出來。當這種高擴展性的誘惑主導了設計師的思惟,隨着模塊的增多項目的變大、慢慢地設計師就會進入本身所建立的「註釋惡夢」中。

 

開放性與耦合度成正比關係

  模塊的開放性接口是具備耦合傳導效應的,控制模塊間的耦合度就能在很大程度上控制了系統的耦合度。模塊間的依賴性越小,耦合度越低才更易於變化儘可能將耦合度集中在某一兩個模塊中(如:Facade 模式),而不是分散在各模塊間。耦合度高的模塊天然而然地成爲「核心」模塊,而其實的「外部」模塊則須要保持自身的封閉性,這樣的設計就不少容易適對未知的變化。

 

由這兩個正比關係結合對實現成本的控制上咱們作出兩個最爲簡單可行的推論:

推論1:「正常狀況下請保持封閉,沒有必要的狀況下毫不開放」。

推論2:「集中開放性,讓模塊間保持陌生」

 

開-閉原則從理論上來談會有不少內容,但實現起來卻很簡單, 就以C#爲例控制模塊開放性的最簡單辦法就是控制做用域:internal , public。

 

3.從函數/方法設計的角度

我爲認爲OCP用到極至的狀況就是應用於方法級,衆所周知:參數越少的方法越好用。開-閉原則能夠簡單地理解爲參數的多寡與返會值的控制

 

在此我更想談談「開-閉原則」在C#中的應用。首先在方法設計上,C# 給了設計人員與開發人員一個極大的空間,到了4.5咱們甚至可使用async 方法來簡單控異步方法,那麼先來總結一下C#的方法參數的種類。

  • 固定參數:public void methodName(string a, out b, ref c);
  • 動態參數:public void methodName(string a, string b=「defautlString」) 
  • 可變參數:public void methodName(params string[] a);
  • 表達式參數(方法注入):public void methodName(Func<string> func, Action act);
  • 泛型參數:public void methodName<T>( T a) where a : class;

在C#中咱們則須要從「注入」這方面來思考和充分發揮語言自身的特性,以達到簡化代碼,加強易讀性的效果。 這裏談的「注入」主要指兩個方面,一 是 「代碼注入」,二是 「類型注入」。

 

「代碼注入」就是向方法傳入「代理」類就是在方法內部開闢出某一「可擴展」的部分以執行未知、可變的功能 ,那麼咱們就能夠對相對「封閉」的方法加強其「開放」性。

經過泛型方法的使用,咱們能夠在對類型「開放」的狀況下對類型的通用操做相對地「封閉」起來,這樣能夠在很大程度上利用泛型複合取代類繼承,下降類的多態耦合度。

 

里氏替換原則(LSP) 

  凡是基類適用的地方,子類必定適用
 
里氏代換原則 (Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類能夠出現的地方,子類必定能夠出現。 LSP是繼承複用的基石,只有當衍生類能夠替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也可以在基類的基礎上增長新的行爲。里氏代換原則是對「開-閉」原則的補充。實現「開-閉」原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,因此里氏代換原則是對實現抽象化的具體步驟的規範。
 
在前文」開-閉原則「關於類設計應用部分已經基本敘述過」替換原則「的用法。 這個原則,我一直是反向理解的,這樣就很是容易運用,我是這樣使用的:
  • 凡是出現大量子類不適用的成員,子類就應該脫離繼承關係
  • 基類中凡是出現大量虛成員,該類就失去成爲基類的條件
 
 
 

依賴倒轉原則(DIP) 

   要依賴抽象,不要依賴具體。
 
  DIP 就像LSP同樣,原文與譯文其實都很是坑爹,這裏我就不直接引入原文了,由於我但願每一個讀這篇文章的朋友都能理解並應用這些原則而不是在玩文字遊戲。DIP 用最爲簡單的表述就是:「面向接口編程」。子類能夠引用父類方法或成員,而父類則絕對不能調用任何的子類方法或成員。一但上層類調的方法調用了子類的方法就會造成依賴環,通常上編譯器會「放過」依賴環認爲這不屬於邏輯錯誤,但具備依賴環的類結構是沒法序列化的(在C#中會直接拋出環狀引用的異常)。
 
通俗點:「 規矩是祖宗定的,子孫只能執行和完善」,用這個口決就能夠徹底掌握此原則 。
在過去(10年前)開發工具還比較落後,這是原則十分重要,而現在能夠藉助VS.net去找到出這種設計錯誤,也能夠直接使用IoC 和 DI 就會天然而充分地尊守此原則 。
  

接口隔離原則 (ISP)  

  使用多個專門的接口比適用單一的接口要好
 
  架構師在邏輯世界就是神,設計軟件的過程就是創造邏輯世界,每個接口就是這個世界中的一種規則,類則是實現規則的作法,實例就是執行規則的人。 在實現工做中,咱們會常常遇到這樣的現象:一個PM可能同時在跟進好幾個項目,或是一個PM要同時充當架構師、PM、程序員甚至售前的角色,這些苦B們是公司內最累的人,同時也是失敗率最高的羣體,爲何? 答案顯而易見:人的精力是有限的,專一於某一件事才能真正有成果。同理,在邏輯世界也是同樣的,當接口要承載多種的任務,被衆多不一樣的類所調用時就會出現「接口過載」或者」接口污染「,實現這些接口的類將會產生很高的耦合度,從而代碼會變得難以閱讀,難以理解,也難以變化。分離接口就是隔離了客戶(接口的使用者),隔離客戶就天然下降耦合度。
 
  一個完美的世界就應該是專人專項,讓擅長的人作其擅長的事,在現實不可能但邏輯世界卻能夠。那麼在設計中如何來把握這種原則呢 ?很簡單,當一個接口上的方法被多個類調用時就要警覺了,若是這些方法間沒有依賴關係,甚至是不一樣類別(在作不一樣的事)的方法那麼就得考慮使用ISP原則將接口分離成兩個獨立的接口,使接口的耦合度從1..n 下降至 1..1. 
 
 

合成/聚合 複用原則(CARP)

  要儘可能使用合成/聚合 ,儘可能不要使用繼承
 
  複用原則是一個很容易被忽略而又極其重要的原則,這個原則具備很是深遠的架構意義。對於小型項目(類庫規模小)即便違反此原則也不會帶來什麼危害,但當構建大規模的類庫(數百甚至數千個類)時,這個原則就能夠防止出現「繼承失控」、過分膨脹、沒法重構等的風險,也決定了整個結構的可重用性和可維護性。在定義中它只是一句簡單的話,但從「繼承」、「合成」與「聚合」就引出了一系列的內容,涵蓋多種設計模式和附帶多個更小層級的應用原則。
 
(注:關於合成/聚合的好處請去百度吧,關於「白箱複用」與「黑箱複用」都被轉爛了)
 
  首先要正確的選擇合成/複用和繼承,必須透徹地理解里氏替換原則和Coad法則。Coad法則由Peter Coad提出,總結了一些何時使用繼承做爲複用工具的條件。 
 
Coad法則:

只有當如下Coad條件所有被知足時,才應當使用繼承關係:

  1. 子類是超類的一個特殊種類,而不是超類的一個角色。區分「Has-A」和「Is-A」。只有「Is-A」關係才符合繼承關係,「Has-A」關係應當用聚合來描述。 
    • 「Is-A」表明一個類是另一個類的一種;
    • 「Has-A」表明一個類是另一個類的一個角色,而不是另一個類的特殊種類。 
  2. 永遠不會出現須要將子類換成另一個類的子類的狀況。若是不能確定未來是否會變成另一個子類的話,就不要使用繼承。 
  3. 子類具備擴展超類的責任,而不是具備置換掉(override)或註銷掉(Nullify)超類的責任。若是一個子類須要大量的置換掉超類的行爲,那麼這個類就不該該是這個超類的子類。 (注:在C# 中 含有 new 的方法、屬性和內部實現單一基類接口就至關於Nullify)
  4. 只有在分類學角度上有意義時,纔可使用繼承。不要從工具類繼承。 
 
對於一個老手Coad法則只是一種總結,很容易理解與運用,但若是你是一個架構新手Coad法則就非常坑爹(個人理解力很低,當年我就被坑了好久)!因此我想另闢蹊徑從其它角度來嘗試解釋這個原則。
 
繼承控制
繼承是面向對象的一種重要構型,複用原則只告訴咱們「儘可能不使用繼承」而不是將繼承魔鬼化,在不少場景下,小結構繼承是很是常見與易讀的。只是,咱們須要瞭解繼承的子代的增長是以整個類結構的複雜度增長n次方在遞增,隨着子代層級的增多「類家族」結構的變化就愈來愈難。其實,咱們能夠找一些自已手上的例子來看看,若是有3代以上繼承關係的類,看看最小的子孫類與基類之間是否已經有點「面目全非」?這一點與人類的繁衍與繼承是很相似的。再深刻一點就是若是向最頂層的基類進行擴展,是則能徹底適用「替換原則」呢 ?更改高層級結構時是否有「揮舞大刀」般的沉重感 ? 對是否有勇氣對穩定的祖代類重構 ?
 
推論:「儘量避免出現三代之外的繼承關係,不然應考慮合成/聚合」
 
 
「合成」與「聚合」從字面意義上去理解是我一直以來都沒法正確理解的內容。多是我語文水平實在過低的緣故吧,對 Composite 和 Aggregation 兩個單詞我反而能在維基百科上找到準確的定義。
 

合成  ( Composite )   - 值聚合 (Aggregation by value)

個人通俗定義:合成的過程是在類的構造過程當中(構造函數或外部的構造方法)在運行期將值或其它類實例組裝到合成類內(經過變量或屬性Hold住)
 
如:
public class Keyboard{}
public class Mouse{}
public class Monitor{}
 
public class Computer
{
    private Keyboard keyboard;
    private Mouse mouse;
    private Monitor monitor;
 
    public Computer() 
    {
         this.keyboard=new Keyboard();
         this.mouse=new Mouse();
         this.monitor=new Monitor();
    }
}

由這個例子可見,所謂的「值(Value)」經過構造函數合成爲 「Computer」的內部成員,有如將各個功能單一的部件裝配成爲一個功能強大的產品。全部的依賴都被「關在」構造函數內,若是將依賴外置就能夠運用工廠(Factory Pattern)和合成模式(Composite Pattern)進行演變。

public class Item{};
 
public class Keyboard:Item{}
public class Mouse:Item {}
public class Monitor:Item{}
public ComputerFactory 
{
   public Item Keyboard() { return new Keyboard(); }
   public Item Monitor() { return new Monitor(); }
   public Item Mouse() { return new Mouse(); }
}
 
public class Computer
{
    public List<Item> Items{get;set;}
    
    public Computer(ComputerFactory factory) 
    {
        this.Items.Add(factory.Keyboard());
        this.Items.Add(factory.Mouse());
        this.Items.Add(factory.Monitor()); 
    }
} 
經過簡單的演變,就能夠將Computer 1-3的耦合變成 1-1 的耦合,全部的依賴都集中到ComputerFactory上,只須要繼承ComputerFactory建立更多的工廠傳入Computer類就能夠生產出各類各樣的Computer實例,而無需更改Computer的任何代碼,這就是所謂的「黑箱複用」。
 
思考:試試用Builder模式改寫上面的例子,會有不一樣的效果。
 
這裏只是有3個部件,但若是將部件變成30個或者更多時改變的也只是 「合成的構造者」 ,應對再複雜的場景:樹型合成結構也只是將構造者演變爲遞歸式構造。因而可知到「合成」原則的運行對大量類組合的強大之處。
 
 

聚合  ( Aggregation )  - 引用聚合(Aggregation by reference)

 
聚合在面向對象的實現上是一個極爲簡單的代碼,說白了就是:對象屬性。以上面第一個範例說明 (不繼承Item基類)
 
public class Computer
{
   public Mouse Mouse{ get;set; }
   public Monitor Monitor{ get; set; }
   public Keyboard Keyboard {get;set;}
}
 
public class Host
{
    public static void Main()
    {
         var computer=new Computer()
         {
              Mouse=new Mouse(),
              Monitor=new Monitor(),
              KeyBoard=new KeyBoard()
         };
    }
}

  聚合類中Hold住的是實例化類的引用,不單是值。聚合類的意義在於將引用依賴集中一處,從某意義上說這個Computer類也是一個Facade 模式。這種方式常見於大規模對象模型的入口類,如Office的 Application 對象,這樣的設計能夠便於開發者「尋找」類的引用。同時也能夠用做 上下文的設計 如:.net中的System.Web.HttpContext。值得注意的是:聚合類是須要慎用的,對於類自己是收斂類引用耦合,同時聚合類也具備耦合傳導的特性,由其是構造函數。就拿EF說事吧,咱們用EF訪問數據庫都須要這樣的代碼:

public void OrderManager
{
    public List<Order> GetOrder()
    {
        using (var ctx=new DbContext( )
        {
            //
        }
    }
}

當這個new 在代碼各處出現時就壞菜了!構造引用的耦合度每調用一次就增長一分,當遍及整個訪問層甚至系統時 DBContext就是一個不可變動的超巨型耦合腫瘤!要解決這個問題能夠採用單件模式自構造或是用IoC、DI將構造移到一個集中的地方,防止構造耦合散播。 

小結

  若是你是一位.net 體系的開發人員,只要你打開vs.net的代碼檢查規則你就會發現一個新的世界,一個基於原則/規範 的世界,若是你的代碼能80%地經過vs.net中最高級別的代碼檢查準則,那麼事實上你的代碼已是很是優質。內中的每一條代碼檢查準則都值得咱們細細地去品味與學習。
相關文章
相關標籤/搜索