具有类型参数的通用工厂

时间:2013-01-11 02:40:33

标签: c#

我有以下情况。

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

Strategy1,Strategy2等都来自一个常见的StrategyBase类。但是,每个策略都有一个不同的Validation机制,它是Factory类的类型参数。但是,StrategyValidators不是任何常见类型,并且具有不同的接口。

因此,在下面的代码中,我无法在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'。

4 个答案:

答案 0 :(得分:2)

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

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);
    }
}

答案 1 :(得分:1)

我认为在这种情况下的答案是,取决于您希望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作为通用类型进行调用,或Product2S1作为通用类型进行调用,那么为什么首先使用泛型?您可以轻松地将产品与其相关策略结合起来,并且还可以显着简化代码。

如果我错过了某些内容(或整个问题),请发表评论,我会尝试调整我的答案。

编辑:既然您已经重新定义了示例并使用了S1S2作为接口,我就能明白您的意思了。一种方法是为Factory.Create定义多个泛型类型和约束。例如:

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

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

答案 2 :(得分:0)

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

public static StrategyBase CreateStrategy<StrategyValidator>(String Type)

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

要回答你的问题,你无法避免条件检查。

为了简化代码,可以将不同的组合(“Type1”,“Type2”等)移动到dictionary或者使用Dependency 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>

答案 3 :(得分:0)

您是否考虑过重新创建&lt;&gt;功能?我现在没有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 { }

这可能有助于减轻您的一些麻烦。