首先談談模板式設計,我相信模板對於每一位開發人員和設計人員來講都是很是好的東西,由於它能夠「快速」構建出「成熟」的代碼、結構或UI。「拿來主義」在業界盛極不衰,對於架構師而言模板也有這種功效,在設計的過程當中咱們會常常遇到不少必須而不重要的「雞肋」模塊,沒有它們系統會變得不完整,而它們的存在並不能爲系統增長任何的「特點功能」,如:用戶管理、角色管理或系統設置等。常見作法是,直接採用第三方模塊或是從已有的其它項目中複用相似的模塊,你是這樣的嗎 ?至少我是常常這樣作的,由於咱們的中國式項目一般是「驗收驅動」,能經過驗收、成熟可用就好。若是整個項目都只是由各種模板化的模塊所構成,那麼這個項目其實不須要架構師,由於不存在任何設計,全部的工做只是一種「融合」(Fusion)。可能這樣說會有不少人會吐槽說這是一種「資源整合」能力,從「趕項目」的角度來講這無可口非,但從技術含量與本質上說確實不存在任何設計成分,這類拼裝性或是「複製」性的項目只須要項目經理配備幾個高級程序員就能完成了。html
我曾在「表達思惟與駕馭方法論」一文中提到與銷售的溝通方法,其中就有一條:「至少說出系統的三個特點」,這個表述對銷售具備市場意義之外 , 其實對於架構師是起到一個重要的提醒做用同時也是在創建一種設計原則:程序員
架構設計中模板的拼裝是不可避免的,重要的是必須加入屬於你的特點設計數據庫
很難有人記得住整個軟件的設計師,而卻很容易記住某項極具特點功能的設計者。「特點」 是架構師在軟件中所留下的一種重要的印記,也是在團隊中配備架構師的意義所在。設計出徹底可被模板化重用的設計是一功力,而當中小型企業內出現這樣的設計之日就是架構師離開企業之時,或許這也是當下中國架構師之殤。保持特點保住飯碗,你懂的。編程
惟一不變的就是變化自己 — Jerry Marktos《人月神話》
不變只是願望,變化纔是永恆 — Swift
變化是表像,不穩定且可定製的;本質是核心,必須穩定,可擴展而不可修改;被固定的變化則可歸入核心。
永遠不要投資將來,毫不設計沒有回報的功能設計模式
不知道你是否擁有相似的經歷:安全
衡量標準的尺子掌握在架構師手中,若是設計中出現林林總總的這些「將來功能」您會如何來對待呢 ?是直接砍掉仍是將其包裝成爲「特點」呢 ?此時架構師不僅僅是須要做爲一名技術人員的角度考慮這個功能是否在未來可用,而更多的是須要考慮「成本」。每一個功能甚至每行代碼都須要付出「人-月」成本,一旦成本失控,軟件就會化身「人狼」吞掉你的項目,而最後也只能後悔沒有找到「銀彈」。每一個「將來」功能如何不能對現有項目帶來即時性的回報,必須砍掉!即便這個功能有如何的美妙、高深或是在未來具備非凡的意義,仍是將它放入「研究室」成爲其它項目的技術儲備吧。站在商人的立場:每一分錢的成本投入,都須要有足夠的利益回報。架構
將來永遠是美好的、豐滿的同時也是浮雲,而現實卻每每是充滿骨感。在架構或代碼中透支將來極少數可得到回報,由於這些「投資」都具備不可預見性只是一些嘗試,在產品中除了「市場策略」須要外的這類過度投資就得有陷入「維護將來」的心理覺悟。新的功能、將來的特點更應該收集起來,做爲一下版本中可選項,經過詳細的市場研究再考慮加入到產品中。固然,對於大型軟件企業這個原則基本上是多餘的,由於不少成熟的軟件企業對需求的控制極其嚴格與規範。但若是你所在的企業尚未這樣的管理意識,或具備超脫性的設計自由,那麼這條原則是很是重要的,咱們是用代碼換錢的人,更少的代碼換更多的錢纔是咱們最基本的生存須要。異步
在沒有代碼的時候就應該重構,重構是寫出優雅代碼的方法而不單純是修改代碼的理論。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#的方法參數的種類。
在C#中咱們則須要從「注入」這方面來思考和充分發揮語言自身的特性,以達到簡化代碼,加強易讀性的效果。 這裏談的「注入」主要指兩個方面,一 是 「代碼注入」,二是 「類型注入」。
「代碼注入」就是向方法傳入「代理」類就是在方法內部開闢出某一「可擴展」的部分以執行未知、可變的功能 ,那麼咱們就能夠對相對「封閉」的方法加強其「開放」性。
經過泛型方法的使用,咱們能夠在對類型「開放」的狀況下對類型的通用操做相對地「封閉」起來,這樣能夠在很大程度上利用泛型複合取代類繼承,下降類的多態耦合度。
凡是基類適用的地方,子類必定適用
要依賴抽象,不要依賴具體。
使用多個專門的接口比適用單一的接口要好
要儘可能使用合成/聚合 ,儘可能不要使用繼承
只有當如下Coad條件所有被知足時,才應當使用繼承關係:
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()); } }
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將構造移到一個集中的地方,防止構造耦合散播。