过于复杂的工厂方法 - 任何解决方案?

时间:2011-04-28 12:56:49

标签: c# java design-patterns

工厂方法是在创建某些对象族时隐藏复杂性的好方法。精细。但是当工厂方法本身开始变得复杂时会发生什么?

例如,我需要基于两个或多个标志/属性/值创建一个对象,如下所示:

public class MyClassFactory 
{
    public static IMyClass GetClass(int prop1, int prop2, int prop3)
    {
        switch(prop1)
        {
         case (1):
            switch(prop2) 
            {
                case(1):
                if(prop3 == x)
                    return new ImplMyClassA();
                else
                    return new ImplMyClassB();
                ... 
                //etc ad infinitum 
            }
        }
    }
}

这很快变得丑陋。好的,所以你隐藏了客户的复杂性,但你的工厂代码正在成为维护上的头痛。

此问题有哪些其他解决方案?是否存在某种多值查找模式可能使其更容易阅读和维护?

6 个答案:

答案 0 :(得分:4)

把这些碎片放在一起。 :)

将参数映射到匿名创建者。

Class propertySet{

  int prop1
  int prop2
  int prop3

  public equals(..){... }
}

interface creator {
   IMyClass create()
}

Map<propertySet, classCreator> creatorsMap = new HashMap();

static {

creatorsMap.add(new propertySet(1,2,3), new creator() { ... create() {return ImplMyClassA(); } });
......
creatorsMap.add(new propertySet(7,8,9), new creator() { ... create() {return ImplMyClassB();} });

}


public IMyClass create(x,y,z) {
   return creatorsMap.get(new propertySet(x,y,z)).create()
}

答案 1 :(得分:2)

根据3个属性的性质,您可以创建定义每个类的自定义属性。所以你会有这样的课程:

[IMyClass(Prop1 = 1, Prop2 = 3, Prop3 = 4)]
public class Blah : IMyClass

[IMyClass(Prop1 = 4, Prop2 = 5, Prop3 = 6)]
public class Blah2 : IMyClass

然后在您的工厂方法中,您可以使用反射循环遍历IMyClass的所有实现,检索它们的IMyClassAttribute实例,并检查属性是否匹配。这使您不必在两个地方保留映射,并保持类的映射包含在类定义中。

编辑仅供参考,这是基于C#,我不知道Java是否具有相似的功能(虽然我确定它有)

答案 2 :(得分:2)

如果您的规则变得比简单的属性比较更复杂,您可能希望留下一个选项来做更多的事情,例如:

interface IFactoryRule 
{
    bool CanInstantiate(PropertySet propSet);
}

简单的实现将如下所示:

// compares property set to given parameters
public SimpleRule : IFactoryRule
{
    private readonly int a,b,c;
    public SimpleRule(a,b,c) { ... }

    public bool CanInstantiate(PropertySet propSet)
    {
        return
            propSet.a == a &&
            propSet.b == b &&
            propSet.c == c;
    }
}

但您也可以创建任何类型的复杂自定义规则:

// compares property set using a delegate
public ComplexRule : IFactoryRule
{
    private readonly Func<PropertySet, bool> _func;
    public ComplexRule(func) { ... }

    public bool CanInstantiate(PropertySet propSet)
    {
        return _func(propSet);
    }
}

向工厂添加各种决策:

public class MyClassFactory
{  
    private static List<Tuple<IFactoryRule, Func<IMyClass>>> _rules = new List();

    static MyClassFactory()
    {
        // rules are evaluated in this same order
        _rules.Add(new SimpleRule(1,2,3), () => new Simple());
        _rules.Add(new ComplexRule(p => p.a + p.b == p.c), () => new Complex());
    }

    public static IMyClass Create(PropertySet pset)
    {
        if (pset == null)
            throw new ArgumentNullException("pset");

        // try to find a match
        Tuple<IFactoryRule, Func<IMyClass>> rule = 
            _rules.FirstOrDefault(r => r.First.CanInstantiate(pset));

        if (rule == null)
            throw new ArgumentException(
                "Unsupported property set: " + pset.ToString());

        // invoke constructor delegate
        return rule.Second();
    }
}

[编辑:添加了MyClassFactory.Create方法]

如您所见,此解决方案中没有规则的哈希映射,因此在Create方法中逐个评估规则列表(FirstOrDefault将迭代列表,直到第一个匹配为止实测值)。

如果您有很多规则(比如20),并且您要实例化一百万个对象,您会注意到与HashSet解决方案相比的速度差异(但这两种方法实际上并不能实现因为hashset只能进行相等比较)。

除此之外,其用法类似于Andrew's solution

IMyClass instance = MyClassFactory.Create(propSet);

答案 3 :(得分:1)

您可以将其转换为从属性元组(封装在适当的类中)到类名的映射,然后可以使用反射对其进行实例化。或者,地图的层次结构。在Java中,我将使用Spring在XML配置文件中定义这样的映射;可能在C#中也有类似的方法来实现这一点。

答案 4 :(得分:0)

有很多选择,但这取决于它如何扩展。条件有哪些其他值/逻辑?

一种选择是使用组合swich

public static IMyClass getClass(int prop1, int prop2, int prop3) {
    switch(prop1*1000000+prop2*1000+prop3) {
        case 1001001:

    }
}

一种选择是使用反射。

public static IMyClass getClass(int prop1, int prop2, int prop3) {
    Method m = MyClassFactory.class.getMethod("create"+prop1+"_"+prop2+"_"+prop3);
    return (IMyClass) m.invoke(this);
}

public static IMyClass create1_1_1() {
    return new ...;
}

答案 5 :(得分:0)

如果这是无益的,我道歉,但我无法抗拒地指出这种东西在F#中写起来很容易,并且可以很容易地从C#调用。实际上,这个F#代码的签名与问题中的示例方法的签名相同 - 它看起来与调用它的C#代码相同。

module MyClassFactory =
    let GetClass = function
        | 1, 1, 1 -> ImplMyClassA() :> IMyClass
        | 1, 1, 2 -> ImplMyClassB() :> IMyClass
        | 1, 1, _ -> ImplMyClassC() :> IMyClass
        | 2, 1, 1 -> ImplMyClassD() :> IMyClass
        | 2, 1, _ -> ImplMyClassE() :> IMyClass
        | 2, 2, _ -> ImplMyClassF() :> IMyClass
        | _       -> ImplMyClassDefault() :> IMyClass

如果你不能使用MEF,我猜你也不能使用F#。然而,事实上“某种多值查找模式可能会使它更容易阅读和维护” - 它只是在C#中不存在。