Pull to refresh

Comments 71

Программирую довольно давно. Но почему-то еще ни разу не приходилось использовать фабрики ( и тем более фабрики фабрик). Что я делаю не так?
Но почему-то еще ни разу не приходилось использовать фабрики

Ну, во-первых, это удобно, когда вы хотите использовать DI с пробросом зависимостей через конструктор (что рекомендуется). Как бы вы создавали такие классы? Все зависимости контролировали бы вручную? Предполагаю, вы пользуетесь каким-нибудь синглтоном для этого.

Во-вторых, они могут быть удобны для отложенного создания. Что-то вроде: «создай класс при помощи этой фабрики когда произойдет что-то».

А фабрика-фабрик — когда фабрике, которую вы используете для отложенного создания нужно передать зависимости.
Что я делаю не так: не занимаетесь саморазвитием.
Когда я писал комментарий выше, не надеялся на развернутый ответ. В чем тут развитие? Паттерн фабрики известен с начала 90-х. Было бы интересно, чем подход с использованием фабрик лучше просто передачи функции в качестве параметра? Может производительность системы от этого возрастет, или станет код проще поддерживать. По маленькому абстрактному примеру очень сложно понять — насколько оправдан этот подход. Есть ли у него недостатки? И да, синглтонами я тоже не пользуюсь. Знаю одно — что универсальные вещи плохо работают.
Ну, вот зачем все эти объекты, полиморфизм и прочие инкапсуляции? Можно же просто в функцию передавать контекст, содержащий всё что требуется, и с ним работать. На ассемблере разумеется.
лучше просто передачи функции в качестве параметра
Функция — та же фабрика, но обрезанная. Производительность, конечно, не вырастет — вызов функции и вызов метода приблизительно равны.

И да, синглтонами я тоже не пользуюсь. Знаю одно — что универсальные вещи плохо работают.
Как вы передаете зависимости? Ну вот где-то в контроллере необходим коннекшн к базе или к ОРМке. Как вы эту зависимость получаете?

Похоже на thunk:

(options) => () => return whatever
Options — это что? Список возможных зависимостей?

Да, набор любых параметров.

Ага. ServiceLocator, значит. Ну, посредственное решение, но ладно.
Кстати, а кто эту функцию будет вызывать? Кто будет подставлять этот СервисЛокатор? Вот у вас есть:


var WhateverFactoryFactory => (options) => () => return whatever;

Да, вы написали фабрику фабрик, просто в сокращенном синтаксисе. Для этого шаблона синтаксис непринципиален. Вам нужно её вызвать:


var WhateverFactory = WhateverFactoryFactory(serviceLocator)

Как это сделать? Как тот, кто хочет создать Whatever получит WhateverFactory? Или она будет в глобальном пространстве? Если так, то что, если для разных частей приложения нужны разные зависимости? Скажем, у вас игровой сервер, где в одном потоке может крутиться много инстансов игры?


Я понимаю, что это слишком сложная задача для тех, кто считает Редакс хорошим решением, но может повезет.

Подставлять Service Locator в качестве options это была ваша мысль — сами предложили, сами с собой поспорили).


Вообще говоря, в данном примере это не принципиально. Общая идея состоит в том, чтобы на шаге создания объекта получить зависимость и запомнить, а в процессе выполнения — обращаться к ней. В случае классов зависимости сохраняются в this, в примере с thunk для этого используется замыкание. Вы можете использовать DI контейнер как с классами, так и с такими вот конструкциями, в соседней ветке приводили в качестве примера awilix.

Подставлять Service Locator в качестве options это была ваша мысль — сами предложили, сами с собой поспорили).

Нет. То, что вы предложили — и есть сервис локатор. То, что вы называете его по модному-молодежному не делает его по сути не СервисЛокатором.


Вообще говоря, в данном примере это не принципиально

В данном примере это очень даже принципиально. DIContainer и ФабрикаФабрик — спасибо. Я получил ответ.

Не могли бы и Вы и sinc сопровождать дискуссию короткими примерами кода? Я то вот например я ни его ни Вас толком понять не могу.
Ну вот представьте, что у вас есть какой-то класс с сообственными полями и зависимостью (пример далее — смесь джавы, шарпов, Zenject и псевдокода)

public class CustomerBusinessLogic
{
  // зависимость
  private readonly ICustomerDataAccess dataAccess;

  // собственное поле
  public readonly int id;

  public CustomerBusinessLogic(int id, ICustomerDataAccess dataAccess) {
    this.id = id;
    this.dataAccess = dataAccess;
  }

  public string Name() {
    return dataAccess.GetCustomerName(id);
  }
}


Теперь каждый раз, когда его нам надо использовать — приходится вручную искать и передавать эту зависимость? Звучит очень неудобно. Но мы, как опытные разработчики, используем DI Container (например, Zenject). Он сам разруливает эти зависимости. На класс всё-равно создать надо, через new ведь не получится. Потому мы создаем фабрику:

public class CustomerBusinessLogicFactory
    : Factory<int, CustomerBusinessLogic> {}


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

class AppPart {
  private readonly CustomerBusinessLogicFactory logicFactory;

  public AppPart (CustomerBusinessLogicFactory logicFactory) {
    this.logicFactory = logicFactory;
  }

  public Start () {
    var customer = logicFactory.Create(123);
    var name = customer.Name();
  }
}
Честно говоря, пока появилась только еще одна сущность, а проще пользоваться не стало. Нужен какой-нибудь реальный пример.

Всмысле "проще использовать не стало"? Проще чем что? Чем ничего? Да, это значительно сложнее, чем ничего.


Я задал конкретный вопрос — как вы передаёте зависимости? Можете привести любой пример, где у одного класса есть зависимость от другого. Например, от соединения с базой данных. Или, к примеру, если у вас есть вьюшка, которая зависит от РендерКонтекста. Любая сторонняя зависимость.

А зачем может понадобиться фабрика фабрик? Представим, что мы создаём фабрику не в Composition Root и не биндим её в DI контейнер. Она у нас должна передаваться во время работы приложения.
Ну вот, к примеру (пример натянутый) — у нас есть список, который автоматически заполняется. Для того, чтобы он заполнился — нам надо передать ListItemFactory с методом Create(string title).
Но ListItemFactory имеет свои зависимости:


class ListItemFactory {
  public readonly string className;

  private readonly IRenderer renderer;

  public ListItemFactory(string className, IRenderer renderer) {
    this.className = className;
    this.renderer = renderer;
  }

  public IListItem Create(string title) {
    return new ListItem(title, className, renderer);
  }
}

class ListItemFactoryFactory : Factory<string, ListItemFactory> {}

class AppPart2 {
  // ...

  private ListItemFactoryFactory listItemFactoryFactory;
  private ListRenderer listRenderer;

  public void DoIt() {
    var goodButtonFactory =
      listItemFactoryFactory.Create("good-button");
    var evilButtonFactory =
      listItemFactoryFactory.Create("evil-button");

    var goodButtons = listRenderer.Render(items, goodButtonFactory);
    var evilButtons = listRenderer.Render(items, evilButtonFactory);

  }
}

Но лично у меня такого ещё не встречалось.

Вот с GUI делать что-то автоматически я бы точно не стал. Мне часто приходится писать приложения, которые должны обрабатывать много информации и что-то иногда выводить на экран пользователю. И, как правило, данные поставляет устройство (железка), которая может и отвалиться… И тут начинается. Возможно сейчас меня закидают помидорами (я готов это пережить), но чаще всего обновление экрана не нужно делать автоматически. Если данные появляются часто, то я обычно делаю для их вывода на экран таймер, который раз в несколько сотен миллисекунд просматривает, что можно обновить и обновляет.

Ну вот всё это в listRenderer и делается. Или вы всю эту логику пишете в каждом классе?

нет. один таймер, который работает в основном потоке. только так.
а кто вызывает doIt?

Ну это пример ведь. Не было цели показать как оптимизировать рендеринг UI. Была цель показать, как передаются зависимости и откуда появляются фабрики фабрик.

В конечном счете цель не фабрика фабрик, а вменяемый рендеринг и поддерживаемый код.

Ну так для поддерживаемости кода фабрики и нужны. Например, они позволяют относительно просто добавить новую зависимость, избежав последовательного "проброса" этой зависимости через всю программу.

Ну так если вы передаете в функцию другую функцию возвращающую инстанс то так то это тоже фабрика, нет?
Абстрактные фабрики, на самом деле, далеко не везде нужны. Хороший пример в данной ситуации — это кроссплатформенный GUI framework, в котором вам необходимо создавать визуальные элементы (кнопки, поля ввода и т. д.) одинаковым образом независимо от платформы. То есть, у вас будет интерфейс по типу IVisualElementFactory с методами CreateButton, CreateTextBox и так далее, у которого будут реализации, например, WindowsVisualElementFactory и MacVisualElementFactory. Клиентский код получает из фреймворка уже готовый экземпляр IVisualElementFactory и конструирует свой визуальный интерфейс, не требуя знания о платформе.
Если вы не разрабатываете подобные программные системы — такие шаблоны вам без надобности.

Это уже паттерн Bridge у вас вышел, а не Abstract Factory.

Я вас разочарую, но это именно абстрактная фабрика. Смотрите, например, вики. Это, конечно, похожие шаблоны, но абстрактная фабрика — это порождающий шаблон, а мост — структурный.
почему нельзя иметь класс button, в конструктор которого передавать для какой именно платформы его создавать? и сразу исчезнет 4 сущности

Тогда в каждой кнопке должна быть реализация для всех платформ. А если добавиться ещё одна платформа — придётся дополнительно расширять этот класс. В итоге — у нас будет один класс GodButton в котором огромный свитч на все платформы вместо N маленьких классов LinuxButton, MacButton, WinButton. Отличное "упрощение"

вот этот пример ближе к реальности. но, к сожалению, новые платформы появляются не часто, а три платформы это не много.

Win Xp, Win 7, Win 10, Ubuntu, OpenSuse, Gentoo, Android, iOs

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

Даже на двух-трех платформах — лучше разнести на неймспейсы, привязанные к этим платформам, а не размазывать логику тонким слоем по всему.

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

Редко. Как вы будете пробрасывать зависимость? Можете ответить на этот вопрос? Где вы возьмёте ссылку на соединение с базой данных. Пусть будет оно одно, но где именно вы его возьмёте?

Боюсь, что не смогу ответить на этот вопрос. Универсального решения я не вижу. Все зависит от того, как объект работает с базой. Имеется ли монопольный доступ к базе, каков объем данных передается из базы, сколько объектов должно работать одновременно… и куча всего еще.

Очень выгодная позиция: "Вы все — не ДАртаньяны, но я сам ничего написать не могу"

Туше)
Давайте попробуем на примерах.
Пусть есть источник данных БДСВ (база данных сферическая в вакууме). И это будет список List (здесь и далее c#).
Будем заполнять ее из другого потока как-нибудь так:
ThreadPool.QueueUserWorkItem(new WaitCallback(
  (object obj) =>
  {
    Random rnd = new Random();
    while (runned)
    {
      data.Add(rnd.NextDouble());
      Thread.Sleep((int)(10 * rnd.NextDouble()));
    }
 }));

Далее требуется выводить на форму (например в лейблы) средние значения по окну 10, 100 и 1000. Как бы Вы стали это делать? А если источников несколько?
Как бы Вы стали это делать?

У нас есть модель, в которой хранятся данные. Когда данные обновляются — они попадают в модель, приходит эвент, вся вьюшка или её часть помечается как Dirty, в следующий кадр все зарегистрированные рендереры выполняют свою работу.


А если источников несколько?

Как вы понимаете — неважно сколько источников и как они обновляют модель.


Я вам попытался ответить, хотя тоже мог сказать, что недостаточно данных. А теперь снова задам вам вопрос — как вы прокинете зависимости в класс?

Как вы понимаете — неважно сколько источников и как они обновляют модель.

Это очень важно! Источники могут иметь разные задержки, они могут даже отвалиться, и при этом ваше приложение не должно упасть. Что если данные обновляются очень часто? Вы будете генерировать событие на каждое добавление в базу? Иногда лучше обрабатывать сразу большой объем информации.
Зависимости в класс скорее всего передам через конструктор. Но опять же через конструктор менеджера объектов, который будет обслуживать сразу несколько объектов.
Такое ощущение, что в вашем случае типичный producer/consumer, а там уже давно всё изобретено за вас, и частично уже есть в .Net:

Отчасти на базе этих интерфейсов построена, например, замечательная библиотека Rx.Net, рекомендую ознакомиться, если вы и правда испытываете такие проблемы.
В вашем случае, вы можете унаследовать свои источники событий от IObservable, а модель — от IObserver. Реализуете свою логику обработки поступающих данных в OnNext/OnError/OnCompleted, подписываете модель на источники событий, и оказывается, что количество источников вовсе не так уж и важно, да и если они отвалятся — ничего страшного не произойдёт.
Это очень важно! Источники могут иметь разные задержки, они могут даже отвалиться, и при этом ваше приложение не должно упасть. Что если данные обновляются очень часто?

В контексте обсуждения как именно передавать зависимости — это не важно.

Да, я согласен, что мы говорим совершенно о разных вещах.
Это очень важно!

Нет, в описанной мной архитектуре это неважно.


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

А почему ему падать? Когда и если данные придут — модель обновится. Пока не пришли — модель не обновилась. Чему тут падать?


Что если данные обновляются очень часто? Вы будете генерировать событие на каждое добавление в базу?

Невнимательно читаете, если добавлений будет 1000 за кадр — они пометят вьюшку грязной и только скопом её обновят.


Но опять же через конструктор менеджера объектов, который будет обслуживать сразу несколько объектов.

Через фабрику, значит.

UFO just landed and posted this here
Я так понимаю вопрос ко мне.
Монструозная конструкция. Здесь скорее всего даже энкодер и декодер лишние.
UFO just landed and posted this here
А вы попробуйте сами для начала сделать такой класс.
Смотрите, есть два варианта (базовых, вариантов конечно же больше):
  1. Определяете интерфейсы для всех компонентов UI (Margin, Width, Measure и т.д.), делаете абстрактную фабрику, знания о платформе концентрируете в конкретных реализациях — получаете простой для сопровождения код, максимально абстрагированный от платформы
  2. Убираете «лишние сущности» и оставляете только классы элементов, передавая в конструктор платформу. С таким подходом каждый отдельно взятый визуальный элемент должен знать о каждой платформе


А теперь возьмите вариант #2, и представьте, что вам необходимо добавить поддержку ещё одной платформы.
UFO just landed and posted this here
sinc
Что я делаю не так?
скорее всего ты делаешь именно так, как надо. ты просто отказался от «золотого молотка» и в этом твоё преимущество
Jesting
Что я делаю не так: не занимаетесь саморазвитием.
а может наоборот — его саморазвитие позволило отказаться от шаблонов?

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

Можно отказаться от шаблона Abstract Factory — но не от фабрик вообще.

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

Ладно, если когда-то использовал, но потом нашёл решение получше. Тогда это похоже на саморазвитие.
А что делать, если мне надо ввести третий параметр? Например, хочу получить конструктор с АКПП или МКПП.
КПП относится к авто. А значит вы должны добавить новый интерфейс Car, от него наследуете интерфейсы Седан и Купе, а в сам интерфейс Car добавляете метод Transmission getTransmission();
Далее создаёте два класса ManualTransmission и AutomaticTransmission и реализуете создание с одной из этих трансмиссий во внутренностях фабрики. Итого: весь контракт сохранён, старый и зависимый код работает исправно, вы великолепны.
Круто, а почему так же нельзя было сделать с маркой?
Скорее вопрос здравого смысла. Если КПП можно поменять с помощью хитрых способов (например, сделать уродливый SharazhGaraj со статическими методами, который через рефлексию поменяет transmission, типа
SharazhGaraj
    .modify(car, "transmission", new AutomaticTransmission(...))
), то марку уже так просто не поменять.
UFO just landed and posted this here
Три раза описали один и тот-же метод и три раза другой, ни строчки кода ни сэкономили. Всё только для того чтобы грубо говоря обвернуть синтаксисом два класса в один(третий)… Где выгода, нафига мы это делали? Ничего не мешает при создании объекта перед использованием выбрать нужный нам вид автомобиля (тип класса), зачем эти дополнительные извращения ;) #ооп_головного_мозга
image
обвернуть синтаксисом два класса в один(третий)… Где выгода, нафига мы это делали?

А это вы поймете, как только вашим кодом «оберткой» начнут пользоваться сторонние потребители, и тут вы, как водится совершенно неожиданно, захотите слегка изменить нутро…
На мой взгляд аналогия с холдингом не очень передает суть парадигмы. Вы называете холдинг фабрикой фабрик, это действительно так, однако холдинг инкапсулирует фабрики, т.е. вмещает в себя. А в статье речь об абстрактной фабрике от которой наследуются другие, конкретные фабрики. Я вообще не понимаю (на самом деле жутко неперевариваю) когда в книгах для программистов начинают приводит примеры из реального мира. Это только сильнее запутывает. Приводите примеры чисто программисткие.
Вот здесь, достаточно ясно показана идея абстрактной фабрики
refactoring.guru/design-patterns/abstract-factory
Пардон, но по Вашей же ссылке пример на основе реальных вещей — Меб. магазина, кресла, дивана, столиков.
Вначале действительно муть со мебелью, а в конце код, который отлично раскрывает суть идеи. Нужно было сразу об этом написать, виноват, исправлюсь.

Не для красного словца, а чтоб разобраться, пишу этот коммент. Сам пару дней назад прочитал про этот шаблон и мне его смысл показался в другом.


Для начала разберем случай в статье: у вас есть «фабрика» которая создаёт автомобили Тойота и Форд, которые могут быть седанами или купе. В данном случае, как мне кажется, можно создать один класс, назовём его Car, у которого будут свойства Brand и Type например. И создавать с помощью этого класса объекты автомобилей.


Что же касается абстрактной фабрики, то, на сколько я понял, она будет полезна в случае, если вы создаёте и Автомобили и Самолеты и ещё что-то. То есть вещи не совсем совместимые. При этом Клиент может обратится к абстрактной фабрике с помощью ее «универсального» интерфейса, а она сама решает, какой объект создать: автомобиль или самолёт, с помощью интерфейсов уже конкретных фабрик автомобилей и самолетов, в которых скрыта реализация.

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

Вы сейчас сделали типичную ошибку. Суть в том, что бренд и тайп — это не свойства одного и того же обьекта. Это какие-то основополагающие вещи от которых зависит конкретная реализация. Если продолжать аналогию с машинами — то давайте представим, что тайп — это либо бензин, либо электричество. И тогда где-то внутри вашего класса Car когда я вызову метод поехали() будет if бензин поехали одним способом else другим. А если спустя время надо будет добавить ещё и водородное топливо — ваш if/else будет расти и расти и класс Car превратится в огромную простыню кода и god object к-й выбирает различную реализацию в зависимости от какого-то свойства класса, что тяжело и тестировать и поддерживать. Точнее понять проблему можно если взять не класс Car, а некий интерфейс Vehicle у к-го есть метод move(). При этом реализации у самолёта к-й vehicle и у автомобиля к-й тоже vehicle, как и у велосипеда, к-й тоже vehicle — абсолютно разные, с абсолютно разными зависимостями внутри себя. Но я, как пользователь конечного кода, не хочу ничего об этом знать. Все что я хочу это метод move() к-й как-то там внутри создаст мне форд на электротяге(к-й есть разновидность Car, к-й в свою очередь vehicle), или создаст мне Боинг(к-й разновидность Plane, к-й тоже vehicle). При этом в теории поставщик фреймворка хочет иметь возможность в будущем без боли добавить ещё и скейт(к-й тоже vehicle). И любой из этих конечных способов передвижения может сделать move() абсолютно по разному, каждый с миллионом своих собственных зависимостей(двигатель коробка колёса в различных комбинациях). А результат будет один — это перемещение из точки а в точку б. Вот тут и нужна фабрика фабрик, что бы без боли и расширяемо можно было создать какой-то конечный vehicle из нескольких групп. Фабрика фабрик позволит мне взять бензиновый форд на механической коробке и совершить перемещение абсолютно не ставя в известность как этот самый бензиновый форд на механической коробке надо собирать. А если я передумаю в любой момент бензиновый форд можно поменять на электрический, или на самокат или самолет, и все так же попасть из точки а в точку б абсолютно не зная как этот процесс совершился и как строился конечный vehicle. При этом каждая из конкретных реализаций остаётся изолированной от остальных и легко тестируемой. Надеюсь я понятнее донес то, что пытался автор. Удачи :)

Благодарю за подробный ответ.


Возможно я не так выразился, но в последнем абзаце моего прошлого комментария я вроде бы написал то, что вы подробно изложили в своём комментарии: про самолёты, автомобили и прочие транспортные средства, имеющие одинаковые методы, но разную реализацию, которые создаются при помощи абстрактной фабрики. Конечно, можно создавать и разные автомобили, как предлагает автор, но мне показалось, что это не так наглядно демонстрирует возможности абстрактной фабрики, потому как тип и бренд авто в данном случае не так сильно влияют на реализацию, как в случае авто и самолётов влияет тип транспортного средства(move() у авто «ехать по дороге», move() у самолета «лететь в воздухе»).


Спасибо за подробный ответ.

Интересно и просто описано, но хотелось бы узнать для чего и зачем нужна такая структура приложения. Примеры и параллель из жизни это хорошо, но какие преимущества дает это, может быть конкретные open source проекты привести в пример. Потому что понять необходимость и важность данного паттерна очень сложна на таком маленьком примере. Спасибо за статью:)
Один из самых наглядных примеров — это унификация кода работы с GUI-компонентами под разные платформы.

Чтобы создавать всякие кнопки, поля ввода, текстовые поля и т.д. хорошо бы иметь их генератор-фабрику, т.е. UIFactory, тогда в коде можно писать UIFactory.CreateButton(«Ok») и т.п. А ещё потребуется хранить пользовательские данные, например, аватарки — это StoreProvider.CreateAvatar(). Или работать с контактами — ContactsProvider.CreateContactFromAddessBook().

А когда вы захотите адаптировать приложение под разные платформы, вам потребуются разные фабрики под разные платформы. Можно каждый раз писать разные вызовы под в зависимости от платформы, а можно сделать фабрику фабрик — HostSpecifiedFactory.
Тогда у вас появится HostSpecifiedFactory.CreateUIFactory, и HostSpecifiedFactory.CreateStoreProvider и т.д.
Под капотом HostSpecifiedFactory будет определение типа платформы и создание конкретных типов фабрик, а с наружи единый унифицированный API.
Sign up to leave a comment.

Articles