當前位置:
首頁 > 最新 > C#的「友元」類實現 Builder 模式

C#的「友元」類實現 Builder 模式

友元是 C++ 中的概念,包含友元函數和友元類。被某個類聲明為友元的函數或類可以訪問這個類的私有成員。友元的正確使用能提高程序的運行效率,但同時也破壞了類的封裝性和數據的隱藏性,導致程序可維護性變差。因此,除了 C++ 外很難再看到友元語法特性。

提出問題

但是友元並非一無是處,在某些時候確實有這樣的需求。舉例來說,現在我們需要定義一個 類,為了避免 對象在使用過程中屬性被修改,需要將它設計成 Immutable 的。到目前為止還沒有什麼問題,但接下來問題來了——由於用戶信息較多,其屬性設計有十數個,為了 Immutable 全部通過構造方法的參數來設置屬性是件讓人悲傷的事情。

那麼一般我們會想到這樣幾個方案:

方案簡述方案一,使用參數對象

這是 JavaScript 中常用的做法,使用參數對象,在構造 的時候,通過參數對象提供所有設置好的屬性,再由 的構造方法從參數里把這些屬性拷貝出來設置給只讀成員。那麼實現可能像這樣:

為了簡化代碼,只定義了 、 和 三個屬性。下同。

public sealed class User { public ulong Id { get; } public string Username { get; } public string Name { get; } public User(Properties props) { Id = props.Id; Username = props.Username; Name = props.Name; } public sealed class Properties { public ulong Id; public string Username; public string Name; } }

一個屬性就需要重複寫三遍,如果代碼是按行付費,這個定義會非常賺!

一次性設置

這種做法是自定義屬性的 函數,或者定義一個 方法,判斷如果值為 則可以設置,一但設置將不能再設置(理論上來說應該拋異常,但這裡示例簡化為無作為)。

下面的示例通過 和 演示了一次性設置的兩種方法

public class User { public ulong Id { get; } public string Username { get; private set; } public void SetUsername(string username) { if (Username == null) { Username = username; } } public string Name { get { return name; } set { if (name == null) { name = value; } } } private string name; public User(ulong id) { Id = id; } }

這種方法中的 並非 Immutalbe,只是近似,因為它的屬性不能從「有」到「無」,卻可以從「無」到「有」。

而且,我發現這個方法比上一個方法更賺錢。

Builder

Builder 模式嘛,就是為了解決初始化複雜對象問題的。

public class User { public ulong Id { get; } public string Username { get; internal set; } public string Name { get; internal set; } public User(ulong id) { Id = id; } } public class UserBuilder { private readonly User user; public UserBuilder(ulong id) { user = new User(id); } public UserBuilder SetUsername(string username) { user.Username = username; } public UserBuilder SetName(string name) { user.Name = name; } public User Build() { // 驗證 user 的屬性 // 或者對某個屬性進行一些後期加工(比如計算,格式化處理……) return user; } }

為了避免外部訪問, 的各屬性(除 )的 都聲明為 的,因為只有這樣 才能調用它們的 。

顯然,採用這種方式在同一個 Assembly 中,比如 App Assembly 中, 的屬性仍然未能得到保護。

內部類實現「友元」特性

基於上面 Builder 模式的解決方案,很容易想到,如果把 定義為 的內部類(嵌套類),那它直接就可以訪問 的私有成員,其形式如下

public class User { // .... public class UserBuilder { // .... } }

這其實和 C++ 的友元類語法還是有相似之處——就是都需要在 內部去聲明,C++ 是聲明友元,C# 則在聲明的同時進行了定義

// C++ 代碼 class UserBuilder; class User { friend class UserBuilder; } class UserBuilder { // .... }內部類實現 Builder 模式

結構上沒有問題了。再利用 C# 的分部類( ) 特性將 類和 類分別寫在兩個源文件中,然後簡化一下 的名稱,簡化為 ,因為它定義在 的內部,語義已經非常明確了。

// User.cs public sealed partial class User { ulong Id { get; } public string Username { get; private set; } public string Name { get; private set; } public User(ulong id) { Id = id; } public static Builder CreateBuilder(ulong id) { return new Builder(id); } }// User.Builder.cs partial sealed class User { public class Builder { private readonly User user; public Builder(ulong id) { user = new User(id); } public Builder SetUsername(string username) { user.Username = username; return this; } public Builder SetName(string name) { user.Name = name; return this; } public User Build() { // 驗證和後期加工 return user; } } }

上面這段代碼就達到了 Immutable 的目的,同時代碼還很優雅,通過分部類拆分源文件,代碼結構也很清晰。不過還有一點小小的瑕疵…… 可以重複調用,而且在調用之後仍然可以修改 的屬性。

再嚴謹一點可重複使用的 Builder

如果想把 變成可多次調用,每次調用生成新的 對象,同時生成的 對象不受之後 的 影響,可以在 的時候,產生一個 的複本返回。

另外,由於每個 對象的 應該不同,所以由生成 的時候指定改為 的時候指定:

public partial class User { // .... public static Builder CreateBuilder()) { return new Builder(); } } partial class User { public class Builder { private readonly User user; public Builder() { user = new User(0); } // .... public User Build(ulong id) { var inst = new User(id); inst.Username = user.Username; inst.Name = user.Name; return inst; } } }

其實這裡 內部的 被當作參數對象使用了。

一次性 Builder

一次性 Builder 相對簡單一些,不需要在 的時候去拷貝屬性。

partial class User { public class Builder { private User user; // 這裡 user 不再是 readonly 的 public Builder(ulong id) { user = new User(id); } // .... public User Build() { if (user == null) { throw new InvalidOperationException("Build 只能調用一次") } // 驗證和後期加工 var inst = user; user = null; // 將 user 置 null return inst; } } }

一次性 在 之後將 設置為 ,那麼再調用所有 方法都會拋空指針異常,而再次調用 方法則會拋 異常。

小結

其實這個很普通的 C# 的內部類實現。但它確實可以解答「C# 中沒有友元怎麼辦」這之類的問題。Java 中也可以類似的實現,只不過 Java 沒有分部類,所以代碼都得寫在一個源文件里,這個源文件可能會很長很長……

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 推酷 的精彩文章:

即學即用,網頁首屏頁面7種吸睛的設計方法
vue-cli定製腳手架
Orbit框架解析
傳統服裝市場不景氣,服裝行業如何實現消費升級?
像Excel一樣使用R進行數據分析

TAG:推酷 |

您可能感興趣

C#ConcurrentBag的實現原理
SpringCloud如何實現Eureka集群、HA機制?
SpringCloud實現Eureka集群配置
SAP Cloud for Customer Extensibility的設計與實現
Wormhole:一種基於Bitcoin Cash的智能合約實現方案
NET Core微服務之基於Ocelot+IdentityServer實現統一驗證與授權
CSS各種姿勢實現Sticky Footer
用WebRTC在Firefox上實現YouTube直播
Google的「ARCore 1.2」實現對iPhone系統設備的兼容
用TensorFlow Estimator實現文本分類
使用Jira software+Structure實現大規模跨團隊項目管理
基於Tensorflow實現DeepFM
通過 Docker 實現在 Linux 容器中運行 Microsoft SQL Server 資料庫
用Pytorch 實現的 Capsule Network
網友客制的Virgil Abloh x NIKE 「Chicago」配色真的實現了?Air Max 98新配色蓄勢待發!
Android如何實現帶有粘性頭部的ScrollView
Occipital推出MR創作工具Bridge Engine,實現密實3D映射
使用Tensorflow Object Detection API實現對象檢測
從 Encoder到Decoder 實現 Seq2Seq 模型
用Scratch+IBM Watson實現機器學習