2

I have the following situation.

My Factory class needs to create appropriate Strategy objects based on the input string argument to the CreateStrategy function.

Strategy1, Strategy2 etc are all derived from a common StrategyBase class. However each strategy has a different Validation mechanism which is the type parameter to the Factory class. However, the StrategyValidators are not of any common type and have different interfaces.

Therefore, in the below code, I am unable to specify any common constraint on the StrategyValidator type.

I am new to C# and hence not sure if there exists any mechanism to get over this design issue. Please suggest

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

Here's the intended usage

Factory f = new Factory(); 

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

where WMIValidator and ABCDValidator are unrelated types, but the actual classes created by CreateStrategy function are related in a hierarchy e.g. having a common base StrategyBase

Here is a sample code to illustrate the issue

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

The error I get is

The type 'T' cannot be used as type parameter 'T' in the generic type or method 'TestCSharp.Product1'. There is no boxing conversion or type parameter conversion from 'T' to 'TestCSharp.S1'.

4

4 回答 4

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);
    }
}
于 2013-01-11T03:52:52.350 回答
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");
    }
};

现在,您也说过Strategy1并且Strategy2有不同的验证机制。但是,在我看来,您在相同的方法和上下文中使用它们(因此也使用相同的参数和变量),所以一定有一些东西使它们相似。尽管如此,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它的泛型,那么为什么首先使用泛型呢?您可以轻松地将产品与其相关策略相结合,并显着简化代码。

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

编辑:因为现在你已经重新定义了你的例子并使用S1S2作为接口,我明白你的意思。一种方法是为Factory.Create. 例子:

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

正如您正确指出的那样,否则这是不可能的,因为您的班级没有共同的祖先S1并且S2可以接受。Product

于 2013-01-11T03:49:29.773 回答
0

您是否考虑过重载 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 { }

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

于 2013-01-11T06:13:13.720 回答
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>
于 2013-01-11T04:29:30.310 回答