【问题标题】:c# generics class factory questionc#泛型类工厂问题
【发布时间】:2011-07-22 08:23:56
【问题描述】:

我想控制一堆类的创建,这些类都共享一个公共接口,并且在构造中都需要一些逻辑。另外,除了类工厂之外,我不希望任何其他代码能够从这些类创建对象。

我的主要绊脚石是:

(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
}
}

非常感谢任何帮助:)

【问题讨论】:

  • 不要做通用工厂。创建一个抽象工厂(使用初始化代码和一个抽象的 DoCreate/CreateUnitialized/...),然后为每个类添加一个工厂。

标签: c# generics static factory


【解决方案1】:

您似乎正在尝试推出自己的服务定位器。

您是否考虑过采用依赖注入 (DI) 方法?在那里are reasons why you may want to avoid a service locator.

我强烈建议您查看一些流行的 IOC 容器,它们可以执行您尝试构建的那种功能。回想起来,我很高兴我选择了 DI 而不是服务定位器。

-Ninject

-Autofac

-Unity

【讨论】:

  • 即使你确实想要一个服务定位器,你仍然可以使用那些 IoC 容器来处理实际的构建,并像工厂/服务定位器一样使用它们,而不是自己滚动。
  • 感谢您的回复。我确实经常使用 Unity,但我认为它在这里没有帮助。这不是我需要特定服务的替代实现的情况。它只是一小组相似的类,所有这些类都被客户端一起使用,我想通过需要执行一些构造逻辑的工厂方法来控制创建。
【解决方案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() 约束的泛型方法中调用它。

【讨论】:

  • 谢谢,这可能是一个后备解决方案......但确实很丑。
【解决方案3】:

考虑一种反射方法。

只需将类的构造函数标记为私有,这样它们就不能以常规方式实例化,您的工厂将使用反射调用这个私有类构造函数。

反射对性能有影响,但调用这个构造函数并不是一个大的反射操作。

查看这篇 MSDN 文章以了解有关如何使用反射调用私有构造函数的更多信息:

不过可以用这段代码sn-p来概括:

typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Private, Type.EmptyTypes, Type.DefaultBinder, null).Invoke(null);

更新

顺便说一句,我相信以任何方式影响性能和增加代码复杂性,因为您不希望开发人员仅通过使用此类工厂方法来实例化一个类,这并不是一个强有力的理由。

有时,一个好的开发者准则比任何代码约束都要好。我这么说是因为,在你的情况下,我会使用 T 泛型参数和相同的泛型约束来实现该工厂方法,如果我的文档文件说“如果你想要一个 T 类型的实例,你需要使用这个工厂”有些人决定不遵守这条规则,这不是我的责任,任何麻烦都会用“手册说你需要使用工厂”来回答。

良好的习惯比处理人类决策的额外防御性代码更好。

【讨论】:

  • +1 这会很好用。没有意识到你可以反映私有构造函数,这很酷。
  • 谢谢。是的,我不喜欢反射为这么小的一组类带来的额外开销和复杂性。所以我认为@ThomasLevesque 建议的文档、约定和 ObsoleteAttribute 的混合可能是正确的平衡。
【解决方案4】:

你可以做的是制定一个(隐藏的)约定,即所有实现你的接口的对象也有一个工厂可以调用的特定构造函数。这就是 ISerializable 的工作原理。缺点是编译器不检查构造函数的存在。在您的工厂中,通过反射找到构造函数并使用正确的参数调用。构造函数可以被保护。

// Get constr with string arg
var constr = theType.GetConstructor(new[]{typeof(String)});

T result = (T)constr.Invoke(new[]{"argString"});

【讨论】:

  • 我试过了,但是如果你尝试将参数传递给这个隐藏的构造函数,就会出现编译错误。您是否建议删除 new() 约束?我试过了,它会按预期生成编译错误。
  • 是的,删除新的约束。通过使用反射找到适当的constructorinfo对象并调用它来创建您的对象
【解决方案5】:

这可能对你有用——使用反射而不是 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);
    }
} 

【讨论】:

    【解决方案6】:

    使用“Create”方法定义接口 IFactory,然后为要创建的每个类 TT,定义实现 IFactory 的静态对象。然后,您可以在要创建 TT 的任何地方传递 IFactory 对象,也可以创建一个静态类 FactoryHolder,然后使用该类注册适当的工厂。请注意,泛型类对​​于泛型类型的每种组合都有一组单独的静态变量,这可以提供一种非常方便且类型安全的方法来存储类型和工厂实例之间的静态映射。

    有一点需要注意的是,必须为想要找到的每一种确切类型注册一个工厂。可以将派生类型工厂注册为基类型的生产者(例如,FactoryHolder.GetFactory() 将返回 IFactory),但除非明确注册给定确切类型的工厂T, FactoryHolder 将出现空。例如,IFactory 或 IFactory 都可以用作 IFactory,但除非其中一个注册为 IFactory,否则 FactoryHolder.GetFactory 都不会返回()。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-30
      相关资源
      最近更新 更多