我想控制一堆类的创建,这些类都共享一个公共接口,并且在构造中都需要一些逻辑。另外,我不希望除了类工厂之外的任何其他代码能够从这些类创建对象。
我的主要障碍是:
(1)对于能够创建类实例的泛型方法,我需要new()约束,这意味着我必须在类上有一个公共构造函数,这意味着它们可以公开创建。
(2)另一种方法是让类本身具有一个返回类实例的静态方法。但我无法从我的泛型类中调用它,因为我需要处理接口/类型,并且不能通过接口进行静态处理。
这是我目前得到的东西,但是它使用了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
}
}
任何帮助都非常感谢:)
答案 0 :(得分:3)
看起来您正在尝试推送自己的服务定位器。
您是否考虑采用依赖注入(DI)方法?正如are reasons why you may want to avoid a service locator.
我强烈建议您查看一些可以执行您尝试构建的功能的流行IOC容器。回顾过去,我很高兴我选择了DI over service locator。
- Ninject
- Autofac
- Unity
答案 1 :(得分:2)
你可以做些什么,但它真的很难看......
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()
约束的泛型方法中调用它。
答案 2 :(得分:1)
你可以做的是做一个(隐藏的)约定,实现你的接口的所有对象也有一个工厂可以调用的特定构造函数。这就是ISerializable的工作原理。缺点是编译器不检查构造函数的存在。在您的工厂中,通过反射找到构造函数并使用正确的参数调用。构造函数可以受到保护。
// Get constr with string arg
var constr = theType.GetConstructor(new[]{typeof(String)});
T result = (T)constr.Invoke(new[]{"argString"});
答案 3 :(得分:1)
这可能对您有用 - 使用反射而不是new()约束。另请注意,构造函数是私有的,您需要向派生类添加一个方法,该类返回自身的实例(静态):
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);
}
}
答案 4 :(得分:1)
考虑一种反思方法。
只需将类的构造函数标记为私有,这样就不能以常规方式实例化这些构造函数,并且工厂将使用反射调用此私有类构造函数。
反射对性能有影响,但调用此构造函数不是一个大的反射操作。
查看此MSDN文章,以了解有关如何使用反射调用私有构造函数的更多信息:
但可以使用以下代码片段进行总结:
typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Private, Type.EmptyTypes, Type.DefaultBinder, null).Invoke(null);
<强>更新强>
顺便说一下,我认为影响性能和增加代码复杂性的方式是因为你不希望开发人员仅仅通过使用这样的工厂方法来实例化这样的类并不是一个强有力的理由。
有时一个好的开发人员guildelines比任何代码约束都要好。我这样说,因为,在你的情况下,我将使用T通用参数和相同的通用约束实现该工厂方法,如果我的文档文件说“如果你想要一个T类型的实例,你需要使用这个工厂”有些人决定不遵守这条规则,这不是我的责任,任何麻烦都会以“手册说你需要使用工厂”来回答。
良好的习惯比处理人类决策的防御性代码要好。
答案 5 :(得分:0)
定义接口IFactory&lt; out T&gt;使用“创建”方法,然后对于要创建的每个类TT,定义实现IFactory&lt; TT&gt;的静态对象。然后你可以绕过IFactory&lt; TT&gt;您想要创建TT的任何地方的对象,或者您可以创建静态类FactoryHolder&lt; T&gt;然后您可以注册相应的工厂。请注意,泛型类对于泛型类型的每个组合都有一组单独的静态变量,这可以提供一种非常方便且类型安全的方法来存储类型和工厂实例之间的静态映射。
一个小小的警告是,必须为每个想要找到的确切类型注册工厂。可以将派生类型工厂注册为基类型的生产者(例如,FactoryHolder&lt; Car&gt; .GetFactory()将返回IFactory&lt; FordFocus2Door&gt;),但除非有人明确注册给定确切类型的工厂T,FactoryHolder&lt; T&gt;会空洞的。例如,IFactory&lt; FordFocus2Door&gt;或IFactory&lt; FordFocus4Door&gt;可以用作IFactory&lt; FordFocus&gt;,但除非其中一个注册为IFactory&lt; FordFocus&gt ;,否则FactoryHolder&lt; FordFocus&gt; .GetFactory()都不会返回。