Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 28.03.11 02:47
Оценка:
Здравствуйте,

Похожая тема на этом форуме уже была, но я решил обсудить более сложные случаи.

Не всегда получается избежать обращения к пользователю из методов бизнес-логики. В простых случаях я обычно передаю callback'и — либо интерфейс, либо делегат, т.е. например так:

public void SomeBusinessLogicMethod(..., ISomeCallback callback)
{
  ...
  if(callback.Ask(message) == DialogResult.No)
    return;
  ...
}


Но давайте рассмотрим такую ситуацию. В слое бизнес-логики есть базовый класс и несколько наследников:

class SupplierOrderGenerator { ... }

class StockOrderGenerator : SupplierOrderGenerator { ... }

class AnotherOrderGenerator : SupplierOrderGenerator { ... }

// и т.д.


Конкретный тип выбирается в рантайме с помощью фабричного метода, так же находящегося в слое бизнес-логики

class SupplierService
{
  ...
  public SupplierOrderGenerator GetSupplierOrderGenerator(Supplier supplier)
  {
     ...
  }
  ...
}


И вот теперь, в одном из классов, предположим в StockOrderGenerator, нужно обратиться к пользователю:

class StockOrderGenerator : SupplierOrderGenerator
{
  ...
  public void SomeMethod(...)
  {
    ..
    // здесь нужно, чтобы оператор подтвердил что мы продадим запчасть со склада со скидкой и выбрал по какой цене
    // т.е. если бы я тупо зашился бы на WinForms то это могло бы выглядеть как-то так:
    using(var form = new SomeForm("bla bla bla Customer wants this part for $100 bla bla Please choose lowest possible price bla bla bla", ...))
    {
      if(form.ShowDialog() == DialogResult.Ok)
      {
        decimal chosenPrice = form.ChosenPrice;
        ...
      }
    }
    ...
  }
  ...
}


Получается, что чтобы передать callback данному методу, его надо передать сначала из UI классу SupplierService, тот передаст его фабричному методу GetSupplierOrderGenerator, тот передаст его уже классу StockOrderGenerator. Это при том, что остальным классам наследникам от SupplierOrderGenerator этот коллбек вообще не нужен, но из-за StockOrderGenerator мы его будем тянуть по всей цепочке. Не нравится мне это.

Что посоветуете?

Тут у меня еще мысли находят на паттерн Service Locator, или правильнее наверное тут будет сказать Dependency Injection Container, но мне почему-то кажется что этот паттерн не далеко ушел от глобальных переменных (или я не умею его готовить?). Смущает, что каждый может указать какую-то свою реализацию (читай — изменить значение глобальной переменной), а можно вообще забыть указать конкретную реализацию (если же коллбек явно надо в метод передавать, то точно не пропустишь). Или я заморачиваюсь?
Re: Обращение к пользователю из слоя бизнес-логики
От: Sshur Россия http://shurygin-sergey.livejournal.com
Дата: 28.03.11 04:47
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Здравствуйте,

MC>И вот теперь, в одном из классов, предположим в StockOrderGenerator, нужно обратиться к пользователю:

MC>
class StockOrderGenerator : SupplierOrderGenerator
MC>{
MC>  ...
MC>  public void SomeMethod(...)
MC>  {
MC>    ..
MC>    // здесь нужно, чтобы оператор подтвердил что мы продадим запчасть со склада со скидкой и выбрал по какой цене
MC>    // т.е. если бы я тупо зашился бы на WinForms то это могло бы выглядеть как-то так:
MC>    using(var form = new SomeForm("bla bla bla Customer wants this part for $100 bla bla Please choose lowest possible price bla bla bla", ...))
MC>    {
MC>      if(form.ShowDialog() == DialogResult.Ok)
MC>      {
MC>        decimal chosenPrice = form.ChosenPrice;
MC>        ...
MC>      }
MC>    }
MC>    ...
MC>  }
MC>  ...
MC>}


Чисто для уточнения задачи. Почему нельзя "тупо зашиться на винформс"?
Шурыгин Сергей

"Не следует преумножать сущности сверх необходимости" (с) Оккам
Re: Обращение к пользователю из слоя бизнес-логики
От: Sinix  
Дата: 28.03.11 05:03
Оценка: 1 (1)
Здравствуйте, MozgC, Вы писали:

MC>Что посоветуете?

Не нарушать Liskov's principle: контракт наследника не должен требовать большего, чем контракт родителя

Придётся либо отказаться от универсального всемогутора, либо пихать все сервисы в контракт базового класса, либо скатиться к динамике. Как будет реализовано последнее — через service locator, каст параметров к интерфейсу или DI — неважно, один фиг пользоваться и тестировать будет сложнее.

Лично я обычно не наследую логику от хелпер-классов (а базовый order generator — именно такой хелпер) и прячу всевозможные костыли внутрь слоя логики.
Re: Обращение к пользователю из слоя бизнес-логики
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.03.11 05:47
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Получается, что чтобы передать callback данному методу, его надо передать сначала из UI классу SupplierService, тот передаст его фабричному методу GetSupplierOrderGenerator, тот передаст его уже классу StockOrderGenerator. Это при том, что остальным классам наследникам от SupplierOrderGenerator этот коллбек вообще не нужен, но из-за StockOrderGenerator мы его будем тянуть по всей цепочке. Не нравится мне это.

Должно не нравится то, что разные генераторы требуют разного к себе внимания со стороны SupplierService через посредников (фабричный метод). SupServ либо должен знать что всем генераторам нужен callback, либо должен знать что никому не нужен callback.

MC>Что посоветуете?

Посоветую пересмотреть дизайн. Генератор — это такая хрень, которая не должна общаться с пользователем, просто по метафоре. У генератора должны быть все данные, необходимые для того что бы выдать то что от него ожидают. Т.е. решение нужно будет принимать либо до обращения к генератору, либо после. Можно еще переименовать генератор в нечто более соответствующее тому, кто принимает решение, но проблему это не снимет.

MC>Тут у меня еще мысли находят на паттерн Service Locator, или правильнее наверное тут будет сказать Dependency Injection Container, но мне почему-то кажется что этот паттерн не далеко ушел от глобальных переменных (или я не умею его готовить?). Смущает, что каждый может указать какую-то свою реализацию (читай — изменить значение глобальной переменной), а можно вообще забыть указать конкретную реализацию

У продвинутых SL и DIC интерфейс конфигурации отделен от интерфейса доставания сущностей, т.е. сможет не каждый. А то что ничто не забыто можно проверить тестами.

MC> (если же коллбек явно надо в метод передавать, то точно не пропустишь). Или я заморачиваюсь?

Да, передавать callback прямо в метод — это вариант. Но проблема остается, потому как передавать его придется в методы всех генераторов, а реально нужен он только одному. Изменилась лишь форма передачи.
Re[2]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 28.03.11 18:53
Оценка:
Здравствуйте, Sshur, Вы писали:

S>Чисто для уточнения задачи. Почему нельзя "тупо зашиться на винформс"?


Не то чтобы нельзя, я это переживу , просто это не очень хорошо с точки зрения правильного дизайна. Такой код будет сложнее перенести на другую платформу, проблематично тестировать. Вот допустим, что нам теперь этот код надо вызывать без участия оператора и предоставить какие-то действия по умолчанию, вместо ответа оператора.
Re[2]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 28.03.11 18:59
Оценка:
Здравствуйте, Sinix, Вы писали:

MC>>Что посоветуете?

S>Не нарушать Liskov's principle: контракт наследника не должен требовать большего, чем контракт родителя
А где я нарушаю LSP?

S>Придётся либо отказаться от универсального всемогутора, либо пихать все сервисы в контракт базового класса, либо скатиться к динамике. Как будет реализовано последнее — через service locator, каст параметров к интерфейсу или DI — неважно, один фиг пользоваться и тестировать будет сложнее.

Можно чуть конкретнее? Как бы ты поступил?

S>Лично я обычно не наследую логику от хелпер-классов (а базовый order generator — именно такой хелпер) и прячу всевозможные костыли внутрь слоя логики.

Почему ты решил что это хелпер? UI передает в SupplierService.GenerateOrder() параметры для генерации заказа, в частности поставщика и список запчастей которые этому поставщику надо заказать. Наследники нужны потому, что процедура генерации заказа для разных поставщиков немного различается. До последнего момента взаимодействовать с пользователем вообще не надо было, но сейчас бизнес-требования при заказе на склад усложнились. По-моему это нормально, что при конкретном типе заказа код для осуществления этого конкретного типа заказа кладется в соответствующий конкретный генератор.
Re[2]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 28.03.11 19:04
Оценка:
Здравствуйте, samius, Вы писали:

S>Должно не нравится то, что разные генераторы требуют разного к себе внимания со стороны SupplierService через посредников (фабричный метод). SupServ либо должен знать что всем генераторам нужен callback, либо должен знать что никому не нужен callback.

Насколько я помню, фабричный метод для того и нужен — он знает как инициализировать конкретный тип и это нормально. Так вроде было написано в Head First Design Patterns. Или ты не о том?

S>Посоветую пересмотреть дизайн. Генератор — это такая хрень, которая не должна общаться с пользователем, просто по метафоре. У генератора должны быть все данные, необходимые для того что бы выдать то что от него ожидают. Т.е. решение нужно будет принимать либо до обращения к генератору, либо после. Можно еще переименовать генератор в нечто более соответствующее тому, кто принимает решение, но проблему это не снимет.

Ты считаешь, что в любой ситуации решение можно принять до обращения к генератору?

S>У продвинутых SL и DIC интерфейс конфигурации отделен от интерфейса доставания сущностей, т.е. сможет не каждый. А то что ничто не забыто можно проверить тестами.

Т.е. думаешь я неоправданно стремаюсь DIC и в данной ситуации это вариант?

MC>> (если же коллбек явно надо в метод передавать, то точно не пропустишь). Или я заморачиваюсь?

S>Да, передавать callback прямо в метод — это вариант. Но проблема остается, потому как передавать его придется в методы всех генераторов, а реально нужен он только одному.
Да, я про это написал в самом начале... Вспоминается Interface Sergegation Principle.

S>Изменилась лишь форма передачи.

В смысле изменилась? А какой она была?
Re[3]: Обращение к пользователю из слоя бизнес-логики
От: samius Япония http://sams-tricks.blogspot.com
Дата: 28.03.11 19:41
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Здравствуйте, samius, Вы писали:


S>>Должно не нравится то, что разные генераторы требуют разного к себе внимания со стороны SupplierService через посредников (фабричный метод). SupServ либо должен знать что всем генераторам нужен callback, либо должен знать что никому не нужен callback.

MC>Насколько я помню, фабричный метод для того и нужен — он знает как инициализировать конкретный тип и это нормально. Так вроде было написано в Head First Design Patterns. Или ты не о том?
я отвечал вот на что:

Получается, что чтобы передать callback данному методу, его надо передать сначала из UI классу SupplierService, тот передаст его фабричному методу GetSupplierOrderGenerator, тот передаст его уже классу StockOrderGenerator. Это при том, что остальным классам наследникам от SupplierOrderGenerator этот коллбек вообще не нужен, но из-за StockOrderGenerator мы его будем тянуть по всей цепочке. Не нравится мне это.

Я так понял, что тебе не нравится передавать callback тем, кому он не нужен. Но если SupplierService будет разбираться, куда передавать callback, а куда — нет, то это будет типичный LSP violation. Потому либо передавать callback всем фабричным методам, либо ниодному из них.

S>>Посоветую пересмотреть дизайн. Генератор — это такая хрень, которая не должна общаться с пользователем, просто по метафоре. У генератора должны быть все данные, необходимые для того что бы выдать то что от него ожидают. Т.е. решение нужно будет принимать либо до обращения к генератору, либо после. Можно еще переименовать генератор в нечто более соответствующее тому, кто принимает решение, но проблему это не снимет.

MC>Ты считаешь, что в любой ситуации решение можно принять до обращения к генератору?
Я считаю что обращение к callback-у из генератора можно избежать, правда путем усложнения интерфейса "генератора" и перекладыванием ответственности за обращение к UI на клиента (а судя по описанному тобой сценарию, SS знает UI). Тогда не нужно будет протаскивать callback даже в фабричные методы.

S>>У продвинутых SL и DIC интерфейс конфигурации отделен от интерфейса доставания сущностей, т.е. сможет не каждый. А то что ничто не забыто можно проверить тестами.

MC>Т.е. думаешь я неоправданно стремаюсь DIC и в данной ситуации это вариант?
Да, это вариант. Но странным образом выглядит "в данной ситуации". Или ты используешь DIC, или нет. Если ты его используешь с умом, то во многих местах он безусловно вариант. Если ты его не используешь вообще — без него можно обойтись. Но использование его в данной ситуации и только — весьма подозрительное решение.

MC>>> (если же коллбек явно надо в метод передавать, то точно не пропустишь). Или я заморачиваюсь?

S>>Да, передавать callback прямо в метод — это вариант. Но проблема остается, потому как передавать его придется в методы всех генераторов, а реально нужен он только одному.
MC>Да, я про это написал в самом начале... Вспоминается Interface Sergegation Principle.
Передача дополнительного параметра вряд ли скажется на ISP.

S>>Изменилась лишь форма передачи.

MC>В смысле изменилась? А какой она была?
Ну как же, была предположительно вот такой:
MC>Получается, что чтобы передать callback данному методу, его надо передать сначала из UI классу SupplierService, тот передаст его фабричному методу GetSupplierOrderGenerator, тот передаст его уже классу StockOrderGenerator.
а стала такой
MC>(если же коллбек явно надо в метод передавать, то точно не пропустишь).
Re: Обращение к пользователю из слоя бизнес-логики
От: bl-blx Россия http://yegodm.blogspot.com
Дата: 28.03.11 19:50
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Тут у меня еще мысли находят на паттерн Service Locator, или правильнее наверное тут будет сказать Dependency Injection Container, но мне почему-то кажется что этот паттерн не далеко ушел от глобальных переменных (или я не умею его готовить?). Смущает, что каждый может указать какую-то свою реализацию (читай — изменить значение глобальной переменной), а можно вообще забыть указать конкретную реализацию (если же коллбек явно надо в метод передавать, то точно не пропустишь). Или я заморачиваюсь?


Я думаю, DIC отличается от глобальных переменных тем, что правильный
контейнер следит за доступностью имплементаций на этапе связывания.
Поэтому невозможно получить null там, где контейнеру дана инструкция выполнить DI.
В случае с SL связывание откладывается и вполне может случится нехорошее.

MC>...Смущает, что каждый может указать какую-то свою реализацию

— но именно эта особенность DI позволяет легко заменить имплементацию компонента
в тестах.
El pueblo unido jamás será vencido.
Re[4]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 28.03.11 22:39
Оценка:
Здравствуйте, samius, Вы писали:

S>Я так понял, что тебе не нравится передавать callback тем, кому он не нужен. Но если SupplierService будет разбираться, куда передавать callback, а куда — нет, то это будет типичный LSP violation. Потому либо передавать callback всем фабричным методам, либо ниодному из них.

А завтра появится еще какой-то наследник, и только ему нужен будет еще один параметр? Вообще не то чтобы мега косяк, но попахивает?

S>Я считаю что обращение к callback-у из генератора можно избежать, правда путем усложнения интерфейса "генератора" и перекладыванием ответственности за обращение к UI на клиента (а судя по описанному тобой сценарию, SS знает UI). Тогда не нужно будет протаскивать callback даже в фабричные методы.

Можешь пожалуйста чуть подробнее написать как ты это видишь?

S>>>У продвинутых SL и DIC интерфейс конфигурации отделен от интерфейса доставания сущностей, т.е. сможет не каждый. А то что ничто не забыто можно проверить тестами.

MC>>Т.е. думаешь я неоправданно стремаюсь DIC и в данной ситуации это вариант?
S>Да, это вариант. Но странным образом выглядит "в данной ситуации". Или ты используешь DIC, или нет. Если ты его используешь с умом, то во многих местах он безусловно вариант. Если ты его не используешь вообще — без него можно обойтись. Но использование его в данной ситуации и только — весьма подозрительное решение.
Ну.. может когда-то надо начинать?
Хотя меня почему-то немного отталкивают эти DIC.. Ихмо они ведут к заметному усложнению кода и снижению надежности.
Например, я не очень понимаю как правильно (а точнее где) инициализировать DIC. Допустим есть UI-сборка с методом Main() в котором мы будем регистрировать типы, а резолвить их будем например в сборке с BLL. Если у нас одна UI-сборка, то в принципе все ок, но у меня несколько UI-сборок использующих BLL-сборку. Соответственно, если в какой-то из них я забуду проинициализировать DIC то в рантайме при попытке резолва могу получить TypeNotRegisteredException. Если я добавлю новый тип в контейнер, то мне надо будет не забыть его регистрировать из всех сборок где может выполниться код, который этот тип попытается резолвить.

S>>>Изменилась лишь форма передачи.

MC>>В смысле изменилась? А какой она была?
S>Ну как же, была предположительно вот такой:
MC>>Получается, что чтобы передать callback данному методу, его надо передать сначала из UI классу SupplierService, тот передаст его фабричному методу GetSupplierOrderGenerator, тот передаст его уже классу StockOrderGenerator.
S>а стала такой
MC>>(если же коллбек явно надо в метод передавать, то точно не пропустишь).
В этих предложениях я имел в виду абсолютно одно и то же..
Re[2]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 28.03.11 22:42
Оценка:
Здравствуйте, bl-blx, Вы писали:

BB>Я думаю, DIC отличается от глобальных переменных тем, что правильный

BB>контейнер следит за доступностью имплементаций на этапе связывания.
BB>Поэтому невозможно получить null там, где контейнеру дана инструкция выполнить DI.

Может я просто не знаю как это все правильно делается в хороших DIC контейнерах. Можете посмотреть что меня волнует в DIC в моем предыдущем посту (ответ samius'у).

BB>

MC>>...Смущает, что каждый может указать какую-то свою реализацию

BB>- но именно эта особенность DI позволяет легко заменить имплементацию компонента
BB>в тестах.
Ну все-таки классический DI и DIC это разные вещи.. Я говорил именно о DIC.
Re: Обращение к пользователю из слоя бизнес-логики
От: adontz Грузия http://adontz.wordpress.com/
Дата: 29.03.11 01:31
Оценка: 7 (1)
Здравствуйте, MozgC, Вы писали:

Мне видится, что тут проблема не в архитектуре, а в языке C#. Вернее в том, что для данной задачи нет синтаксического сахара.

SomeMethod должен зависеть от ответов оператора. Ты, с одной стороны, пытаешься представить эти ответы как действия (callback) с другой стороны говоришь о тестировании и значениях по умолчанию. Я бы предложил абстрагироваться от диалогов и делегатов и рассмотреть опциональные параметры как лениво вычисляемые значения некоторой функции. ИМХО передавать делегаты (в том числе анонимные) в данном случае оптимальное решение. Но их надо как-то разметить (а вот этого C# не умеет) чтобы понять для чего они нужны. Получится как-то так
public void SomeMethod(params Delegate[] mixins)
{
  ...

  foreach (Delegate mixin in mixins)
  {
    SupplierValidatorDelegate svd = mixin as SupplierValidatorDelegate ;

    if (svd != null)
    {
       svd(ref supplier);
    }
  }

  ...

  // Это лучше в хелпер
  foreach (Delegate mixin in mixins)
  {
    ItemValidatorDelegate ivd = mixin as ItemValidatorDelegate;

    if (ivd != null)
    {
       ivd(ref item);
    }
  }
}

Основная беда тут в изнлишней динамичности. Можно пойти дальше. Объявить интерфейс IMixin, к нему пару наследников и для каждого класс-реализацию-обёртку принимающую делегат. Получистя так.
public void SomeMethod(params IMixin[] mixins)
{
  ...

  foreach (IMixin mixin in mixins)
  {
    ISupplierValidatorMixin svm = mixin as ISupplierValidatorMixin;

    if (svm != null)
    {
       svm(ref supplier);
    }
  }

  ...

  // Это лучше в хелпер
  foreach (IMixin mixin in mixins)
  {
    IItemValidatorMixin ivm = mixin as IItemValidatorMixin;

    if (ivm != null)
    {
       ivm(ref item);
    }
  }
}

и вызов соответсвенно как
SomeMethod(
  new ItemValidatorMixin(delegate(Item item) { return true; }),
  new SupplierValidatorMixin(delegate(Supplier item) { return false; }),
)

Это уже не так динамично и ИМХО приемлемо.
A journey of a thousand miles must begin with a single step © Lau Tsu
Re[3]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 29.03.11 02:41
Оценка:
Для лучшего понимания приведу диаграмку последовательности действий:



Мы чуть подробнее поговорили с adontz'ем о его варианте.
Решили, что создавать конкретные экземпляры миксинов надо в UI и создавать их на все случаи, т.е. для всех генераторов где они могут понадобится, т.е. это будет выглядеть как-то так:

public interface IMixin { }


public interface IStockOrderGeneratorMixin : IMixin
{
  decimal? AskForStockItemPrice(...);
}


public class StockGeneratorWinFormsMixin : IStockOrderGeneratorMixin
{
  public decimal? AskForStockItemPrice(...)
  {
    using(var form = new SomeForm("bla bla bla Customer wants this part for $100 bla bla Please choose lowest possible price bla bla bla", ...))
    {
      if(form.ShowDialog() == DialogResult.Ok)
      {
        return form.ChosenPrice;
      }
      ...
    }
  }
}

...

// UI

public void SomeMethodInUI()
{
  ...

  SupplierService.GenerateOrder(
    supplier, 
    orderLines,
    new StockGeneratorWinFormsMixin(),
    ... /* другие миксины которые могут понадобится в других местах или других генераторах */ );

  ...  
}

В принципе в этом варианте мы все равно тянем эти миксины-коллбеки через цепочку вызовов, но плюс по сравнению с классическим подходом в том, что если завтра нам понадобится добавить еще что-то, то не придется менять все сигнатуры, прописывать делегирование нового параметра, и все полностью перекомпилировать.

В общем имхо интересный вариант. Не факт что я выберу именно его в итоге, но вариант интересный.
Re[4]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 29.03.11 02:44
Оценка: +1
Хотя.. Я тут подумал.. В этом варианте ослабляется контроль со стороны компилятора. Если у нас несколько UI, вызывающих SupplierService.GenerateOrder(), то если какому-то генератору понадобится новый миксин, то надо не забыть будет добавить его создание и передачу во всех UI-клиентах...
Re[3]: Обращение к пользователю из слоя бизнес-логики
От: Sinix  
Дата: 29.03.11 02:54
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>А где я нарушаю LSP?

Так у тебя выходит, что наследник не будет работать, пока ему не передашь параметры, нафиг не нужные в базовом классе.

S>>Придётся либо отказаться от универсального всемогутора, либо пихать все сервисы в контракт базового класса, либо скатиться к динамике. Как будет реализовано последнее — через service locator, каст параметров к интерфейсу или DI — неважно, один фиг пользоваться и тестировать будет сложнее.

MC>Можно чуть конкретнее? Как бы ты поступил?

Зависит от того, является ли выдача сообщений пользователю ответственностью view или нет. Если да, т.е. конкретный кусок UI отвечает за оформление сообщения (мессаджбоксом, или попапом или полоской аля IE), то 2 варианта:
1. Контроллер принимает делегат/интерфейс для вывода сообщений в качестве параметра.
2. Контроллер выставляет сообщение SomeActionCalling.
Выбор между двумя выше зависит от сложности контроллера. Если метод требует обязательного наличия несколько каллбэков, то годится только интерфейс в качестве параметра. Если таких методов куча и у всех очень разные запросы, куча событий будет очень неудобной в использовании.

А вот если за вывод сообщений отвечает приложение в целом, я бы просто передавал нужные сервисы в конструктор, или использовал бы typed service provider — обычный static-класс со свойствами-сервисами. Разумеется, в этом случае сервис будет универсальным и поэтому примитивным — на уровне мессаджбокса. Ещё один недостаток — контроллер должен будет передавать в сервис локализованные сообщения, т.е. частично выполнять функции ui-слоя.

В любом случае я не стал бы усложнять всё дело использованием DI-фреймворка. Для такой задачи это оверкилл.

S>>Лично я обычно не наследую логику от хелпер-классов (а базовый order generator — именно такой хелпер) и прячу всевозможные костыли внутрь слоя логики.

MC>Почему ты решил что это хелпер? ... Наследники нужны потому, что процедура генерации заказа для разных поставщиков немного различается.
О как.
Тогда всё плохо — начнём с того, что для добавления/удаления поставщика нужен программист
Никак нельзя ограничиться введением вынесением всяких параметров в данные/в шаблон отчёта?

Если нет, то либо расширять контракт базового класса, либо передавать сервис вывода сообщений в конструктор order generator-а.
Re[4]: Обращение к пользователю из слоя бизнес-логики
От: MozgC США http://nightcoder.livejournal.com
Дата: 29.03.11 03:19
Оценка:
Здравствуйте, Sinix, Вы писали:

MC>>А где я нарушаю LSP?

S>Так у тебя выходит, что наследник не будет работать, пока ему не передашь параметры, нафиг не нужные в базовом классе.
Параметры ему передадут в конструкторе, после этого он будет работать так как ожидается согласно контракту базового класса. Так что имхо нарушается ли тут LSP вопрос спорный.

S>Зависит от того, является ли выдача сообщений пользователю ответственностью view или нет. Если да, т.е. конкретный кусок UI отвечает за оформление сообщения (мессаджбоксом, или попапом или полоской аля IE), то 2 варианта:

S>1. Контроллер принимает делегат/интерфейс для вывода сообщений в качестве параметра.
S>2. Контроллер выставляет сообщение SomeActionCalling.
S>Выбор между двумя выше зависит от сложности контроллера. Если метод требует обязательного наличия несколько каллбэков, то годится только интерфейс в качестве параметра. Если таких методов куча и у всех очень разные запросы, куча событий будет очень неудобной в использовании.

S>А вот если за вывод сообщений отвечает приложение в целом, я бы просто передавал нужные сервисы в конструктор, или использовал бы typed service provider — обычный static-класс со свойствами-сервисами. Разумеется, в этом случае сервис будет универсальным и поэтому примитивным — на уровне мессаджбокса. Ещё один недостаток — контроллер должен будет передавать в сервис локализованные сообщения, т.е. частично выполнять функции ui-слоя.


Я не использую MVC.. Но я стараюсь делать UI нежирным (не таким тонким как в MVC но все-таки), он просто дергает методы бизнес-логики передавая им параметры заданные пользователем, и обновляет интерфейс в зависимости от ответов бизнес-логики. Поэтому мне сложно понять как это будет выглядеть при обычном для desktop (не MVC) подходе..

S>>>Лично я обычно не наследую логику от хелпер-классов (а базовый order generator — именно такой хелпер) и прячу всевозможные костыли внутрь слоя логики.

MC>>Почему ты решил что это хелпер? ... Наследники нужны потому, что процедура генерации заказа для разных поставщиков немного различается.
S>О как.
S>Тогда всё плохо — начнём с того, что для добавления/удаления поставщика нужен программист
Может я зря так выразился, что повлекло поспешные выводы. Базовый класс предоставляет реализацию по умолчанию, которая подходит практически для всех поставщиков. Вмешательство программиста нужно в очень специфичных ситуациях, раз в год.
Re[5]: Обращение к пользователю из слоя бизнес-логики
От: Sinix  
Дата: 29.03.11 03:30
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Параметры ему передадут в конструкторе, после этого он будет работать так как ожидается согласно контракту базового класса. Так что имхо нарушается ли тут LSP вопрос спорный.

Тогда не нарушается

MC>Я не использую MVC..

Так и я использую не тру-MVC
Автор: Sinix
Дата: 10.02.09
, просто термины более-менее устоявшиеся. В посте выше можно спокойно заменить view на "класс из ui-слоя (layer)", controller — на "класс бизнесс-логики" и модель — на "класс данных".

MC>Может я зря так выразился, что повлекло поспешные выводы. Базовый класс предоставляет реализацию по умолчанию, которая подходит практически для всех поставщиков. Вмешательство программиста нужно в очень специфичных ситуациях, раз в год.

Так всё равно невесело для коробочного продукта. Для вещи с пожизненным саппортом — вполне нормально.
Re[2]: Обращение к пользователю из слоя бизнес-логики
От: samius Япония http://sams-tricks.blogspot.com
Дата: 29.03.11 04:13
Оценка:
Здравствуйте, adontz, Вы писали:

A>Основная беда тут в изнлишней динамичности. Можно пойти дальше. Объявить интерфейс IMixin, к нему пару наследников и для каждого класс-реализацию-обёртку принимающую делегат. Получистя так.

A>
A>public void SomeMethod(params IMixin[] mixins)
A>{
A>  foreach (IMixin mixin in mixins)
A>  {
A>    ISupplierValidatorMixin svm = mixin as ISupplierValidatorMixin;

A>    if (svm != null)
A>    {
A>       svm(ref supplier);
A>    }
A>  }
A>}
A>

A>и вызов соответсвенно как
A>
A>SomeMethod(
A>  new ItemValidatorMixin(delegate(Item item) { return true; }),
A>  new SupplierValidatorMixin(delegate(Supplier item) { return false; }),
A>)
A>

A>Это уже не так динамично и ИМХО приемлемо.

Осталось сделать один шаг до классического Service Locator — вынести хранение и перебор миксинов в отдельный класс
Re[5]: Обращение к пользователю из слоя бизнес-логики
От: samius Япония http://sams-tricks.blogspot.com
Дата: 29.03.11 04:48
Оценка:
Здравствуйте, MozgC, Вы писали:

MC>Здравствуйте, samius, Вы писали:


S>>Я так понял, что тебе не нравится передавать callback тем, кому он не нужен. Но если SupplierService будет разбираться, куда передавать callback, а куда — нет, то это будет типичный LSP violation. Потому либо передавать callback всем фабричным методам, либо ниодному из них.

MC>А завтра появится еще какой-то наследник, и только ему нужен будет еще один параметр? Вообще не то чтобы мега косяк, но попахивает?
Когда нужен будет — передашь Registry/Service Locator или вставишь DIС.

S>>Я считаю что обращение к callback-у из генератора можно избежать, правда путем усложнения интерфейса "генератора" и перекладыванием ответственности за обращение к UI на клиента (а судя по описанному тобой сценарию, SS знает UI). Тогда не нужно будет протаскивать callback даже в фабричные методы.

MC>Можешь пожалуйста чуть подробнее написать как ты это видишь?
Могу. Но учти, что это не пуля для твоего случая, а ответ на конкретный вопрос — как не передавать callback по цепи:
вместо
generator.Generate(callback); 
// или fact.GetGen(supplier, callback).Generate(), не суть важно

сделать
generator.Generate(generator.NeedSomeValue() ? callback() : null);

Сделать так можно, но если не получится сделать это достаточно обобщенно для разных генераторов, то делать так не нужно.

MC>Хотя меня почему-то немного отталкивают эти DIC.. Ихмо они ведут к заметному усложнению кода и снижению надежности.

Ты с одной стороны хочешь гибкости, но не хочешь платить.

MC>Например, я не очень понимаю как правильно (а точнее где) инициализировать DIC. Допустим есть UI-сборка с методом Main() в котором мы будем регистрировать типы, а резолвить их будем например в сборке с BLL. Если у нас одна UI-сборка, то в принципе все ок, но у меня несколько UI-сборок использующих BLL-сборку. Соответственно, если в какой-то из них я забуду проинициализировать DIC то в рантайме при попытке резолва могу получить TypeNotRegisteredException. Если я добавлю новый тип в контейнер, то мне надо будет не забыть его регистрировать из всех сборок где может выполниться код, который этот тип попытается резолвить.

Вообще говоря суть DIC — находить зависимости самостоятельно (возможно с учетом некоторой разметки метаданными или XML), а регистрировать нужно в SL. Однако это не суть важно, т.к. в любом случае ты рискуешь не найти тип в рантайме. Решается тестами, проверяющими доступность нужных типов. Они будут относиться к интеграционным.
Код конфигурации контейнера можно вынести в одно место и сделать его ответственным за поиск реализаций интерфейсов в доступных сборках.
Re[3]: Обращение к пользователю из слоя бизнес-логики
От: adontz Грузия http://adontz.wordpress.com/
Дата: 29.03.11 08:59
Оценка:
Здравствуйте, samius, Вы писали:

S>Осталось сделать один шаг до классического Service Locator — вынести хранение и перебор миксинов в отдельный класс


А тут мы опять потеряем типизированность.
A journey of a thousand miles must begin with a single step © Lau Tsu
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.