【问题标题】:Generic Factory with type parameter具有类型参数的通用工厂
【发布时间】:2013-01-11 02:40:32
【问题描述】:

我有以下情况。

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

Strategy1、Strategy2 等都派生自一个通用的 StrategyBase 类。然而,每种策略都有不同的验证机制,它是工厂类的类型参数。但是,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' 的转换。

【问题讨论】:

  • 我对你的意图有点困惑,你能添加一些你想如何使用工厂的代码吗?
  • 我想传入与不同 Strategy 对象关联的不同验证类型。 CreateStrategy 只是帮助我用用户指定的验证类实例化适当的策略对象
  • 而且由于策略使用完全不同的机制来实现相同的目标,因此它们的验证是唯一的,通常没有共性
  • 我特别有兴趣看到使用代码,因为我不太清楚为什么你在这里有类级别的泛型而不是方法级别。顺便说一句,也许它们的共同点是它们都是一种策略?
  • @Chubsdad 我不明白。如果您可以编辑ABCDValidatorWMIValidator,您不能简单地给它们一个共同的祖先类/接口,即使是空的,然后将其用作约束?

标签: c#


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    我认为在这种情况下的答案是,它取决于您希望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&lt;T&gt; 的约束。所以,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;
        }
    };
    

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

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

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

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

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

    【讨论】:

    • 就我而言,即使我有一个通用函数 ExecuteLogic,它也会为每个策略类接受不同的参数。另外请查看最新的代码帖子,关于我想分别成为 S1 和 S2 而不是 IStrategy 的 Product1 和 Product2 的约束
    • @Chubsdad 我会说在这种情况下,您只需从 IStrategy 中删除 ExecuteLogic 方法(使其为空),但继续将 IStrategy 作为类的装饰以表明它是一种策略.这提高了可读性,并使我们的策略更加安全(?)。
    • @Chubsdad 正如上面的评论者所说,如果Product1 被限制为S1Product2S2,我看不到使用泛型会产生替代品的情况(即使它是可能的)将产品与其各自的策略相结合。它还可以为您省去使用IStrategy 作为接口的麻烦,因为正如您所说,不同的策略有不同的签名。您可以将整个逻辑放入产品中。
    • 在我的例子中,S1 和 S2 是接口,我可以从这些接口中采用不同的策略并配置我的产品类。这产生了将产品与策略相结合的替代方案
    • class Product1 : ProductBase where T : S1, new() { void g() { T t = new T(); t.f1(); } };
    【解决方案3】:

    您可以更改函数以将 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>
    

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

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