Comments 71
Что я делаю не так?
А вообще если это не нужно лично вам, не значит, что это ненужно никому.
Но почему-то еще ни разу не приходилось использовать фабрики
Ну, во-первых, это удобно, когда вы хотите использовать DI с пробросом зависимостей через конструктор (что рекомендуется). Как бы вы создавали такие классы? Все зависимости контролировали бы вручную? Предполагаю, вы пользуетесь каким-нибудь синглтоном для этого.
Во-вторых, они могут быть удобны для отложенного создания. Что-то вроде: «создай класс при помощи этой фабрики когда произойдет что-то».
А фабрика-фабрик — когда фабрике, которую вы используете для отложенного создания нужно передать зависимости.
лучше просто передачи функции в качестве параметраФункция — та же фабрика, но обрезанная. Производительность, конечно, не вырастет — вызов функции и вызов метода приблизительно равны.
И да, синглтонами я тоже не пользуюсь. Знаю одно — что универсальные вещи плохо работают.Как вы передаете зависимости? Ну вот где-то в контроллере необходим коннекшн к базе или к ОРМке. Как вы эту зависимость получаете?
Похоже на thunk:
(options) => () => return whatever
Да, набор любых параметров.
Ага. ServiceLocator, значит. Ну, посредственное решение, но ладно.
Кстати, а кто эту функцию будет вызывать? Кто будет подставлять этот СервисЛокатор? Вот у вас есть:
var WhateverFactoryFactory => (options) => () => return whatever;
Да, вы написали фабрику фабрик, просто в сокращенном синтаксисе. Для этого шаблона синтаксис непринципиален. Вам нужно её вызвать:
var WhateverFactory = WhateverFactoryFactory(serviceLocator)
Как это сделать? Как тот, кто хочет создать Whatever получит WhateverFactory? Или она будет в глобальном пространстве? Если так, то что, если для разных частей приложения нужны разные зависимости? Скажем, у вас игровой сервер, где в одном потоке может крутиться много инстансов игры?
Я понимаю, что это слишком сложная задача для тех, кто считает Редакс хорошим решением, но может повезет.
Подставлять Service Locator в качестве options это была ваша мысль — сами предложили, сами с собой поспорили).
Вообще говоря, в данном примере это не принципиально. Общая идея состоит в том, чтобы на шаге создания объекта получить зависимость и запомнить, а в процессе выполнения — обращаться к ней. В случае классов зависимости сохраняются в this, в примере с thunk для этого используется замыкание. Вы можете использовать DI контейнер как с классами, так и с такими вот конструкциями, в соседней ветке приводили в качестве примера awilix.
Подставлять Service Locator в качестве options это была ваша мысль — сами предложили, сами с собой поспорили).
Нет. То, что вы предложили — и есть сервис локатор. То, что вы называете его по модному-молодежному не делает его по сути не СервисЛокатором.
Вообще говоря, в данном примере это не принципиально
В данном примере это очень даже принципиально. DIContainer и ФабрикаФабрик — спасибо. Я получил ответ.
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);
}
}
Но лично у меня такого ещё не встречалось.
Ну вот всё это в listRenderer и делается. Или вы всю эту логику пишете в каждом классе?
Если вы не разрабатываете подобные программные системы — такие шаблоны вам без надобности.
Это уже паттерн Bridge у вас вышел, а не Abstract Factory.
Тогда в каждой кнопке должна быть реализация для всех платформ. А если добавиться ещё одна платформа — придётся дополнительно расширять этот класс. В итоге — у нас будет один класс 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, в следующий кадр все зарегистрированные рендереры выполняют свою работу.
А если источников несколько?
Как вы понимаете — неважно сколько источников и как они обновляют модель.
Я вам попытался ответить, хотя тоже мог сказать, что недостаточно данных. А теперь снова задам вам вопрос — как вы прокинете зависимости в класс?
Как вы понимаете — неважно сколько источников и как они обновляют модель.
Это очень важно! Источники могут иметь разные задержки, они могут даже отвалиться, и при этом ваше приложение не должно упасть. Что если данные обновляются очень часто? Вы будете генерировать событие на каждое добавление в базу? Иногда лучше обрабатывать сразу большой объем информации.
Зависимости в класс скорее всего передам через конструктор. Но опять же через конструктор менеджера объектов, который будет обслуживать сразу несколько объектов.
Отчасти на базе этих интерфейсов построена, например, замечательная библиотека Rx.Net, рекомендую ознакомиться, если вы и правда испытываете такие проблемы.
В вашем случае, вы можете унаследовать свои источники событий от IObservable, а модель — от IObserver. Реализуете свою логику обработки поступающих данных в OnNext/OnError/OnCompleted, подписываете модель на источники событий, и оказывается, что количество источников вовсе не так уж и важно, да и если они отвалятся — ничего страшного не произойдёт.
Это очень важно! Источники могут иметь разные задержки, они могут даже отвалиться, и при этом ваше приложение не должно упасть. Что если данные обновляются очень часто?
В контексте обсуждения как именно передавать зависимости — это не важно.
Это очень важно!
Нет, в описанной мной архитектуре это неважно.
Источники могут иметь разные задержки, они могут даже отвалиться, и при этом ваше приложение не должно упасть.
А почему ему падать? Когда и если данные придут — модель обновится. Пока не пришли — модель не обновилась. Чему тут падать?
Что если данные обновляются очень часто? Вы будете генерировать событие на каждое добавление в базу?
Невнимательно читаете, если добавлений будет 1000 за кадр — они пометят вьюшку грязной и только скопом её обновят.
Но опять же через конструктор менеджера объектов, который будет обслуживать сразу несколько объектов.
Через фабрику, значит.
Смотрите, есть два варианта (базовых, вариантов конечно же больше):
- Определяете интерфейсы для всех компонентов UI (Margin, Width, Measure и т.д.), делаете абстрактную фабрику, знания о платформе концентрируете в конкретных реализациях — получаете простой для сопровождения код, максимально абстрагированный от платформы
- Убираете «лишние сущности» и оставляете только классы элементов, передавая в конструктор платформу. С таким подходом каждый отдельно взятый визуальный элемент должен знать о каждой платформе
А теперь возьмите вариант #2, и представьте, что вам необходимо добавить поддержку ещё одной платформы.
Что я делаю не так?скорее всего ты делаешь именно так, как надо. ты просто отказался от «золотого молотка» и в этом твоё преимущество
Jesting
Что я делаю не так: не занимаетесь саморазвитием.а может наоборот — его саморазвитие позволило отказаться от шаблонов?
ну а по статье — довольно просто и доходчиво описано.
а может наоборот — его саморазвитие позволило отказаться от шаблонов?
Можно отказаться от шаблона Abstract Factory — но не от фабрик вообще.
а может наоборот — его саморазвитие позволило отказаться от шаблонов?Как можно отказаться от того, чего никогда не использовал? Похоже не никогда не ездящего за рулём, который решил отказаться от езды за рулём.
Ладно, если когда-то использовал, но потом нашёл решение получше. Тогда это похоже на саморазвитие.
Далее создаёте два класса ManualTransmission и AutomaticTransmission и реализуете создание с одной из этих трансмиссий во внутренностях фабрики. Итого: весь контракт сохранён, старый и зависимый код работает исправно, вы великолепны.
SharazhGaraj
.modify(car, "transmission", new AutomaticTransmission(...))
), то марку уже так просто не поменять.![image](https://habrastorage.org/getpro/habr/comment_images/95a/687/5c9/95a6875c9562fac6e7b205d3ba313829.jpg)
Вот здесь, достаточно ясно показана идея абстрактной фабрики
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() у самолета «лететь в воздухе»).
Спасибо за подробный ответ.
Чтобы создавать всякие кнопки, поля ввода, текстовые поля и т.д. хорошо бы иметь их генератор-фабрику, т.е. UIFactory, тогда в коде можно писать UIFactory.CreateButton(«Ok») и т.п. А ещё потребуется хранить пользовательские данные, например, аватарки — это StoreProvider.CreateAvatar(). Или работать с контактами — ContactsProvider.CreateContactFromAddessBook().
А когда вы захотите адаптировать приложение под разные платформы, вам потребуются разные фабрики под разные платформы. Можно каждый раз писать разные вызовы под в зависимости от платформы, а можно сделать фабрику фабрик — HostSpecifiedFactory.
Тогда у вас появится HostSpecifiedFactory.CreateUIFactory, и HostSpecifiedFactory.CreateStoreProvider и т.д.
Под капотом HostSpecifiedFactory будет определение типа платформы и создание конкретных типов фабрик, а с наружи единый унифицированный API.
Абстрактная фабрика на пальцах