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

本文关键字:方法 工厂 任何 解决方案 于复杂 复杂 | 更新日期: 2023-09-27 17:53:23

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

例如,我需要基于两个或多个标志/属性/值创建一个对象,如下所示:
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 
            }
        }
    }
}

这很快就会变得很难看。好的,所以你对客户隐藏了复杂性,但是你的工厂代码正在成为维护的头痛问题。

对于这个问题还有其他的解决方案吗?是否存在某种多值查找模式,使其更易于阅读和维护?

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

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

参数映射到匿名创建者。

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

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

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解决方案相比的速度差异(但是这两种方法实际上不能比较,因为哈希集只能进行相等比较)。

除此之外,用法类似于Andrew的解决方案:

IMyClass instance = MyClassFactory.Create(propSet);

根据这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是否有类似的功能(尽管我确信它有)

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

有很多选择,但这取决于如何缩放。条件还有什么其他值/逻辑?

一个选项是使用组合开关

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

如果这没有帮助,我很抱歉,但我忍不住要指出,这类事情在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#中。