標籤:模式 而不是 產生 編程 cep 拋出異常 特性 百分比 回呼函數
第 9 章:常用的設計模式9.1 彙總組件
考慮為常用的特性域提供彙總組件。
要用彙總組件來對高層的概念(物理對象)進行建模,而不是對系統級的任務進行建模。
要讓彙總組件的名字與眾所周知的系統實體相對應,比如 MessageQueue、Process 或 EventLog,這樣就能使類型更加引人注目。
要在設計彙總組件時使初始化儘可能地簡單,這樣使用者只需進行簡單的初始化就可以使用組件。如果某一項初始化是必需的,那麼由於沒有對組件進行初始化而引發的異常應該明確地告訴使用者應該怎麼做。
不要要求彙總組件的使用者在一個情境中顯式地執行個體化多個對象。
要保證讓彙總組件支援 Create-Set-Call 使用模式,這樣使用者就可以先執行個體化組件,然後設定它的屬性,最後調用一些簡單的方法,以實現大多數情境。
要為所有的彙總組件提供預設建構函式或非常簡單的建構函式。
要為彙總組件提供可讀寫的屬性來與建構函式中的所有參數相對應。
要在彙總組件中使用事件,不要使用基於委託的 API。
考慮用事件來代替需要被覆蓋的虛成員。
不要要求彙總組件的使用者在常用情境中使用繼承、覆蓋方法及實現介面。
不要要求彙總組件的使用者在常用情境中除了編寫代碼之外,還要做其他的做工。例如,不應該讓使用者用設定檔來配置組件,也不應該讓使用者產生資源檔,等等。
考慮讓彙總組件能夠自動切換狀態。
不要涉及有多種狀態的因子類型。
考慮將彙總組件整合到 VS 的設計器中。
考慮把彙總組件和因子類型分開,各自放在不同的程式集中。
考慮把彙總組件內部的因子類型暴露給外界訪問。
9.2 Async 模式
要實現基於事件的 Async 模式 - 如果類型是一個支援視覺化設計工具的組件(也就是說類型實現了 IComponent)。
要實現經典的 Async 模式 - 如果必須支援等待控制代碼。
考慮在實現高層 API 時使用基於事件的 Async 模式。例如,彙總組件就應該實現該模式。
考慮在實現底層 API 時使用經典的 Async 模式,在這種情況下更強大的功能、更少的記憶體消耗、更好的靈活性、更少的磁碟佔用要比可用性更重要。
避免在同一個類型中甚至是一組相關的類型中同時實現兩種 Async 模式。
要在為非同步作業定義 API 時遵循下面的約定。給定名為 Operation 的同步方法,應該提供名為 BeginOperation 和 EndOperation 的方法,它們的方法簽名如下面所示(注意,輸出參數不是必需的)。
要確保 Begin 方法的傳回型別實現了 IAsyncResult 介面。
要確保同步方法的按值傳遞和按引用傳遞的參數在 Begin 方法中都是按值傳遞的。同步方法的輸出參數不應該出現在 Begin 方法的簽名中。
要確保 End 方法的傳回型別與同步方法的傳回型別相同。
要確保同步方法的任何輸出參數和按引用傳遞的參數都作為 End 方法的輸出參數。同步方法中安置傳遞的參數不應該出現在 End 方法的簽名中。
不要繼續執行非同步作業 - 如果 Begin 方法拋出了異常。
要一次通過下面的機制來通知調用方非同步作業已經完成。
將 IAsyncResult.IsCompleted 設為 true。
啟用 IAsyncResult.AsyncWaitHandle 返回的等待控制代碼。
調用非同步回呼函數。
要通過從 End 方法中拋出異常來表示無法成功地完成非同步作業。
要在 End 方法被調用時同步完成所有尚未完成的操作。。
考慮拋出 InvalidOperationException 異常 - 如果使用者用同一個 IAsyncResult 兩次調用 End 方法,或 IAsyncResult 是從另一個不相關的 Begin 方法返回的。
要把 IAsyncResult.CompletedSynchronously 設為 true - 若且唯若非同步回呼函數將在調用 Begin 方法的線程中啟動並執行時候。
要確保在正確的線程中呼叫事件處理常式。與經典 Async 模式相比,這是使用基於事件的 Async 模式的主要好處之一。
要確保無論是操作已經完成,還是操作出錯,還是操作被取消,都是種會呼叫事件處理常式。不應該讓應用程式無休止地等待一間永遠不會發生的事件
要確保在非同步作業失敗後,訪問時間參數類的屬性會引發異常。換句話說,如果有錯誤導致操作無法完成,那麼就不應該允許使用者訪問操作的結果。
不要為傳回值為空白的方法定義新的事件處理常式或事件參數類型。要使用 AsyncCompletedEventArgs,AsyncCompletedEventHandler 或 EventHandler<AsyncCompletedEventArg>。
要確保如果在一個一步操作中實現了 PaogressChanged 事件,那麼在操作的完成事件被觸發之後,不應該再出現此類事件。
要確保如果使用了標準的 ProgressChangedEventArgs,那麼 ProgressPercentage 始終能用來表示進度的百分比(不一定要完全精確,但表示的一定要百分比)。如果使用的不是標準進度,那麼從 ProgressChangedEventArgs 派生一個子類會更合適,這種情況下應該保持 ProgressPercentage 為 0 ;
要在有增量結果需要報告的時候出發 ProgressChanged 事件。
要對 ProgressChangedEventArgs 進行擴充來儲存增量結果資料,並用擴充後的時間參數類來定義 ProgressChanged 事件。
要把增量結果報告與進度報告分開。
要為每個非同步作業定義單獨的 <MethodName>ProgreessChanged 事件和相應的事件參數類,來處理該操作的增量結果資料。
9.3 相依性屬性
要提供相依性屬性 - 如果需要用他們來支援各種 WPF 特性,比如樣式、觸發器、資料繫結、動畫、動態資源以及繼承。
要在設計相依性屬性的時候繼承自 DependencyObject 或它的子類型。該類型實現的屬性儲存區區非常高效,它還自動支援 WPF 的資料繫結。
要為每個相依性屬性提供常規的 CLR 屬性和存放 System.Windows.DependencyProperty 執行個體的公有靜態唯讀欄位。
要通過調用 DependencyObject.GetValue 和 DependencyObject.SetValue 的方式來實現相依性屬性。
要用相依性屬性的名字加上“Property”尾碼來命名相依性屬性的靜態欄位。
不要顯式地在代碼中設定相依性屬性的預設值,應該在中繼資料中設定預設值。
不要在屬性的訪問器中添加額外的代碼,而應該使用標準代碼來訪問靜態欄位。
不要使用相依性屬性來儲存保密資料。任何代碼都能訪問相依性屬性,即使它們是私人的。
不要把相依性屬性的驗證邏輯放在訪問器中,而應該把驗證毀掉函數傳給 DependencyProperty.Register 方法。
不要在相依性屬性的訪問器中實現屬性改變的通知,而應該向 PropertyMetadata 註冊改變通知的回呼函數,後者是相依性屬性本身提供的一項特性,為了支援改變通知,必須使用該特性。
不要在相依性屬性的訪問器中實現屬性強制賦值邏輯,而應該向 PropertyMetadata 註冊強制賦值的回呼函數。後者是相依性屬性本身提供的一項特性,為了支援強制賦值,必須使用該特性。
9.4 Disopse 模式
要為含有可處置類型執行個體的類型實現基本 Dispose 模式。
要為類型實現基本 Dispose 模式並提供終結方法 - 如果類型持有需求由開發人員顯式釋放的類型,而且後者本身沒有終結方法。
考慮為類實現基本 Dispose 模式 - 如果類本身並不持有非託管資源或可處置對象,但是它的子類型卻可能會持有非託管資源或可處置對象。
要按下面的方法來實現 IDisposable 介面,即先調用 Dispose(true),然後再調用 GC.SuppressFinalize(this)。
不要將無參數的 Dispose 方法定義為虛方法。
不要為 Dispose 方法聲明除了 Dispose() 和 Dispose(bool) 之外的任何其它重載方法。
要允許多次調用 Dispose(bool) 方法。他可以在第一次調用之後就什麼也不做。
避免從 Dispose(bool) 方法中拋出異常,除非是緊急情況,所處的進程已經遭到破壞(比如泄漏、共用狀態不一致,等等)。
要從成員中拋出 ObjectDisposedException 異常 - 如果該成員在對象終結之後就無法繼續使用。
考慮在 Dispose() 方法之外在提供一個 Close() 方法 - 如果 close 是該領域中的一個標準術語。
避免定義可終結類型。
不要定義可終結的實值型別。
要將類型定義為可終結類型 - 如果該類型要負責釋放非託管資源,且非託管資源本身不具備終結方法。
要為所有的可終結類型實現基本 Dispose 模式。
不要在終結方法中訪問任何可終結對象,這樣做存在很大的風險,因為被訪問的對象可能已經被終結了。
要將 Finalize 方法定義為受保護的。
不要在終結方法中放過任何異常,除非是致命的系統錯誤。
考慮建立一個用於緊急情況的可終結對象 - 如果終結方法在應用程式定義域被強制卸載或線程異常退出的情況下都務必要執行。
9.5 Factory 模式
要優先使用建構函式,而不是優先使用工廠,因為與特殊的物件建構機制相比,建構函式一般來說更容易使用、更一致,也更方便。
考慮使用工廠 - 如果建構函式提供的對象建立機制不能滿足要求。
要使用工廠 - 如果開發人員可能不清楚待建立的對象的確切類型,比如對基類或介面編程就屬於這種情況。
考慮使用Factory 方法 - 如果這是讓操作不言自明的唯一方法。
要在轉換風格的操作中使用 factory。
要盡量將工廠操作方法實現為方法,而不是實現為屬性。
要通過方法的傳回值而不是方法的輸出參數來返回新建立的對象執行個體。
考慮把 Create 和要建立的類型名連在一起,一次來命名Factory 方法。
考慮把要建立的類型名和 Factory 連在一起,一次來命名工廠類型。例如,可以考慮把建立 Control 對象的工廠類型命名為 ControlFactory。
9.6 對 LINQ 的支援
要實現 IEnumerabl<T>,其目的是為了得到基本的 LINQ 支援。
考慮實現 ICollection<T>,其目的是為了提高查詢的效能。
考慮實現 IQueryable<T> - 如果必須要訪問傳給 IQueryable 的成員的查詢運算式。
不要草率地實現 IQueryable<T>,要理解這樣做可能會對效能產生什麼影響。
要在 IQueryable<T> 的方法中拋出 NotSupportedException - 如果你的資料來源上不支援該操作。
要在新類型中將 Query 模式實現為執行個體方法 - 如果在 LINQ 以外的場合,這些方法在類型中仍然有存在的意義。否則,應該將它們實現為擴充方法。
要讓實現了 Query 模式在類型實現了 IEnumerable<T>。
考慮在設計 LINQ 操作符時,讓它們返回領域特有的可枚舉類型。雖然從本質上來說,Select 查詢方法可以返回任何類型,但是大家通常都希望查詢的結果是可枚舉類型。
避免只實現 Query 模式的一部分 - 如果不希望退回到基本的 IEnuerable<T> 實現。
要為有序序列定義單獨的類型,從而將它和對應的無序序列分開。這樣的類型應該定義 ThenBy 方法。
要延遲執行實際的查詢操作。對 Query 模式的大多數成員來說,我希望它們只是建立一個新的對象,並在枚舉的時候才產生集合重負荷查詢條件的元素。
要將用於查詢的擴充方法放在主命名空間中的一個名為“Linq” 的子命名空間中。例如,為 System.Data 特性定義的擴充方法被放在 System.Data.Linq 命名空間。
要在參數中使用 Expression<Func<...>>,而不是 Func<...> - 如果需要查詢查詢運算式。
9.7 Optional Feature 模式
考慮將 Optional Feature 模式用於抽象中的可選特性。
要提供一個簡單的布爾屬性來讓使用者檢測對象是否支援可選特性。
要在積累中將可選特性定義為虛方法,並在該方法中拋出 NotSupportedException 異常。
9.8 Simulated Convariance 模式
考慮使用 Simulated Convariance 模式 - 如果需要有一種統一的類型來表示泛型型別的所有執行個體。
要確保以等價的方式來實現根基底類型成員和對應的泛型型別成員。
考慮使用抽象基類來表示根基底類型,而不是使用介面來表示根基底類型。
考慮用非泛型型別作為根基底類型 - 如果這樣的類型已經存在。
9.9 Template Method 模式
避免將公有成員定義為虛成員。
考慮使用 Template Method 模式來更好地控制擴充性。
考慮以非秀成員的名字加“Core”尾碼為名字,來命名為該費虛成員提供擴充點的受保護的虛成員。
9.10 逾時
要優先讓使用者通過參數來制定逾時長度。
要優先使用 TimeSpan 來表示逾時長度。
要在逾時後拋出 System.TimeoutException 異常。
不要通過返回錯誤碼的方式來告訴使用者發生了逾時。
9.11 可供 XAML 使用的類型
考慮提供預設建構函式 - 如果想讓類型能用於 XAML。
要提供標記延伸 - 如果想讓 XAML 讀取程式能夠建立不可變的類型。。
避免定義新的類型轉換器,除非這樣的轉換是自然而直觀的。一般來說,應該將類型轉換器的使用範圍限制在 .NET 架構中已經使用了類型轉換器的地方。
考慮將 ContentPropertyAttribute 用於最常用的屬性,從而得到更方便的 XAML 文法。
《.NET 設計規範》第 9 章:常用的設計模式