泛型和工厂

4

我对泛型还不熟悉,一直在尝试弄清楚如何从工厂返回一个基于泛型类的实例。请参考下面的示例代码。问题在工厂类中被突出显示:

public abstract class MyGenericBaseClass<T>
{
    public string Foo()
    {...}
}

public sealed class MyDerivedIntClass : MyGenericBaseClass<int>
{

}

public sealed class MyDerivedStringClass : MyGenericBaseClass<string>
{

}

public static class MyClassFactory
{
    public static MyGenericBaseClass<T> CreateMyClass<T>()
    {
        // **********************************************
        if (typeof(T) == typeof(int))
        {
            return new MyDerivedIntClass();
        }

        if (typeof(T) == typeof(string))
        {
            return new MyDerivedStringClass();
        }
        // **********************************************
    }
}

我该怎么解决这个问题?非常感谢您的帮助。Ohgee

1
我不明白为什么你要使用一个通用工厂,如果你实际使用的类型根本不是通用的。我认为你应该重新考虑你的设计。 - Joren
7个回答

7
在我的大部分通用类中,我会加上非泛型接口。实际上,我会这样实现:
public interface INonGenericInterface
{
    string Foo();
}

public abstract class MyGenericBaseClass<T> : INonGenericInterface
{
    public string Foo()
    {...}
}

public static class MyClassFactory
{
    public static INonGenericInterface CreateMyDerivedIntClass()
    {
        return new MyDerivedIntClass();
    }

    public static INonGenericInterface CreateMyDerivedStringClass()
    {
        return new MyDerivedStringClass();
    }
}

这样做可以清楚地说明哪些类型可以被创建,同时将调用者与具体类型解耦。
当然,在这种情况下,您不一定需要非泛型接口,但实际上,您很可能需要它。

你比我快了 :p 但这是正确的方式! - Stormenet
public INonGenericInterface CreateOnRequirement<T>() where T : INonGenericInterface, new() { return new T(); }有什么问题? - Royi Namir
在这个问题中,工厂的用户不知道返回的类型(例如MyDerivedIntClass),它只知道通用参数T(例如int)。因此,new T()是无法工作的。如果它知道MyDerivedIntClass,它可以直接调用构造函数本身,而不需要工厂。 - Stefan Steinegger

3
首先,不清楚为什么要从泛型类派生出类,而不直接使用泛型类。同样不清楚为什么需要工厂而不是直接实例化所需的类。这些模式很重要且有用,通常是解决某些问题的唯一方法,但它们并不是必需品。您应该有充分的理由来实现它们。
尽管如此,我假设您确实有充分的理由,并且为了简洁起见省略了这些理由。
使用工厂模式的重点是让某些代码实例化正确的类,而不需要该代码知道正确的类。请注意,在您的示例中缺少此分离:调用MyClassFactory.CreateMyClass<T> ()的人必须知道正确的类,因为该代码必须将类型作为泛型参数传递,以确定正确的类。如果我足够了解调用CreateMyClass<int> (),那么我足够了解调用new MyDerivedIntClass ()
实现工厂模式的两种主要方法是静态函数和工厂类。
使用这两种方法,您都需要在泛型“下方”拥有一个抽象基类或接口:
public interface IMyInterface
{
    string Foo ();
}

public abstract class MyGenericBaseClass<T> : IMyInterface
{
    // ...
    abstract /* or virtual */ string Foo ();
}

使用静态函数,需要在某处定义一个委托类型(省略类范围):
// note: I'm not sure this is the correct syntax, but I think I'm in the ballpark
delegate IMyInterface MyInterfaceFactory ();

类可以实现它们以返回正确的类型。

public sealed class MyDerivedIntClass : MyGenericBaseClass<int>
{
    // ...
    static IMyInterface CreateObject () { return new MyDerivedIntClass (); }
}

public sealed class MyDerivedStringClass : MyGenericBaseClass<string>
{
    // ...
    static IMyInterface CreateObject () { return new MyDerivedStringClass (); }
}

你将静态函数传递给实例化对象的函数:
// ... somewhere else in the code ...

// create a IMyInterface object using a factory method and do something with it
void Bar (MyInterfaceFactory factory)
{
    IMyInterface mySomething = factory ();
    string foo = mySomething.Foo ();
}

// ... somewhere else in the code ...
void FooBarAnInt ()
{
    Bar (MyDerivedIntClass.CreateObject);
}

第二种方法是使用工厂类:
public interface IMyInterfaceFactory
{
    IMyInterface CreateObject ();
}

public class MyDerivedIntFactory : IMyInterfaceFactory
{
    public IMyInterface CreateObject () { return new MyDerivedIntClass (); }
}

public class MyDerivedStringFactory : IMyInterfaceFactory
{
    public IMyInterface CreateObject () { return new MyDerivedStringClass (); }
}

// ... somewhere else ...

// create a IMyInterface object using a factory class and do something with it
void Bar (IMyInterfaceFactory factory)
{
    IMyInterface mySomething = factory.CreateObject ();
    string foo = mySomething.Foo ();
}

// ... somewhere else in the code ...
void FooBarAnInt ()
{
    Bar (new MyDerivedIntFactory ());
}

请注意,您可以(并且可能应该)将工厂类也设置为单例模式,但这是一个不同的问题。此外,您可以使用抽象基类代替接口;您需要根据需要添加abstract(或virtual)和override
从根本上说,某个人在某个地方以某种方式必须知道正确的对象类型来实例化。工厂对象的目的不是完全抽象化这些知识,而是将创建对象的代码与创建什么类型的对象的知识分离开来。如果您不需要这种分离,则工厂模式并不特别有用。

非常感谢所有的回复,我在这里获得了巨大的教育。采用这种方法的主要原因是:我有一个基类,其中包含一个“ID”属性,其数据类型在运行时确定(字符串、整数或长整型是主要处理的类型)。工厂类/方法将返回一个派生类,该派生类具有适当的数据类型,以用于派生类的ID。这就是为什么我认为我不能采用非泛型基类/接口的路线的原因。 - OhGee

2
我通常为每个工厂使用一个Dictionary<string,Type>,在其中注册工厂可以返回的每种可用实现类型(嗯...有关更多信息,请参见工厂方法中是否适用switch语句?)。您可以根据需要进行调整,并保留所有派生类的Dictionary<Type,Type>。
 ...
 Factories.StaticDictionary.Add(typeof(int),typeof(MyDerivedIntClass));
 Factories.StaticDictionary.Add(typeof(string),typeof(MyDerivedStringClass));
 ...

(或者让你的每个派生类自动将其添加到此字典中)

这样,您的工厂就可以简单地实现:

    public static class MyClassFactory
    {
        public static MyGenericBaseClass<T> CreateMyClass<T>()
        {
          Activator.CreateInstance(
          Factories.StaticDictionary[typeof(T)]);
        }

    }

0

非常感谢所有的回复,我在这里获得了巨大的教育。

采用这种方法的主要原因是:我有一个基类,其中包含一个“ID”属性,其数据类型在运行时确定(处理的主要类型为字符串、整数或长整型)。工厂类/方法应返回一个派生类,该派生类具有适当的数据类型以用于派生类的ID。这就是为什么我认为我不能采用非泛型基类/接口的路线。

大约30分钟前发现了这个想法,它在工厂方法中实现了这一点:

public static class MyClassFactory
{    
    public static MyGenericBaseClass<T> CreateMyClass<T>()    
    {        
        // **********************************************        
        if (typeof(T) == typeof(int))        
        {
            MyGenericBaseClass<int> typedDerived = new MyDerivedIntClass();
            return (MyGenericBaseClass<T>)(object)typedDerived;      
        }        

        if (typeof(T) == typeof(string))        
        {            
            MyGenericBaseClass<string> typedDerived = new MyDerivedStringClass();
            return (MyGenericBaseClass<T>)(object)typedDerived;         
        }        
        // **********************************************    
    }
}

看起来有点hacky,特别是双重转换,但似乎可以工作。你们认为这在可维护性方面怎么样?


0

如果您不想进行类型检查并实例化适当的类,我能想到的另一种方法就是反射。这种方法在有多个类时可以节省一些代码行数,但实现起来会更加复杂。

说实话,我认为您现在的做法是完全有效的工厂实现方式,我真的看不出为什么要改变它。


0

我认为这并不一定是件坏事。在某个时候,您需要声明正在创建的对象的类型。通过隐藏对各种派生类构造函数的调用,工厂让您拥有一个单一位置,其中已知具体类。

您可以以多种方式实现此目标 - 一组if语句、字典、企业级xml配置文件 - 但最终您需要某种将int类型参数映射到MyDerivedIntClass新实例的方法。这可能不太美观,但至少您只需编写一次代码。


0
如果你想要比其他人发布的一些示例更进一步,你应该看看依赖注入框架。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接