Чрезмерно сложные заводские методы - есть ли решения? - PullRequest
2 голосов
/ 28 апреля 2011

Фабричные методы - хороший способ скрыть сложность при создании определенных семейств объектов.Хорошо.Но что происходит, когда сами фабричные методы начинают становиться сложными?

Например, мне нужно создать объект на основе двух или более флагов / свойств / значений, например:

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 ]

4 голосов
/ 28 апреля 2011

Соберите все это вместе. :)

Карта параметров анонимным создателям.

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()
}
2 голосов
/ 28 апреля 2011

В случае, если ваши правила могут стать более сложными, чем простое сравнение свойств, вы можете оставить опцию, чтобы сделать немного больше, например ::10000

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 может только сравнивать равенство).

Кроме того, использование похоже на Решение Эндрю :

IMyClass instance = MyClassFactory.Create(propSet);
2 голосов
/ 28 апреля 2011

В зависимости от характера 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 аналогичные возможности или нет (хотя я уверен, что это так)

1 голос
/ 28 апреля 2011

Вы можете преобразовать это в отображение из наборов свойств (инкапсулированных в собственно класс) в имена классов, которые затем можно создать с помощью отражения. Или, альтернативно, иерархия карт. В Java я бы использовал Spring для определения такого отображения в XML-файле конфигурации; Вероятно, есть аналогичный способ добиться этого и в C #.

0 голосов
/ 29 апреля 2011

Я прошу прощения, если это бесполезно, но я не могу удержаться, указывая, что такого рода вещи действительно легко написать на 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 #.

0 голосов
/ 28 апреля 2011

Есть много вариантов, но это будет зависеть от того, как это масштабируется. Какие еще значения / логика имеют условия?

Один из вариантов - использовать комбинированный свич

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 ...;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...