[英]c# generics class factory question
我想控制一堆類的創建,這些類都共享一個公共接口,並且在構造中都需要一些邏輯。 此外,除了 class 工廠之外,我不希望任何其他代碼能夠從這些類創建對象。
我的主要絆腳石是:
(1) 為了使泛型方法能夠創建類的實例,我需要 new() 約束,這意味着我必須在類上有一個公共構造函數,這意味着它們可以公開創建。
(2) 另一種方法是類本身具有 static 方法,該方法返回 class 的實例。 但是我不能從我的通用 class 中調用它,因為我需要處理接口/類型,並且您不能通過接口獲得靜態。
這是我目前擁有的那種東西,但它使用了 new() 約束,它允許我的類被公開創建:
internal static class MyClassFactory
{
internal static T Create<T>(string args) where T : IMyType, new()
{
IMyType newThing = new T();
newThing.Initialise(string args);
return (T)newThing;
}
}
public interface IMyType
{
void Initialise(string args);
}
public class ThingA: IMyType
{
public void Initialise(string args)
{
// do something with args
}
}
非常感謝任何幫助:)
看起來您正在嘗試推出自己的服務定位器。
您是否考慮過采用依賴注入 (DI) 方法? 因為您可能希望避免使用服務定位器是有原因的。
我強烈建議您查看一些流行的 IOC 容器,它們可以執行您嘗試構建的功能。 回想起來,我很高興我選擇了 DI 而不是服務定位器。
-忍者
- Autofac
-團結
你可以做一些事情,但它真的很難看......
public class ThingA: IMyType
{
[Obsolete("This constructor must not be called directly", true)]
public ThingA()
{
}
public void Initialise(string args)
{
// do something with args
}
}
如果您嘗試顯式調用構造函數,這將導致編譯錯誤,但不會阻止在具有new()
約束的泛型方法中調用它。
您可以做的是制定一個(隱藏的)約定,即所有實現您的接口的對象也具有工廠可以調用的特定構造函數。 這就是 ISerializable 的工作原理。 缺點是編譯器不檢查構造函數的存在。 在您的工廠中,通過反射找到構造函數並使用正確的 arguments 調用。 構造函數可以被保護。
// Get constr with string arg
var constr = theType.GetConstructor(new[]{typeof(String)});
T result = (T)constr.Invoke(new[]{"argString"});
這可能對你有用——使用反射而不是 new() 約束。 另請注意,構造函數是私有的,您需要向派生的 class 添加一個方法,該方法返回自身的實例(靜態):
internal static class MyClassFactory
{
internal static T Create<T>(string args) where T : IMyType
{
IMyType newThing =
(T)typeof(T).GetMethod("GetInstance").Invoke(default(object), null);
newThing.Initialise(args);
return (T)newThing;
}
}
public interface IMyType
{
void Initialise(string args);
}
public class ThingA: IMyType
{
private ThingA() { }
public static IMyType GetInstance()
{
return new ThingA(); // control creation logic here
}
public void Initialise(string args)
{
// do something with args
}
}
編輯
只是為了完善這一點,正如所指出的,您可以通過反射訪問私有構造函數,如下所示:
internal class MyClassFactory
{
internal static T Create<T>(string args) where T : IMyType
{
IMyType newThing = (T)typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, Type.DefaultBinder, Type.EmptyTypes, null).Invoke(null);
newThing.Initialise(args);
return (T)newThing;
}
}
public interface IMyType
{
void Initialise(string args);
}
public class ThingA : IMyType
{
private ThingA() { }
public void Initialise(string args)
{
Console.WriteLine(args);
}
}
考慮一種反思方法。
只需將類的構造函數標記為私有,這樣它們就不能以常規方式實例化,並且您的工廠將使用反射調用此私有 class 構造函數。
反射對性能有影響,但調用這個構造函數並不是一個大的反射操作。
查看此 MSDN 文章以了解有關如何使用反射調用私有構造函數的更多信息:
但可以用這個代碼片段來概括:
typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Private, Type.EmptyTypes, Type.DefaultBinder, null).Invoke(null);
更新
順便說一句,我相信以任何方式影響性能和增加代碼復雜性,因為您不希望開發人員僅通過使用這種工廠方法來實例化 class 並不是一個強有力的理由。
有時,一個好的開發者准則比任何代碼約束都要好。 我這么說是因為,在你的情況下,我會使用 T 泛型參數和相同的泛型約束來實現該工廠方法,如果我的文檔文件說“如果你想要一個 T 類型的實例,你需要使用這個工廠”有些人決定不遵守這條規則,這不是我的責任,任何麻煩都會用“手冊說你需要使用工廠”來回答。
良好的習慣比處理人類決策的額外防御性代碼更好。
使用“Create”方法定義接口 IFactory<out T>,然后為每個要創建的 class TT 定義一個實現 IFactory<TT> 的 static object。 然后,您可以在要創建 TT 的任何地方傳遞 IFactory<TT> object,或者您可以創建 static class 然后向您注冊適當的 FactoryHolder<T>。 請注意,泛型類對於泛型類型的每種組合都有一組單獨的 static 變量,這可以提供一種非常方便且類型安全的方法來存儲類型和工廠實例之間的 static 映射。
一個小小的警告是,必須為想要找到的每一種確切類型注冊一個工廠。 可以將派生類型工廠注冊為基類型的生產者(例如 FactoryHolder<Car>.GetFactory() 將返回 IFactory<FordFocus2Door>),但除非明確注冊給定確切類型的工廠T, FactoryHolder<T> 將出現空。 例如,IFactory<FordFocus2Door> 或 IFactory<FordFocus4Door> 都可以用作 IFactory<FordFocus>,但除非其中一個注冊為 IFactory<FordFocus>,否則 FactoryHolder<FordFocus>.GetFactory 都不會返回()。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.