开发者

Overly complex factory methods - any solutions?

开发者 https://www.devze.com 2023-03-01 20:47 出处:网络
Factory methods are a nice way of hiding away the complexity when creating certain families of objects. Fine. But what happens when the factory methods themselves start to become complex?

Factory methods are a nice way of hiding away the complexity when creating certain families of objects. Fine. But what happens when the factory methods themselves start to become complex?

For example, I need to create an object based on two or more flags/properties/values, like so:

public class MyClassFactory 
{
    public static IMyClass GetClass(int prop1, 开发者_JS百科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 
            }
        }
    }
}

This gets ugly pretty quickly. Ok, so you have hidden complexity from clients but your factory code is becoming the maintenance headache.

What other solutions are there for this issue? Is there some kind of multi-valued lookup pattern that might make this a little easier to read and maintain?


Put this pieces together. :)

Map of parameters to anonymous creators.

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


In case your rules could ever become more complex than simple comparison of properties, you might want to leave an option to do a bit more, e.g.:

interface IFactoryRule 
{
    bool CanInstantiate(PropertySet propSet);
}

Simple implementation would then look like this:

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

But you could also create complex custom rules of any kind:

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

add all sorts of decisions to your factory:

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

[Edit: added the MyClassFactory.Create method]

As you can see, there is no hash mapping of rules in this solution, so list of rules is evaluated one by one in the Create method (FirstOrDefault will iterate the list until first match is found).

If you have a lot of rules (more than, say, 20), and you are instantiating a million of objects, you will notice the speed difference compared to a HashSet solution (but these two approaches cannot actually be compared since hashset can only do equality comparison).

Other that that, usage is similar to Andrew's solution:

IMyClass instance = MyClassFactory.Create(propSet);


Depending on the nature of the 3 properties, you could create a custom attribute that defines each class. So you would have classes like:

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

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

Then in your factory method you can use reflection to loop through all implementations of IMyClass, retrieve their IMyClassAttribute instances, and check if the properties match. This keeps you from having to keep mappings in two places and keep a class' mapping contain in the class definition itself.

Edit FYI, this is based on C#, I have no idea if Java has similar capabilities or not (though I'm sure it does)


You could transform this into a mapping from tuples of properties (encapsulated in a class proper) to class names, which you can then instantiate using reflection. Or alternatively, a hierarchy of maps. In Java I would use Spring to define such a mapping in an XML config file; probably there is a similar way to achieve this in C# too.


There are lots of options, but it would depend on how this scales. What other values/logic do the conditions have?

One option is to use a combined swich

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

    }
}

One options is to use reflection.

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


I apologize if this is unhelpful, but I can't resist pointing out that this sort of thing is really easy to write in F#, and can be called from C# very easily. In fact, the signature of this F# code is identical to that of the sample method in the question - it would appear identical to C# code calling it.

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

If you can't use MEF I'm guessing that you can't use F# either. However, there is in fact "some kind of multi-valued lookup pattern that might make this a little easier to read and maintain" - it just doesn't exist in C#.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号