1. 程式人生 > 實用技巧 >[讀書筆記] 《修煉之道:.NET 開發要點精講》

[讀書筆記] 《修煉之道:.NET 開發要點精講》

修煉之道:.NET開發要點精講》

作者:周見智;博圖軒


第 1 章 另闢蹊徑:解讀.NET

1.7 本章思考> 位置 465

1. 簡述. NET 平臺 中 CTS、 CLS 以及 CLR 的 含義 與 作用。

A:CTS指 公共 型別 系統, 是. NET 平臺 中 各種 語言 必須 遵守 的 型別 規範;CLS指 公共 語言 規範, 是. NET 平臺 中 各種 語言 必須 遵守 的 語言 規範;CLR指 公共 語言 執行時, 它是 一個 虛擬 機,. NET 平臺 中 所有 的 託管 程式碼 均需 要在 CLR 中 執行, 可將 其 視為 另外 一個 作業系統。

第 2 章 高屋建瓴:梳理程式設計約定

2.2 方法與執行緒的關係> 位置 519

只要 我們 確定 了 兩個 方法 只會 執行 在 同一個 執行緒 中, 那麼 這 兩個 方法 就不 可能 同時 執行, 跟 方法 所處 的 位置 無關。

只可 能 一前一後 執行, 此時 我們 不需要 考慮 方法 中 訪問 的 公共 資源 的 執行緒 是否 安全。

2.7 執行緒安全> 位置 595

如果 操作 一個 物件( 比如 呼叫 它的 方法 或者 給 屬性 賦值) 為 非 原子 操作, 即可 能 操作 還沒 完成 就 暫停 了, 這個時候 如果 有 另外 一個 執行緒 開始 執行 同時 也 操作 這個 物件, 訪問 了 同樣 的 方法( 或 屬性), 那麼 這時 就可能 會 出現 一個 問題: 前一 個 操作 還未 結束, 後 一個 操作 就 開始 了, 前後 兩個 操作 一起 出現 混亂。 當 多個 執行緒 同時 訪問 一個 物件 時, 如果 每次 執行 都得 到 不一樣 的 結果, 甚至 出現 異常, 那麼 這個 物件 便是 “非 執行緒 安全”。 造成 一個 物件 非 執行緒 安全 的 因素 有 很多, 除了 前面 提到 的 由於 非 原子 操作 執行 到 一半 就 中斷 以外, 還有 一種 情況 是由 多個 CPU 造成 的, 即 就算 操作 沒有 中斷, 由於 多個 CPU 可以 真正 實現 多 執行緒 同時 執行, 所以 就有 可能 出現 “ 對 同一 物件 同時 操作 出現 混亂” 的 情況,如圖 2- 4 所示。

2.7 執行緒安全 > 位置 627

在 Winform 程式設計 中, 我們 之所以 經常 會 遇見 “不在 建立 控 件 的 執行緒 中 訪問 該 控 件” 的 異常, 原因 就 是對 UI 控 件 的 操作 幾乎 都不 是 執行緒 安全 的( 部分 是)。 一般 UI 控 件 只能 由 UI 執行緒 操作, 其餘 的 所有 操作 均需 要 投遞 到 UI 執行緒 之中 執行, 否則 就會 像 前面 講過 的 那樣, 程式 出現 異常 或不 穩定。

可以 使用Control. InvokeRequired屬性 去 判斷 當前 執行緒 是否 是 建立 控 件 的 執行緒( UI 執行緒), 如果 是, 則 該 屬性 返回 false, 可以 直接 操作 UI 控 件, 否則, 返回 true, 不能 直接 操作 UI 控 件。

注: Control 含有 若干個 執行緒 安全 的 方法 和 屬性, 常見 的 主要 有 Control. InvokeRequired 屬性、 Control.Invoke方法、 Control.BeginInvoke方法( Control. Invoke 的 非同步 版本)、 Control.EndInvoke方法 以及 Control.CreateGraphics方法。 這些 屬性 和 方法 都可以 在 非 UI 執行緒 中 使用, 並且 跨線 程 訪問 這些 方法 和 屬性 時不 會 引起 程式 異常。

2.8 呼叫與回撥> 位置 661

.NET 平臺 開發 中的 回 調 主要 是 通過委託來 實現 的。 委託 是一 種 代理, 專門 負責 呼叫 方法。

2.9 託管資源與非託管資源> 位置 666

.NET 中 物件 使 用到 的 非 託管 資源 主要 有 I/ O 流、 資料庫 連線、 Socket 連線、 視窗 控制代碼 等 各種 直接 與 作業系統 相關 的 資源。

2.13 協議> 位置 751

圖 2- 11   網路 七 層 協議

第 3 章 程式設計之基礎:資料型別

3.1 引用型別與值型別> 位置 844

通常 值 型別 又 分為 以下 兩個 部分。

(1) 簡單 值 型別: 包括 類似 int、 bool、 long 等. NET 內建 型別。 它們 本質上 也是 一種 結構 體。

(2) 複合 值 型別: 使用 Struct 關鍵字 定義 的 結構 體, 比如 System. Drawing. Point 等。 複合 值 型別 可以 由 簡單 值 型別 和 引用 型別 組成。

3.3 賦值與複製> 位置 1118

引用 型別 的 賦值、 淺 複製、 深 複製 的 區別, 如圖 3- 15 所示。

值 型別 的 賦值、 淺 複製、 深 複製 的 區別, 如圖 3- 16 所示。

物件 深 複製 的 過程, 如圖 3- 17 所示。

3.3 賦值與複製 > 位置 1155

NET 中 可以 使用 “序列 化 和 反 序列 化” 的 技術 實現 物件 的 深 複製, 只要 一個 型別 及其 所有 成員 的 型別 都 標示 為 “ 可 序列 化”, 那麼 就可以 先 序列 化 該類 型 物件 到 字 節流, 然後 再將 位元組 流 反序 列 化成 源 物件 的 副本。 這樣一來, 源 物件 與 副本 之間 沒有 任何 關聯, 從而 達到 深 複製 的 效果。

第 4 章 物以類聚:物件也有生命

4.1 堆和棧> 位置 1235

棧 主要 用來 記錄 程式 的 執行 過程, 它有 嚴格 的 儲存 和 訪問 順序; 而 堆 主要 儲存 程式 執行 期間 產生 的 一些 資料, 幾乎沒有 順序 的 概念。

4.1 堆和棧 > 位置 1269

堆 跟 棧 的 本質 都是 一段記憶體塊。

4.2 堆中物件的出生與死亡> 位置 1272

棧 中的 物件 由 系統 負責 自動 存入 和 移 除, 正常 情況下, 跟我 們 程式 開發 的 關聯 並不 大。

4.2 堆中物件的出生與死亡 > 位置 1354

謹慎 使用 物件 的析構方法。 析 構 方法 由 CLR 呼叫, 不受 程式控制, 而且 容易 造成 物件 重生。 析 構 方法 除了 用作 管理 非 託管 資源 外, 幾乎 不能 用作 其他 用途。

4.3 管理非託管資源> 位置 1381

GC. SuppressFinalize 方法 請求 CLR 不要 再 呼叫 本 物件 的 析 構 方法, 原因 很 簡單, 既然 非 託管 資源 已經 釋放 完成, 那麼 CLR 就 沒 必要 再繼續 呼叫 析 構 方法。

注: CLR 呼叫 物件 的 析 構 方法 是一 個 複雜 的 過程, 需要 消耗 非常 大的 效能, 這也 是 儘量 避免 在 析 構 方法 中 釋放 非 託管 資源 的 一個 重要 原因, 最好 是 徹底 地 不 呼叫 析 構 方法。

4.4 正確使用 IDisposable 介面> 位置 1547

如果 一個 型別 使 用了 非 託管 資源, 或者 它 包含 使 用了 非 託管 資源 的 成員, 那麼 開發者 就應 該 應用 “Dispose模式”: 正確地 實現( 間接 或 直接) IDisposable 介面, 正確地 重寫 Dispose( bool disposing) 虛 方法。

4.6 本章思考> 位置 1587

呼叫 一個 物件 的 Dispose() 方法 後, 並不 意味著 該 物件 已經 死亡, 只有 GC 將對 象 例項 佔用 的 記憶體 回收 後, 才 可以說 物件 已死。 但是 通常 情況下, 在調 用 物件 的 Dispose() 方法 後, 由於 釋 放了 該 物件 的 非 託管 資源, 因此 該 物件 幾乎 就 處於 “無用” 狀態,“ 等待 死亡” 是它 正確 的 選擇。

第 5 章 重中之重:委託與事件

5.1 什麼是.NET 中的委託> 位置 1625

像 宣告 一個 普通 方法 一樣, 提供 方法 名稱、 引數、 訪問 修飾 符 以及 返回 值, 然後 在前面 加上 Delegate 關鍵字, 這樣 就 定義 了 一個 委託 型別。

委託 型別 定義 完成 後, 怎樣 去 例項 化 一個 委託 物件 呢? 其實 很 簡單, 跟 例項 化 其他 型別 物件 一樣, 我們 可以 通過 new 關鍵字 建立 委託 物件。

5.1 什麼是.NET 中的委託 > 位置 1655

使用 委託 呼叫 方法 時, 我們 可以 直接 使用 “委託物件( 引數 列表);” 這樣 的 格式, 它 等效 於 “ 委託物件.Invoke( 引數 列表)”。

給 委託 賦值 的 另外 一種 方式 是: 委託物件 = 方法。

怎樣 讓 一個 委託 同時 呼叫 兩個 或者 兩個 以上 的 方法 呢? 答案 是 直接 使用 加法 賦值 運算子( =) 將 多個 方法 附加 到 委託 物件 上。

5.1 什麼是.NET 中的委託 > 位置 1744

委託 內部 的 “鏈 表” 結構 跟 單向 鏈 表 的 實現 原理 卻不 相同。 它 並不是 通過 Next 引用 與 後續 委託 建立 關聯, 而是 將 所有 委託 存放 在 一個 陣列中, 如圖 5- 6 所示。

每一個 委託 型別 都有 一個 公開 的 GetInvocationList() 的 方法, 可以 返回 已 附加 到 委託 物件 上 的 所有 委託, 即 圖 5- 6 中 陣列 列表 部分。 另外, 我們 平時 不 區分 委託 物件 和 委託 鏈 表, 提到 委託 物件, 它 很有可能 就 表示 一個 委託 鏈 表, 這 跟 單向 鏈 表 只 包含 一個 節點 時 道理 類似。

5.1 什麼是.NET 中的委託 > 位置 1781

委託 跟 String 型別 一樣, 也是 不可改變 的。 換句話說, 一旦 委託 物件 建立 完成 後, 這個 物件 就不能 再被 更改, 那麼 我們 前面 講到 的 將 一個 委託 附加 到 另外 一個 委託 物件 上 形成 一個 委託 鏈 表 又 該 如何 解釋 呢? 其實 這個 跟 String. ToUpper() 過程 類似, 我們 對 委託 進行 附加、 移 除 等 操作 都會 產生 一個 全新 的 委託, 這些 操作 並不 會 改變 原有 委託 物件。

5.1 什麼是.NET 中的委託 > 位置 1821

框架 有兩 種 呼叫 框架 使用者 編寫 的 程式碼 的 方式, 一種 便是 面向 抽象 程式設計, 即 框架 中 儘量 不出 現 某個 具體 型別 的 引用, 而是 使用 抽象化 的 基 類 引用 或者 介面 引用 代替。 只要 框架 使用者 編寫 的 型別 派生 自 抽象化 的 基 類 或 實現 了 介面, 框架 均可 以 正確地 呼叫 它們。

框架 呼叫 框架 使用者 程式碼 的 另外 一種 方式 就是 使用 委託, 將 委託 作為 引數( 變數) 傳遞 給 框架, 框架 通過 委託 呼叫 方法。

5.2 事件與委託的關係> 位置 1860

問題,. NET 中 提出 了 一種 介於 public 和 private 之間 的 另外 一種 訪問 級別: 在 定義 委託 成員 的 時候 給出event關鍵字 進行 修飾, 前面 加了 event 關鍵字 修飾 的 public 委託 成員, 只 能在 類 外部 進行 附加 和 移 除 操作, 而 呼叫 操作 只能 發生 在 型別 內部。

我們 把 類 中 設定 了 event 關鍵字 的 委託 叫作 “事件”。“ 事件” 本質上 就是 委託 物件。 事件 的 出現, 限制 了 委託 呼叫 只能 發生 在 一個 型別 的 內部, 如圖 5- 12 所示。

在 圖 5- 12 中 由於 server 中的 委託 使用 了 event 關鍵字 修飾, 因此 委託 只能 在 server 內部 呼叫, 對 外部 也 只能 進行 附加 和 移 除 方法 操作。 當 符合 某一 條件 時, server 內部 會 呼叫 委託, 這個 時間 不由 我們( client) 控制, 而是 由 系統( server) 決定。 因此 大部分 時候, 事件 在 程式 中 起 到了 回 調 作用。

呼叫 加了 event 關鍵字 修飾 的 委託 也稱 為 “激發事件”。 其中, 呼叫 方( 圖 5- 12 中的 server) 被稱為 “事件釋出者”; 被 呼叫 方( 圖 5- 12 中的 client) 被稱為 “事件註冊者”( 或 “事件觀察者”、“事件訂閱者” 等, 本書 中統 一 稱之為 “事件 註冊 者”); 附加 委託 的 過程 被 稱之為 “註冊事件”( 或 “繫結事件”、“監聽事件”、“訂閱事件” 等, 本書 中統 一 稱之為 “註冊 事件”); 移 除 委託 的 過程 被 稱之為 “登出事件”。 通過 委託 呼叫 的 方法 被稱為 “事件處理程式”。

5.3 使用事件程式設計> 位置 1940

在調 用 委託 鏈 時, 如果 某一個 委託 對應 的 方法 丟擲 了異常, 那麼 剩下 的 其他 委託 將 不會 再 呼叫。 這個 很容易 理解, 本來 是按 先後 順序 依次 呼叫 方法, 如果 其中 某一個 丟擲 異常, 剩下 的 肯定 會被 跳過。 為了 解決 這個 問題, 單單 是將 激發 事件 的 程式碼 放在 try/catch 塊 中 是 不夠 的, 我們 還 需要 分 步調 用 每個 委託, 將 每 一步 的 呼叫 程式碼 均 放在 try/catch 塊 中。

5.3 使用事件程式設計 > 位置 1967

除了 事件 本身 的命名, 事件 所屬 委託 型別 的 命名 也 同樣 有 標準 格式, 一般以 “ 事件 名 EventHandler” 這種 格式 來給 委託 命名, 因此 前面 提到 的 NewEmailReceived 事件 對應 的 委託 型別 名稱 應該 是 “NewEmailReceivedEventHandler”)。 激發 事件 時會 傳遞 一些 引數, 這些 引數 一般 繼承 自 EventArgs 型別( 後者 為. NET 框架 預定 義 型別), 以 “ 事件 名 EventArgs” 來 命名, 比如 前面 提到 的 NewEmailReceived 事件 在 激發 時 傳遞 的 引數 型別 名稱 應該 是 “NewEmailReceivedEventArgs”。

5.3 使用事件程式設計 > 位置 2004

之所以 要把 激發 事件 的 程式碼 放在 一個 單獨 的 虛 方法 中, 是 為了 讓 從 該 型別( EmailManager) 派生 出來 的 子類 能夠 重寫 虛 方法, 從而 改變 激發 事件 的 邏輯。

虛 方法 的 命名 方式 一般 為 “On事件 名”。 另外, 該 程式碼 中的 虛 方法 必須 定義 為 “protected”, 因為 派生 類 中 很可能 要 呼叫 基 類 的 虛 方法。

5.3 使用事件程式設計 > 位置 2039

圖 5- 13   屬性 和 事件 的 作用

5.4 弱委託> 位置 2077

在 事件 程式設計 中, 委託 的 Target 成員, 就是 對 事件 註冊 者 的 強 引用, 如果 事件 註冊 者 沒有 登出 事件, 強 引用 Target 便會 一直 存在, 堆 中的 事件 註冊 者 記憶體 就 一直 不 會被 CLR 回收, 這對 開發 人員 來講, 幾乎 是 很難 發覺 的。

像 “A a= new A();” 中的 a 被稱為 “顯式強引用( Explicit Strong Reference)”, 類似 委託 中 包含 的 不明顯 的 強 引用, 我們 稱之為 “隱式強引用( Implicit Strong Reference)”。

弱引用與 物件 例項 之間 屬於 一種 “弱 關聯” 關係, 跟 強 引用 與 物件 例項 的 關係 不一樣, 就算 程式 中有 弱 引用 指向 堆 中 物件 例項, CLR 還是 會把 該 物件 例項 當作 回收 目標。 程式 中 使用 弱 引用 訪問 物件 例項 之前 必須 先 檢查 CLR 有沒有 回收 該 物件 記憶體。 換句話說, 當 堆 中 一個 物件 例項 只有 弱 引用 指向 它 時, CLR 可以 回收 它的 記憶體。 使用 弱 引用, 堆 中 物件 能否 被 訪問, 同時 掌握 在 程式 和 CLR 手中。

建立 一個 弱 引用 很 簡單, 使用 WeakReference 型別, 給 它的 構造 方法 傳遞 一個 強 引用 作為 引數 即可。

5.4 弱委託 > 位置 2105

在 程式設計 過程中, 由於 很難 管理 好強 引用, 從而 造成 不必 要的 記憶體 開銷。 尤其 前面 講到 的 “隱式 強 引用”, 在 使用 過程中 不易 發覺 它們 的 存在。 弱 引用 特別 適合 用於 那些 對 程式 依賴 程度 不高 的 物件, 即那 些 物件 生命 期 主要 不是 由 程式控制 的 物件。 比如 事件 程式設計 中, 事件 釋出者 對 事件 註冊 者 的 存在 與否 不是 很 關心, 如果 註冊 者 在, 那就 激發 事件 並 通知 註冊 者; 如果 註冊 者 已經 被 CLR 回收 記憶體, 那麼 就不 通知 它, 這 完全 不會 影響 程式 的 執行。

5.4 弱委託 > 位置 2109

前面 講到 過, 委託 包含 兩個 部分: 一個 Object 型別 的 Target 成員, 代表 被 呼叫 方法 的 所有者, 如果 方法 為 靜態 方法, 則 Target 為 null; 另一個 是 MethodInfo 型別 的 Method 成員, 代表 被 呼叫 方法。 由於 Target 成員 是 一個 強 引用, 所以 只要 委託 存在, 那麼 方法 的 所有者 就會 一直 在 堆 中 存在 而 不 能被 CLR 回收。 如果 我們將 委託 中的 Target 強 引用 換成 弱 引 用的 話, 那麼 不管 委託 存在 與否, 都不 會 影響 方法 的 所有者 在 堆 中 記憶體 的 回收。 這樣一來, 我們 在使 用 委託 呼叫 方法 之前, 需要 先 判斷 方法 的 所有者 是否 已經 被 CLR 回收。 我們 稱 將 Target 成員 換成 弱 引用 之後 的 委託 為 “弱委託”, 弱 委託 定義 程式碼 如下:

//Code 5- 26 
class WeakDelegate 
{ 
    WeakReference _weakRef; //NO. 1 
    MethodInfo _method; //NO. 2 
    
    public WeakDelegate(Delegate d) 
    { 
        _weakRef = new WeakReference(d.Target); 
        _methodInfo = d.Method; 
    } 
    
    public object Invoke( param object[] args) 
    {
        object obj = _weakRef.Target; 
        if(_weakRef.IsAlive) //NO. 3 
        { 
            return _method.Invoke(obj, args); //NO. 4 
        } 
        else 
        { 
            return null; 
        } 
    } 
}

弱 委託 將 委託 與 被 呼叫 方法 的 所有者 之間 的 關 系由 “強 關聯” 轉換 成了 “ 弱 關聯”, 方法 的 所有者 在 堆 中的 生命 期 不再 受 委託 的 控制, 如圖 5- 16 所示, 為 弱 委託 的 結構。

本 小節 示例 程式碼 中的 WeakDelegate 型別 並沒有 提供 類似 Delegate. Combine 以及 Delegate. Remove 這樣 操作 委託 鏈 表 的 方法, 當然 也 沒有 弱 委託 鏈 表 的 功能。 這些 功能 可以 仿照 單向 鏈 表 的 結構 去 實現, 把 每個 弱 委託 都 當作 鏈 表中 的 一個 節點。 其 方法 可 參照 5. 1. 2 小節 中 講到 的 單向 鏈 表。

5.5 本章回顧> 位置 2151

委託 的 3 個作用: 第一, 它 允許 把 方法 作為 引數, 傳遞 給 其他 的 模組; 第二, 它 允許 我們 同時 呼叫 多個 具有 相同 簽名 的 方法; 第三, 它 允許 我們 非同步 呼叫 任何 方法。 這 3 個 作用 奠定 了 委託 在. NET 程式設計 中的 絕對 重要 地位。

第 6 章 執行緒的升級:非同步程式設計模型

6.1 非同步程式設計的必要性> 位置 2171

通常 情況下, 呼叫 一個 方法 都 符合 這樣 一個 規律: 呼叫 執行緒 開始 呼叫 方法 A 後, 在 A 返回 之前, 呼叫 執行緒 得不到 程式 執行 的 控制 權。 也就是說, 方法 A 後面 的 程式碼 是 不可能 執行 的, 直到 A 返回 為止, 這種 呼叫 方式 被 稱之為 “同步呼叫”; 相反, 如果 呼叫 在 返回 之前, 呼叫 執行緒 依舊 保留 控制 權, 能夠 繼續 執行 後面 的 程式碼, 那麼 這種 呼叫 方式 被稱為 “非同步呼叫”。

同步 呼叫 也 被 一些 學者 稱為 “阻塞呼叫”; 一些 相對 的 非同步 呼叫 則 被稱為 “非阻塞呼叫”。

6.2 委託的非同步呼叫> 位置 2202

理論上 講, 任何 一個 方法, 通過 委託 包裝 後, 都可以 實現 非同步 呼叫。

.NET 編譯器 定義 的 每個 委託 型別 都 自動 生成 了 兩個 方法: BeginInvoke 和 EndInvoke。 這 兩個 方法 專門 用來 負責 非同步 呼叫 委託。

BeginInvoke返回 一個IAsyncResult介面 型別, 它可 以 唯一 區分 一個 非同步 呼叫 過程。 BeginInvoke 一 執行 就能 馬上 返回, 不會 阻塞 呼叫 執行緒。EndInvoke表示 結束 對 委託 的 非同步 呼叫, 但這 並不 意味著 它可 以 中斷 非同步 呼叫 過程, 如果 非同步 呼叫 還未 結束, EndInvoke 則 只能 等待, 直到 非同步 呼叫 過程 結束。 另外, 如果 委託 帶有 返回 值, 我們 必須 通過 EndInvoke 獲得 這個 返回 結果。

6.2 委託的非同步呼叫 > 位置 2233

委託 非同步 呼叫 開始 後, 系統 會在 執行緒 池 中 找到 一個 空閒 的 執行緒 去 執行 委託。

6.2 委託的非同步呼叫 > 位置 2296

非同步 呼叫 委託 時, 由於 方法 實際 執行 在 其他 執行緒 中( 執行緒 池 中的 某一 執行緒, 非 當前 呼叫 執行緒), 因此 當前 執行緒 捕獲 不了 異常, 那麼 我們 怎樣 才能 知道 非同步 呼叫 過程中 到底 是否 會有 異常 呢? 答案 就在 EndInvoke 方法 上, 如果 非同步 呼叫 過程 有異常, 那麼 該 異常 就會 在 我們 在 呼叫 EndInvoke 方法 時 丟擲。 所以 我們 在 呼叫 EndInvoke 方法 時, 一定 要把 它 放在 try/catch 塊 中。

6.3 非委託的非同步呼叫> 位置 2358

.NET 中 提供 非同步 方法 的 型別 有 Stream( 或 其 派生 類)、 Socket( 或 其 派生 類) 以及 訪問 資料庫 的 SqlConnection 型別 等。 它們 的 使用 方式 跟 委託 的 BeginInvoke 和 EndInvoke 方法 類似, 只是 命名 有所 差別, 基本上 都是 “Begin 操作” 和 “ End 操作” 這種 格式。 比如 FileStream. BeginRead 表示 開始 一個 非同步 讀 操作, 而 FileStream. EndRead 則 表示 結束 非同步 讀 操作。

6.6 本章思考> 位置 2446

非同步 程式設計 與 多 執行緒 程式設計 的 效果 類似, 都是 為了 能夠 並行 執行 程式碼, 達到 同時 處理 任務 的 目的。非同步程式設計時, 系統 自己 通過執行緒池來 分配 執行緒, 不需要 人工干預, 非同步 程式設計 邏輯 複雜 不易 理解, 而 多 執行緒 程式設計 時, 完全 需要 人為 去 控制, 相對 較 靈活。

第 7 章 可複用程式碼:元件的來龍去脈

7.1 .NET 中的元件> 位置 2457

在. NET 程式設計 中, 我們 把 實現( 直接 或者 間接) System.ComponentModel.IComponent介面 的 型別 稱為 “元件”,

7.1 .NET 中的元件 > 位置 2472

元件 和 控 件 不是 相等 的,元件包含控制元件, 控 件 只是 元件 中的 一個 分類。

7.1 .NET 中的元件 > 位置 2477

圖 7- 2   Windows Forms 中 控 件 之間 的 關係

所 有的 控 件 均 派生 自Control類, Control 類 又 屬於 元件, 因此 所有 控 件 均 具備 元件 的 特性。

不管 元件 還是 控 件, 它們 都是 可以 重複 使用 的 程式碼 集合, 都 實現 了IDisposable介面, 都 需要 遵循 第 4 章 中 講到 的 Dispose 模式。如果 一個 型別 使 用了 非 託管 資源, 它 實現 IDisposable 介面 就可以 了, 那 為什麼 還要 在. NET 程式設計 中 又 提出 元件 的 概念 呢?

這樣做 可以 說完 全是 為了 實現 程式 的 “視覺化開發”, 也就是 我們 常說 的 “所見 即 所得”。 在 類似 Visual Studio 這樣 的 開發 環境 中, 一切 “ 元件” 均 可被 可 視 化 設計, 換句話說, 只要 我們 定義 的 型別 實現 了 IComponent 介面, 那麼 在開 發 階段, 該 型別 就可以 出現 在窗 體 設計 器 中, 我們 就可以 使用 窗體 設計 器 編輯 它的 屬性、 給 它 註冊 事件。 它 還能 被 窗體 設計 器 中 別的 元件 識別。

7.2 容器 – 元件 – 服務模型> 位置 2499

在. NET 程式設計 中, 把 所有 實現( 直接 或 間接) System. ComponentModel.IContainer介面 的 型別 都 稱之為 邏輯容器( 以下 簡稱 “容器”)。

容器 是 為 元件 服務 的。

.NET 框架 中有 一個 IContainer 介面 的 預設 實現: System. ComponentModel.Container型別, 該類 型 預設 實現 了 IContainer 介面 中的 方法 以及 屬性。

7.2 容器 – 元件 – 服務模型 > 位置 2514

傳統 容器 僅僅 是在 空間 上 簡單 地 將 資料 組織 在一起, 並不 能為 資料 之間 的 互動 提供 支援。 而 本章 討論 的邏輯容器, 在 某種 意義上 講, 更為 高階。 它 能為 元件( 邏輯 元素) 之間 的 通訊 提供 支援, 元件 與 元件 之間 不再 是 獨立 存在。 此外, 它 還能 直接 給 元件 提供 某些 服務。物理 容器 和 邏輯 容器 分別 與 元素 之間 的 關係, 如圖 7- 4 所示。

物理 容器 中的 元素 之間 不能 相互 通訊, 物理 容器 也不 可能 為 其內 部 元素 提供 服務; 邏輯 容器 中的 元件 之間 可以 通過 邏輯 容器 作為 橋樑, 進行 資料 交換; 同時, 邏輯 容器 還能 給 各個 元件 提供 服務。 所謂服務, 就是 指 邏輯 容器 能夠 給 元件 提供 一些 訪問 支援。 比如 某個 元件 需要 知道 它的 所屬 容器 中共 包含 有 多少 個 元件, 那麼 它 就 可以向 容器 發出 請求。 容器 收到 請求 後 會為 它 返回 一個 獲取 元件 總數 的 介面。

在 本章 7. 1. 1 小節 中 我們 提 到過 IComponent 介面 中有 一個ISite型別 的 屬性。 當時 說 它是 起到 一個 “定位” 的 作用。 現在 看來, 元件 與 容器 之間 的紐帶就 是它, 元件 通過 該 屬性 與 它 所屬 容器 取得 了 聯絡。

7.2 容器 – 元件 – 服務模型 > 位置 2574

Component、 Site 以及 Container 3 個 型別 均 包含 有 獲取 服務 的 方法 GetService。 現在 我們 可以 整理 一下 元件 向 容器 請求 服務 的 流程, 如圖 7- 6 所示。

注: 容器 將 元件 新增 進來 時( 執行 Container. Add), 會 初始化 該 元件 的 Site 屬性, 讓 該 元件 與 容器 產生 關聯, 只有 當 這一 過程 發生 之後, 元件 才能 獲取 容器 的 服務。

7.2 容器 – 元件 – 服務模型 > 位置 2601

在 我們 向 窗體 設計 器 中 拖動控制元件 時, 是 會 執行 類似 “new Button();” 這樣 的 程式碼, 在 記憶體 中例項化一個 元件 例項。

7.2 容器 – 元件 – 服務模型 > 位置 2655

圖 7- 10   窗體 設計 器 中的 元件 與 生成 的 源 程式碼

在 圖 7- 10 中, 圖中 左邊 顯示 我們 拖放 到 設計 器 中的 一個 Button 控 件。 在 這個 過程中, 窗體 設計 器 除了 會 例項 化 一個 Button 控 件( 圖中 左邊 Form2 中), 還會 為我 們 生成 右邊 的 程式碼。

7.3 設計時與執行時> 位置 2672

任何 元件 都有 兩種 狀態: 設計時 和 執行時。

判斷 元件 的當前狀態有 以下 兩種 方法。

(1) 判斷 元件 的 DesignMode 屬性。 每個 元件 都有 一個 Bool 型別 的 DesignMode 屬性, 正如 它的 字面 意思, 如果 該 屬性 為 true, 那麼 代表 元件 當前 處於 設計 時 狀態; 否則 該 元件 處於 執行時 狀態。

(2) 隨便 請求 一個 服務, 看 返回 來的 服務 介面 是否 為 null。 前面 提 到過, 當 一個 元件 不屬於 任何 一個 容器 時, 那麼 它 通過 GetService 方法 請求 的 服務 肯定 返回 為 null。

注:(1)(2) 方法 均不 適合巢狀元件, 因為 窗體 設計 器 只會 將 最外 層 元件 的 DesignMode 屬性 值 設定 為 true。

有 一種 可以 解決 巢狀 元件 中 無法 判斷 其 子 元件 狀態 的 方法, 那就 是 通過 Process 類 來 檢查 當前 程序 的 名稱。 看 它是 否 包含 “devenv” 這個 字串。 如果 有, 那麼 說明 元件 當前 處於 Visual Studio 開發 環境 中( 即 元件 處於 設計 時), if( Process. GetCurrentProcess (). ProcessName. Contains(”devenv”)) 為 假, 說明 元件 處於 執行時。 這種 方法 也有 一個 弊端, 很 明顯, 如果 我們 使用 的 不是 Visual Studio 開發 環境( 即 程序 名 不 包含 devenv), 或者 我們自己 的 程式 程序 名稱 本身 就 已經 包含 了 devenv, 那麼 該 怎麼辦 呢?

在開 發 一些 需要授權的 元件 時, 就可以 用到 元件 的 兩種 狀態。 這些 需要 授權 的 元件 收費 物件 一般 是 開發者。 因此, 在 開發者 使用 這些 元件 開發 系統 的 時候( 處於 開發 階段), 就應 該有 授權 入口, 而 當 程式 執行 之後, 就不 應該 出現 授權 的 介面。

7.4 控制元件> 位置 2776

無論是 複合 控 件、 擴充套件 控 件 還是 自定義 控 件, 我們 均可 以 重寫 控 件 的視窗過程: WndProc 虛 方法, 從 根源 上 接觸 到 Windows 訊息, 這個 做法 並不是 自定義 控 件 的 專利。

第 8 章 經典重視:桌面 GUI 框架揭祕

8.2 Win32 應用程式的結構> 位置 2841

程式 是 無法 直接 識別 使用者 鍵盤 或者 滑鼠 等 裝置 的 輸入 資訊, 這些 輸入 必須 先由 作業系統 轉換 成 固定 格式 資料 之後, 才能 被 程式 使用。

在 Windows 程式設計 中, 我們 把 由 作業系統 轉換 之後 的 固定 格式 資料 稱為Windows 訊息。 Windows 訊息 是一 種 預 定義 的 資料 結構( 比如 C 中的 Struct), 它 包含 有 訊息 型別、 訊息 接收者 以及 訊息 引數 等 資訊。 我們 還把 程式 中 獲取 Windows 訊息 的 結構 稱之為Windows 訊息迴圈。 Windows 訊息 迴圈 在 程式碼 中就 是一 個 迴圈 結構( 比如 while 迴圈), 它 不停 地 從 作業系統 中 獲取 Windows 訊息, 然後 交給 程式 去 處理。

8.2 Win32 應用程式的結構 > 位置 2895

在 Windows 中, 其實 將 訊息 分成 了 兩類, 一類 需要 存入 訊息 佇列, 然後 由 訊息 迴圈 取出 來之 後才 能被 視窗 過程 處理, 這類 訊息 被 稱之為 “佇列訊息”( Queued Message)。 這類 訊息 主要 包括 使用者 的 滑鼠 鍵盤 輸入 訊息、 繪製 訊息 WM_ PAINT、 退出 訊息 WM_ QUIT 以及 時間 訊息 WM_ TIMER。 另 一類 是 不需要 存入 訊息 佇列, 也不 經過 訊息 迴圈, 它們 直接 傳遞 給 視窗 過程, 由 視窗 過程 直接 處理, 這類 訊息 被 稱之為 “非佇列訊息”( Nonqueued Message)。 當 作業系統 想要 告訴 視窗 發生了 某 件事 時, 它 會 給 視窗 傳送 一個 非 佇列 訊息, 比如 當 我們 使用 SetWindowPos API 移動 視窗 後, 系統 自動 會 傳送 一個 WM_ WINDOWPOSCHANGED 訊息 給 該 視窗 的 視窗 過程, 告訴 它 位置 發生 變化 了。

8.4 Windows Forms 框架> 位置 3148

在 Windows Forms 框架 中, 以 Control 為 基 類, 其他 所有 與 窗體 顯示 有關 的 控 件 幾乎 都 派生 自 它; Control 類 中的 WndProc 虛 方法 就是 我們 在 Win32 開發 模式 中 所 熟悉 的 視窗 過程。 另外, 前面 也 講到 過, 窗體 和 控 件 本質上 是一 個 東西, 只是 它們 有著 不同 的 屬性, 所以 我們 可以 看到, 窗體 類 Form 間接 派生 自 Control 類。

Winform 程式 中 包含 3 個 部分: 訊息 佇列、 UI 執行緒 以及 控 件。

8.5 Winform 程式的結構> 位置 3180

在 每個 Winform 程式 的 Program. cs 檔案 中, 都有 一個 Main 方法, 該 Main 方法 就是 程式 的 入口 方法。 每個 程式 啟動 時 都會 以 Main 方法 為 入口, 建立 一個 執行緒, 這個 執行緒 就是 UI 執行緒。 可能 你會 問 UI 執行緒 怎麼 沒有 訊息 迴圈 呢? 那是 因為 Main 方法 中 總是 會 出現 一個 類似 Application. Run 的 方法, 而 訊息 迴圈 就 隱 藏在 了 該 方法 內部( 具體 參見 下一 小節 內容)。 一個 程式 理論上 可以 有 多個 UI 執行緒, 且 每個 執行緒 都有 自己的 訊息 佇列( 由 作業系統 維護)、 訊息 迴圈、 窗體 等 元素, 如圖 8- 13 所示。

由於 UI 執行緒 之間 的 資料 交換 比較 複雜, 因此 在 實際 開發 中, 在 沒有 特殊 需求 的 情況下, 一個 程式 一般 只 包含 有一個 UI 執行緒。

8.5 Winform 程式的結構 > 位置 3265

對 UI 執行緒 的 認識:

① 一個 程式 可以 包含 有 多個 UI 執行緒, 我們 完全可以 通過 System. Threading. Thread 類 新創 建 一個 普通 執行緒, 然後 在 該 執行緒 中 呼叫 Application. Run 方法 來 開啟 訊息 迴圈;

② 一個 UI 執行緒 結束( 該 執行緒 中的 訊息 迴圈 個數 為 “0”) 後, 將會 激發 Application. ThreadExit 事件, 告知 有 UI 執行緒 結束, 只有 當 所有 UI 執行緒 都 結束( 程式 中 訊息 迴圈 總數 為 “ 0”), 才會 激發 Application. Exit 事件, 告知 應用 程式 退出。

最後 我們 再來 看一下 Windows Forms 中 訊息 迴圈 的 結構圖, 如圖 8- 14 所示。

8.5 Winform 程式的結構 > 位置 3319

Windows Forms 框架 將 窗體 和 視窗 過程 封 裝在 了 一起, Control( 或 其 派生 類, 下同) 類 中的WndProc虛 方法 就是 控 件 的 視窗 過程。 之所以 將 視窗 過程 宣告 為 虛 方法, 這是 因為 Windows Forms 框架 是 面向 物件 的, 它 充分 地利 用了 面向 物件 程式設計 中的 “多 態” 特性。 因此, 所有 Control 類 的 派生 類 均可 以 重寫 它的 視窗 過程, 從而 從 源頭 上 攔截 到 Windows 訊息, 處理 自己 想要 處理 的 Windows 訊息。

視窗 過程 做了 一個 非常 重要的 事: 將 Window 訊息轉換成了. NET 中的 事件。

第 9 章 溝通無礙:網路程式設計

9.1 兩種 Socket 通訊方式> 位置 3518

(TCP)資料 是按 順序 走在 建立 的 一條 隧道 中, 那麼 資料 就應 該 遵循 “先走 先 到達” 的 規則, 並且 隧道 中的 資料 以 “ 流” 的 形式 傳輸。 傳送 方 傳送 的 前後 兩次 資料 之間 沒有 邊界, 需要 接收 方自 己 根據 事先 規定 好的 “ 協議” 去 判斷 資料 邊界。

9.1 兩種 Socket 通訊方式 > 位置 3555

UDP通訊 中, 資料 是以 “數 據報” 的 形式 傳輸, 以 一個 整體 傳送、 以 一個 整體 接收, 因此 UDP 存在 資料 邊界。 但是 UDP 接 收到 的 資料 是 無序 的, 先 傳送 的 可能 後接 收, 後 傳送 的 可能 先 接收, 甚至 有的 接收 不到。

9.1 兩種 Socket 通訊方式 > 位置 3605

在. NET 中 有關Socket通訊 程式設計 的 型別 主要 有 5 種, 見表 9- 1。

TcpListener和TcpClient的 關係 如圖 9- 9 所示

圖 9- 9 中, TcpListener 偵聽 來自 客戶 端 的 “連線” 請求, 返回 一個 代理 TcpClient, 該 代理 與 客戶 端 的 TcpClient 進行 資料 交換。

UdpClient在 UDP 通訊 中 所處 的 角色 如圖 9- 10 所示:

NetworkStream型別 是 System. IO. Stream 類 的 一個 派生 類。NetworkStream 只能 用於 TCP 通訊 中。

Socket型別 是一 個 相對 較 基礎 的 通訊 型別。 它 既能 實現 TCP 通訊 也能 實現 UDP 通訊, 可以 認為 TcpListener、 TcpClient 以及 UdpClient 進一步 封裝 了 Socket 型別。

9.3 UDP 通訊的實現> 位置 3840

之所以 將TCP通訊 中 應用 層 協議 的 資料 結構設計 成位元組流的 形式, 是因為 TCP 通訊 中 資料 是以 流的 形式 傳輸, 以 位元組 流的 形式 格式化 資料 後, 更 方便 程式 判斷 資料 邊界。

而在UDP通訊 中, 資料 以 資料 報的 形式 傳輸, 每次 接 收到 的 資料 是 完整 的, 對於 當前 區域網 即時 通訊 例項 來講, 協議 使用文字的 形式 更 方便 程式 處理 資料, 當然, 我們 完全 也可以 將 UDP 通訊 中 應用 層 協議 的 資料 結構設計 成 位元組 流的 形式。

9.4 非同步程式設計在網路程式設計中的應用> 位置 3991

使用 Socket.BeginSendTo() 方法 開始 一個 非同步 傳送 過程, 併為 該 方法 提供 一個AsyncCallback的 回 調 引數。 該 方法 的 呼叫 不會 阻塞 呼叫 執行緒。 我們 在 回 調 方法 OnSend 中 可使用 Socket.EndSendTo() 方法, 結束 非同步 傳送 過程, 該 方法 返回 實際 傳送 的 資料 長度。

9.4 非同步程式設計在網路程式設計中的應用 > 位置 4011

非同步 程式設計 也能 實現迴圈接收 資料, 但卻 看 不到 顯 式 地 建立 的 執行緒, 也 看不 到 類似 while 這樣 的 迴圈 語句。

9.6 本章思考> 位置 4062

所有 的通訊協議本質上 都是 一種資料結構。 通訊 雙方 都 必須 按照 這種 資料 結構 規定 的 形式 去 傳送 或 接收( 解析) 資料。

基於 TCP 協議 的 通訊 在 進行 資料 互動 之前 需要 先 建立 連線, 類似 打電話。 這種 通訊 方式 保證 了 資料 傳輸 的 正確性、 可靠性。 基於 UDP 協議 的 通訊 在 進行 資料 傳輸 之前 不需要 建立 連線, 類似 發 簡訊。 這種 通訊 方式 不能 保證 資料 傳輸 的 正確性。

第 10 章 動力之源:程式碼中的 “泵”

10.2 常見的 “泵” 結構> 位置 4150

桌面 程式 的 UI 執行緒 中 包含 一個 訊息 迴圈( 確切 地說, 應該 是 While 迴圈)。 該 迴圈 不斷 地 從 訊息 佇列 中 獲取 Windows 訊息, 最終 通過 呼叫 對應 的 視窗 過程, 將 Windows 訊息 傳遞 給 視窗 過程 進行 處理。

10.2 常見的 “泵” 結構 > 位置 4179

瀏覽器 每次 傳送 http 請求 時, 都 必須 與 Web 伺服器 建立 連線。 Web 伺服器 端 請求 處理 結束 後, 連線 立刻 關閉。 瀏覽器 下一 次 傳送 http 請求 時, 必須 再一次 重新 與 伺服器 建立 連線。 由此可見, 我們 所說 的 HTTP 協議 是 面向 無 連線 的, 具體 指 Web 伺服器 一次 連線 只 處理 一個 請求, 請求 處理 完畢 後, 連線 關閉, 瀏覽器 在前 一次 請求 結束 到下 一次 請求 開始 之前 這段 時間, 它是 處於 “斷開” 狀態 的, 因此 稱HTTP協議 是 “無連線” 協議。

Web 伺服器 除了 跟 瀏覽器 之間 不會 保持 持久 性的 連線 之外, 它 也不 會 儲存 瀏覽器 的狀態。 也就是說, 同一 瀏覽器 先後 兩次 請求 同一個 Web 伺服器, 後者 不會 保留 第一次 請求 處理 的 結果 到 第二次 請求 階段; 如果 第二次 請求 需要 使用 第一次 請求 處理 的 結果, 那麼 瀏覽器 必須 自己 將 第一次 的 處理 結果 回 傳到 伺服器 端。

第 11 章 規繩矩墨:模式與原則

11.1 軟體的設計模式> 位置 4308

程式 的 執行 意味著 模組 與 模組 之間、 物件 與 物件 之間 不停 地 有數 據 交換。觀察者模式要 強調 的 是, 當 一個 目標 本身 的 狀態 發生 改變( 或者 滿足 某一 條件) 時, 它 會 主動 發出通知, 通知 對 該 變化 感興趣 的 其他 物件。 將 通知者 稱為Subject( 主體), 將被 通知者 稱為Observer( 觀察者),

11.1 軟體的設計模式 > 位置 4358

“觀察者 模式” 是 所有 框架 使用 得 最 頻繁 的 設計 模式 之一。 原因 很 簡單,“ 觀察者 模式” 分隔 開了 框架 程式碼 和 框架 使用者 編寫 的 程式碼。 它是 “好萊塢原則”( Don\’ t call us, we will call you) 的 具體 實現 手段, 而 “好萊塢 原則” 是 所有 框架 都要 嚴格遵守 的。

11.1 軟體的設計模式 > 位置 4361

在 Windows Forms 框架 中, 可以說 “觀察者 模式” 無處不在。 Windows Forms 框架 中的 “ 觀察者 模式” 不是 通過 “ 介面 – 具體” 這種 方式 去 實現 的, 而是 更多 地 通過 使用. NET 中的 “委託 – 事件” 去 實現。 這 在 第 8 章 講 Winform 程式 結構 時 已經 有所 說明, 比如 控 件 處理 Windows 訊息 時, 最終 是以 “事件” 的 形式 通知 事件 註冊 者, 那麼 這裡 的事件註冊者就是 觀察者 模式 中的 “觀察者”,控制元件就是 觀察者 模式 中的 “主體”。

可以 認為, 事件 的 釋出者 等於 觀察者 模式 中的 “主體”( Subject), 而 事件 的 註冊 者 等於 觀察者 模式 中的 “ 觀察者”,

11.1 軟體的設計模式 > 位置 4397

根據 各種 設計 模式 的 作用, 將 常見 的 23 種設計模式分為 3 大類, 見表 11- 1。

11.5 本章思考> 位置 4605

五大原則及 英文 全稱 分別 如下。

① 單一 職責 原則( Single Responsibility Principle)。

② 開閉 原則( Open Closed Principle)。

③ 裡 氏 替換 原則( Liskov Substitution Principle)。

④ 介面 隔離 原則( Interface Segregation Principle)。

⑤ 依賴 倒置 原則( Dependency Inversion Principle)。

第 12 章 難免的尷尬:程式碼依賴

12.1 從面向物件開始> 位置 4738

類繼承強調 “我是( Is- A)” 的 關係, 派生 類 “ 是” 基 類( 注意 這裡 的 “ 是” 代表 派生 類 具備 基 類 的 特性), 而介面繼承強調 “我 能做( Can- Do)” 的 關係, 實現 了 介面 的 型別 具有 介面 中 規定 的 行為 能力( 因此 介面 在 命名 時 均以 “ able” 作為 字尾)。

12.1 從面向物件開始 > 位置 4743

在使 用繼承時, 應 遵循 以下準則。

(1) 嚴格遵守 “裡 氏 替換 原則”, 即 基 類 出現 的 地方, 派生 類 一定 可以 出現。 因此, 不要 盲目 地 去使 用 繼承, 如果 兩個 類 沒有 衍生 的 關係, 就不 應該 有 繼承 關係。

(2) 由於 派生 類 會 繼承 基 類 的 全部 內容, 所以 要 嚴格控制 好 型別 的 繼承 層次, 不然 派生 類 的 體積 會 越來越大。 另外, 繼承 是 增加 耦合 的 最重要 因素, 基 類 的 修改 必然會 影響 到 派生 類。

(3) 繼承 強調 型別 之 間的 通 性, 而非 特性。 因此 一般 將 型別 都 具有 的 部分 提取 出來, 形成 一個 基 類( 抽象 類) 或者 介面。

12.2 不可避免的程式碼依賴> 位置 4815

為了 衡量 物件 之間 依賴 程度 的 高低, 引進 了 “耦合” 這一 概念。 耦合 度 越高, 說明 物件 之間 的 依賴 程度 越高。 為了 衡量 物件 獨立 性的 高低, 引進 了 “內聚” 這一 概念。 內聚性 越高, 說明 物件 與外 界 互動 越少, 獨立性 越 強。 很 明顯, 耦合 與 內 聚 是 兩個 相互 對立 又 密切相關 的 概念。

12.3 降低程式碼依賴> 位置 4936

除了 上面 說到 的 將相 同 部分 提取 出來 放到 一個 介面 中, 有時候 還需 要將 相同 部分 提取 出來, 生成 一個 抽象化 的 基 類, 如 抽象類。介面 強調 相同 的 行為, 而 抽象 類 一般 強調 相同 的 屬性, 並且 要使 用在 有 族群 層次 的 型別 設計 中。

12.3 降低程式碼依賴 > 位置 4987

通過 屬性 產生 的 依賴 關係(屬性注入) 比較 靈活, 它的 有效期 一般 介於 “構造 注入” 和 “ 方法 注入” 之間。

在 很多 場合, 3 種依賴注入的 方式 可以 組合 使用, 即 可以 先 通過 “構造 注入” 讓 依賴 者 與 被 依賴 者 產生 依賴 關係, 後期 再 使用 “ 屬性 注入” 的 方式 更改 它們 之間 的 依賴 關係。“ 需要 注意 的 是, 依賴 注入” 是以 “ 依賴 倒置”” 為 前提 的。

12.4 框架的 “程式碼依賴”> 位置 4998

注:“ 控制轉換、 依賴倒置 以及 依賴注入 是 3 個 不同 性質 的 概念。“控制轉換” 強調 程式控制 權 的 轉移, 注重 軟體 執行 流程;“依賴倒置” 是一 種 降低 程式碼 依賴 程度 的 理論 指導思想, 它 注重 軟體 結構;“依賴注入” 是對 象之 間 產生 依賴 關係 的 一種 具體 實現 方式, 它 注重 程式設計 實現。

“控制 轉換” 又稱 “ 好萊塢 原則”, 它 建議 框架 與 開發者 編寫 程式碼 之間 的 關係 是 Don\’ t call us, we will call you, 即 整個 程式 的 主動權 在 框架 手中。

12.6 本章思考> 位置 5022

“依賴 倒置 原則” 中的 “倒置” 二字 作 何 解釋?

A: 正常 邏輯思維 中, 高層 模組 依賴 底層 模組 是 天經地義、 理所當然 的, 而 “依賴 倒置 原則” 建議 我們 所有 的 高層 模組 不應該 直接 依賴於 底層 模組, 而 都 應該 依賴於 一個 抽象。 這裡 的 “ 倒置” 二字 並不是 “ 反過來” 的 意思( 即 底層 模組 反過來 依賴於 高層 模組), 它 只是 說明 正常 邏輯思維 中的 依賴 順序 發生了 變化, 把 所有 違背 了 正常 思維 的 東西 都 稱之為 “ 倒置”。

原創文章,轉載請註明:轉載自獨立觀察員・部落格

本文連結地址:[讀書筆記] 《修煉之道:.NET 開發要點精講》[http://dlgcy.com/dotnet-program-key-point/]

微信公眾號