只有你能 new 出來!.NET 隱藏建構函式的 n 種方法(Builder Pattern / 構造器模式)
如果你給類寫了一個公有建構函式,那麼這個類就能被其他開發者 new 出來。如果你不想讓他們 new 出來,把建構函式 private
就好了呀。
然而還有更多奇怪的方式來隱藏你類的構造方法。
為什麼要隱藏建構函式?
有些型別,只有元件的設計者才知道如何正確建立其型別的例項,多數開發者都無法正確將其創建出來。典型的如 string
:絕大多數開發者都不能正確創建出 string
的例項,但通過寫一個字串由編譯器去建立,或者使用 StringBuilder
來構造則不容易出錯。
再或者,我們只希望開發者使用到某個抽象的例項,而不是具體的型別,那麼這個時候開發者也需要有方法能夠拿到抽象介面的例項。我們可能會使用工廠或者某些其他的方法讓開發者在不知道具體型別的時候獲取到抽象型別的例項。
這正是構造器模式的典型應用場景。在維基百科中對它適用性的描述為:
在以下情況使用生成器模式:
- 當建立複雜物件的演算法應該獨立於該物件的組成部分以及它們的裝配方式時;
- 當構造過程必須允許被構造的物件有不同的表示時。
詳見: ofollow,noindex" target="_blank">生成器模式 - 維基百科,自由的百科全書
接下來,我們使用一些奇怪的方式來建立物件的例項,完完全全把建構函式隱藏起來。
隱式轉換和顯式轉換
典型的像 long a = 1;
, bool? b = true
這都是語法級別的隱式轉換。這真的只是語法級別的隱式轉換,實際上這兩個都是編譯器原生支援,編譯時即已轉換為真實的型別了。
[System.Runtime.Versioning.NonVersionable] public static implicit operator Nullable<T>(T value) { return new Nullable<T>(value); } [System.Runtime.Versioning.NonVersionable] public static explicit operator T(Nullable<T> value) { return value.Value; }
於是我們可以考慮寫一個神奇的類,其建立是通過隱式轉換來實現的:
Fantastic fantastic = "walterlv"; Console.WriteLine(fantastic);
以上程式碼的輸出是 walterlv is fantastic
。
namespace Walterlv.Demo.Patterns { public class Fantastic { private readonly string _value; private Fantastic(string value) => _value = value; public static implicit operator Fantastic(string value) => new Fantastic(value); public override string ToString() => $"{_value ?? "null"} is fantastic."; } }
而使用顯式轉換,我們還可以寫出更奇怪的程式碼來。比如下面這個,我們的例項是通過強制轉換一個 null
來實現的:
Fantastic fantastic = (IFantastic) null; Console.WriteLine(fantastic);
以上程式碼的輸出是 ` is fantastic` 字串。呃……前面有個空格。
namespace Walterlv.Demo.Patterns { public struct Fantastic { private readonly IFantastic _value; private Fantastic(IFantastic value) => _value = value; public static implicit operator Fantastic(IFantastic value) => new Fantastic(value); public override string ToString() => $"{_value} is fantastic."; } public class IFantastic { } }
那個 IFantastic
必須得是一個類,而不能是介面,因為隱式轉換不能從介面轉,也不能轉到介面。
▲ 不能定義從介面進行的隱式轉換
運算子過載
使用運算子過載,也可以讓型別例項的構造隱藏起來。比如下面的 Scope
型別,從字串建立,然後通過與不同的字串進行位或運算來得到其他的 Scope
的例項。
Scope scope = "A"; var full = scope | "B" | "C"; Console.WriteLine(full);
當然這段程式碼也少不了隱式轉換的作用。
以上 Scope
型別的實現在 github 上開源,其表示 OAuth 2.0 中的 Scope
。
關於運算子過載的更多內容,可以參考我的另外兩篇文章:
- C# 中那些可以被過載的操作符,以及使用它們的那些喪心病狂的語法糖 - walterlv
- C# 空合併操作符(??)不可過載?其實有黑科技可以間接過載! - walterlv
本文會經常更新,請閱讀原文: https://walterlv.com/post/hide-your-constructor.html ,以避免陳舊錯誤知識的誤導,同時有更好的閱讀體驗。
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名 呂毅 (包含連結:https://walterlv.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請 與我聯絡 ([email protected]) 。