Factory Pattern, многие параметры нуждаются в подсказках

Я разрабатываю небольшое решение "Rule Engine". Основная идея модуля заключается в том, что он проверяет, удовлетворяют ли полученные взаимодействия с пользователем некоторым правилам, и если да, то начисляет им бонусные баллы. Короче говоря, есть набор интерфейсов List<IRule>, когда пользователь взаимодействует с системой, скажем, покупает вещи, я перебираю все IRule и проверяю, возвращает ли bool Execute(ITransactionContext context) значение true. Вот интерфейс IRule

public interface IRule
{
    bool Execute(IContext context);
}

И проблема в том, что конкретные Rule : IRule классы отличаются друг от друга, имеют разные параметры и значения. Например.

  • AmountRule имеет Min, Max свойства
  • DatePeriodRule имеет параметры From и To
  • EqualsRule имеет параметры CheckProperty и ExpectedValue и так далее... Это очень простые правила, могут существовать более сложные правила.

    public class AmountRule : IRule
    {
        private decimal _min;
        private decimal _max;
    
        public bool Execute(IContext context)
        {
            return context.Amount >= _min && context.Amount <= _max;
        }
    }
    
    public class DatePeriodRule : IRule
    {
        private DateTime _from;
        private DateTime _to;
    
        public bool Execute(IContext context)
        {
            return context.ProcessDate >= _from && context.ProcessDate <= _to;
        }
    }
    

Таким образом, создание правил — непростая задача, и я решил использовать шаблон Factory (или любой шаблон, связанный с ним, неважно, будет ли это Abstract Factory, Factory Method или Builder) и иметь интерфейс, IRuleFactory который отвечает за создание любого класса реализации IRule.

public interface IRuleFactory
{
    IRule Create(RuleType type);
}

Здесь я сталкиваюсь с реальной проблемой из-за разнообразия параметров для конкретных реализаций IRule. Например. Если я хочу создать AmountRule, то моему RuleFactory нужны MinAmount, MaxAmount параметры, если DatePeriodRule нужны From, To параметры и так далее...

Я ищу хорошие подходы к решению этой проблемы. Является ли Factory просто дополнительной головной болью?


person Levan    schedule 20.05.2014    source источник
comment
Почему вы используете фабрику для начала? Что вы надеетесь получить от его использования вместо явного конструирования объектов?   -  person Servy    schedule 20.05.2014
comment
Мне нужна фабрика, чтобы получить то, для чего она хороша :) Я хочу отделить логику создания IRule от остального кода. Сохраняйте принцип открытости-закрытости, пока в систему со временем добавляется много новых IRules и т.д.   -  person Levan    schedule 20.05.2014
comment
Вы используете его, потому что понимаете, для чего он нужен, и видите, как эти преимущества применяются здесь, или вы используете его, потому что считаете, что фабрики в целом лучше, чем создание объектов? Идея разделения логики построения хороша, когда логика построения не имеет отношения к коду, использующему объект. Здесь явно не тот случай. Является ли возможность динамически добавлять правила без изменения потребляющего кода требованием вашей программы? Вам придется приложить много усилий, чтобы получить эту функциональность. Вам это действительно нужно?   -  person Servy    schedule 20.05.2014
comment
Я думаю, вам нужно уточнить, почему вы считаете, что разные конкретные классы, которым нужны разные параметры для их конструкторов, вообще являются проблемой. Если бы это был метод интерфейса, требующий различных параметров в зависимости от конкретной реализации, это было бы проблемой, но в конструкторе? Без дополнительной информации, мне кажется, все в порядке   -  person Ben Aaronson    schedule 20.05.2014


Ответы (3)


Для вашего Factory нормально создавать определенные типы, которые реализуют интерфейс, но имеют конструкторы с другими параметрами. Это именно тот тип абстракции, для которого он предназначен. Таким образом, ваш метод Create может выглядеть примерно так:

IRule Create(RuleType type){
    if(type == RuleType.ValidAmount){
        return new ValidAmountRule(10, 20);
    }
    else{
        return new OtherKindOfRule("some other param");
    }
}

Конечно, здесь предполагается, что ваша фабрика знает, какими должны быть параметры для конструкторов. Если это известно только клиенту метода Create, то я бы сказал, что косвенность не нужна, и клиент должен сам создать правило. Параметр RuleType немного подозрительный, поскольку вызывающая сторона явно должна знать что-то о желаемом IRule. Если существует однозначное соответствие между RuleType и конкретным типом, который нужно создать, то это, безусловно, ненужная сложность, если только у вызывающей стороны нет информации, необходимой для непосредственного построения правила.

В общем, цель обработчика правил — уменьшить сложность за счет удаления множества сложных операторов if/else и инкапсуляции правил, чтобы ими можно было управлять изолированно и, возможно, настраивать вне развертывания кода. Ваши конкретные правила должны получать информацию, необходимую им для применения своей логики, от некоторой комбинации конструктора объекта и вызывающей стороны объекта. То, что не может быть известно в конструкторе объекта, должно быть передано вашему методу Execute. Это означает, что параметр в вашем интерфейсе IRule должен быть высокоуровневым объектом, содержащим много информации, которая может даже не понадобиться некоторым из ваших правил.

person Jason Nesbitt    schedule 20.05.2014

На самом деле я реализовал RulesEngine под названием Ariadne, поэтому здесь я говорю по своему опыту. Ваша дилемма возникает из-за того, что у вас есть разные типы операндов, и они должны выстраиваться. Ведь нельзя просить

'foo' > 10

это выражение неверно, потому что foo — это строка, а 10 — это число. Точно так же вы не можете спросить, если

Вес > 15 фунтов

Потому что компьютеры не знают, что такое «Вес» или что такое «15 фунтов».

Вы должны справиться с этим с помощью множества шаблонов и продуманного дизайна.

Архитектура дизайна Ариадны (и вы можете позволить себе некоторые вольности) была такой:

  • Операции (предикаты, уравнения и т. д.)
  • Операторы (плюс, минус, деление и т.д.). (Ариадна для простоты выполняет бинарные операции)
  • OperandOwners (это объекты, которые извлекают числовое значение из «Вес», чтобы вы могли делать такие вещи, как «Вес> 10».
  • Все реализации вышеперечисленного полностью не имеют состояния. Сбор любой необходимой им информации из контекста вызова.

Ваша первая проблема заключается в том, чтобы хранить все операции, операторы и владельцы операндов в одной и той же базе знаний. Это крайне важно для запуска более 1 в вашем приложении. Я сделал это с помощью шаблона AbstractFactory.

В этот пример вы можете увидеть:

KnowledgeBase kb = KnowledgeBase.getInstance();

Вот как я контролирую эти 3 штуки. Если вы пытаетесь управлять оператором/операндом/операндовером, все они проходят через эту базу знаний, потому что вам нужно вызывать ее функции.

Вы также должны иметь возможность включите создание правил в свои методы.

public Predicate getPredicate(OperandOwner lho, String op, OperandOwner rho) throws AriadneException {
    return operationFact.getPredicate(lho, op, rho);
}

Это процесс регистрации, который позволяет повторно использовать общие объекты с помощью шаблона Flyweight и в кроме того, это фабричный метод для создания предикатов.

Все это хорошо, но на самом деле хочется иметь возможность произвольно комбинировать правила. Для этого вам нужно продолжать их комбинировать, но выставлять их как единое правило. Этот шаблон многие как один является составным шаблоном. Рассмотреть возможность:

  • сегодня вторник?
  • идет дождь ?
  • мой вес> 10 фунтов

У вас есть 3 отдельных предиката, которые можно комбинировать. И в любом сочетании «и» или «или» у вас все еще есть сказуемое. Вы должны уметь представлять множество комбинированных предикатов как один.

Пример того, как это работает: посмотри сюда

Итог: надежная реализация будет:

  1. Эффективная память. Правила обязаны быть бесконечными
  2. Без гражданства. Побочные эффекты могут все испортить
  3. Открыт для расширения (Ariadne позволяет/рекомендует вам создавать собственные операторы/владельцы операндов)
  4. закрыто для модификации
  5. Почти невозможно неправильно создать правило.

Для этого у вас есть:

  • Наилегчайший вес
  • Композитный
  • Абстрактная фабрика
  • Заводской метод

Удачи

person Christian Bongiorno    schedule 20.05.2014
comment
Это действительно ответило на ваш вопрос? - person Christian Bongiorno; 23.05.2014

Я бы применил шаблон прототипа для регистрации правил. Если все правила, которые вы ожидаете иметь, не имеют состояния, вы даже можете обойтись без клонирования.

person vrudkovsk    schedule 20.05.2014