Есть интерфейс и несколько производных классов которые реализуют метод Validate. В довесок к нему, существует интерейс IPaymentProcessor,
которы применяет все правила, т.е. создает объект и вызвает метод Validate() у Rule1, Rule2, итд.
Задача: реализовать IPaymentProcessor
public interface IBusinessRule
{
bool Validate();
}
class Rule1 : IBusinessRule
{
public Rule1(string name) { ... }
bool Validate() { ... }
}
class Rule2 : IBusinessRule
{
public Rule1(int month, int year) { ... }
bool Validate() { ... }
}
interface IPaymentProcessor
{
bool MakePayment(CreditCard card);
}
class MyPaymentProcess : IPaymentProcessor
{
public bool MakePayment(CreditCard card)
{
// Вот тут засада. Если нам нужно добавить/удбрать правило, меняется конструктор итд.,
// То надо лезть в этот код. Что совсем не правильно.var rules = new List<IBusinessRule>() { new Rule1(card.Name), new Rule2(card.Month, card.Year) }; foreach(var r in rules) if(!r.Validate()) { return false; }
return true;
}
}
Вопрос: как заменить создание списка на лету, какой-нибудь фабрикой или контейнером, чтобы уменьшить связность? Желательно из готовых решений под .NET, еще лучше через Ninject (так в требованиях).
Копая в сторону Ninject/DI, натыкался на проблему: либо непонятно как разное кол-во параметров в конструкторе,
либо непонятно в по какому принципу делать ninjectKernel.Get, или в нашем случае что-то вроде GetAll, для перечисления.
Здравствуйте, licedey, Вы писали:
L>Вопрос: как заменить создание списка на лету, какой-нибудь фабрикой или контейнером, чтобы уменьшить связность? Желательно из готовых решений под .NET, еще лучше через Ninject (так в требованиях).
У тебя список меняется во время работы программы?
Если нет просто положи его в статическую переменную. Если хочется модноты, то зарегистрируй все экземпляры в ioc-контейнере и вызывай getall.
Если да, то как устроен сериализатор? Может ли он создать экземпляр класса с параметрами?
Если у тебя конкретные правила зависят от контекста, то есть используют параметры доступные в конкретном запросе, то просто добавь уровень коссвенности. Сделай класс-билдер, который в конструкторе принимает фиксированные парамтеры, а в методе build — контекстные. И сохраняй список билдеров.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, licedey, Вы писали:
L>>Вопрос: как заменить создание списка на лету, какой-нибудь фабрикой или контейнером, чтобы уменьшить связность? Желательно из готовых решений под .NET, еще лучше через Ninject (так в требованиях).
G>У тебя список меняется во время работы программы? G>Если нет просто положи его в статическую переменную. Если хочется модноты, то зарегистрируй все экземпляры в ioc-контейнере и вызывай getall.
В статическую не получится, потому что приходит куча запросов с разными CreditCard, а уже на основе этого объекта и создаются валидаторы.
G>Если да, то как устроен сериализатор? Может ли он создать экземпляр класса с параметрами?
Эмм...а зчем тут сериализатор, это бизес-логика ASP.NET MVC приложения. По Api прихоят данные о CreditCard, а задача класса PaymentProcessor
валидировать ее корректность и вернуть true/false
G>Если у тебя конкретные правила зависят от контекста, то есть используют параметры доступные в конкретном запросе, то просто добавь уровень коссвенности. Сделай класс-билдер, который в конструкторе принимает фиксированные парамтеры, а в методе build — контекстные. И сохраняй список билдеров.
Про билдер подумаю, пока сделал так, но в том же Ninject или AutoFixter, это как-то все моднее и на основе рефлексии.
Пока улушли решение через делегаты-creator'ы, которые лежат в списке, мы по нему пробегаем и делагаты создают каждый Instance. Но до сих пор кажется что велосипед.
Здравствуйте, licedey, Вы писали:
L>Вопрос: как заменить создание списка на лету, какой-нибудь фабрикой или контейнером, чтобы уменьшить связность? Желательно из готовых решений под .NET, еще лучше через Ninject (так в требованиях).
Под .NET не знаю, но вот классика QMetaObject. Его мы можем получить с помощью const QMetaObject * QObject::metaObject() const. В QMetaObject есть метод newInstance, хотя это не особо принципиально даже если бы пришлось создавать фабричный метод, особенно учитывая, что большинство классов унаследованы от QObject.
Важны на мой взгляд две вещи:
1) Первое это enumerators, то есть перечислители. Можно динамически получить название методов, а так же вызывать их, или там связать сигналы слоты. А можно работать со свойствами и так далее. Другое дело не стоит забывать, что всё это сделано на switch.
2) Второе это получение интерфейса и соответственно полиморфизм посредством виртуальных методов.
Всё это даёт возможность добавлять новые объекты из тех же плагинов и создавать новый функционал связывая их каким-либо способом. Ну, а что касается .NET, то на Qt я ушёл не только потому, что это кроссплатформа и C++, а ещё и потому, что Qt обладал теми же самыми возможностями, то есть интроспекцией. Если на Qt всё это протестировал лично с плагинами, то в те далёкие времена на .NET мне это как-то не довелось.
Здравствуйте, licedey, Вы писали:
G>>Если нет просто положи его в статическую переменную. Если хочется модноты, то зарегистрируй все экземпляры в ioc-контейнере и вызывай getall. L>В статическую не получится, потому что приходит куча запросов с разными CreditCard, а уже на основе этого объекта и создаются валидаторы.
1. Разделить код на две части: классы с логикой обработки и подбор нужного класса.
2. Сделать удобное API для каждой из частей.
3. Спрятать за API реализацию. Стартовые два варианта: или добавляем в обработчик метод CanProcess(), или храним в настройках сопоставление "набор условий => обработчик". Дальше допиливается по обстановке.
L>Про билдер подумаю, пока сделал так, но в том же Ninject или AutoFixter, это как-то все моднее и на основе рефлексии.
Лучше сначала решить проблему, а уж затем с чистой совестью гоняться за шашечками. Меньше переписывать придётся.
вы лучше скажите, почему ваши абстрактные рулы принимают какие-то абстрактные имя, месяц, год. Если я правильно понял, они валидируют именно имя владельца карты, или месяц год окончания действия карты. Т.е. они же работают не с абстрактными "строка", "дата", а с вполне себе конкретными — с данными карты, и логика проверки знает о том, что это данные карты, а некакие-то абстрактные. Вот и передавайте туда карту целиком. Тогда ваш вопрос разрешится сам собой.
Здравствуйте, velkin, Вы писали:
V>Здравствуйте, licedey, Вы писали:
L>>Вопрос: как заменить создание списка на лету, какой-нибудь фабрикой или контейнером, чтобы уменьшить связность? Желательно из готовых решений под .NET, еще лучше через Ninject (так в требованиях).
V>Под .NET не знаю, но вот классика QMetaObject. Его мы можем получить с помощью const QMetaObject * QObject::metaObject() const. В QMetaObject есть метод newInstance, хотя это не особо принципиально даже если бы пришлось создавать фабричный метод, особенно учитывая, что большинство классов унаследованы от QObject. V>
Признаться не понял как это работает указанный код на плюсах, но по описанию похоже на рефлексию в .NET. Возиться с ней только не хочется, сильно громоздкий код получается,
с кучей проверок. Поэтому пытаясь сделать решение наиболее лаконичным, пока остановился на делегатах, которые создают сущности с любыми конструкторама, возвращая инстансы наследников IRule.
А в идеальном мире, хотелось бы что-то вроде:
// Вот в этом месте, хотелось бы IRuleFactory на лету создать. Делается через Ninject/DI-паттернpublic PaymentProcessor(IRuleFactory factory)
{
// И тут что вроде такого билдера. Но это надо свой велосипед писать. В принципе его и написал уже.this._factory = factory.Build().Add<Rule1>(c => c.CardName).Add<Rule2>(c => c.Month, c => c.Year);
}
public bool MakePayment(CreditCard c)
{
return _factory.GetAll(c).All(v => v.Validate());
}
Здравствуйте, antropolog, Вы писали:
A>Здравствуйте, licedey, Вы писали:
A>вы лучше скажите, почему ваши абстрактные рулы принимают какие-то абстрактные имя, месяц, год. Если я правильно понял, они валидируют именно имя владельца карты, или месяц год окончания действия карты. Т.е. они же работают не с абстрактными "строка", "дата", а с вполне себе конкретными — с данными карты, и логика проверки знает о том, что это данные карты, а некакие-то абстрактные. Вот и передавайте туда карту целиком. Тогда ваш вопрос разрешится сам собой.
Мне на StackOverflow уже 10 человек об этом сказали . Но это тестовое задание (причем не мое), на знание DI, ООП, SOLID, Юнит-тестирования.
В приложении к нему такие классы прицепили, т.е. задача надуманная, а требование от меня — показать скиллы с выдподвыпертом.
Здравствуйте, licedey, Вы писали:
L>Мне на StackOverflow уже 10 человек об этом сказали . Но это тестовое задание (причем не мое), на знание DI, ООП, SOLID, Юнит-тестирования. L>В приложении к нему такие классы прицепили, т.е. задача надуманная, а требование от меня — показать скиллы с выдподвыпертом.
Это новая форма поиска кандидатов:
1. Запостить чье-то ТЗ на форум в профильный раздел.
2. Попросить критику/подсказки/etc.
3. Понравившихся берем в оборот.
Здравствуйте, licedey, Вы писали:
L>Мне на StackOverflow уже 10 человек об этом сказали . Но это тестовое задание (причем не мое), на знание DI, ООП, SOLID, Юнит-тестирования. L>В приложении к нему такие классы прицепили, т.е. задача надуманная, а требование от меня — показать скиллы с выдподвыпертом.
А, собеседования, бессмысленные и беспощадные. Удачи!
Здравствуйте, StatujaLeha, Вы писали:
SL>Здравствуйте, licedey, Вы писали:
L>>Мне на StackOverflow уже 10 человек об этом сказали . Но это тестовое задание (причем не мое), на знание DI, ООП, SOLID, Юнит-тестирования. L>>В приложении к нему такие классы прицепили, т.е. задача надуманная, а требование от меня — показать скиллы с выдподвыпертом.
SL>Я угадал?
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, licedey, Вы писали:
L>>Мне на StackOverflow уже 10 человек об этом сказали . Но это тестовое задание (причем не мое), на знание DI, ООП, SOLID, Юнит-тестирования. L>>В приложении к нему такие классы прицепили, т.е. задача надуманная, а требование от меня — показать скиллы с выдподвыпертом.
S>А, собеседования, бессмысленные и беспощадные. Удачи!
Спасибо Передам французу ,которому этот код объяснить еще надо будет..
Здравствуйте, licedey, Вы писали:
G>>У тебя список меняется во время работы программы? G>>Если нет просто положи его в статическую переменную. Если хочется модноты, то зарегистрируй все экземпляры в ioc-контейнере и вызывай getall. L>В статическую не получится, потому что приходит куча запросов с разными CreditCard, а уже на основе этого объекта и создаются валидаторы.
Не очень интерфейс у вашего валидатора. По интерфейсу "валидатор" является оберткой над Boolean (с точностью до момента вычисления). Естественно, с этим работать очень неудобно.
Это все прекрасно лечится исправлением интерфейса:
public interface Validator<T> {
public boolean validate(T item);
}
public final class CompositeValidator<T> implements Validator<T> {
private List<Validator<T>> peers;
CompositeValidator(List<Validator<T>> peers) {
this.peers = peers;
}
@Override
public boolean validate(T item) {
for (Validator<T> peer : peers)
if (!peer.validate(item))
return false;
return true;
}
}
public final class LegacyRule1Adapter implements Validator<CreditCard> {
@Override
public boolean validate(CreditCard item) {
return new Rule1(item.Name).validate();
}
}
public final class LegacyRule2Adapter implements Validator<CreditCard> {
@Override
public boolean validate(CreditCard item) {
return new Rule2(item.Month, item.Year).validate();
}
}
public final class MyPaymentProcessor {
private final Validator<CreditCard> validator;
MyPaymentProcessor(Validator<CreditCard> validator) {
this.validator = validator;
}
@Override
public boolean makePayment(CreditCard card) {
if (!validator.validate(card))
return false;
return true;
}
}
// Somewhere in the app startup/config/injection code:final Validator<CreditCard> paymentCardValidator = new CompositeValidator<>(
new LegacyRule1Adapter(), new LegacyRule2Adapter()
);
final PaymentProcessor = new MyPaymentProcessor(paymentCardValidator);
Legacy Adapter'ов в простейшем случае наделать для каждого правила. В более сложном можно посмотреть на какой-нибудь reflection на базе класса, реализующего IBusinessRule (например, принимающий тип аргумента, конструктор правила и имена полей для передачи в конструктор). Еще можно композитный процессор за статической фабрикой спрятать. Еще композицию можно развивать (conditional validators, etc...), все зависит от языка (нужна удобная поддержка лямбд (анонимных функций)).
В итоге — никаких лишних уровней абстракции вроде "фабрик возвращающих фабрики возвращающих фабрики".
Здравствуйте, maxkar, Вы писали:
M>Это все прекрасно лечится исправлением интерфейса: M>
M>public interface Validator<T> {
M>
А мне так не нравится. На пустом месте использовать генерики.
M>посмотреть на какой-нибудь reflection на базе класса
И очередной рефлективный фреймворк-всемогутер.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Ну и далее в конструкторе фабрики регистрируем все нужные инстансы IRule, и в случае чего в рантайме тоже вызываем Register для новых правил.
·>ЗЫЖ Ninject — фтопку, как и все IoC-фреймворки.
С этого места по подробнее пожалуйста. Это чуть ли не первый вопрос на каждом интервью. Рассказать про DI. А с ним и про Ninject.
Здравствуйте, licedey, Вы писали:
L>·>ЗЫЖ Ninject — фтопку, как и все IoC-фреймворки. L>С этого места по подробнее пожалуйста. Это чуть ли не первый вопрос на каждом интервью. Рассказать про DI. А с ним и про Ninject.
, в которой довольно много про DI и контейнеры.
Если кратко, то чаще всего контейнеры для DI нафиг не нужны, а их повсеместное втыкание лишь усложняет понимание и поддержку кода.
Здравствуйте, licedey, Вы писали:
L>·>А следующим шагом я бы изменил IBusinessRule чтобы они ещё репортили ошибку c подробностями что случилось, а не просто true/false. L>Мое решение похоже, только одним классом RulesFactory, не плодя сущности. С методом L>
Да какая разница. Вместо явного типа CreditCardValidatorFactory ты используешь догадайся-что-я-имел-в-виду Func<CreditCard, IRule>.
Если так рассуждать, то и IRule можно заменить, пиши уж сразу Func<CreditCard, Func<bool>>.
L>Ну и далее в конструкторе фабрики регистрируем все нужные инстансы IRule, и в случае чего в рантайме тоже вызываем Register для новых правил.
Фи.
L>·>ЗЫЖ Ninject — фтопку, как и все IoC-фреймворки. L>С этого места по подробнее пожалуйста. Это чуть ли не первый вопрос на каждом интервью. Рассказать про DI. А с ним и про Ninject.
Вкратце: DI+CI — хорошо, IoC-containers — плохо.
Подробности: О "наивном" DI и об архитектурном бессилии
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ·, Вы писали:
·>Здравствуйте, licedey, Вы писали:
L>>·>А следующим шагом я бы изменил IBusinessRule чтобы они ещё репортили ошибку c подробностями что случилось, а не просто true/false. L>>Мое решение похоже, только одним классом RulesFactory, не плодя сущности. С методом L>>
·>Да какая разница. Вместо явного типа CreditCardValidatorFactory ты используешь догадайся-что-я-имел-в-виду Func<CreditCard, IRule>. ·>Если так рассуждать, то и IRule можно заменить, пиши уж сразу Func<CreditCard, Func<bool>>.
Разница в избавлении от лишнего класса, как в решении выше и использовании паттерна Factory. По моему из имени параметра и названии класса понятно, "что я имел ввиду"