Как стать автором
Обновить
6
0
Андрей Бычко @courage_andrey

инженер-программист

Отправить сообщение
Согласен, привязывать базовый класс к потомкам — не лучшая идея. Пример, как можно заметить, сильно упрощён (и судя по комментариям, я зря опустил ряд шагов, посчитав их слишком очевидными). Я бы решал задачу заполнения словаря warehouse не с помощью рефлексии, а предоставив классам, имеющим на то причины, возможность вызвать примерно такой метод:
abstract class Engine
{
	...

	private static readonly IDictionary<Type, Engine> warehouse = new Dictionary<Type, Engine>();

	public /*internal, protected - выбрать по необходимости*/ static void RegisterEngine<EngineT>(EngineT engine)
		where EngineT : Engine
	{
		warehouse[typeof(EngineT)] = engine;
	}
}
Цитирую Ваше сообщение:
Паттерн Стратегия не определяет, каким образом контекст получает экземпляр стратегии. Контекст может получать ее в аргументах конструктора, через метод или свойство или получать ее у третьей стороны.
Ничто не мешает сделать двигатель параметром конструктора.
И в третий раз повторяю: можно «не прибивать». И подменять во время исполнения никто не мешает. Цитирую статью по ссылке:
Обратите внимание, что классический паттерн Стратегии весьма абстрактен.

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

Если есть требование менять мотор прямо в полёте — не проблема, просто код станет чуточку длиннее.
Стратегию характеризует возможность замены этого поведения во время работы. Т.е. надо либо просунуть стратегию (двигатель) через конструктор конкретного клиента (машины, самолета), либо запросить его из клиента без указания конкретного типа стратегии.
По-моему, больше в сути стратегии всё-таки инкапсуляции выносимого поведения, чем в возможности горячей замены. Но одно другому не мешает. Говоря в терминах статьи, рикша вполне может пересесть на велосипед.
var car = new Car();
car.SetEngine(engine);

car.RemoveEngine();

Абстрагируйтесь, пожалуйста, от автомобилей. Если говорить про код, а не про автомобили, то никто и ничто в этом мире не мешает нам использовать один и тот же экземпляр дважды.
IБуксир буксир1 = new Пароход();
баржаСЗерном.ПрицепитьК(буксир1);
баржаСУглем.ПрицепитьК(буксир1);
паромСТуристами.ПрицепитьК(буксир1);

Пул — это здорово, это дальнейшее развитие идеи статьи с целью увеличить число поддерживаемых use case-ов. Можно, конечно, и так.
Стратегия в данном примере есть вынесение двигателя из транспортного средства (под катом), а дальше начинаются выкрутасы с generic-ами в попытках сократить и упростить код.
Заметьте, что в данном примере появилось ограничение new() на типе двигателя. Сделано это исключительно для краткости. ...
Да, действительно,
клиенту можно подсунуть любую стратегию
, на что и указывает данный комментарий.
Здесь должна быть… гарантия, что всегда существует не более одного инстанса любого типа, использующего данный двигатель.
Вы говорите верно с точки зрения логики реального мира, в котором один двигатель действительно нельзя засунуть в две кредитопомойки.
Видимо, топливо из общего глобального бензобака, и крутим глобальные колеса, которые прилеплены ко всем автомобилям мира?
Да, всё понято абсолютно верно. Другими словами: я родил не слишком удачный пример. Чтобы звучало чуть более логично, я попробую переформулировать задачу следующим образом: «Предположим, на складе есть один рабочий двигатель, который подходит к нескольким транспортным средствам. Таких в наличии имеется три: трактор для вспахивания земли, Волга председателя для выезда в райцентр и (внезапно) дизель-генератор для освещения сельского клуба. Трактор работает только днём по будням, председатель катается днём в выходные, а танцы в клубе происходят каждую ночь.» В таком случае метод GetFromWarehouse заменяется слесарем Петровичем в промасленной робе, который перекидывает движок из одного места в другое, а синхронизация потоков (случаи выездов в поле и в райцентр во время танцев) не нужна в связи с поставленной задачей. Но если председателю припрёт скататься в райцентр посреди уборочной (описанная Вами ситуация), то да, всё будет плохо и Петровичу придётся уходить в запой.
В чем проблема создавать двигатель заново для каждого конкретного автомобиля?
Это может быть накладно по ресурсам. Такой «двигатель» может быть не просто Console.WriteLine(«Hello, World»), а тяжеловесным ресурсом ОС, работающим через COM и маршалинг. Абстрагируйтесь от машин, они здесь мешают.
С фабричным методом я не согласен, потому что что ни Vehicle не служит для порождения Engine, ни наоборот. Про прототип — я бы сказал, не совсем 100%. Если честно, то судя по комментариям (весьма справедливым, надо признать), название статьи нужно сменить на что-то вроде «Я прицепил к стратегии generic, и смотрите сколько ещё паттернов повылазило.»
А в вашем примере Engine.GetFromWarehouse() это service locator почти в чистом виде.
Да, согласен. Я бы сказал, что последний абзац описывает процесс перехода от одного к другому.
Лучше уж
Вопросы «лучше бы ...» всегда попахивают началом священных войн.Я не люблю спорить на тему того, какой из двух подходов лучше, потому что in real life приходится, как правило, использовать оба. Всё зависит от конкретной ситуации: требования, среда выполнения, legacy, перспективы развития, командный пинок сверху и т.д.
Это не «переставлять» двигатель. Это значит: к одному двигателю прицепить все машины в мире.
Абсолютно верно.
Все дружно поедут?
А вот это уже зависит от реализации. В реальном мире один мотор засунуть в несколько машин не получится, потому что машины не сделаны потокобезопасными. :) А в мире программирования не только получится, но даже иногда и приходится. Да хранят нас средства синхронизации!
Задача, озвученная в первом предложении:
Эта статья посвящена созданию модели данных, которая красиво ложилась бы на SQL и содержала в себе «правильное» ООП наследование.

Потом перечислены четыре подхода, как это делалось, со словами «плюс данного подхода» и «минус данного подхода». Последний (мой собственный) подход сравнивается с остальными (более общепринятыми).

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

Одна задача, разные способы, сравнение. Что ещё надо? Ах да, флейм впустую развести забыл! Ан нет, всё нормально, комментаторы за меня справятся.
Тогда я предлагаю Вам, как очевидно более умному и опытному, сформулировать более точные выводы, которые больше подошли бы для этой статьи.
а давало ли какие-либо преимущества натягивание ООП модели на реляционную БД?

Проект представлял из себя многокомпонентную систему, в которой доступ к данным быстро и без головной боли делался с помощью Entity Framework («быстро» было очень важно). Всех всё устраивало. Так как с самого начала было понятно, что разработка и сопровождение растянутся на 5+ лет, то структуру данных решено было делать максимально простой и прозрачной, чтобы уменьшить головную боль себе и коллегам, которые приступят к доработке спустя пару лет. То есть, максимально использовать принцип KISS, в том числе и заставить таблицы в БД соответствовать классам уровней бизнес-логики (их было 3, если я ничего не путаю).
хочу узнать каков профит получился.

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

Наследование — не более чем инструмент. Есть места, где он уместен, и есть, где он выглядит как седло на корове. Естественно, не все проекты, над которыми я работал, активно использовали наследование. Если сейчас в силу каких-либо новшеств становится всё больше проектов, где наследование скорее вредит, то вполне естественно, что его будут использовать меньше. И я его не буду пихать куда угодно просто в силу какой-то извращённой любви. Если я шутки ради (ну и в качестве курсового проекта, чего греха таить) написал (сильно упрощая) полиморфный вирус на T-SQL, это ни разу не означает, что T-SQL — это идеальный язык для написания полиморфных вирусов. Каждому инструменту — своё место на полке.
Попробую ответить покороче. Эта статья есть суммирование тех подходов, которые использовались для максимально близкого и простого сопряжения ООП, EF и SQL на примерно пяти совершенно разных проектах, разрабатывавшихся разными командами на протяжении примерно десяти лет. И требования там тоже разные были. И DTO был, и без него тоже обходились. Другими словами, я рассказал про цвета и формы ручек молотков, которыми мы забивали гвозди, (и, чего греха таить, иногда даже шурупы с болтами). И я очень не хочу спорить на тему того, что десять лет назад мне нужно было всё это делать отвёрткой, включая забивание гвоздей и закручивание гаек.
М-м-м, чем пахнет? По-моему, священной войной. Структура таблиц в БД может как соответствовать структуре классов на одном из уровней, так и не соответствовать. Я подчёркиваю: в статье я перечислил подходы, с помощью которых одно умышленно пыталось быть приведённым в соответствие со вторым в разных проектах (и разных организациях) на протяжении 10 лет. Причин так делать тоже было много и разных, в том числе навязанных дядьками предпенсионного возраста с другой половины глобуса.
Ну и да, теперь у вас две параллельных структуры классов (для хранения и для бизнес-логики). Что у них с областями видимости и применения?
Я наблюдал и больше, чем две параллельные структуры для одного и того же набора сущностей одновременно. Всё определяется требованиями.
Спасибо, поправил.
А что такое "«правильное» ООП-наследование"?
Я имею в виду, что структура классов должна более-менее соответствовать структуре таблиц, и всё это должно подчиняться логике ООП. А не «всё в одной таблице», как в варианте classification.
Дайте я угадаю, вы используете model-first, не code-first?
Да, в 4-х enterprise-проектах с Entity Framework использовался именно model-first.
Зачем вам в этой схеме дискриминатор? А без дискриминатора это та же первая схема, только с нормальными именами таблиц, и называется это table-per-type.

И уж точно нет никакого смысла делать дискриминатор отдельной таблицей.
Поясню:
1) Совсем без дискриминатора делать не вариант, потому что иногда приходится читать просто таблицу A. А как узнать «класс» записей в ней без дополнительных join-ов?
2) Дискриминатор и триггеры позволяют избежать ситуации, когда одной записи в таблице А соответствуют записи сразу в нескольких дочерних таблицах (например, B, C и F). Это вполне может быть некоторым оригинальным архитектурным решением, но с точки зрения ООП такая ситуация является ошибкой (если быть более точным, то это подмена наследования композицией).
3) В большом проекте отдельная таблица дискриминатора может использоваться для тестов. Пример из реального проекта: тест проверял, чтобы всем классам из структуры были проставлены корректные атрибуты (DataContact, использовавшийся WCF, и прочие, поддерживавшие инфраструктуру), перечисление (ClassEnum в тексте статьи) содержало все необходимые значения из базы и наоборот, а таблицы в БД соответствовали классам уровня Business Logic.

Информация

В рейтинге
Не участвует
Откуда
Warszawa, Warszawa, Польша
Зарегистрирован
Активность