Как стать автором
Обновить

Комментарии 531

То есть ты взял и превратил монгу в тупой key/value store. Если бы в примере была реляционная БД, выглядело бы еще нелепее.
Ну, почему же. Если что-то еще нужно от базы — нужно реализовать аналогично. А если надо перейти на реляционные — то это должно быть прозрачно для пользователя, и выглядело бы точно также.
Подобная абстракция приводит все базы данных к общему знаменателю. И либо ты получаешь оракл, работающий как k/v store, либо переход не будет «прозрачным для пользователя».

Диагностирую здесь синдром архитектора :)
Ну давайте подумаем. Что с точки зрения ООП нужно от БД? Правильно только сохранение и восстановление объектов. Это тут и показано. Если что-то нужно еще — то нужна уже реляционная база, и там нужно добавить вызов хранимых-процедур. И все.
Хранимые процедуры есть далеко не везде. Там где есть — сильно различаются по возможностям.

А что такое «точка зрения ООП» — мне непонятно. Есть точка зрения приложения. Приложению нужно получать данные. Мне сложно представить себе такое хоть сколько-нибудь полезное приложение, которому будет достаточно такого вот урезанного доступа к данным.

Именно имея такое приложение, я и решил попробовать MongoDB. Ну, и потом, все что нужно дополнительно можно легко дореализовать. Но речь идет не о том, сколько всего нужно, а о том, как к этому всему легко и естественно обращаться.
Реляционная база данных — это не хранимые процедуры, о чем вы???? MуSQL долгое время жил без таковых, но при этом был реляционной.
Реляционная база данных — это связи между обеъктами и выполенение четырех принципов ACID.

Как вы реализруете транзакционность в вашем прмере? Рано или поздно она понадобится, пример я уже вам приводил в прошлый раз.
Привидет пример как вы делаете выборку из вашей «базы», через тот же LINQ, например, не просто загрузку одной сущности.
И таких вопрсоов все больше и больше.
Реляционная база данных — это естественно выполнение нормальных форм. Но реализация реляционных баз данных без хранимых процедур — это не серьезно.

Как только мне понадобится делать сложные выборки — я перейду на реляционную базу данных и напишу select в хранимой процедуре. И не буду никогда заниматься глупостями в виде LINQ.

Транзакции на уровне приложения — тоже бред, им место в хранимых процедурах.

Но и потом, я же не говорю, что то что я тут реализовал достаточно на все случаи жизни. Но все это легко дореализуется в подобном стиле.
«Что с точки зрения ООП нужно от БД? Правильно только сохранение и восстановление объектов. „
То есть поиск не нужен? Типовая операция “найди все контракты за этот год» в ООП никак не отражается?
Нет, это проблематика баз данных.
С какой это радости? Это бизнес-операция, выросшая из бизнес-потребности, почему она не отражается в коде приложения?

(а представьте себе приложение вообще без БД, в нем такой операции быть не может?)
Если такая операция реализуется в приложении без баз данных — значит это ПО слишком низкого качества.
Аргументируйте.

Ну и да, вы проигнорировали первый вопрос. На всякий случай поясню: вот у меня есть бизнес-приложение, в виде, скажем, веб-сайта. Оно, натуральным образом, состоит из слоев (layer, не tier): представление, бизнес-логика, хранилище. Соответственно, представление и бизнес-логика реализованы в рамках ООП (это условия данной задачи, а не всемирное правило).

Теперь, внимание, вопрос: как так выходит, что решаемая пользователем бизнес-задача («найти все контракты за год») есть в представлении (иначе пользователь ее не решит), есть в бизнес-логике (представление контактирует только с бизнес-логикой), но не имеет выражения в ООП? У нас в приложении внезапно есть не-ООП часть?
Давайте договоримся на берегу, что БД — у нас реляционная. Так?

Тогда я вас не понимаю, вы никогда не догадывались, что работа реляционной базы является не-ООП частью?
Или вы не знаете, что обращение к базам по определению не является объектным (а всего лишь где-то лучше/где-то хуже эммулирует объектный стиль)?
И кстати, именно поэтому я предлагаю использовать хранимые процедуры, т.к. их вызов наименее разрушает ООП-стиль
Вы просто никогда не админили сколько-нибудь сложную базу с хранимыми процедурами.
Никакой ООП-пуризм не стоит мучений от внезапной необходимости обновить и подлатать хранимые процедуры на полудюжине боевых узлов БД работающего проекта.
Дорогой мой, я конечно не админ, но наш продукт стоит на MS SQL Server, и никто пока из админов не жаловался на такие высосанные из пальца проблемы. А обновления мы шлем достаточно часто.
Одно-единственное развёртывание продукта, никаких «маленьких патчей для больших клиентов», никакого шардинга (судя по всему) — да просто ваши админы в раю живут, можно сказать:).
Ну не знаю, если кому-то сильно приперло — то есть понятие транзакции :)
Вот-вот.
И на одном шарде из20, скажем, транзакция подвисла и отвалилась из-за непредвиденного чего-то.
Ура! Теперь у вас есть 19 серверов с одной версией хранимой процедуры и один — с другой. Осталось найти, какая где.

Плюс очевидный момент — изменение архитектуры таблиц это долгий и дорогой процесс. Не надо этого делать практически никогда. Собственно, из этих посылок и появилась профессия DBA (который Architect).
Обновление версии на сервере приложений — гораздо более дешёвый манёвр, и гораздо лучше отработанный. Имеет смысл ограничиваться им по возможности.

Ну и да, я вот толком не понимаю как хранимые процедуры тестировать. То есть с CIв таком подходе тоже сложности получаются.
> Обновление версии на 20 серверах приложений

Спорно, но тут моя компетенция кончается…
это фраза должна была появиться в голове еще до написания статьи
А вам бы вообще молчать, так как не понимаете о чем пишите
«Тогда я вас не понимаю, вы никогда не догадывались, что работа реляционной базы является не-ООП частью?»
Ненене, вы не торопитесь, вам до БД еще надо два слоя пройти.

«Или вы не знаете, что обращение к базам по определению не является объектным (а всего лишь где-то лучше/где-то хуже эммулирует объектный стиль)? „
А то, что вы пишете выше (в посте) и ниже (в комментариях, “мыслите объектно») — это что тогда?

Если же мы говорим о БД-объектной, такой о какой идет речь здесь MongoDB.

То я как раз показал паттерн, где обращение к ней будет объектным.

Тут же предлагаю использовать LINQ — которое совершенно противоречит объектности. И под каким бы соусом это не подавалось бы — оно объектным не станет.

Производители MongoDB не предоставляют мне истинно объектного способа использовать их объектную базу. Поэтому я и пишу свои адаптеры. Можно написать адаптер и к вашей задачи «найти все контракты за год». Нужно лишь в объекте Контракты ввести понятие полей-ограничений, и подгружать только те объекты которые соответствуют эти условиям. Вот тогда то и будет объектная логика загрузки. Сейчас же производители объектных баз по привычке размышляют в терминах select запроса, отсюда и корявость.
С точки зрения ООП. Такая операция реализуется так: получаем идентификацию всех контрактов (куда входят даты) — и потом уже получив пробегаем по ним отбирая нужные. Но так как на стороне клиента — это делается медленно, то такие запросы выполняют базы данных на стороне сервера.
То есть вы предлагаете отказаться от столь любимого вами ООП ради быстродействия?

Впрочем, мне интересно: а вы в курсе, что в том же .net уже давно есть технологии, позволяющие решать подобные задачи не отклоняясь от ООП, но при этом с быстродействием операции, выполняемой на стороне СУБД?
> не отклоняясь от ООП

О чем речь, думаю у нас разные оценки этих «технологий»
Вот об этом, конечно же:
[...]
return AllContracts.Having(contract => contract.Date.Year == 2005)
[...]
Снова LINQ мне хотите подсунуть? Или вы действительно думаете, что этот код соответствует ООП?
Ваш код соответствует ООП в гораздо меньшей степени. Оттого, что в коде есть слово class, код не станет ООП. Как и использование рефлексии его таковым не сделает.
Что в моем коде не соответствует ООП, а то ля-ля все горазды
Всё. Начиная с копипасты и заканчивая рефлексией
Видите ли, рефлексия используется в одном конкретном месте. Вы же предлагаете что? Загадить весь проект обращением к базе вместо этого так?
Я предлагаю почитать фаулера в частности раздел с паттерном Repository. А потом домашнее задание — описать репозиторий так, чтобы он не зависел от типа хранилища. И не будет обращений к базе данных, и будет ООП во все поля и мир во всем мире.
> описать репозиторий так, чтобы он не зависел от типа хранилища

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

Так паттерн Репозиторий тут не лучший вариант, в то время как мне легко поменять объект Database, на DatabaseNew и все.
Да, чтобы взять и поменять монго на другое хранилище.

Паттерн Репозиторий никак вам не помешает перейти на другое хранилище. Подсказка. Репозиторий не синглтон.

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

Задача с двумя звездочками. Реализуйте сохранение разных частей одного объекта в разные хранилища.
Да легко же. Я же вам и говорю, делаю наследников от Database — и все. А TaskManager решает когда подменить ссылку на текущую базу. Что может быть проще?

Ваш же паттерн Репозиторий предлагает стругать интерфейсы — нафиг не кому не нужны, да к тому, же тогда нет общего класса типа Database, где находится общая логика всех хранилищ. Я уже это не раз повторял тут и в прошлой статье.

P.S. И не надо считать себя шибко умным и говорить в превосходной форме — не солидно. Я уж и поболее знаю, но ведь не выпендриваюсь, а?
> Я уж и поболее знаю, но ведь не выпендриваюсь, а?
Взаимоисключающие параграфы. You've made my day ©.

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

Еще раз, как сохранить разные объекты в разные хранилища (одновременно используется 2 хранилища в приложении, да-да, бывает и такое). Я так понимаю, что наследоваться от разных Database? А что тогда делать с таск-менеджером?
А как сохранить разные части одного объекта в разные хранилища. Ответа я не получил.
«Ваш же паттерн Репозиторий предлагает стругать интерфейсы — нафиг не кому не нужны, да к тому, же тогда нет общего класса типа Database, где находится общая логика всех хранилищ»
Простите, а на основании какого определения паттерна Repository вы делаете такие выводы? Есть прекрасные обобщенные решения Repository{of T}. А интерфейсы вообще в паттерне никак не упоминаются, они берутся в конкретных реализациях, и обычно только из желания сразу сделать удобную абстракцию для юнит-тестирования.
Причем рефлексия — вполне объектна
Хотя согласен, что нужна только тогда, без нее не обойтись. Тут не обойтись. Хотя есть близкое решение на обобщенных классах, но не то см. ниже в комментариях вариант от lair
А почему нет? AllContracts — объект класса «коллекция контрактов», имеющий метод «все, удовлетворяющие условию», условие передается специлизированным классом (паттерн QueryObject).

Возражения?

(Заметим, LINQ тут не при чем)
Вот, если бы вы это так написали, другое дело, но у вас оператор лямда выражения =>… перепишите без него, тогда посмотрим.
Гм. А как по-вашему, во что разворачивается «оператор лямбда-выражения»?
Во уж не знаю… но его не разворачивать нужно, а убрать эту глупую нотацию
То есть, вы не разобравшись, что это такое, считаете, что имеете право судить о том нужно оно или нет?
А на хрена мне знать как работает хрень?
Вероятно, чтобы не изобретать хрень самому?
Это типа посмотреть как хрено предлагают сделать, чтобы так не делать. Не, уже насмотрелся на разные хрени, экономлю время теперь даже на просмотр, задницей уже чую где дрянь.
Судя по вашему коду, вы много чего задницей делаете ;)
А судя по вашей болтовне, вы много только языком…
Ну да, девушкам нравится; р
Я этого хабраюзера лично знаю и да, он на многое способен, в том числе и языком. В смысле объяснить. А вам рекомендую долбить матчасть — полезная вещь.
Я так и думал, что не знаете. Почитайте Джона Скита, C# in Depth, раздел 9.3, Expression Trees. После этого вы узнаете, что лямбда-выражение может разворачиваться в AST, то есть — аналог Query Object.

Я, очевидно, могу переписать этот код на чистые Expressions, но это будет громоздко. Результат все равно будет ровно тем же самым. Эта, как вы выражаетесь, «глупая нотация» сэкономила мне шесть-восемь строчек кода.

Вы же foreach используете? Или тоже вместо него явно обращаетесь к методам IEnumerator?
О! В этом и дело — «громоздко». А у меня в реализации нет!
У вас в реализации и поиска нет. Так что сравнивать бесполезно.
Я вам показал как это реализовывать, поэтому сравнивать легко.
Поиск? С произвольными критериями? У вас этого нет в коде нигде.
А с произвольными критериями — никому на практике не нужно! Запомните это. Поиск идет по идентификации, или по ряду типовых ограничений в виде периода по дате.

Как только нужен будет поиск более серьезный — нужно взять и перейти на SQL, написав хранимую процедуру.

Читайте самое начало, предложение было сделано для задач с небольшим объемом. На большие объектные базы не годятся. И нечего тут насиловать продукты, которые не для этого.
tac, вы так любите хранимки… А расскажите как вы версии хранимок контролируете и на продакш их выкладываете. Правда, интересно ;)
Вот были бы вы не троль, я бы вам рассказал… а так зачем?

Вы как детё, честное слово, хранимки — это текст в .sql файле. Что такое SVN слышали? Для файлов годится? А зачем спрашивать?

Или не умеете запускать .sql скрипты на базу?
Ага. А как вы, бишь, гарантируете порядок накатки и целостность базы?
Снова не существующую проблему придумали :) запускается же не вовремя работы пользователей.
скажите это проектам с аптаймом в 99.9 процентов.
Еще раз целостность гарантирует транзакция, но выполнять горячие копирование — это вообще-то бред. Нет таких «больших клиентов», которые не могут подождать до утра.
поверьте, есть такие клиенты. мир не ограничивается миром Ынтырпрайз ПО. Попробуйте например систему управления работой скорой помощи попросить подождать до утра пока ваша база «безопасно обновляется»
Вы о транзакциях слышали? Так в чем проблема?
вам уже выше описали. например, случай с шардингом. кроме того обновление в транзакции вполне может вызвать (и скорее всего вызовет) блокировку и задержку работы. хотя о чем я говорю. ведь ваша библиотека не для таких случаев, она же для одного пользователя максимум.
«Еще раз целостность гарантирует транзакция,»
Не гарантирует. Когда вы накатываете обновление, состоящее из полусотни скриптов на нескольких связанных SQL-серверах, никакие транзакции не помогут.

«Нет таких «больших клиентов», которые не могут подождать до утра. „
Это у вас их нет.
А какая разница, когда запускается? Порядок-то все равно должен быть фиксированный, и целостность базы все равно надо гарантировать.
Порядок чего? Сколько я помню нет таких хранимых процедур, которые закидывать надо в нужном порядке. Но если сильно нужно, ну выполните это в транзакции, только по мне это всегда практически лишние.
«Сколько я помню нет таких хранимых процедур, которые закидывать надо в нужном порядке.»
Ну, хранимые процедуры, прямо скажем, вообще весьма к этому пофигистичны. Но надо же быть честным и понимать, что модификации БД хранимыми процедурами не ограничиваются.
Ну, блин, вы уж вначале определитесь или мы за хранимые процедуры говорим, или за все администрирование БД
А это сильно взаимосвязанные вещи. Прямо скажем, одно — часть другого.
И как, всегда все легко накатывается/откатывается?
«А с произвольными критериями — никому на практике не нужно!»
640k достаточно всем, да?

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

«Как только нужен будет поиск более серьезный — нужно взять и перейти на SQL, написав хранимую процедуру. „
Вот ведь уперлись вы в хранимые процедуры… зачем писать хранимую процедуру, когда нужно простое условие отбора по двум-трем полям?

“Читайте самое начало, предложение было сделано для задач с небольшим объемом.»
А при чем тут объемы вообще? я о задачах говорю.

Ну и да, ваше решение — оно, типа, универсальное, статья ведь называется «Отделение логики базы данных», а не «работа с объектными БД».
А сейчас вы говорите — совсем о других задачах. Я такие не решаю, просто за то, что заказчику «надо было искать по дате, а сегодня — по категории» — я беру дополнительные деньги, и изменяю select и GUI. Если же вы говорите о динамической подмене без вашего присутствия — это другого рода задачи. Я о них не говорил.
«Я такие не решаю, просто за то, что заказчику «надо было искать по дате, а сегодня — по категории» — я беру дополнительные деньги, и изменяю select и GUI.»
Угу. Вот только заказчик, когда стоит выбор между изменением с ценой около рабочего дня и изменением с ценой около недели, выбирает почему-то первых исполнителей.
А так все таки у вас тоже не полностью автоматизировано. Тогда лирика.

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

«А вот сделать доработку в предлагаемой мной идеологии будет как раз быстрее и естественнее. „
Естественнее лично вам? Может быть, с этим спорить сложно. Естественнее c#-разработчику, не имеющему SQL-опыта? Вряд ли.

Ну а что касается “быстрее» — тут вы заведомо неправы, потому что в вашем случае надо менять (и развертывать) два фрагмента системы вместо одного.
Да, конечно, я всегда готов отказаться от ООП ради быстродействия.
Правда только тогда, когда быстродействие реально нужно, а не впрок
Еще: может я, конечно, что-то и забыл, но этот обильный рефлекшон не пойдет на пользу производительности :)
И это заметьте в Java, а C# вообще-то должно быть еще лучше… но надо проверять (еще не добрался)
Что-то я не пойму… автор пытается, типа, переизобрести/имплементировать ActiveRecords?
В комментариях к своей первой статье автор отказался читать про паттерны типа Repository или Table и уперся в изобретение свеогое квадрато-колесого велосипеда и данная статья призвана была пояснить его мысль.
Дорогой мой, не стоит выдавать свои соображения за мои. Я в курсе этих паттернов — но не считаю их более удобным, чем то, что я здесь предложил.
Почему же тогда ваши топики одни за другим набирают минусы и комментарии непонимния? /Если что, это риторический вопрос, не требующий ответа.../
Я тоже в недоумении, почему многие люди не понимают элементарных вещей и минусуют, без объяснения претензий ?!
Вам все в комментариях тут и там все доходно объясняют, только хотите ли вы это слушать? может это вы недопонимаете элементарных вещей?
Где?
Ну, и какой по вашему элементарной вещи я не понимаю?
Можете ли Вы предложить пример как надо сохранить объект в MongoDB, покажите, тогда поговорим чем от лучше/хуже. А так вы пока воздух сотрясаете, и не более того
Правильнее сказать применить ActiveRecords с суровой действительности. Впрочем думаю отличия есть.
А можно минусующих попросить представить хоть маломальские аргументы?
Хабр уже не торт, школота набежала и всё такое. Забей. Пойти и децл покодить будет гораздо более полезным времяпрепровождением. :)

Я вот не вижу смысла сюда писать статьи.
Ок, школота — так школота. Думал тут умеют серьезно обсуждать
Читать Фаулера. До полного просветления. И не изобретать очередной велосипед.
А Вы хоть попробуйте рассказать, что у него может быть лучше?
Ок, но паттерн Репозиторий — на порядок хуже.
Вот смотрим хотя бы на эту реализацию паттерна Репозиторий

И вы хотите меня убедить что обращение

using (var dataContext = new HotelsDataContext())
{
var hotelRepository = new Repository(dataContext);
var cityRepository = new Repository(dataContext);

City city = cityRepository
.SearchFor(c => c.Name.StartsWith("Ams"))
.Single();


чем то лучше

City city = new City("Ams");
city.Load();

По коду выше я понимаю, что именно этот код делает.
По нижнему — нет.

Поэтому да, код выше — лучше.
Ваши привычки тут не имеют ни какой роли. А привязывать и раскидывать типо-зависимый код по проекту — это просто ужас, а не код.
Жжете ;)
Да уж, с несерьезными людьми — говорить серьезно не получается.
Вот тут я с вами внезапно полностью соглашусь ;)
Пишите бредокод дальше, не буду вам мешать.
Спасибо, вы мне как-то и раньше не мешали ;)))
Если точнее

City city = new City( 0, «Ams_»);
city = city.Load();
Это как же надо привыкнуть писать не естественные обращения к объектам, чтобы элементарное сохранение объекта, осуществлять путем написания кучи странных строк кода, вместо того чтобы сказать

city.Save() и забыть. И эти люди меня еще чему то учат ;)

Вот оно. Казалось бы, объектные базы такие как MongoDB должны лишь способствовать усвоению теории ООП. Но по факту оказывается, что люди как не знали, так и не знают. Так зачем же вам MongoDB, если оно вам не способствует приведению кода к ООП стилю?
Я конечно не такой талантище в области ООП как вы, о хитроумнейший tac, но будет ли мне позволено заметить, что ваш код мало того что жестоко насилует принципы SOLID (что еще простительно), но и с громким грохотом упадет при первом же race condition, разметав по километровой окрестности мозги того бедолаги, которому придется поддерживать этот код?
Да, глупости вы говорите, что он нарушает? Какие именно проблемы вы видите? Ну, и не надо предъявлять претензии к побочным вещам, когда речь сейчас идет о другом.
Если переписать ваш код так, чтобы он был понятен и привычен нам, убогим быдлокодерам, то внезапно получится тот «репозитарий» про который вам регулярно напоминают.
Ну, перепишите — посмотрим, что получится :)
За меня это уже сделал майкрософт. Entity Framework, называется.
Репозиторий, пожалуй, был в прошлой статье. В этой уже активрекорд.
Имхо, до активрекорд тут далеко.
Но ближе к ней чем к чему-либо ещё.
Эти две статьи про одно и тоже :)
Скажем так, вбирает в себя лучшие качества Репозиторий + активрекорд.
Что может быть проще логики, создать объект в котором поместить условия поиска и выполнить загрузку объекта, которому соответствуют искомые параметры.

Разучились мыслить объектно? Так надо элементарно забыть, что всякие операции с объектами проводятся в базе данных, и вызывать методы объекта.

У прикладного разработчика не должно быть понятия «работаю с базой». Есть лишь понятие «работаю с объектами», как они сохраняются/восстанавливаются — это не его уровень компетенции. Тот кто будет настраивать работу приложения (через класс TaskManager) — он её прозрачно настроит.
Это у вас в джаве так принято? Я то вот как-то привык что разработчики обычно компетентны. А некомпетентные не нужны :)
Причем тут джава? Код на шарпе :)

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

user = User.find_by_email("me@example.com");

user_orders = Order.where(user_id: user.id)


А то что у тебя — это онанизм какой-то :)
Да, на всякий случай, в коде класса User _нет_ метода find_by_email, а в коде класса Order — метода where. Это всё «из коробки».
И что тут естественного?
Всё. До последней буквы. Нет ничего лишнего. Код читается как художественный текст. А теперь давай посмотрим, как это выразить в твоей парадигме.
user = new user (0,"",«me@example.com»).Load();

user_orders = new order(user.id).Load();
Ну и че это за 0 и ""? И нахера мне у нового объекта вызывать Load. Я ж его уже создал, не?
Это параметры конструктора. Создали вы объект для поиска, и вам теперь нужно загрузить его полные данные.

Не знаю от куда нотация

user = User.find_by_email(«me@example.com»);

Но она видимо предполагает создание специального статического метода find_by_email — зачем?
Я не хочу создавать объект для поиска, это неестественно. Я хочу сразу получить объект.

Язык — или руби или питон. Специального статического метода там не нужно, курите метапрограммирование.
Ладно все ясно — ваших глупостей я наслушался.
Если что — в сишарпе, начиная с 4-го тоже так можно сделать ;)

А вообще, tac — вам пора придумывать свой язык программирования. Правильный, умный и воистину объектно-ориентированный. Куда ж до вас майкрософту ;)
Руби :)
Метод find_by_email — это сахар для

User.where(email: "me@example.com").first


Причем он нигде не определен и писать его не нужно. В похожей манере можно переписать вторую строчку

Order.find_all_by_user_id(user.id)
Дорогой, мы говорим на C#, все остальное меня не интересует.
Не знаю как ты, а я говорю про естественные интерфейсы. В общем ладно, история всех расставит по своим местам :)
Естественные интерфейсы? На каком это языке?
В четвертом сишарпе можно сделать то же самое. И даже через такой же механизм ;)))
Продемонстрируй, тогда поговорим
Ты что творишь? Представляешь, какого говнокода он теперь сможет натворить? :)

(если освоит, конечно)
Не думаю. Он сейчас скажет, что майкрософт — идиоты и придумали ненужную хрень.
А можно от туда выбрать, именно то о чем мы говорим, и написать на наших примерах.
Не могу лишить вас удовольствия хоть что-нибудь почитать.
> Дорогой

Хамите, парниша :)
По крайней мере, на «ты» я с Вами не переходил
Ути-пути, какие мы вежливые. :)

Ну и объясните мне как появляется метод find_by_email — если его не надо определять и писать?
method_missing и немного светлой магии. Читай ссылку от retran.
Но вы все равно пишите метод, реализующий это. Польза этого сильна сомнительна. Аналогично макросам в С++, что лучше не использовать.
Я же говорил ;)
Но она видимо предполагает создание специального статического метода find_by_email — зачем?

Чтобы найти пользователя с данным email :) При этом мы можем прозрачно использовать все нюансы системы хранения (вплоть до хранимых процедур в БД), и в то же время быстро её поменять, изменив только код инициализации или конфиг.
А в динамических языках система сама нам этот метод и «напишет».
user_id — это первичный ключ у ордера? Не было таких установок. Вот дополнение

user_orders = Order.where(user: user.id, is_archived: false)
tac, я вас поздравляю. Вы наконец-то объяснили убогим хабрахомячках зачем нужен DAL и куча паттернов его реализации.
Я вам всего лишь показал одну (лучшую реализацию) DAL
Все еще продолжаете жечь ;)
А у вас пока нет серьезных возражений, так почему же не словить лулзов… как только разговор станет серьезнее, так и я начну говорить
tac, я вам хочу сказать «спасибо». Я с ваших статей ловлю лулзов и поднимаю себе настроение больше чем от чего-либо еще на хабре ;)

А серьезных аргументов и не будет пока вы не начнете видеть менее серьезные.
«Что может быть проще логики, создать объект в котором поместить условия поиска и выполнить загрузку объекта, которому соответствуют искомые параметры. „
Угу. Вот только это должен быть другой объект, нежели тот, который вы загружаете и сохраняете. И объясняется это очень просто: бизнес-сущность ничего не знает о том, что она хранится в БД. Ей это не надо. Ее интерфейс — это те бизнес-действия, которые она совершает. Это как книга, которая ничего не знает о шкафу, в котором она стоит.

Соответственно, принцип единой ответственности говорит нам: положите бизнес-ответственности в бизнес-объект, а ответственности по хранению — в объект хранилища.

Это как раз и есть “мыслить объектно».
Нет, если бы вы читали Буча, то знали бы, что сохранность объектов — это одно из их свойств. Т.е. именно объект и должен уметь сохраняться. Как именно, так он и не знает — это как решит TaskManager
Вообще-то, Буч пишет о persistance в другом контексте немного.

И не забывайте про доменную модель, без нее никуда, а в ней нет persistance.
а это что за жесть?

m.Name == «GetCollection»
А попробуйте понять почему метод GetCollection имеет такую строку :)
Вам намекают, что искать метод перебором — говнокод, и что «магические» строки в коде — еще больший говнокод.
Серьезно? А вы вот возьмете, такой умный и найдите этот метод не перебором
Вперед, попробуй применить и получить тот же результат. Ну, что за дети, честное слово…
Там уже есть пример, учимся читать ВНИМАТЕЛЬНО.
Вначале думаем, потом пишем… Пример в студию, и проверяем как он работает… учитесь дальше
Опять жжете ;)
Это вы никак не поймете, что ваши примеры тут не работают. Ладно, слили, так слили…
«Начнем с нашего управляющего класса в нашем ПО.»
А зачем он нужен? Без него обойтись никак?

Ну и да, тестируемость этого класса _и всех его потребителей_ на высоте: «thisInstance = this; currentDatabase = new Database();». (Кстати, вас не смущает, что вы инициализируете статическое поле в конструкторе экземпляра? Типовая реализация паттерна Singleton не для вас?)

«MethodInfo[] myMethod = locType.GetMethods();
foreach (MethodInfo m in myMethod)
{
if (m.Name == „GetCollection“)»
LINQ тоже не для вас. Хотя он бы повысил читаемость вашего «поиска метода» в несколько раз.

Ну а весь ваш класс DBData — это нарушение принципа persistance ignorance, о котором в интернетах написано существенно больше десяти раз. Но ладно бы persistance ignorance, у вас еще и нарушается SRP: ваш DBData содержит методы Count, Load и LoadAllID, которые работают не с самим объектом, у которого вызываются, а с чем-то другим, а от объекта используют только тип.

«StrategiesData SD = new StrategiesData(1, „Test1“); SD.Save();»
Наглядная демонстрация всех проблем вашего TaskManager. Как вы собираетесь делать изолированный тест этого кода?

Ну и да. Во-первых, вы продолжаете изобретать паттерн Repository. А во-вторых, весь ваш DBData и Database можно было бы переписать на дженерики, и не возиться с рефлекшном (который вы даже не кэшируете к вашему стыду).
Ну давайте разбираться.

> А зачем он нужен? Без него обойтись никак?
Не обойтись. Нужен как управляющий класс, который решает какую базу использовать.

> LINQ тоже не для вас.
Это уж точно. Пора запомнить, что этот ненаглядный бред я не использую.

>ваш класс DBData — это нарушение принципа persistance ignorance
Ложь. Он как раз обеспечивает независимость логики хранения

> ваш DBData содержит методы Count, Load и LoadAllID, которые работают не с самим объектом

Зато вызывающий действительно прозрачно работает с базой. А эти метод классический паттерн Адаптера, в чем проблемы ни какой нет.

Что Вы предложите взамен? Чтобы вызывающий эти методы, заботился о получении ссылки на базу и работал с этими методами через базу? Так вот это как раз и есть плохой стиль.

> весь ваш DBData и Database можно было бы переписать на дженерики, и не возиться с рефлекшном
Попробуйте это сделать. Не получится.
«Не обойтись. Нужен как управляющий класс, который решает какую базу использовать.»
Слова DI-container и Dependency Resolver вам ничего не говорят?

«Это уж точно. Пора запомнить, что этот ненаглядный бред я не использую.»
Вы, видимо, не умеете читать по-английски? Потому что LINQ-запрос — это просто английский текст.

«Ложь. Он как раз обеспечивает независимость логики хранения»
Вы путаете «независимость» и «незнание». Бизнес-объекты не должны знать о своей персистентности.

«А эти метод классический паттерн Адаптера, в чем проблемы ни какой нет. „
Простите, а что делает адаптер в бизнес-объекте?

“Что Вы предложите взамен? Чтобы вызывающий эти методы, заботился о получении ссылки на базу и работал с этими методами через базу? Так вот это как раз и есть плохой стиль.»
Ну, если писать в _вашем_ стиле (обратите внимание, что вы пропустили все вопросы, связанные со стилем кода), то такие вещи делаются статиками на соответствующем классе. Это как минимум. На самом же деле, они должны быть у соответствующих репозиториев (а вот получать на них ссылки, или использовать, как вы, глобальные объекты — вопрос второй).

«Попробуйте это сделать. Не получится.»
Можно было на деньги поспорить.

pastebin.com/4BjGuwGW
> Можно было на деньги поспорить.

Да, но согласитесь, что

public class Person: EntityBaseсильно не естественно в отличии от

public class Person: EntityBase
А кого это волнует? Рефлексия не более естественна, но это — работает без кучи мусорного нетипизованного кода.

(я так понимаю, что по остальным пунктам вам сказать банально нечего)
По остальным пунктам лень пока.

А это волнует! Выше я уже пояснял, рефлексия только в одном конкретном месте, и она написали и забыли. А вот объекты нужно всегда создавать новые, и нужно специально научить разработчиков делать менее естественное наследование.
«По остальным пунктам лень пока.»
Новая, неожиданная версия отмазки.

«А вот объекты нужно всегда создавать новые, и нужно специально научить разработчиков делать менее естественное наследование. „
Это уже другой разговор. Изначально речь шла о том, реализуемо это, или нет. Как видите, реализуемо.

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

А ведь я там перечислял конкретные ошибки в вашем коде.
съелось, речь о

public class Person : EntityBase<Person>
 
Попробую пояснить, почему этот код плох без отсылок к книжкам Фаулера или стадартным шаблонам типа ActiveRecord/DomainObject+DataMapper.

У него ужасное соотношение сигнал-шум.
Ну зачем, скажите мне, заниматься вот этим вот? Только чтобы использовать reflection? Показать, что вы это умеете?

MethodInfo locMethodGetCollection = GetCollection(argObject);
var locCollection = locMethodGetCollection.Invoke(db, null);
MethodInfo locMethodLoad = GetMethod(locCollection, «FindOne», 1, «Object»);
object[] locArgs = { new { ID = argObject.ID } };
return (DBData)locMethodLoad.Invoke(locCollection, locArgs);

MethodInfo locMethodGetCollection = GetCollection(argObject);
var locCollection = locMethodGetCollection.Invoke(db, null);
MethodInfo locMethodDelete = GetMethod(locCollection, «Delete», 1, «Object»);
object[] locArgs = { new { ID = argObject.ID } };
locMethodDelete.Invoke(locCollection, locArgs);

Зачем методы перебирать?

Я вначале подумал, что вы дурак, tac, но я посмотрел ваши статьи. Вы не дурак, tac, нет. У вас интересные хорошие статьи. Видимо, дело в том, что просто программирование — не для вас. В этом нет ничего страшного или плохого, все люди разные. Не нужно насиловать себя и языки программирования на которых вы пишете. Бросайте это глупое занятие, tac, подберите себе лучше занятие по душе. Может быть, через несколько лет, именно вы найдёте лекарство от рака или же возглавите колонизацию Марса.
И это Вы будите оценивать, что программирование не для меня :)

Ну давайте разберемся

> Зачем методы перебирать?

А Вы подумайте! Вы по другому это не сделаете! Надеюсь не сольете, как выше retran, а на самом деле попробуете и поймете.
Ага не сделали, вы забыли, что нужен метод не «Test», а FindOne у которого есть один параметр типа Object, а не другие его перегрузки
А теперь нужно, чтобы метод этот был generic
А он разве не дженерик? Еще раз — учитесь читать внимательно.
Стыдно должно быть. Методы FindOne оба дженерики.

public static string FindOne<T>(string arg) {}
public static string FindOne<T>(object arg) {}
typeof (GenericMethodContainer).GetMethod(«FindOne», new Type[] {typeof(Object)})
Научитесь уже читать документацию
Во. А это интересно. Попробуем.
Тьфу, нет не интересно, я то думал это вы метод generic, нашли… а это у вас он так называется GenericMethodContainer

Пробуйте дальше. Что делать когда нужен найти метод с сигнатурой

private ArrayList LoadAllID<T>(DBData argObject) where T:DBData
 
 


Во-первых, в вашем коде дженериков нет, а тут вы их требуете.
Во-вторых, что же вы тогда вообще свой парсер C# не написали. Рефлексия это слишком просто для вас.
Во-третьиъ, вспоминается история с буханкой хлеба и проволокой.

Опять же, 5 минут гугления
MethodInfo method = typeof(YourType).GetMethod(«LoadAll», new Type[] {typeof(DBData)});
MethodInfo generic = method.MakeGenericMethod(YourT);

Что не меняет того факта, что это нахрен не нужно
Вы свои обрывки хотя проверяете? Не работающий код, вырванный кусками — тут не пройдет. Нашли метод MakeGenericMethod — молодец. Теперь осталось понять что он делает. Внимательно прочли бы мою статью, нашли бы раньше, и может быть поняли бы.

Но уже на первой строчке вы получите иксепшен, т.к. могут существовать два метода LoadAll — один обычный, другой обобщенный.
Ну это уж извините, на сишарпе не пишу, и компилятора не имею. Методом простого гугления я нашел то, что несколько ваших копипаст по 15 строк легко заменяются одним методом. вы начали требовать того, чего у вас в коде нет.
Это надо было сказать в самом начале. А не учить тому, чего сами не знаете.

Ну, гуглить то вы может и научились, а программировать еще вопрос.

Я всего лишь спросил вас как вы по другому реализуете именно то, что есть в коде см. метод private MethodInfo GetCollection(object argObject). Там именно то, что нужно.

Вы думаете я не знал найденного вами способа? Нет, дорогой, я прекрасно знал. Но избрал я другой способ, т.к. он более общий, и подходит на все случаи жизни. Да, я поленился обобщать, то что не до конца понятна в какую сторону обобщать. Останавливаться на нужном уровне обобщения вам поможет практика. Но имея поиск по циклу, мне более легко поддерживать такой код, дальше его обобщать, мало ли какие методы в базе Mongo мне потребуется найти. Так я ставлю нужное дополнительное условие и получаю результат. А вы вначале все равно развертываете поиск по циклу. Поэтому принебрежение к копипасте — это вас в школе выдрисовали, но увы лишь в одну сторону и вы не знаете когда она допустипа и лучше чем «гладкий» код. И вс ледующий раз малыш думай прежде чем гнать на дядю.

facepalm.jpg.to
Вам выше уже предложили готовое решение. Но оно вас чем-то (и вы не говорите, чем) не устраивает.

Напоминаю еще раз: pastebin.com/mYwS8f1J

Есть ровно один сценарий, когда это не сработает: когда у вас дженерик-метод с параметром, тип которого задается самим дженериком (т.е. Save{of T}(T data)). Вот _это_ нельзя найти через GetMethod. Но вы этот сценарий нигде не озвучивали.
msdn.microsoft.com/ru-ru/library/6hy0h0z1.aspx

mInfo = typeof(Program).GetMethod(«MethodA»,
new Type[] { typeof(int), typeof(int) });

Это по поводу поиска методов, но даже если библиотечный метод вас не устраивает, то догадаться кешировать метод, чтобы не искать его в списке перебором за O(N), а за O(1) все-таки стоит.

Про RC в вашем замечательном TaskManager вам уже рассказали. Есть типовая реализация синглтона (если уж возникла жуткая необходимость его использовать) защищенная от этих ошибок. Но это видимо не для вас.

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

Про нарушение принципов SOLID вам тоже уже видимо сказали. Но добавлю еще от себя. С какого перепуга объект должен знать о том, что его в принципе будет кто-то куда-то сохранять. Объект должен реализовывать только свою логику. Простой пример. У вас есть картотека с карточками. неужели кто-то вызывает метод карточки, после чего карточка магическим образом встает в шкаф на нужное место? Нет. Есть некий архивариус, который занимается этим.

Опишите пожалуйста любой объект любой предметной области с парой методов (применительно к вашему «замечательному» хранилищу само собой) и напишите юнит-тест на этот класс. А тесты вам безусловно понадобятся, иначе ваше приложение тупо упадет посреди работы. Просто потому что вы забыли указать в вашем классе замечательный метод с именем count, который выгребается рефлексией и, соответственно, ошибок компиляции не произойдет.

Я присоединяюсь к вышесказанному, не пишите больше программ, а уж тем более статей как надо писать программы. И поумерьте свой пыл по поводу изобретения «лучшего в мире механизма работы с базой».
Про типовую реализацию синга — знаю. Она хуже чем моя.
Проблема в том, что ваша работает до первой пары конкурентных запросов.
А эти проблема мы решим когда нужно будет, а пока не стоит заморачиваться. Как там — излишнее перепроектирование !?
Thread safety для DAL — одна из первоочередных проблем, але. Потому что имеет прямое отношение к целостности данных.
По поводу объекта и сохранения — это глупость. Сохранение объекта — это должна быть его базовое поведение, если я его наследую от объекта «которые умеют сохраняться». И наличие «объектов умеющих сохраняться» — это основы основ ООП.
Почитайте хотя бы о DDD, если здравого смысла не хватает. Там специально выделяются «объекты которые умеют сохраняться». Хотя даже у Буча объекты разделялись на долгоживущие и короткоживущие.

Поэтому туфта не пройдет.
Пруфлинк на самосохраняющиеся объекты в ddd?
Так как минимум есть разделение на те которые сохраняются, и прочие. А то, что те которые сохраняются — должны делать это сами — это элементарно. Это их ответственность сохраняться, раз уж они могут. Разве так сложно усвоить, что в ООП объекты должны быть максимально самостоятельными, и выполнять то, что им свойственно. И да, есть объекты, которым свойственно жить дольше жизни работы ПО.
Еще раз — я хочу знать где вы это вычитали.
Big Blue Book
Эванс?

А вот насколько я помню, он то как раз и напирал на то, что объекты должны сохраняться только репозитариями.
Кстати, а множественное наследование в C# есть? Если вдруг нет, то что делать, если сущность, скажем, Admin надо унаследовать от User? Забить на естественное наследование в предметной области ради наследования от DBData?
Множественное наследование тут не нужно. Admin наследуете от User, а User от DBData
А User не должна храниться, или должна но не в БД.
Ну, как так может быть? Не придумывайте искусственные примеры.
User — это внешняя учетка, скажем, в соцсети, все данные там хранятся, а Admin — учётка соцсети, но с расширенными данными хранящимися на нашем хосте.
Ничего не изменится. Admin наследуете от User, а User от DBData.

Вы думаете, что это лишние? Проблемы тут не велика, мы создаем много объектов, и удаляем их воспользовавшись не всеми их методами. А тут вообще ни каких затрат.
Легко может быть, если User — абстрактный класс, не имеющий собственного хранилища.
И да попробуйте тоже самое сделать без reflection! Снова не сделаете!
Серьёзно, tac, послушайте что я вам говорю: не так давно набирали добровольцев, которые 500 дней жили в полной изоляции. Ну как «не так давно», весной 10го года. То есть эксперимент закончен уже, к сожалению. Но подготовка идёт дальше, а вы с вашей пунктуальностью, вашим вниманием к деталям и вашим знанием биологии будете действительно полезным в экспедиции на Марс.
И программирование вам для этого — ни к чему, tac, бросьте. Космос — вот что важно.
Смешной вы человек, на Марс я не хочу :)

А вы реально решите, а потом будем говорить, стоит ли вам заниматься программированием… а то может оказаться, что слишком поспешно делаете выводы, но вот программно реализовать у вас не получается.
Дело в том, дорогой мой друг tac, что программирование — это наука инженерная. В ней нет свободного полёта мысли. Простые решения, как правило, являются и правильными, а если что-то сложно, то это вредно и не нужно. Ваш код — он как правительство России, нельзя критиковать какого-то одного конкретного министра или депутата, нужно всю систему менять. Так ведь? А вы в ответ — «а как ещё так сделать», а не надо так делать вообще. Вообще не надо!
Но все эти ограничения, tac, слишком тесны, и в этом я с вами согласен. То ли дело в биологии. Огромные белковые макромолекулы, пространственные структуры, взаимодействие. Потрясающий в своём многообразии микромир, настоящая вселенная в пробирке. Ограниченный ум окажется заперт в космическом корабле во время перелёта, но вы, tac, несомненно найдёте достаточно важную научную проблему, чтобы скоротать время при перелёте. А программирование вам в этом всём никак не поможет. Ну его, tac, бросьте.
Да? Ну тогда обеспечьте мне, чтобы я вызывал

MyObject.Save() и он сохранялся бы в базе данных MongoDB — как угодно. Прошу, и тогда будем говорить, а пока один лишь гон. Вон lair хоть что-то сделал, только вот мне не нравится наследовать объекты как

public class Person : EntityBase<Person>
 


когда нужно

public class Person : EntityBase
 


Но у вас даже такой попытки нет. Так что — летите те как вы на Марс первым, уступаю.
Отчитав таким образом tac'а, гость осведомился:
— Профессия?
— Программист, — почему-то неохотно признался Иван.
Пришедший огорчился.
— Ох, как мне не везет! — воскликнул он, но тут же спохватился, извинился и спросил: — А как ваш аккаунт на хабре?
— tac.
— Эх, эх… — сказал гость, морщась.
— А вам, что же, мои программы не нравятся? — с любопытством спросил tac.
— Ужасно не нравятся.
— А вы какие читали?
— Никаких я ваших программ не читал! — нервно воскликнул посетитель.
— А как же вы говорите?
— Ну, что ж тут такого, — ответил гость, — как будто я других не читал? Впрочем… разве что чудо? Хорошо, я готов принять на веру. Хороши ваши программы, скажите сами?
— Чудовищны! — вдруг смело и откровенно произнес tac.
— Не пишите больше! — попросил пришедший умоляюще.
— Обещаю и клянусь! — торжественно произнес tac.

Ладно, чёрт с ним, с Марсом. Не хотите — не летите. Исследуйте рак, лечите Альцгеймера, секвенируйте ДНК или, на худой конец, оперируйте. Главное, tac, самому себе не лгите. Лгущий самому себе и собственную ложь свою слушающий до того доходит, что уж никакой правды ни в себе, ни кругом не различает, а стало быть входит в неуважение и к себе и к другим. Не уважая же никого, перестает любить, а чтобы, не имея любви, занять себя и развлечь, предается страстям и грубым сладостям, и доходит совсем до скотства в пороках своих, а все от беспрерывной лжи и людям и себе самому. Фу таким быть, tac!
Вот, блин, еще один комик, хоть и уважаемый :)
«Ну тогда обеспечьте мне, чтобы я вызывал MyObject.Save() и он сохранялся бы в базе данных MongoDB — как угодно»
Любой уважающий себя программист на этом месте должен вас послать нафиг. Потому что задачи в такой формулировке не ставятся — вы уже навязываете решение, вместо того, чтобы сформулировать задачу.

Правильная формулировка задачи: «сделайте удобный и прозрачный способ сохранения».

К удобству (со всех сторон) вашего способа есть множество претензий, неоднократно описанных в разного рода статьях. Можно начать хотя бы вот:
msdn.microsoft.com/en-us/magazine/dd882510.aspx#id0420053
Видимо, по второму кругу… объяснять устал.
Просто в ваших «объяснениях» нет ни капли аргументации, кроме вашего собственного удобства. А все ссылки на статьи других людей, в том числе и уважаемых в отрасли, вы игнорируете.
Все ваши ссылки — это ссылки на паттер Репозиторий. Других не было. Причем конкретную реализацию вы не показываете. Покажите какая именно реализация по вашему лучше?

Ну, а если удобство вызова для вызывающего — это не САМЫЙ ГЛАВНЫЙ аргумент, то я тогда не знаю что такое программирование вообще.
«Все ваши ссылки — это ссылки на паттер Репозиторий.»
То есть статью с msdn, которая вообще про другое, вы не прочитали.

«Покажите какая именно реализация по вашему лучше? „
_любая_ чистая реализация репозитория (Load, Load(query), Save, Delete) и POCO-объекты данных. Почему лучше? Потому что ответственности разнесены очевидным образом.
Если MyObject.Save() чем-то кому-то не удобен — значит он не хрена не смыслит в ООП. И это диагноз.
……………………………………..________
………………………………,.-‘"……………….``~.,
………………………..,.-«……………………………..»-.,
…………………….,/………………………………………..":,
…………………,?………………………………………………\,
………………./…………………………………………………..,}
……………../…………………………………………,:`^`..}
……………/……………………………………………,:"………/
…………..?…..__…………………………………..:`………../
…………./__.(….."~-,_…………………………,:`………./
………../(_…."~,_…….."~,_………………..,:`…….._/
……….{.._$;_……"=,_……."-,_…….,.-~-,},.~";/….}
………..((…..*~_……."=-._……";,,./`…./«…………../
…,,,___.\`~,……»~.,………………..`…..}…………../
…………(….`=-,,…….`……………………(……;_,,-"
………….\`~.*-,……………………………….|,./…..\,__
,,_……….}.>-._\……………………………..|…………..`=~-,
…..`=~-,_\_……`\,……………………………\
……………….`=~-,,.\,………………………….\
…………………………..`:,,………………………`\…………..__
……………………………….`=-,……………….,%`>--==``
…………………………………._\……….._,-%…….`\
……………………………..,<`.._|_,-&``…………….`\
И опять жжете.
Вас я посмотрю напугал код отображения, которого нельзя избежать. А вот то что код обращения к базе

var collection = db.GetCollection();
collection.Save(argObject);

будет раскидан по коду всего проекта - вас ни сколько не пугает? И о чем мы тогда говорим? Вы хоть учились программировать ?
Простите, а консистентность вы как обеспечивать будете? Интересует процесс выполнения ROLLBACK.
Вас это реально интересует? Я тут пока устал, школьников просвещать. На серьезные вопросы отвечаю завтра. Только прошу расширить ваши пожелания к новой функциональности, тогда мне будет легче показать как я это предполагаю решить. Если же это очередной стеб на «загрузить» и не выслушать… тогда не буду больше тратить свое время.

Все ушел смотреть «побег 2» :)
lair, мы опять школьники и тролли ;)
Ну, так это же чистая правда, тролли из Викиреальности :)
К сожалению, не имею никакого отношения к викиреальности. Тутошние мы ;)
М-да? А поведение такое же :)
Я думаю, что вас везде встретят примерно одинаково.
Нет, это меня интересует весьма поверхностно, поскольку описанное вами мне кажется не слишком эффективным при работе с монго. Вот чем-то вроде riak'а — может быть.
Мне кажется, или перед нами новый Денис Попов? Или может быть это он и есть?
Неееее, Попов просто пхпшник. А тут все гораздо грустнее.

Вот он, наш герой — ru.vlab.wikia.com/wiki/Кабинет: Сергей_Яковлев
То-то думаю причем тут РНК и марс. Теперь все понятно.
Он к сожалению регулярно пишет. И поведение всегда одно и то же.
Гении, они такие. Никто их не понимает :)
Делаем абстрактный класс/интерфейс Storage с методами save, load, delete, find и т. п. Наследуем от него/реализуем его) конкретный класс конкретного хранилища (базы данных, файловой системы, мемкэша и т. д.). Если надо хоть 100 таких классов. Работаем так (псевдокод):
class Person {
  public int id;
  public string name;
}

Storage storage = new MongoDbStorage();

Person person = new Person("VolCh");
storage.save(person);
int person_id = person.id
delete person;

person = storage.load("Person", person_id);
persons = storage.find("Person", "name like Vol*");
storage.delete("Person", person_id);


Классы моделей (person) совершенно независимы от системы хранения и её интерфейсов. Замена системы хранения — замена одной строчки в коде storage = new ..., а то и в конфиге (плюс, если ещё не написан написать класс, инкапсулирующую конкретную систему хранения и предоставляющий интерфейс Storage). Конкретный класс-наследник Storage может быть как универсальным (рефлексия и т. п.), так и сильно заточенным под конкретные типы и хранилища.

Чем такая система не нравится?
… и вы тоже описываете паттерн Repository. Только еще и со строковыми запросами вместо QueryObject.
Я в курсе, просто автор очень не любит ссылки на паттерны и т. п. Решил привести наглядный пример, а вы всю малину испортили :(
Вы действительно не понимаете чем это хуже? Или стебетесь? Нет, если не понимаете я вам объясню… точно надо?
Действительно не понимаю. Код подобный этому я постоянно использую, даже без всяких навороченных ORM, но при желании заменить мои «велосипеды» на ORM/ODM это сделать будет просто, парой строчек может не обойдётся, на близко к тому.
Увы, для реляционной базы я тоже использую нечто такое. По привычке. Но объектная база на то и объектная, что может позволить немного большего.

Давайте разберем этот кусок.

Storage storage = new MongoDbStorage();

Person person = new Person("VolCh");
storage.save(person);


Я так понимаю аналогичные кусочки у вас по всему коду. Т.е. логика такая вы создаете объект хранилища, затем нужный вам объект, и затем отправляете в хранилище. Так?

Т.е. переменная storage — локальна? Где-то за кадром у вас находится реальное обращение в базе, вы же не подключаетесь к ней каждый раз? Как это у вас происходит?
> Я так понимаю аналогичные кусочки у вас по всему коду. Т.е. логика такая вы создаете объект хранилища, затем нужный вам объект, и затем отправляете в хранилище. Так?
Для этого существует менеджер подключений, что-то вроде

Person person = new Person(«VolCh»);
Storage.instance.save(person);
Хотя мне и не нравится синглтон, но есть и другие варианты

> вы же не подключаетесь к ней каждый раз? Как это у вас происходит?
И тут на сцену выходит пул подключений, что это такое думаю не надо объяснять. А у вас что, каждый объект хранит в себе отдельное подключение? или одно подключение на приложение?
Слушайте дайте с людьми поговорить, а?

Вот зачем писать «существует менеджер подключений», и тупить когда я говорю о TaskManager. Не нравится синглтон? Ну так зачем предлагать мой же вариант? Предложите эти ваши «другие варианты».

Ну а пул то тут причем? Т.е. вы реально каждый раз выполняете код типа

mongo = new Mongo();
mongo.Connect();
db = mongo.GetDatabase(DatabaseName);


и пусть за это думает пул, вам то до лампочки. Так? Где этот код у вас, в конструкторе Storage?
Да, и кстати, и Mongo есть пул подключений?
нет, я каждый раз не выполняю код типа указанного вами. и хранилищ у меня несколько. мало того, явных вызовов загрузки и сохранения у меня тоже нет. метод обработки бизнес-логики обрабатывает только бизнес логику. сохранение отдельно, загрузка отдельно, проверка входных данных тоже отдельно. зато я это легко и непринужденно тестирую.
Разговор шел про менеджер подключениЙ, а не менеджер подключениЯ. К тому же, если уж пошел разговор, то (а) название TaskManager не соответствует реализации и (б) вы что, реально запускаете сервер БД только когда приложение стартует?
> явных вызовов загрузки и сохранения у меня тоже нет

Да? И когда же данные попадают в базу?

> вы что, реально запускаете сервер БД только когда приложение стартует?
Я конечно извиняюсь но когда еще?
«Не нравится синглтон? [...] Предложите эти ваши «другие варианты».»
А их вам тоже уже предлагали, только вы не услышали. Dependency Injection и Service Locator.
Да, я часто использую «одно подключение на приложение». Пока проблем нет. Ну, не хотел бы уходить в сторону… вы и так не можете сконцентрироваться на предмете статьи, и мне тут отвечай за все админство и БД… :)
Вот про что мы вам и говорим, у себя используйте что хотите, но не надо советовать это делать другим и выставлять это как совершенное произведение инженерной мысли.
Я что-то не понял, где я говорил про «совершенное произведение инженерной мысли»?
Такой вариант чисто для демонстрации. storage инициализируется при запуске приложения, может быть глобальной переменной, может быть синглтоном, может статическим членом класса, может храниться в реестре приложения или DI-контейнере, может возвращаться фабрикой из пула, может просто передаваться параметром в конструкторы, сеттеры или методы по цепочке. В общем любым привычным способом передаётся в область видимости места использования. Аналогом вашего TaskManager.GetInstance().GetDatabase() тоже может.
> Классы моделей (person) совершенно независимы от системы хранения и её интерфейсов

Вы хотите неявно сказать, что в моей реализации классы сущностей (person) зависимы от системы хранения? Ведь нет же. Или поясните.
Напишите тест сохранения объекта не сохраняя сам объект в реальную базу
Дорогой вы меня утомили, и что мне делать — я разберусь сам. А если хотите говорить — говорите внятно. Зачем что-то писать, в чем проблема и т.д.
Я уже не говорю о том, что я противник всяких тестов, но снова речь то не об этом.
ей богу, вы смешны. умываю руки
«Я уже не говорю о том, что я противник всяких тестов»
Это как? Вы приложения заказчику отдаете без тестирования?

(впрочем, памятуя ваш код перцептронов, я не сильно удивлен)

Сильны, да. Тогда понятно, откуда у вас такой дизайн приложения.
StrategisData унаследован от DBData — это не зависимость? В «моей» схеме сущности предметной области с системой хранения вообще никак не связаны, её вообще может не быть. Хранением данных модели занимается приложение, а не сама модель. Модель существуют в вакууме можно сказать и занимается только моделированием предметной области. Откуда даггые берутся, куда потом передаются — это всё вне области её компетенции. Модель, если угодно, чистая функция без побочных эффектов. Подаст клиент на вход пользовательский ввод, записи из БД или файлов, захардкоженные данные или полученные из стороннего сервера по http — ей всё равно, при одинаковом входе будет одинаковый выход. А вход — только параметры конструкторов, сеттеров и методов. Как исключение — присваивание паблик свойств.

А DBData зависит от TaskManager.GetInstance.GetDatabase, который возвращает Database. Это не зависимость? При изменении Database на работу с например мускулом StrategisData не нужно перекомпилировать? (хотя может и не нужно, я не сильно в курсе).
> StrategisData унаследован от DBData

Это ровно такая же зависимость как у вас Person унаследован от object. Т.е. Это наследование как раз указывает на то, что эти объекты могут сохраняться. В этом нет ничего страшного.

> Хранением данных модели занимается приложение, а не сама модель

А вот это плохо. Я понимаю когда нет возможности. Но когда есть это плохо. Во-первых мне не нравится название «модель». В ООП нет такого понятия, и это привнесенное понятие. Есть понятие долгоживущего объекта, сущности если хотите. Т.е. я подозреваю, что у вас модель не имеет методов? Тогда это вообще не ООП. А если нет, чем так особен метод сохранения? Вам кажется сохранение это не бизнес-логика?

> А DBData зависит от TaskManager.GetInstance.GetDatabase, который возвращает Database. Это не зависимость?

Да, это зависимость, но она в одном конкретном месте — инкапсулирована в объект. Но какой именно объект наследник от Database будет возвращен системой зависит от логики окружения. Поэтому это слабая зависимость.

В отличии от сильной зависимости, когда вы делаете Storage storage = new MongoDbStorage();

И более того, эти зависимости у вас плодятся кучами ВСЮДУ где вы начинаете сохранять объекты. У вас сотни сильных зависимостей против одно слабой. Разницы не чувствуете, или вы не видите, что написав Storage storage = new MongoDbStorage(); вы создаете зависимость?

Это ровно такая же зависимость как у вас Person унаследован от object.

:) не ровно такая, как минимум теряю свободу в выборе свойств и методов. И object не может меняться волею разработчика, а DBData может.

Во-первых мне не нравится название «модель». В ООП нет такого понятия, и это привнесенное понятие.

Это разделение ответственности между классами или, более обще, декомпозиция задачи — необходимый этап для разработки любого приложения, сложнее helloworld, на любом языке в любой парадигме.

Т.е. я подозреваю, что у вас модель не имеет методов?

Имеют, ещё как имеют. Процентов 80 кода это эти методы.

А если нет, чем так особен метод сохранения?

Он возлагает на класс вторую обязанность. Не только обрабатывать данные, но и заботится об их сохранении.

Вам кажется сохранение это не бизнес-логика?

Я в этом уверен, если мы не говорим о продуктах вроде IDE или админок к БД, где файлы или записи в БД являются объектами с которыми пользователь работает, а не средством хранения между сессиями. Возьмём например бухгалтерию. Там есть понятия «создать документ», «провести документ», «сторнировать документ», но нет понятия «сохранить документ в базу данных». «Сохранить» — это чисто логика приложения, как «печать» — логика представления.

И более того, эти зависимости у вас плодятся кучами ВСЮДУ где вы начинаете сохранять объекты. У вас сотни сильных зависимостей против одно слабой.

Я уже пояснил, что это чисто для примера. Инстанцирование хранилища происходит при запуске приложения один раз. Причём все виды хранилищ являются объектами одного типа Storage и полностью взаимозаменяемы на уровне не то что разработчика, а администратора и чуть ли не пользователя. Зависимость у меня от интерфейса (или абстрактного класса, что почти одно и то же в данном контексте).
Тогда покажите как вы вместо создания объекта

Storage storage = new MongoDbStorage();

получаете доступ к объекту? От куда берется ваш storage в локальном месте?

(и вот это большая проблема, чем все то о чем вы ниже пишите)
Например App.GetInstance().GetStorage().Save(person)
мы все идиоты. поясните же нам наконец. а то все заладили «вы что, не понимаете, чем это хуже?»
Если в двух словах, то слишком много кода при работе с объектом Person. И это постоянно. Странно, что копи-пасту в моем классе в одном месте заметили, а дублирование работы с лишним объектом storage не земечаете.

Ведь все можно сделать прозрачно.

Ваш код надо переписать так:

class Person {
public int id;
public string name;
}

Person person = new Person("VolCh");
person.save();
int person_id = person.id
person.Delete();

person = person.load(person_id);
persons = person.LoadAll("name like Vol*");
person.delete(person_id);


Как видите разница не сильно большая, но нет ни каких лишних объектов storage, да и вызовы естественнее будут. Т.е. код становится действительно независимым от реализации storage.
последнию строку, тоже надо заменить с

person.delete(person_id);

на

person.delete();
а выше, где person.Delete(); ей соответствует нотация на С++

delete person;

я спутал — надо заменить на person=null;
Что касается persons = person.LoadAll(«name like Vol*»);

тоже возможны варианты, лучше будет

persons = new Person().LoadAll(«name like Vol*»);
Вы действительно считаете, что персоны = новая Персона.ЗагрузиВсе(имя как Vol*) лучше чем персоны = Хранилище.найди(имя как Vol*)?
Да. Хотя в известном смысле это натяжка. Но это лучше, чем получать ссылку на хранилище, для осуществления работы. Возможно, тут как и у Фаулера, лучше применить статический метод Найди — для данного метода это дискуссионно.
Вы получаете ссылку на базу данных, я на хранилище — разница?
Откуда слишком много кода? Соответствие построчное. storage замена вашему наследованию от DBData (про которое вы забыли в этом примере :) ). Насчёт естественнее — давайте на русским переведём.
Мой вариант:

персона = новая Персона(VolCh)
хранилище.сохрани(персона)
персона = хранилище.загрузи(Персона №)
персоны = хранилище.найди(Персона имя как Vol*)
хранилище.удали(Персона №)

Ваш:

персона = новая Персона(VolCh)
персона.сохрани()
персона = персона.загрузи()
персоны = персона.найди_все(имя как Vol*)
персона.удали

Что сохрани, что загрузи, что найди, что удали?! Как по мне, то мой естественнее, даже если не учитывать, что я названия методов (в частности load) подбирал под ваш DBData. У вас названия методов не согласуются с именем объекта. Кто ищет все? Персона?
Особенно логичен вот этот вот код:

person = person.load(person_id);

(Уже созданный объект внезапно возвращает другой объект. А что произошло с состоянием первого объекта?)

Ну и да:
«Person person = new Person(»VolCh");"
Как мне понять, что этот объект еще не получил данные из БД? Что вернет его свойство int Age до загрузки из БД?
Что-то у вас критика мало конструктивная. Предлагаю так. Покажите мне две конкретных реализации паттернов Репозиторий и ActiveRecords, и я тогда смогу тогда сравнить и объяснить, чем моя реализация лучше обеих вместе взятых :) Ну, а так по идеи — это конечно компиляция эти паттернов, но это на уровне идей, а на практике вон — видим как бывает.
Только не просто каких-то реализаций паттернов, а тех за качество которых вы отвечаете, и не будите менять по ходу рассмотрения.
Впрочем если смотреть по Фаулеру — то мной действительно предложена реализация ActiveRecords, но т.к. я против static — то методы не статические, и в отличии от примеров Фаулера, я работаю не с реляционной базой. А идея да, та же самая.
Но это так естественно, что изучать это как паттерн — я никогда не изучал. Но вот странно, что люди возражают против использования этого паттерна…
Не зря у Фаулера самым мощным считается паттерн DataMapper.
Это уже лирика, зачем мне что-то более мощное в более простом случае?
pastebin.com/7t6f0fNE

Вот вам конкретная реализация. Это не production-код (специально подчеркиваю), это я на коленке набросал за 20 минут прямо сейчас. Соответственно, реализация БД — абстрактная, по образцу современных ORM.
1. Это конкретная реализация чего?

2. В ней та же самая проблема, что у VolCh. Нужно или создавать объект репозитория в 100 местах (у вас в конструкторе каждого объекта, где используется сохранение), или так или иначе получить ссылку на репозиторий (у вас это не показано как). Это настолько большая проблема, что все остальные мелочи можно не рассматривать (хотя наследование от обобщенного класса (class Person: IIdentifiableObject) — это мощно и незачем!)

Если это ваш идеал работы с базой — то я умываю руки. Но это бред, именно об этом я и говорил, когда писал в статье «не разбрасывать код вызовов по всему проекту». А Вы так привыкли к не правильному стилю, что ужас. Не видите проблемы? Вы же клепаете сильно связанный код, и наращиваете его бородой. Я то в одном месте (TaskManager) подменю ссылку на хранилище, а вы где?

Тьфу, перепутал, вы не создаете объект репозитория в 100 местах, вы получаете к нему доступ в 100 местах. Но проблема не меняется.
Ну, и так на всякий случай — чего это у вас все классы, которые использую сохранения в качестве параметра конструктора принимают хранилище? И это вы называете не глупостью? Это так все бизнес-классы не занимаются ничем другим, кроме бизнес-логики — хотя каждый из них вынужден для своего создания принимать объект системного класса — хранилище. И это нормальная архитектура?, тьфу.
tac, вы реально жжете. Какого хрена вы нам тут вещаете, если не знаете даже что такое Inversion of Control?
Потому что видимо это хрень по его мнению, а хрень ему недосуг изучать.
Меня просто поражает, как этот человек считает всех вокруг идиотами и сам же расписывается в собственном незнании. Его даже троллить не надо, он сам себя троллит.
Видите ли, есть программисты, и начинающие. Если вы чего-то не понимаете из того, что я вам объясняю — вы начинающий — и обижаться тут не стоит. Но Вы не идиот — вы воинствующий невежда. Вы бы хоть в попад отвечали бы, а то все мимо и не конкретно.
tac, к вашему глубочайшему сожалению, мой (и ваш) профессиональный уровень оцениваете не вы, а рынок. Грубо говоря, приезжайте в Москву и попробуйте устроится на работу. Будете зарабатывать больше, чем я — тогда и будете говорить мне, кто тут невежда.
А мне кажется, что он вас тупо троллит. А вы троллитесь :)

Даже я немного повелся :)
Проблема в том, что выше джуниора вас никто не возьмет с такими представлениями о архитектуре.
И где это у вас Inversion of Control? В коде выше — этого нет! Если это Inversion of Control — то вы просто кидаетесь терминами не зная их значения.
ПЗДЦ.
А можно услышать ваше определение IoC? И источник, на котором оно основано? А если вы еще и реализацию (свою) покажете, так и вообще замечательно будет.
Это так все бизнес-классы не занимаются ничем другим, кроме бизнес-логики — хотя каждый из них вынужден для своего создания принимать объект системного класса — хранилище.

Класса бизнес-логики там ровно два — Person и Apartment, собственно и логики в них нет, как и конструкторов. Репозиторияй передаётся в конструкторе классу, реализующему логику приложения — распечатать персоны, добавить персону в репозиторий — это не бизнес-логика.
Простите что вмешиваюсь. Что-то меня так зацепила эта полемика что и я решил вставить свои пару копеек.
По моему мнению в приведенном коде класс бизнес логики один — UsageSample а Person и Apartment — это модель.
А в целом даже становится смешно наблюдать за происходящим.
ПС lair — спасибо за качественный и хороший пример!
А вот вы можете мне сказать чем отличается класс бизнес логики, от класса модели?
Тем что класс модели как правило является только отражением реального мира и умеет взаимодействовать только с такими же «отражениями», он не должен знать куда сохранятья и откуда загружаться, об этом должна заботиться инфраструктура приложения, бизнес логика это тот участок вашего приложения который оживляет вашу модель. Например у вас есть задача посадить дерево человеком.
Тут у нас будет две модели — Человек и Дерево. И один класс бизнес логики СажательДерева — который должен проверить — умеет ли этот Человек сажать это Дерево, можно ли это Дерево сажать сегодня или его можно посадить только вчера, не нужно ли для посадки Дерева Человеку иметь Лопату итд… И в конце концов посадит это Дерево( — _treeRepos.Add(new Tree(){User=...,Date=....,....}); )
Ух… А скажите как мне на милость, зачем мне еще какой-то СажательДерева, если всего-то нужно if (Человек.УметСажать) Человек.ПосадиДерево

Какая логика мышления кривая — такие и результаты в программах.
По моему в моем примере больше условий, а если человек не агроном то хрен он знает можно это дерево сажать сегодня или нет, а он не агроном — он трудяга, вот для того и нужен класс бизнес логики который увязывает всю логику посадки дерева в одном месте, да еще может интерфейс реализовать — IДелательРаботы и а потом все эти работы где ни буть однообразненько, раз, и выполнили…
Хорошо. Тогда у вас есть наследник агроном от человека. И тогда еще проще.

Агроном.ПосадиДерево()

Зачем вы усложняете? Почему у вас возникают лишние сущности, которые вы искуственно разделяете на модели и бизнес-классы. Здесь все Агроном, Человек и Дерево — это бизнес-классы. Ваше разделение не имеет смысла.
А если вам понадобиться посадить большое дерево, для посадки которого требуется 2 человека, а если вам кроме посадки дерева надо еще сажать траву, сеять поле итд… Вы так и будете все ети умения добавлять человеку в виде методов? Тогда человек у вас разрастется до какого-то монстра, вы нарушите массу принципов SOLID проектирования, поэтому и придумали разделение приложения на слои и стесняться их добавлять нечего…
И да, еще вы зачем выносите бизнес логику наружу, зачем передавать агроному дерево, и прочие условия? Если он агроном он сам должен знать где взять дерево, и при каких условиях сажать. А вы это ему навязываете сверху! Т.е. вы выносите бизнес логику агронома в среду.
Итого, агроном у вас становится не самостоятельным классом.
Агроном не должен знать где брать дерево, этим заведует кладовщик.
Хм… в моем понимании модель и бизнес-логика это почти синонимы. Модель более широкое понятие.
Я тоже думаю, что это синонимы. Модель появилось от слов Model-View-Controller, и тогда Model — это был не класс, а структура без данных. А далее уже кто во что горазд придумать нотации.
> а структура без данных

О написал :) Структура без методов, конечно
> Я то в одном месте (TaskManager) подменю ссылку на хранилище, а вы где?

Ок, я понял вы тоже скорее в одном, но зато конструкторы всех бизнес-классов будут у вас требовать ссылку на репозиторий (хранилище) — это ужас (вместо того, чтобы работать независимо от хранилища).
«Это конкретная реализация чего?»
Паттерна Repository. Из названия не очевидно?

«Нужно или создавать объект репозитория в 100 местах (у вас в конструкторе каждого объекта, где используется сохранение), или так или иначе получить ссылку на репозиторий (у вас это не показано как).»
Я уже озвучивал: dependency injection или service locator. В обоих случаях это уже неоднократно реализовано.

«Ну, и так на всякий случай — чего это у вас все классы, которые использую сохранения в качестве параметра конструктора принимают хранилище?»
Собственно, это и есть dependency injection.

«Это так все бизнес-классы не занимаются ничем другим, кроме бизнес-логики — хотя каждый из них вынужден для своего создания принимать объект системного класса — хранилище.»
Можно заменить на service locator. Эффект будет тем же самым, просто DI существенно очевиднее разработчику и проще в тестировании.

«И это нормальная архитектура?, тьфу. „
Это нормальная современная тестируемая архитектура. Читайте про IoC.

“хотя наследование от обобщенного класса (class Person: IIdentifiableObject) — это мощно и незачем»
Во-первых, это не наследование, а реализация интерфейса. Поздравляю вас спутавши. Во-вторых, это очень конкретное «зачем» — оно позволяет четко определить тип идентификатора объекта для поисков по этому самому идентификатору.

«Не видите проблемы?»
Не вижу.

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

«Я то в одном месте (TaskManager) подменю ссылку на хранилище, а вы где? „
И я тоже в одном — в регистрации DI-контейнера. Причем это место находится снаружи реального выполняемого кода, так что сам репозиторий может лежать в другой библиотеке и даже не рекомпилироваться для этого.

“конструкторы всех бизнес-классов будут у вас требовать ссылку на репозиторий (хранилище) — это ужас (вместо того, чтобы работать независимо от хранилища).»
Ээээ… вы считаете вызов TaskManager.GetInstance().GetDatabase() отсутствием зависимости?
> Не вижу.
> Собственно, это и есть dependency injection.
> У вас какое-то сильно свое понимание «сильной связности». Сильно связанный код — это явные вызовы конкретных конструкторов. А у меня код как раз слабо связанный, потому что практически все передается через интерфейсы, которые могут быть реализованы вообще как угодно.

Сложно слепому показывать проблемы. Вот это ваше dependency injection в данном конкретном месте образует сильную связность. Что тут не видеть?

Когда вы где-то используете ваш UsageSample вы и создаете связанный код, т.к. ему с какого-то перепугу нужна ссылка на хранилище. И таких UsageSample — в коде у вас сотни.
Связность или связанность? Сильная связность — это хорошо, в отличие от сильной связанности. UsageSample это собственно приложение, оно не просто может, оно обязано знать где ему хранить объекты бизнес-логики Person и Apartmentю
> UsageSample это собственно приложение

Да какое это приложение? У вас все приложение состоит из одного класса?
UsageSample — это такой же бизнес объект как и ряд других типа Person
Если сложно на пальцах вот вам пример. Есть сделка во время заключения которой нужно сохранить двух новых персон. UsageSample — это сделка.
UsageSample часть логики приложения, контроллер если говорить в терминах MVC. «Сделка» — объект бизнес-логики, две персоны её свойства, «сохранить» — это уже не бизнес-логика, бизнес-логика заканчивается на присваивании свойств. deal.saler = new Person('name1'); deal.buyer = new Person('name2'); всё, бизнес логика закончилась.

Ну, блин… контролер то тут причем? Контроллер появляется в системе (и вообще все вариации MVC) ТОЛЬКО ТОГДА когда я хочу работать с визуализацией. Если я работаю только с бизнес-логикой, которую не нужно визуализировать (то есть отделять визуализацию от бизнес логики) — то контроллер мне сто лет как не нужен. Это становится лишняя сущность.

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

Никак он не сохраняет, потому что бизнес-сущность может что-то требовать только от других бизнеc-сущностей. И это что-то точно не сохранение. Если контроллер решит создать и сохранить сделку, то это будет выглядеть примерно так:

Person saler = new Person("saler");
Person buyer = new Person("buyer");
Deal deal = new Deal(saler, buyer);
App.GetInstance().GetStorage().Save(Deal);

У нас сохранена и сделка, и обе персоны.
1. Нет контроллер для этого не нужен. Но пусть — это отдельный разговор, и тоже долгий.

2. С какого перепугу контроллер решить сохранять сделку?

3. И поздравляю, у вас в каждом контролере будет просто написано то, что у меня написано единожды причем один в один

App.GetInstance().GetStorage().Save(Deal);

2. Пользователь приказал, таймер стукнул, солнечная вспышка, что угодно — сохранять объект или нет к бизнес-логике никакого отношения не имеет.

3. Один в один написано специально, чтобы не спорить лишний раз о DI и прочем, а обсуждать только вопрос отделение логики хранения от бизнес-логики.
2. Ух, как же не имеет. Давайте так — пользователя нет, GUI нет. По таймеру — не серьезно. А нужно автоматически обработать сделку. Надо взять полуготовую сделку, подобрать нужных персон исходя из бизнес-логики и сохранить это. Какого лешего мне еще нужен пинок от чего-то внешнего? Я закончил свою автоматическую операцию над этой сделкой.

3. Замечательно. И? Для вас лучше дублировать в каждом контроллере? Пора бы уже признать проблему вашей архитектуры.
2. Откуда взять? В какой момент взять? Откуда и когда она появится там, откуда её нужно взять?

3. Способов не дублировать App.GetInstance().GetStorage() в каждом методе тьма.
3. Хоть один покажите!
abstract class AbstractController {
  protected Storage _storage;
  
  public AbstractController(Storage storage) {
    _storage = storage;
  }
}

class SomeController:AbstractController {
 
  public SomeController(Storage storage):base(storage) {}
 
  public addPerson(string name) {
  _storage.save(new Person(name));
  }
}
Ну, еще одна попытка — Вы пишите код и НЕ ДУМАЕТЕ о том, кто его будет вызывать. Да, скажите вы — я создал не связанный код. Но тот кто будет вынужден использовать ваш код, будет также вынужден написать сильно связанный код. Вы хоть немного думаете о тех для кого пишите код?
«Но тот кто будет вынужден использовать ваш код, будет также вынужден написать сильно связанный код.»
Нет. У него есть выбор — самому получить объект снаружи (из инфраструктуры), или обратиться к инфраструктуре самому.

Ну и да, где-то в системе *должно быть* сильно связанное место, потому что что-то должно увязывать компоненты вместе. В современных системах таким местом часто является di-контейнер/резолвер. Посмотрите хотя бы на asp.net mvc начиная со второй версии.
> У него есть выбор — самому получить объект снаружи (из инфраструктуры), или обратиться к инфраструктуре самому.

> где-то в системе *должно быть* сильно связанное место

Вы понимаете, что если у него есть выбор как вы пишите, т.е. у него есть сама возможность это выбрать, как не важно — то он при реализации это выбора как раз и создаст сильно связанное место. И этих мест в системе будет не одно где-то, а всюду, как вызывающий вас будет делать выбор.
«он при реализации это выбора как раз и создаст сильно связанное место»
Не обязательно. Оба перечисленных мной варианта — слабосвязанные. Первый потому, что «пользователь» кода сам _тоже_ получает ссылку снаружи (и значит, он настолько же слабо связан), а второй — потому что «пользователь» не сам определяет, какой класс создать, а запрашивает у инфраструктуры (например, сервис-локатора) «экземпляр чего-нибудь, реализующего такое поведение».

А вся связь интерфейсов и реализаций делается в _одном_ месте — это инициализация сервис-локатора или di-контейнера.
«Сложно слепому показывать проблемы. Вот это ваше dependency injection в данном конкретном месте образует сильную связность. „
Чтобы она была сильной, это должен быть не интерфейс, а класс, и он должен создаваться, а не вбрасываться.

“Когда вы где-то используете ваш UsageSample вы и создаете связанный код, т.к. ему с какого-то перепугу нужна ссылка на хранилище.»
Вполне очевидный «перепуг» — этому конкретному участку приложения надо взаимодействовать с хранилищем. Соседнему будет не надо — он этого хранилища не получит. Дополнительный бонус такого подхода: сразу видно, от чего зависит данный кусок.

Как все запущено. Ответьте только на один вопрос: вы понимаете, что обрекаете того, кто вынужден использовать UsageSample заботится о том, чтобы передать вам ссылку на хранилище. Да или нет? (плохо это или хорошо — оставьте ваши размышления при себе)
Да. Это основа inversion of control.
Что касается основ inversion of control — я промолчу. Пойдем дальше.

Вы обрекли человеку думать где взять ссылку на хранилище. Выше написали, что у него есть выбор «получить объект снаружи (из инфраструктуры), или обратиться к инфраструктуре самому»

Покажите кодом хоть один вариант.
Первый вариант:

class AnotherUsageSample:
{
UsageSample _sample;
public AnotherUsageSample(UsageSample sample)
{
_sample = sample;
}

public void DoSomething()
{
_sample.Do();
}
}

или

class AnotherUsageSample:
{
public void DoSomething(UsageSample sample)
{
sample.Do();
}
}

Соответственно, первое — инъекция в конструктор, а второе — инъекция в метод. И то, и другое — стандартные примеры на IoC, хоть из книжки цитируй.

Второй вариант:

class AnotherUsageSample:
{
public void DoSomething()
{
ServiceLocator.Resolve<UsageSample>().Do();
}
}

Ну и да, это если мы хотим слабой связности. Если бы мы хотели более сильной связности (или точно знали, что этот класс никогда не подменяется), то можно было бы передавать (при инъекции) или резолвить (при локаторе) сразу Repository{of Person, Guid}, и явно создавать UsageSample самим. Но вот это как раз была бы сильная связь — между AnotherUsageSample и UsageSample, репозиторий все равно достается по слабой связи.
А вы в своем Database обрекаете на ещё худшую вещь, человеку нужно будет не просто передать ссылку на класс, реализующий интерфейс, но и почти полностью весь Database переписывать.
Нет ему вообще об этом думать не надо. А тому кто ответственен за Database нет причин переписывать.
Это как не надо? Решу я использовать мускул вместо моного, мне ничего не надо будет переписывать?

И вот ещё хотел спросить, а что делать если часть сущностей должна быть сохранена и в монго, и в мускуле, а часть только в мускуле?
Нет не надо. У вас же будет логика когда использовать то вместо другого? Эту логику вы поместите в TaskManager и все, переписывать ничего не нужно будет.

>И вот ещё хотел спросить, а что делать если часть сущностей должна быть сохранена и в монго, и в мускуле, а часть только в мускуле?

Опять же абстракция — у вас должна быть конкретная логика когда так, а когда иначе. И по этой же логике TaskManager будет давать вам ссылку то одного, то другого наследника Database. Я уже несколько раз это писал. Не доходит?
У себя я напишу
mongostorage.save(person);
mysqlstorage.save(person);


Что писать у вас?
person.save()
person.save()

b TaskManager магически определит, что первый раз надо монго вернуть, а второй мускул?
Нет вам надо написать по прежнему

person.save()

а TaskManager на основе определенной логики решит в каких случаях надо записывать в обе базы.
Пускай такая логика есть (хотя с трудом представляю как её реализовать), тогда вызов TaskManager.GetInstance.GetDatabase в одних случаях должен возвращать MysqlDatabase, в других MongoDatabase, а в третьих MysqlAndMongoDatabase?
> вызов TaskManager.GetInstance.GetDatabase в одних случаях должен возвращать MysqlDatabase, в других MongoDatabase, а в третьих MysqlAndMongoDatabase?

Именно это я показал наглядно в upd. статьи. Да, это так.
Как TaskManager.GetInstance.GetDatabase узнает о контексте вызова? Как он поймёт, что в одном месте person.save() надо сохранять в моного, в другом в мускул, а в третьем и там, и там?
И если бы такие изыски будут, то изменятся методы внутри DBData — там появится прогон по циклу числа дублирования в базы с подстановкой нужной. И заметьте — эти изменения логики НИСКОЛЬКО не затронут клиентскую часть, клиент по прежнему будет наивно пологать что запись где-то записалась, а вот ядро обеспечит, что она запишется там где надо и столько раз сколько нужно.

А у вас сравнительно код поплывет. Вы начнете дублировать уже целыми кусками.
«А у вас сравнительно код поплывет. Вы начнете дублировать уже целыми кусками. „
Не-а.

Как было Repository{of Person}.Save, так и останется. А вот внутри уже будет существенно смешнее, вплоть до того, что для Person будет реализован отдельный репозиторий со своей пропьетарной логикой. Это нормальное течение событий.

При этом заметьте, что знать об этом будет только _один_ класс в системе, и он будет отвечать _только_ за персон. А ваш DBData такими темпами превратится в god class, отвечающий за правила сохранения всех классов в системе.
> А ваш DBData такими темпами превратится в god class, отвечающий за правила сохранения всех классов в системе

Скорее эта ответственность будет распределена между DBData и TaskManager. Но то, что такое централизованное место будет в системе это большой плюс.
«Скорее эта ответственность будет распределена между DBData и TaskManager.»
Нарушение SRP.

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

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

Зато нет бардака с частными проявлениями сохранения.
«Зато нет бардака с частными проявлениями сохранения. „
Есть. Весь в одном классе, что характерно.

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

(“мыслите объектно», да. Это ведь классический пример специализации.)
Т.е. DBData отвечает на вопрос сколько раз сохранять, а TaskManager куда именно сохранять (впрочем эту ответственность TaskManager скорее делегирует еще одному новому классу, т.к. он всего лишь верхушка управления аспектами)
> Чтобы она была сильной, это должен быть не интерфейс, а класс

Так между прочим — чем отличается интерфейс от класса? Да, ничем, так как передаете вы не голый интерфейс, а реализацию этого интерфейса выполненную конкретным классом. Но для вас видимо это сложно понять.
Уф… Интерфейс не содержит никакой реализации и поэтому мы можем передать любую.
Здрасти приехали.

когда вы вызываете такой конструктор

public Repository(IDbContext dbContext)

вы что ему передаете?
Что угодно, реализующее IDbContext. Если бы конструктор был вроде public Repository(MysqlDbContext dbContext), то передать ему бы PostgreSqlDbContext или SqliteDbContext мы бы не смогли (если считать что имена классов соотвествуют их содержимому). Указание конкретного класса в типе параметра — это сильная связанность, указание интерфейса — намного слабее.
Вы хоть читаете себя "реализующее IDbContext", а строкой выше писали «Интерфейс не содержит никакой реализации». Но передаете то вы реализацию интерфейса. Я выше и пишу «реализацию этого интерфейса выполненную конкретным классом» В итоге ни какой разницы.

Если же вы делали акцент на «передать любую», то незабывайте продолжение «реализацию этого интерфейса выполненную конкретным классом». Что аналогично тому, что я могу передать также любого наследника абстрактного класса. И разницы ну нет никакой.

Спорить будите? Или с чем не согласны?

Абстрактный класс может содержать реализацию, более того, реализацию которую наследники не могут переопределить (final). Интерфейс не может и его реализация обязана сделать всё сама. Где больше связанности?
Нет ни какой разницы!!!
> реализацию которую наследники не могут переопределить (final)

И это хорошие свойство. Так DBData в отношении работы с базой должен быть именно таким.
> Если бы конструктор был вроде public Repository(MysqlDbContext dbContext), то передать ему бы PostgreSqlDbContext или SqliteDbContext мы бы не смогли

Ну кто же так делает? конструктор должен быть просто вроде public Repository(DbContext dbContext), а все PostgreSqlDbContext или SqliteDbContext наследниками DbContext
«Так между прочим — чем отличается интерфейс от класса? „
Проще сказать, что у них общего — и тот, и другой могу содержать публичные методы. Все.

А так: интерфейс — это выраженный в терминах C# поведенческий контракт, класс — конкретная реализация этого контракта.

Как следствие, _передаем_-то мы экземпляр класса, но UsageSample _зависит только_ от интерфейса, и это как раз и является основой слабой связанности в этой части системы.
Ну замечательно хоть написали: _передаем_-то мы экземпляр класса

А дальше снова глупости. Если мы передаем экземпляр класса — то мы и в том и другом случае зависим от интерфейса этого переданного экземпляра. Будет ли оно обрезано с помощью директивы interface, или через модификаторы public — нет ни какой разницы. Как была связность ровно такая же, так и останется — а лирику выбросите в корзину.
То есть вы не видите разницы между зависимостью от интерфейса и зависимостью от конкретного класса?

На всякий случай, повторяю: реализацию интерфейса заменить легко, реализацию конкретного класса — сложнее (иногда вообще невозможно).
разницы между зависимостью от интерфейса и зависимостью от абстрактного класса — нет НИ КАКОЙ. Это же классика.
Читайте, пожалуйста, внимательно. Я спрашивал про зависимости «от интерфейса и [...] от *конкретного* класса». А вы мне отвечаете про *абстрактные* классы.

С абстрактными классами ситуация выглядит несколько иначе, она ближе к интерфейсам, но там тоже может быть проблема, когда абстрактный класс имеет в себе какое-то фиксированное поведение. Это плохо и с точки зрения связанности, и с точки зрения, например, тестирования.
Ну вот вы и будьте внимательнее, я все время веду речь о абстрактных классах. Или же ни могут быть формально не абстрактными, а просто иметь непереопределяемые методы.

Т.е. фиксированное поведение — тут необходимо, чтобы некоторые умники не надумали бы поменять базовое поведение.
«Ну вот вы и будьте внимательнее, я все время веду речь о абстрактных классах. „
Это никак не вытекает ни из ваших слов, ни особенно из вашего примера (где все классы конкретные).

“Т.е. фиксированное поведение — тут необходимо, чтобы некоторые умники не надумали бы поменять базовое поведение. „
Вот именно в этот момент у вас и возникает сильная связь — когда _другой_ класс начинает зависеть от фиксированной реализации поведения вашего класса.

Вообще же, вы путаете _поведение_, которое фиксируется контрактом/интерфейсом, и _реализацию_ которая фиксируется абстрактным классом.

Ну и да, я в принципе ничего не имею против абстрактных классов — кроме того, что их существенно менее удобно тестировать.
> ничего не имею против абстрактных классов

Хоть немного дошло… будем считать, что с вас хватит…
Я вообще-то нигде обратного и не утверждал, если что.
Расскажите мне разницу между

public abstract class DBData
{
public void Save()
{}
}

public class DBData
{
public void Save()
{}
}


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

в save находится

TaskManager.GetInstance().GetDatabase().Save(this)
Если вы об этих классах в вашем конкретном примере, то вообще пофиг, потому что этот класс никуда не передается, зависимостью не является и в нашем разговоре не интересен.
Так а какой же тогда класс вам интересен, Database?

Если вы вспомните, этот тред начинался с того, что в моей реализации зависимости передаются через параметры конструктора. Соответственно, спор об интерфейсах и классах возник применительно к ним. Что у вас выступает зависимостью? TaskManager и Database.
Давайте тогда уже говорить полностью.

У меня зависимость между абстрактным классом DBData и своеобразным синглом TaskManager, которые дает ссылку на конкретную реализацию Database. Но вся эта реализация Database адаптируется в том же DBData.

Т.е. есть достаточно сильная связь между парой DBData и Database, при участии посредника TaskManager. Так?

Но у наследников DBData нет ни какой связи больше не с чем.

что вы предлагаете в замен?

Вы каждый раз обязаны передать в качестве параметра экземпляр конкретной реализации Database в наследников DBData.

По вашим словам это слабая связь. Но там где вы выше находите эту конкретную реализацию Database вы еще раз создаете связь. В итоге, ну пусть если вам хочется думать чо это слабая пусть слабая. Но их сотни — всюду где вы работаете с наследниками DBData. С этим то вы согласны?
«Т.е. есть достаточно сильная связь между парой DBData и Database, при участии посредника TaskManager. Так?»
Она мало того, что сильная (конкретный класс), так еще и неявная (без анализа кода класса ее не найдешь).

«Но там где вы выше находите эту конкретную реализацию Database вы еще раз создаете связь.»
Это уже связь других двух классов. Сам класс, в который передана реализация (в моем примере это все Repository и UsageSample), остается слабосвязанным, механизм получения конкретного экземпляра его не затрагивает.

Ну и да, не «я нахожу конкретную реализацию», а «система предоставляет конкретную реализацию», это важное отличие.

Понимаете, при правильно построенном IoC вся система _кроме конфигурации IoC_ состоит из слабых связей, и поэтому любой ее компонент можно выдернуть, заменить, адаптировать и протестировать.
Т.е. вы согласны — сотни т.н. слабых связей? Если уточнить это связи использования, еще конкретнее связи образующиеся при передачи параметров. Так?
Я никогда с этим и не спорил.

(не обязательно при передаче параметров, есть еще Service Location, там параметры не передаются, но связь тоже слабая)
Замечательно. Т.е. вы считаете, что передача сотни параметров лучше, чем связь DBData и Database в моем случае? Вам кажется, что мне будет сложнее вносить изменения, а вам будет легче?
«Т.е. вы считаете, что передача сотни параметров лучше, чем связь DBData и Database в моем случае?»
Да.

«Вам кажется, что мне будет сложнее вносить изменения, а вам будет легче? „
О какого рода изменениях идет речь?

“Покажите как в UsageSample попадает конкретная реализация Repository?»
Я вам ранее (в ответ на этот же вопрос) все показывал.

habrahabr.ru/post/144645#comment_4858670

С Repository то же самое — либо она передается снаружи кода-пользователя (и дальше вплоть до реального инвокера, там может быть по-разному в зависимости от общей архитектуры приложения), либо достается из сервис-локатора запросом Resolve{of Repository{of Person, Guid}}. Собственно, реальный вызывающий код верхнего уровня делает то же самое. просто скрыто от пользователя.

«Как это интересно не вы передаете параметр конструктору, а система?»
Очень просто. Например, в asp.net mvc точкой входа является действие в контроллере. Контроллер — это класс.

Соответственно, все нужные зависимости я просто объявляю параметрами конструктора этого контроллера, а дальше инфраструктура asp.net mvc при создании конструктора по заданным правилам их находит и передает.
> Т.е. вы считаете, что передача сотни параметров лучше, чем связь DBData и Database в моем случае?»
> Да.

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

Вот вы мне расскажите. Ради чего вы так желаете обречь программиста передавать лишние параметры? Какие преимущества вы в этом видите?
Или вас просто не учили минимизировать число параметров? Я честно не понимаю, ради какой такой святой идеи вы готовы на это пойти?
«Вот вы мне расскажите. Ради чего вы так желаете обречь программиста передавать лишние параметры?»
Программисту в среднем никогда не приходится делать это руками.

«Какие преимущества вы в этом видите? „
Полный и явный контроль за зависимостями, используемыми кодом.

“Я честно не понимаю, ради какой такой святой идеи вы готовы на это пойти?»
Автоматизированное изолированное тестирование.

(это не считая модульности и модернизируемости, это реже встречается)
> Программисту в среднем никогда не приходится делать это руками.

А как? ногами?
Почитайте документацию на DI-контейнеры и сервис-локаторы. Программист говорит: я хочу получить экземпляр такого-то класса/интерфейса (не важно, является этот экземпляр аргументом конструктора, или же это явно запросили в Resolve), IoC-фреймворк отдает ему такой экземпляр _с уже заполненными зависимостями_.

Я уже приводил пример: в asp.net mvc параметры конструктора контроллера (а это основной вызывающий код) могут заполняться фреймворком автоматически, после чего у программиста уже есть все нужные ему объекты, кроме доменных (а те традиционно не требуют никаких сервисов). Все, никаких вызовов конструкторов в собственном коде программиста (кроме доменных объектов, повторюсь).
Сомнительная техника, но посмотрю…
1) Явные зависимости лучше неявных. Допустим я захотел использовать ваш класс *Data, вижу зависимость от DBData, копирую его к себе и получаю ошибку компиляции, мне нужно лезть и искать зависимости от DataBase и TaskManager

2) Параметр при вызове конструктора/сеттера/метода подменить куда проще, чем захардкоженные где-то в дебрях иерархии зависимости.
> Параметр при вызове конструктора/сеттера/метода подменить куда проще, чем захардкоженные где-то в дебрях иерархии зависимости.

Да не ужели? параметры вам придется менять по всему коду, в отличии о одного места в ядре.
«Да не ужели? параметры вам придется менять по всему коду»
Вам уже неоднократно объснили, что никто не делает это вручную.
От того, что вы делаете это неявно через фреймфорк — это не означает, что вы этого не делаете.
Это означает, что мне не надо менять параметры по всему коду.
Только в одном месте — месте инстанцирования реализации (или наследника для абстрактных классов) Storage вроде App.Init(config). Если и придётся переписывать, то метод инициализации, где собраны все инициализации приложения, а не что-то где-то в ядре. Далее, параметры передаются по цепочке/дереву, заменив параметр в начале цепочки/корне дерева один раз я заменю его по всей цепочке/дереву. Если мне нужно будет добавить параметр (а не изменить) я его добавлю в конкретную ветвь, там где он нужен.
> Далее, параметры передаются по цепочке/дереву, заменив параметр в начале цепочки/корне дерева один раз я заменю его по всей цепочке/дереву.

Этого я не понимаю, можно псевдокодом?
App.Init() {
  ...
  storage= new MysqlStorage();
  ...
  dispatcher.Init(storage);
  ...
}

Dispatcher.Init(IStorage storage) {
  ...
  _handlers.add('event_type', new SomeController(storage));
  ...
}


Чтобы изменить типа хранилища с MysqlStorage на MongoStorage или FileStorage мне нужно изменить только new в App.Init(). В конструктор SomeController оно попадёт по цепочке через Dispatcher.Init().
> есть еще Service Location
> ServiceLocator.Resolve().Do()

Чем это отличается по сути от

TaskManager.GetInstance().GetDatabase().Save(this)

?
Тем, что ваш TaskManager — это неадекватная реализация сервис-локатора. В частности, сервис-локатор не должен в своем коде содержать ссылки на конкретные реализации сервисов. Ваш TaskManager — это общая фабрика, это другая вещь, она сама содержит сильные связи (да еще и на глобальном уровне, это очень сложно тестировать).
А в этом случае, объясните мне такому непонятливому, почему вы не видите дублирования кода когда в ста места пишите

ServiceLocator.Resolve().Do()


ServiceLocator.Resolve(@UsageSample@).Do()
Это происходит не в ста местах, а существенно реже. И да, это типовая операция, она никого не смущает, как никого не смущает получение логгера или любой другой бойлерплейт.

Ну и да, его можно легко и просто избежать, перейдя на DI.
Это происходит КАЖДЫЙ раз когда вы решили сохранить сущность, если у вас сущностей меньше ста — то меньше, больше — значит больше
Сущность я обычно хочу сохранить не просто в воздухе, а внутри какого-то другого процесса, выраженного объектом (у нас же ООП, да?). Соответственно, этот объект может быть существенно более долгоживущим, чем создавамые объекты, и в нем эта инициализация дешевле (даже если не делать ее автоматически).

«Что такое DI? „
(Made my day)
Dependency injection.
Что такое DI?
Вы кажется говорили, что лучше всех знаете, что такое IoC? И задаете такие вопросы?
Нет, такое смущает и очень сильно. Еще раз зачем это делать порядка сотни раз, вместо одного? Я пока лишь услышал, что вам тесты писать сложно? Но не ваша ли это проблема? И кстати почему сложно?
«Еще раз зачем это делать порядка сотни раз, вместо одного?»
Я уже сказал, как можно этого не делать.

«Но не ваша ли это проблема?»
Моя. Но я проектирую код именно для того, чтобы уменьшить количество своих проблем.

«И кстати почему сложно?»
Возьмем пример вашего кода: ваши объекты, наследуемые от DbData, всегда обращаются при сохранении к TaskManager, а тот всегда создает фиксированный конкретный класс Database. Это значит, что любой код, использующий ваши «бизнес-объекты», вызвав у такого объекта Save, автоматически спровоцирует именно эту цепочку обращений, и в результате будет обращение в реальную живую MongoDb (это если у вас монга, c sql все еще веселее будет). Как следствие, любой тест такого кода приведет к тому, что объекты будут читаться из реальной БД и писаться в реальную БД. Это не изолированный тест, и его сложно сетапить (потому что контролировать состояние БД существено сложнее, чем состояние переменных в коде).

А теперь ответьте мне на вопрос, если не секрет — сколько модульных тестов вы написали в своей жизни?
Проблемы я тут не вижу. Также как легко в моем коде подменить одну базу на другую (см. upd. в статье), ровно также надо ее подменить на TestDB — эмулирующую реальную базу.

я тесты не пишу в своей жизни по идеологическим соображениям :)
«Проблемы я тут не вижу. Также как легко в моем коде подменить одну базу на другую (см. upd. в статье), ровно также надо ее подменить на TestDB — эмулирующую реальную базу. „
Угу. Написав ветку условий “когда тест», да? И это потом пойдет в продакш-код?

А зачем этот велосипед изобретать, когда есть стандартное решение в виде сервис-локатора?

«я тесты не пишу в своей жизни по идеологическим соображениям»
То есть, вы считаете, что ваш код ошибок не содержит?
Ага, мои ошибки ловят тестеры :)
И да, я уверен, что после моей отладки с дебагером, модульные тесты мне не нужны вообще (ошибок они не выявляют, а только тормозят развитие ПО). Если что и нужно только композитные-функциональные. А вот тут как не крути, по факту получается, что хороший тестировщик работает на порядок эффективнее, чем если бы программист писал бы тесты (тут я хотел бы поучится, но не по вершкам, а в реальном проекте, сравнив как получается автоматизировать работу тестировщиков).
«И да, я уверен, что после моей отладки с дебагером, модульные тесты мне не нужны вообще (ошибок они не выявляют, а только тормозят развитие ПО).»
Это показывает, что вы вообще не понимаете, как работает модульное тестирование.

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

Во-вторых, задача модульных тестов — быстрая проверка работоспобности всей (покрытой) системы, а не только того места, которое вы сейчас отлаживаете с дебаггером; то есть, модульные тесты — это средство избежать регрессионных багов, особенно при рефакторинге (правда, я подозреваю, что вы и не рефакторите тоже).

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

Ну и в-четвертых, модульные тесты позволяют вообще не пропустить в VCS неработающий код, тем самым поддерживая практики CI/CD.

(вы прослушали краткий пересказ тестов МакКоннела, Бека и Фаулера, спасибо)
> задача модульных тестов — сделать так, чтобы вам вообще не была нужна отладка с дебаггером при разработке кода. Если она вам нужна — значит, вы уже что-то делаете не так.

А ну тогда, конечно :) ладно бредовых идей я много наслушался… как бы не поверить :)
Можете верить, можете не верить, а я с этой «бредовой идеей» вижусь каждый день.

«Уши бы поотрывал бы сказочникам этим… „
Эти, как вы выражаетесь, “сказочники» опираются на реальный опыт, статистику, исследования и прочий фактический материал. Вы же этим пока похвастаться не можете. Повторюсь, мы пока от вас даже примера рабочего кода не видели.

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

“> я подозреваю, что вы и не рефакторите тоже
Да, нет — делаю это часто.»
И как вы умудряетесь рефакторить без тестовой поддержки? Откуда вы знаете, что ваш структурный рефакторинг не изменил смыслового поведения кода? Что все, что раньше работало, продолжает работать?
> я подозреваю, что вы и не рефакторите тоже

Да, нет — делаю это часто. Это как раз та причина почему я тут с вами вообще о тестах говорю
> вы прослушали краткий пересказ тестов МакКоннела, Бека и Фаулера, спасибо

Уши бы поотрывал бы сказочникам этим…
Ну понятно. Современная культура разработки явно прошла мимо вас.

Надеюсь, вам не надо объяснять, что внешнее тестирование программы (даже если оно автоматизировано, что само по себе редкость) на несколько порядков медленнее и сложнее, чем модульное?

(именно благодаря зависимостям и сетапу ландшафта)
> Современная культура разработки явно прошла мимо вас.

Нет, просто но мне глубоко противна, и я вот не пойму почем люди ведутся на сказки?
> Ну и да, не «я нахожу конкретную реализацию», а «система предоставляет конкретную реализацию», это важное отличие.

Покажите как в UsageSample попадает конкретная реализация Repository? Как это интересно не вы передаете параметр конструктору, а система?
Какая тогда вам разница Database абстрактный или нет?
В вашем примере — никакой, поскольку в нем, в отличие от моего, тема зависимостей не рассматривается.
и да куда еще конкретнее, метод Save() непереопределяемый наследниками
Зависимость класса от двух интерфейсов реализовать можно легко, а от двух абстрактных классов…
> На всякий случай, повторяю: реализацию интерфейса заменить легко, реализацию конкретного класса — сложнее (иногда вообще невозможно).

Это ваши фантазии
Разница у них в другом, но не в силе зависимости.
В абстрактном классе может быть реализация (собственно если реализации нет, то логичнее использовать интерфейс), наследник будет от неё зависеть, повторяю, зависеть от реализации. Какая зависимость сильнее от интерфейса или от интерфейса и реализации?
Вообщем так — с начинающими невеждами retran, lair и EvilBlueBeaver — я на Хабре больше не общаюсь в полный и глубокий игнор (не то чтобы я не любил поговорить с троллями, но что мне могут сказать люди которые по возрасту на 10 лет имеют меньше опыта. Возможно в их годы я знал поменьше, но по крайней мере не был хамом, и думал прежде чем говорить. Они же нахватались по вершкам, и думают что что-то понимают в этой жизни и могут учить других).

По прежнему жду комментариев от серьезных людей. Из дискутирующих остался VolCh о которого жду ответа — понимает ли он проблему которую я ему обозначил?
Эпический стыд…
Я так понимаю, вы только что признались, что аргументированно спорить вы более не способны и за свои слова «покажите мне конкретную реализацию, и я скажу, чем моя реализация лучше» не отвечаете?
Выше я вам ответил, если вы расписываетесь что не видите — ну что я могу сделать.
Вот только никаких аргументов о том, чем ваша реализация лучше — нет. Равно как и ответов на вопросы про тестирование, поиск дженерик-методов, потоковую безопасность и прочие ошибки в _вашем_ коде.
Не видящий, не прозреет.
Я понял вам нравится гонять ссылки на хранилище — там где это не нужно. Замечательно для вас. Но бред для всех программистов.
«Я понял вам нравится гонять ссылки на хранилище — там где это не нужно.»
Вот как раз там, где не нужно, их не передают.

«Но бред для всех программистов.»
То есть IoC via constructor injection придумал лично я, и Unity, AutoFac, nInject, StructureMap, Castle Windsor и кучу всего остального — тоже я. Занятно.
да не вы придумали, вы не умеете использовать там где надо, а суете туда где не надо.
Так-так-так, а расскажите мне, где тогда _надо_ использовать все вышеперечисленное?
Я бы рассказал, но вы не благодарный слушатель.
Ну я тут не единственный, кто вас читает, это вроде не личная переписка.
Тут надо как не будь писать новую статью. Но зачем, если многие хотят жить в своем неведении?
Вообщем так, я понял какие существуют массовые заблуждения. Дальше мне не интересно. (особенно с людьми, которые переводят всегда разговор на другое, когда пора уже увидеть свои заблуждения).

Ушел работать.
> Ушел работать

Лучше сделать уроки
Вот вроде взрослый человек, а туда же… не стыдно?
поздновато пришли. топик становится унылым.
В общем, судя по последнему реверансу, в специальной олимпиаде победил tac.
ПОЗДРАВЛЯЮ!

нужно позвать habrahabr.ru/users/EugeneOZ/ надеюсь его не хватит удар или приступ
И чем нам поможет EugeneOZ?
Теперь я совершенно иначе смотрю на IoC, Repository, ActiveRecord, абстракции, интерфейсы, связанность и DI.
Может расскажите как?
Пусть выжимку ваших тезисов сделает кто-нибудь другой. Я и так пол рабочего дня сорвал чтением комментариев к этому эпичному треду. А я лучше пойду переписывать свой DataLayer в соответствиями с новообретенными знаниями. Ну или, чего уж там, использую ваш. Сэкономлю время на тестировании, отладке и внедрении нового функционала.
Т.е. вы согласны со мной?

P.S. Мне это важно, т.к. видите тут меня забросали помидорами :)
слишком тонко. поциент(с) даже не заметил.
Итак я понял, все ваши возражения навеяны достаточно сомнительной идеологией Dependency Injection

например я почитал DI и IoC для начинающих. Что нового мне это дало? Не много. Надо использовать с умом. А тут мне предлагают от этого отказаться.

Хороший вариант это следующий способ, через атрибуты:

public class MyWindow : Form
 {
   [Dependency]
   public IService Service { get; set; }
 
   public MyWindow()
   {
     ⋮
   }
 }


т.е. когда конструктор не захламляется лишними параметрами.

Как это применить для моей идеологии отделения логики базы данных. Это помогает лишь избавится о обращения к TaskManager, его заменяет как раз DI-контейнер. Все остальное остается как было.

Впрочем вы подумали — как этот фрейворк работает? И как он тормозит? Ведь в отличии от использования этого фреймфорка использование статических членов хоть и не приятно для ООП, но не тормозит. Это фреймфорк наверняка как раз и работает с отображением, и при этом, т.к. он на все случаи жизни — там отображение не такое простое как у меня тут.
Вы когда-нибудь писали юнит-тесты?
«Хороший вариант это следующий способ, через атрибуты: [...] т.е. когда конструктор не захламляется лишними параметрами.»
Зато захламляется интерфейс класса во-первых, и теряется очевидность нужных сервисов во-вторых.

Впрочем, это как раз вопрос предпочтения, идеология от этого не меняется ни на грош.

«Впрочем вы подумали — как этот фрейворк работает?»
А что тут думать, я это в реальной жизни попробовал.

«И как он тормозит?»
На фоне реальных задач (например, обращения к БД или http-запроса) время инициализации пренебрежимо.

«Это фреймфорк наверняка как раз и работает с отображением, и при этом, т.к. он на все случаи жизни — там отображение не такое простое как у меня тут. „
Кстати, а какой “этот фреймворк»?

Но вообще, в любом нормальном di-фреймворке все равно есть многоуровневое кэширование, так что цена рефлексии не так уж велика.

«например я почитал DI и IoC для начинающих.»
А на чем основаны ваши более ранние утверждения об IoC тогда?
> Кстати, а какой “этот фреймворк»?
В статье речь шла о Unity, если пробовали то посоветуйте какой лучше?

> А на чем основаны ваши более ранние утверждения об IoC тогда?
Видимо я спутал термины Inversion of Control, я представлял себе это несколько по другому. Можете сказать, кто придумал этот термин в вашем понимании, и где оригинал объяснения, чтобы мне не читать по искаженным пересказам.

Я то думал — это реально «инверсия управления», когда пользователь в GUI жмет на кнопку, и тогда нужно инверсировать управление, чтобы визуализация не управляла бизнес-логикой… но видимо это понятие так и не ввели, а термин так хорошо для этого подходящий тут — забили совершенно идиотским смыслом.
«В статье речь шла о Unity, если пробовали то посоветуйте какой лучше? „
Зависит от задачи. Нас Unity всем устраивает. Кто-то молится на AutoFac. Кто-то фанат nInject. И так далее. Зависит от задачи и личных предпочтений.

“Видимо я спутал термины Inversion of Control, я представлял себе это несколько по другому.»
Но при этом отказались дать свое определение и ссылку на него. Зато повоевали, что у нас все неправильно реализовано. Как обычно.

Вот, читайте: www.martinfowler.com/articles/injection.html
Мои возражения навеяны не IoC/DI, а наличием у классов-наследников DBData нескольких (прежде всего) ответственностей и неявных захардкоженых сильных зависимостей (в меньшей степени).

Объекты бизнес-логики занимаются ещё и своим хранением, загрузкой и даже загрузкой других объектов бизнес-логики — это плохо.

При необходимости изменить логику хранения нужно лезть куда-то вглубь «ядра» — это плохо.

Ну у нас разные оценки. Если речь действительно не о IoC/DI, то всюду где вы сказали плохо — я говорю прекрасно. Ваше же решение с передачей параметров по цепочке — ужасно. На этом думаю и остановимся.
Знаете почему вам кажется это плохо? Это отнимает у вас свободу делать бардак в коде. Ваше решение не централизованное, а соответственно вы можете один и тот же принцип реализовывать каждый раз по разному — это плохо. Мое решение это не позволяет, т.к. концы ВООБЩЕ отобраны у прикладного программиста, а ядром занимается другой программист, который меняет не в «данном конкретном случае как хочется» — а на все случаи жизни.
«который меняет не в «данном конкретном случае как хочется» — а на все случаи жизни. „
В этом ваша ошибка. Нельзя предусмотреть все случаи жизни. И нельзя их запихнуть в один универсальный базовый класс.

Специализация, специализация.
Глупости, можно. Я же это реально делаю :) Есть классические типовые случаи, я всего лишь выделяю логику, которую буду использовать повторно, и причем прозрачно для остальны, т.к. что они даже не знают как работать с базой. Это то и есть реальное отделение базы данных — это когда человек сохраняя не знает куда и как он сохраняет. Ваш же вариант сохранения — это фикция, вы меня заставляете знать то, что мне не нужно знать работая с бизнес-логикой объектов.
«Я же это реально делаю»
Мы пока не видели ни одного примера вашего «боевого» кода, так что что и как вы делаете, мы не знаем.

«Это то и есть реальное отделение базы данных — это когда человек сохраняя не знает куда и как он сохраняет. „
В случае с репозиторием человек тоже не знает, куда и как он сохраняет, он знает только что он отдал объект в репозиторий.

“вы меня заставляете знать то, что мне не нужно знать работая с бизнес-логикой объектов. „
Вы что-то путаете. Вам уже неоднократно говорили, что при работе с бизнес-логикой объектов часто можно вообще о сохранении не думать. А когда надо о нем думать, очень полезно понимать, что вот — бизнес-логика, а вот — хранилище. И они не смешиваются.
> он знает только что он отдал объект в репозиторий

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

У вас вот в контракте объекта точно так же «светятся» все методы, связанные с хранилищем, хотя они к бизнесу объекта отношения не имеют.
> У вас вот в контракте объекта точно так же «светятся» все методы, связанные с хранилищем

Нет тут хранилище вообще не фигурирую, эти методы указывают лишь на то, что объект — долговременно живет.

> они обеспечивают явную видимость того, где и какая работа с объектом происходит, отделяя два _разных_ аспекта этой работы — бизнес-логику и персистентность

Это словесная эквилибристика, давайте еще ссылку на таймер всюду гонять, ведь время то идет? Не чувствуете абсурдность вашей эквилибристики?
Нет, не чувствую.

(еще и потому, что Хамбл/Фарли советуют и системное время абстрагировать до сервиса, так что получается именно так)

Но вообще, вы умудряетесь смешивать в одном месте сразу три вещи:
— разделение персистентности и бизнеса (POCO и Repository)
— слабую связанность за счет IoC
— механизм реализации IoC (DI vs Service Locator)
> вы умудряетесь смешивать в одном месте сразу три вещи

Да, потомучто по отдельности все красиво звучит, кто же спорит… а на практике все вместе дает гавно
Вот только смешиваете их именно вы. Ну и результат выходит…
Глупости, смешиваю не я — я необходимость реализовывать ПО. Вы также все смешиваете, но не видите, что получается итого, точнее смотрите на это в «розовых очках»
я необходимость -> читать (а необходимость)
Причем даже вот это: — слабая связанность в смысле IoC — это фикция, это протягивание «ниток», через весь проект — связности хуже этой не придумаешь.
Если реализовать через Service Locator, то (для вызывающего) ничем не будет отличаться от вашего TaskManager.

Вы никак не поймете, что IoC — это принцип, смысл которого исключительно в том, что код старается не создавать свои зависимости самостоятельно, а получать их откуда бы то ни было. А DI/SL — всего лишь варианты его реализации.

А вы почему-то ставите знак равенства между IoC и DI.

«связности хуже этой не придумаешь»
Вот вы как меряете связность? Я вот очень просто — сколько действий мне надо совершить, чтобы «боевой» код, в который не внесено никаких специальных изменений, запустить в изолированном тесте, не покалечив при этом соседние тесты. Чем больше действий, тем больше связность. Тупо, банально и прямолинейно. И вот по этой «метрике» DI пока лидирует.

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

А иначе зачем вообще нужна вся эта «слабая связанность»?

«Вы также все смешиваете, но не видите, что получается итого»
Почему же, вижу. Примеры кода приводил. Они простые и понятные, в них нет неявных действий. Это самое важное — отсутствие неявных побочных эффектов.
> Вот вы как меряете связность?
> для разработки важнее возможности заменить один компонент на другой

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

Нестабильно. Захочу я использовать класс SomeClass:DBData в приложении где вообще нет хранилища, калькулятор какой-нибудь. Прикажете тащить с собой всю вашу инфраструктуру из трёх классов? А в случае идеологии PO*O+Repository мы можем класс скопировать без проблем и только его.
«Повторное использование. К сожалению, или к счастью — с этим я имею дело очень часто. И приходится отбирать именно такие варианты реализации, которые это обеспечивают лучше. Идеология изложенная мной тут — это обеспечивает стабильно.»
Идеология, нарушающая SRP, не может давать стабильного повторного использования, потому что каждый, кто повторно использует ваш код, в нагрузку получает множество ненужной (а иногда и вредной) функциональности. И, как вам уже неоднократно показывали, ваш код сильнее связан, а это значит — менее модулен и _хуже приспособлен_ к повторному использованию.

Вообще, известно, что повторное использование — это композиция, а не наследование. Компонуется ваш код отвратительно.

«Возможно этот новомодный подход и можно как-то адаптировать к реальности»
Новомодный? Принципам IoC больше двадцати лет, статье Мартина — шестнадцать, PoEAA Фаулера — девять. Так что этот подход уже успел себя оправдать, спасибо.
> А когда надо о нем думать, очень полезно понимать, что вот — бизнес-логика, а вот — хранилище

Нет. Это вредное знание — лишние просто напросто. Попробуйте ответить на вопрос — зачем мне это знать?
Чтобы не путать операции, происходящие в разных смысловых областях.
Мне вообще не нужно это знать — я и путать не буду ничего.
Попробуйте ответить на вопрос зачем мне как чисто прикладному программисту, занимающемуся чисто бизнес-логикой, знать о необходимости наследоваться от DBData и не переопределять методы save, load и т. д. (про то, что они постоянно будут в автодополнении мотаться я уж умолчу)? Более того, в контексте предметной области у методов save, load и т. д., может быть вполне естественная семантика без привязки к ИТ. Например, загрузить заготовку в станок.
Да, кстати, обратите внимание, что в сценарии с репозиториями прикладной программист тоже может не иметь доступа к коду работы с БД, потому что он пишет только прикладные классы. А репозитории пишет вообще другой человек (вплоть до того, что они в другой сборке).
Да ну? Только вот почему то прикладному программисту — нужны постоянно ссылки на репозиторий, зачем они ему тогда?
Если бы вы реально хотели бы отделить бизнес-логику от логики базы данных, вы обеспечили бы элементарную вещь, без модных терминов. А именно, так чтобы мимо прикладного программиста вообще «не летали» ссылки на репозиторий, а так вы создаете лишь видимость разделения!
Прикладномуони и не нужны, он не занимается задачами хранения, управления, интерфейсами и т. п… Он занимается прикладными задачами, бизнес-процессами, в которых понятия «база данных», «хранлище», «сохранение», «загрузка» просто не существуют или носят совсем другой смысл. А вот в ашей системе он будет постоянно на них натыкаться, хотя они ему и не нужны.
Концы отобраны как раз в «моём» решении. Их просто нет для прикладного программиста, он может решать прикладные задачи вообще не оглядываясь на систему хранения, управления и представления, может решать прикладную задачу любыми средствами. В вашем же случае он как минимум должен наследоваться от DBData (в том числе лишаясь возможности наследоваться от чего-то другого).

И на все случаи жизни всё равно не получится создать систему хранения. Она должна знать о деталях реализации бизнес-логики, чтобы эффективно её сохранять и восстанавливать. Более того, она должна знать даже о деталях эксплуатации, например, для каких объектов чаще востребована операция чтения, а для каких записи.
> Она должна знать о деталях реализации бизнес-логики

Смешно. А зачем тогда вообще отделять БД от бизнес логики :)
Чтоб бизнес-логика не знала о системе хранения. А универсального способа определения что сохранять, а что нет без ИИ наверное не придумать. Сохранять все свойства объекта явно не такое решение.

Про то как выбирать в каком виде сохранять лучше вообще не вспоминать. Отношение наследования например сохраняется в БД минимум тремя стандартными способами.
> Концы отобраны как раз в «моём» решении. Их просто нет для прикладного программиста, он может решать прикладные задачи вообще не оглядываясь на систему хранения

Ну, ваше решение мы уже видели… не стоит тут говорить одно, и держать фигу в кармане. Это вы своим клиентам заливайте. У вас же прикладной разработчик постоянно должен держать ссылку на систему хранения, иначе он просто ничего не сохранит.
Прикладному разработчику и нет нужды что-то сохранять, это не его компетенция.
Вообще это даже смешно звучит, это каким надо быть мазохистом, чтобы на вопрос «Ты хочешь заботиться о нюансах сохранения в БД, или не хочешь даже знать, что есть БД?». Отвечать: «да хочу, и при этом почаще, каждый раз когда сохраняю получать хранилище и именно в него передавать мои объекты.»

Ладно, я еще понимаю какие-то изыски… тогда можешь запросить у TaskManager базу, поколдовать над ней как нужно, и аналогично вашему варианту сохранить. Но в базовых типовых вариантах чтобы обрекать себя на геморой? Это выше моего понимания.
Программирование — вообще гемморой тот еще. Зачем им заниматься, если не нравится?

Тут вопрос в том, какой гемморой геммороистее:
1. Юнит-тесты или поиск иголки в стоге сена с помощью дебаггера.
2. Разделение ответственности с некоторым оверинжинирингом или поиск нужного кода в недрах год-обджекта.
3. Дописывание нужного кода по мере возникновения в нем необходимости или постоянное перепроектирование и загаживание сильно связанного кода.

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

tac, а зачем вам вообще новомодный сишарп? ООП это непонятное. Есть же старый добрый и проверенный временем Фортран и оператор GOTO! А про Кобол почему забыли?
Наследование от DBData и использование (да даже существование) методов save и load в контексте бизнес-логики вы называете «не хочешь даже знать, что есть БД»? Представляю картину как вы объясняете человеку, занимающемуся бизнес-логикой «Нужно унаследоваться от DBData, но тебе не нужно знать зачем, ты не хочешь знать. Будут методы save и load, но тебе не нужно их вызывать и знать зачем они нужны, т этого не хочешь. Если тебе эти названия понадобятся — придумай что-то другое, эти не используй, тэ этого не хочешь».
Неверно. Объяснение простое: «Так как ты занимаешься бизнес-логикой, ты должен решить когда тебе нужно сохранять состояния своих объектов. Если тебе не надо сохранять состояние своих объектов не наследуй от DBData, иначе наследуй. Как именно произойдет сохранение состояния объектов — это не твоя забота, просто дай указание сохрани, получи, удали.»

Что может быть естественее? Ладно, ваша метафизика меня утомила.
Я не считаю, что в компетенции бизнес-логики решать когда сохранять свои объекты и сохранять ли их вообще. Это компетенция логики приложения.
см. пример ниже
«Если тебе не надо сохранять состояние своих объектов не наследуй от DBData, иначе наследуй. „
Ага. Напоминаю, что в c# нет множественного наследования. Предположим, что завтра вы решите аналогичным образом реализовать что-нибудь еще (ну, например, способность к удаленной работе, или логирование), или, хуже того, у нас появится бизнес-класс с общим поведением, который сам не сохраняется (и поэтому не должен наследоваться от DBData), а вот некоторые его наследники — сохраняются. Все, fail, откуда наследоваться?

Это наглядная иллюстрация “естественности».
Распространенная ошибка как раз в том, что вы не понимаете, что только бизнес-логика решает когда сохранить объект, и ничто другое.
Нет в бухучёте понятия «сохранить документ», в логистике тем более нет понятия «сохранить транспорт» — «загрузить транспорт есть», но к хранению объектов они никакого отношения не имеет.
Я тут подсмотрел, вы вроде тоже пишите для банков, поэтому нижеследующий пример должен вам быть понятен. Давайте подумаем что же значит сохранение объекта на языке бизнес-логики. Вот есть платеж, этот платеж находится в том или ином состоянии (принят, верифицирован, проведен, отложен). Клиенты путем оперирования с GUI пытаются поменять это состояние. Но они совершают ряд ошибок. Бизнес-логика проверяет можно ли из такого-то состояния смотря на кучу условий перевести платеж в другое состояние. Как только можно — платеж переводят. Вот это и будет один из случаев, когда платеж нужно сохранить. Видите НИКТО другой не может решить когда нужно сохранить платеж, решает это бизнес-логика!
«Клиенты» — имеется введу работники банка
Писал. Но не суть. Ничего оно не значит. Есть платеж, у него есть состояния, клиенты пытаются поменять, наконец у них получилось, состояние изменилось. (В банке без ИТ поставили штамп «проведён», в банке с ИТ метод payment.post() нормально завершился и метод payment.isPosted() возвращает true).

Всё, бизнес-логика закончилась. В идеальном мире нам бы ничего больше не надо было: компьютеры не выключаются, не перезагружаются, не ломаются, из приложений не выходят и они не крашатся, оперативка не кончается и т. п., наш платеж висит в памяти положенное по закону время как минимум и без нашего ведома с ним ничего не случится. Но мы живём в реальном мире и на изменение состояния бизнес-объекта, зная о том, что приложение не вечно, реагирует логика приложения, сохраняя бизнес-объект на всякий случай в постоянной памяти.
«Давайте подумаем что же значит сохранение объекта на языке бизнес-логики.»
В вашем примере — ничего.

«Бизнес-логика проверяет можно ли из такого-то состояния смотря на кучу условий перевести платеж в другое состояние. Как только можно — платеж переводят. Вот это и будет один из случаев, когда платеж нужно сохранить. Видите НИКТО другой не может решить когда нужно сохранить платеж, решает это бизнес-логика! „
А как из того, что платеж переводят, вытекает, что его надо сохранить? Нелогично.

Обычно это выглядит так: пользователь пытается совершить одно из n действий, любое из которых, _если успешно_, должно привести к сохранению платежа (да и то не факт, об этом позже). Каждое действие — это некий метод конкретного бизнес-объекта платежа. Если метод успешен (=не бросил ошибки или не вернул валидационных сообщений), то _вызывающий_ сохраняет объект. Если не успешен — _вызывающий_ решает, что делать с объектом дальше.

Но на самом деле, даже это не самый правильный способ реализации вашей задачи. Правильно было бы реализовать стейт-машину (на том же WWF) и хранить состояние машины, а не бизнес-объекта (в частности, тот же WWF делает это достаточно прозрачно). Все, задача решена, никаких сохранений в бизнес-логике нет.
А для какого банка ты пишешь?
Вопрос не правильно поставлен: «для скольких банков Вы, уважаемый, пишите? » :)
Ок, для каких? :)
Хотите знать чьими услугами не стоит пользоваться? ;)
Тсссс… :)
У вас неверно сформулирован опрос, если что.
Нет, все там правильно сформулировано. Просто не дает вам возможности передергивать мои слова :)
Отнюдь.

«Бизнес-логика» в вашем опросе — это что? _Код_ бизнес-логики (о котором мы говорим на протяжении всего этого поста), или бизнес-процесс у заказчика (т.е., его процесс работы)?
И хотя я тут на этих всех опросах растерял свою карму, но зато действительно выяснил важные вопросы, которые не так линейны как вы пытаетесь их тут представить

Какая нотация для сохранения данных бизнес-объектов вам кажется более естественной…

Пусть дальше говорят люди, я откланиваюсь. Спасибо еще раз всем за дискуссию, она на поверку оказалась для практического использования.

Публикации