带类型参数的通用工厂

本文关键字:工厂 类型参数 | 更新日期: 2023-09-27 17:51:22

我有以下情况

我的Factory类需要根据CreateStrategy函数的输入字符串参数创建适当的Strategy对象。

Strategy1、Strategy2等都是从一个通用的StrategyBase类派生出来的。然而,每种策略都有不同的验证机制,即Factory类的类型参数。然而,strategyvalidator不是任何通用类型,并且具有不同的接口。

因此,在下面的代码中,我无法在StrategyValidator类型上指定任何公共约束。

我是c#新手,因此不确定是否存在任何机制来克服这个设计问题。请建议

public class Factory
{
    //Create the appropriate Concrete Implementation class based on the type
    public static StrategyBase CreateStrategy<StrategyValidator>(String Type)
    {
        StrategyBase EnumImp = null;
        // WMI based implementation
        if (Type == "Type1")
        {
            s = Strategy1<StrategyValidator>.Instance;
        }
        else if (Type = "Type2")
        {
            s = Strategy2<StrategyValidator>.Instance;
        }
        return s;
    }
    private StrategyBase s;
}

这是预期的用法

Factory f = new Factory(); 
f.CreateStrategy<WMIValidator>("WMI");
f.CreateStrategy<ABCDValidator>("ABCD");

其中WMIValidatorABCDValidator是不相关的类型,但CreateStrategy函数创建的实际类在层次结构中是相关的,例如具有共同的基础StrategyBase

下面是一个示例代码来说明

问题
namespace TestCSharp
{
    public interface IStrategy
    {
    };
    public interface S1 : IStrategy
    {
        void f1();
        void f2();
    };
    public class S1Concrete : S1
    {
        public void f1() { }
        public void f2() { }
    }
    public interface S2 : IStrategy
    {
        void f3();
        void f4();
    };
    public class S2Concrete : S2
    {
        public void f3() { }
        public void f4() { }
    };
    public interface ProductBase
    {
    };
    class Product1<T> : ProductBase where T : S1
    {
    };
    class Product2<T> : ProductBase where T : S2
    {
    };
    public class Factory
    {
        public ProductBase Create<T>(String Type)
        {
            if (Type == "P1")
                return new Product1<T>();
            else if (Type == "P2")
                return new Product2<T>();
        }
    };
    class Program
    {
        static void Main(string[] args)
        {
            Factory f = new Factory();
            ProductBase s = f.Create<S1Concrete>("Type1");
        }
    }
}
我得到的错误是

类型'T'不能用作泛型类型中的类型参数'T'或方法'TestCSharp.Product1'。没有拳击转换或从'T'到'TestCSharp.S1'的类型参数转换。

带类型参数的通用工厂

我真的不完全理解你的场景,但据我所知,你正在使用的工厂模式必须使用反射来实例化产品。这有点难看,因为它没有给消费者任何提示,告诉他们给定的产品名称可以使用哪些策略类型。

public class Factory
{
    public ProductBase Create<T>(string name)
    {
        Type type;
        switch (name)
        {
            case "P1":
                type = typeof (Product1<>);
                break;
            case "P2":
                type = typeof (Product2<>);
                break;
            case "P3":
                type = typeof (Product3<>);
                break;
            default:
                return null;
        }
        type = type.MakeGenericType(typeof (T));
        return (ProductBase) Activator.CreateInstance(type);
    }
}

我认为在这种情况下的答案是,它取决于你想要ProductStrategy做什么。你似乎想把你的逻辑分成两部分。然后,您想通过使用泛型来再次对其进行耦合,但正如您所注意到的,它不起作用。

考虑一个类似于上面的场景——但是每个实现IStrategy的类都有一个而不是两个有副作用的方法(即打印字符串)。当允许的类型范围有一些共同点时,使用泛型。在我刚才提到的例子中,两者都有一个方法返回void并且不接受参数;所以我们可以给IStrategy添加一个方法,例如:

public interface IStrategy
{
    void ExecuteLogic();
};
public class S1 : IStrategy
{
    public void ExecuteLogic()
    {
        OneMethod();
    }
    void OneMethod()
    {
        Console.WriteLine("Hello");
    }
};
public class S2 : IStrategy
{
    public void ExecuteLogic()
    {
        TotallyDifferentMethod();
    }
    void TotallyDifferentMethod()
    {
        Console.WriteLine("World");
    }
};

现在,您还说Strategy1Strategy2具有不同的验证机制。然而,在我看来,你在相同的方法和上下文中使用它们(因此是相同的参数和变量),所以一定有一些东西使它们相似。尽管如此,已经以我们需要的方式定义了IStrategy,我们可以将其用作Create<T>的约束。因此,Factory变成:

public class Factory
{
    public ProductBase Create<T>(String Type) where T : IStrategy
    {
        if (Type == "P1")
            return new Product1<T>();
        else if (Type == "P2")
            return new Product2<T>();
        return null;
    }
};

但仍有一种情况。如果您不希望Product1使用S2作为泛型类型来调用,或者Product2使用S1作为泛型,那么为什么首先使用泛型呢?您可以轻松地将产品与其相关策略结合起来,并且还可以显着简化代码。

如果我遗漏了什么(或整个问题),请留下评论,我会尽量调整我的答案。

EDIT:既然现在您已经重新定义了示例并使用S1S2作为接口,我可以理解您的意思。一种方法是为Factory.Create定义多个泛型类型和约束。例子:

public ProductBase Create<T1, T2>(String Type) where T1 : S1 where T2 : S2

这是不可能的,否则,正如你正确地说,因为没有S1S2的共同祖先,可以被你的Product类接受。

您可以将函数更改为将StrategyValidator作为类型。

public static StrategyBase CreateStrategy<StrategyValidator>(String Type)

public static StrategyBase CreateStrategy<T>(String Type) where T:StrategyValidator

回答你的问题,你不能避免条件检查。

为了简化代码,可以将不同的组合("Type1","Type2"等)移动到dictionaryDependency Injection的配置中,然后可以进行反射。

的例子。

    if (!dict.ContainsKey(key)) 
       throw New InvalidArgumentException();
    StrategyBase EnumImp = null;
    var instance = dict[key].MakeGenericType(typeOf(type)).GetProperty("Instance",  BindingFlags.Static | BindingFlags.Public ));   //dict is Dictionary<string, Type>

您考虑过重载Create<>函数吗?我现在没有VisualStudio,但是下面的代码会为您的情况工作吗?

namespace ... {
    // ... other code here...
    public class Factory {
        public Product1<T> Create<T>() where T : S1 {
            return new Product1<T>();
        }
        public Product2<T> Create<T>() where T : S2 {
            return new Product2<T>();
        }
    }
    class Program {
        static void Main(string[] args) {
            Factory f = new Factory();
            ProductBase s = f.Create<S1Concrete>();
        }
    }
}

另外,您可能希望将类型约束移到较低的级别。考虑编写一个抽象基类ProductBase(继承自IProductBase接口?),如下所示:

class ProductBase<T> : IProductBase where T : IStrategy { }

这可能有助于缓解你的一些头痛