Здравствуйте, dimgel, Вы писали:
IT>>И если ООП где-то пощёл лесом, то это значит только одно — он мне не подошёл в конкретном случае для решения конкретной задачи.
D>Вот я и пытаюсь всё время говорить о конкретной задаче. О той, которую ты разобрал (других тут не проскакивало). С точки зрения IB это выглядит, по-видимому, так: "мы тут, панимаешь, сидим в своём болоте, квакаем о высоких материях, тут приходит какой-то лось, срёт всем на головы и портит малину своими идиотскими требованиями конкретики". С моей же точки зрения это выглядит как разговор со свидетелями Иеговы, которые мастера говорить о высоких материях, знают трактовку каждой буквы в обоих Заветах, но столкни их носом к носу с самым простеньким конкретным чудом, загремят либо в дурку, либо на кладбище.
Во-первых, успокойся, твоя агрессия уже начинает надоедать.
Во-вторых, я тебе не просто так говорил о шкале сложности и о том, что в разных её точках решения могут быть принципиально разными. Это надо понимать не только тем, кто тебе что-то объясняет, но и тебе самому, чтобы лучше понять то, что тебе объясняют. Ситуации действительно бывают разные и поиск универсального решение для всех случаев — это очень нетривиальная задача. Когда тебе говорят о применении того или иного паттерна, то самый простой способ решить подходит он тебе или нет это задать вопрос себе и твоему собеседнику — а какую проблему решает то или иное средство. Может вполне оказаться, что в твоём конкретном случае такой проблемы нет вообще. Этот вопрос, кстати, вообще полезно почаще задавать. Ответ на него помогает более точно выявлять проблемы и, возможно, решать их другими более эффективными способами. Например, в твоём примере оказалось, что разные DTO решают совсем другую проблему, чем ты думал. Ты думал (и аргументировал), что так лучше для целей безопасности, а оказалось, что используя один и тот же тип объекта получается облом с кешированием.
В-третьих, не стоит относится пренебрежительно к высокоумным, как тебе кажется, постам. Есть одна очень правильная поговорка — неясность изложения свидетельствует о путанице в мыслях. Пока ты не сможешь ясно, чётко и главное кратко сформулировать проблему, это будет означать только одно — ты её сам не доконца понимаешь. Абстрактные определения помогают оторвать проблему от конкретики, чётко её сформулировать и применить к другим конкретным случаям и решить уже в них конкретную проблему. Другое дело, что абстакцию бывает сложно понять без конкретных примеров, но это нужно учиться делать по той причине, что абстракций значительно меньше, чем конкретики и, если оставаться только на уровне конкретных примеров, то в них можно легко утонуть и окончательно запутаться.
Когда я вижу хорошее абстрактное определение, то я его всегда плюсую (не всегда мышкой, иногда просто для себя), т.к. такие определения помогают мне самому более чётко сформулировать проблему, оторвать её от моей конкретной конкретики и, в результате, начать выявлять подобные вещи в более широком круге задач. Ты же пытаешься закопаться в своей песочнице и ни с кем не дружить, потому что твой кулич из твоего конкретного песка кажется тебе самым идеальным в мире решением.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, dimgel, Вы писали:
... Н>>Объект выставляет наружу минимально необходимый контракт (приницип минимизации публичного контракта), все остальное -- внешнее по отношению к нему.
D>Даже если это остальное не делает ничего, кроме как делегирует к методам обсновного объекта?
Да, но это не имеет отношения к минимизации. Зачем плясать от реализации, которая вторична по отношению к контрактам? Всякие приватные поля, "обсновные объекты" — это артефакты реализации контракта. Даже выражения вроде "эта сущность содержит приватное поле, которое хранит X" с этой точки зрения не имеют собого смысла до тех пор, пока не сказано(например) что, во-первых — существуют методы 'get_X'/'set_X' и во-вторых, — 'get_X' возвращает то что передали в 'set_X'. И уже из таких(не вышеперечисленных, а вообще) утверждений вытекает необходимость иметь приватное поле для обеспечения контракта.
G>>Обобщенный код по работе с деревьями вряд ли понадобится.
D>Мне одному начинает казаться, что мы спорим, с какого конца чистить яйцо? По сути-то — что в триггерах, что в TreeController, что у меня в базовом классе TreeNode — это отдельный слой. Разница в сущности только в сигнатуре вызова. Не находишь?
Нет, разница в применяемых средствах. То что в SQL делается элементарно, в клиентском коде делается очень тяжело. А при поптыке поддерживать честную согласованную модель БД очень много усилий тратится на то что в БД есть по-умолчанию.
Здравствуйте, gandjustas, Вы писали:
G>Нет, разница в применяемых средствах. То что в SQL делается элементарно, в клиентском коде делается очень тяжело. А при поптыке поддерживать честную согласованную модель БД очень много усилий тратится на то что в БД есть по-умолчанию.
Снова не могу не согласиться. Но это только одна сторона проблемы. Здесь чаще бытует другая точка зрения: размывать логику между хранимками+триггерами и "основным приложением" не есть гуд.
Здравствуйте, Юрий Жмеренецкий, Вы писали:
Н>>>Объект выставляет наружу минимально необходимый контракт (приницип минимизации публичного контракта), все остальное -- внешнее по отношению к нему.
D>>Даже если это остальное не делает ничего, кроме как делегирует к методам обсновного объекта?
ЮЖ>Да, но это не имеет отношения к минимизации. Зачем плясать от реализации, которая вторична по отношению к контрактам?
Хм. Вообще-то этот вопрос правомерно задавать как раз адептам принципа минимизации публичного контракта, поскольку в нём уже прописана определённая зависимость от реализации. Я ничтоже сумняшеся нашпигую класс методами get_X, set_X и прочими, если они мне покажутся там уместными. И наплюю на минимизацию. Или я не так понял?
UPD:
Я пропустил: "даже если собственно X у меня отсутствует". Я тут зациклился на счётчиках, поэтому никак не могу вспомнить другие случаи скрытых данных, которые из интерфейса entity не видны. Но в целом я именно что пытаюсь плясать от интерфейса. Во, вспомнил пример: public isPublished() { return whenPublished != null; } Поскольку для whenPublished тоже есть геттер, на лицо избыточность интерфейса. Зато так удобнее.
Здравствуйте, dimgel, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
G>>Нет, разница в применяемых средствах. То что в SQL делается элементарно, в клиентском коде делается очень тяжело. А при поптыке поддерживать честную согласованную модель БД очень много усилий тратится на то что в БД есть по-умолчанию.
D>Снова не могу не согласиться. Но это только одна сторона проблемы. Здесь чаще бытует другая точка зрения: размывать логику между хранимками+триггерами и "основным приложением" не есть гуд.
Ты не понял, я говорю не о месте расположения логики. SQL запросы можно и в коде программы формировать, только неудобно это. Linq в C#\VB\Nemerle решает.
Что касается триггеров, то это отдельный вопрос. БД поддерживает целостность данных. Если надо нарушать нормализацию для ускорения, то поддерживать целостность лучше внутри БД, а не в коде приложения.
Ок, только я бы не стал ограничивать использование триггеров поддержкой целостности базы. Взять например, паттерн "документы-регистры", на котором построена 1С-торговля. Работу по пересчёту остатков регистров (текущих и промежуточных кешированных) после проведения документа — сам бог велел отдать на сторону сервера: там ворочаются огромные объёмы данных, а приложению возвращается void.
Но есть ещё одна чисто техническая проблема с кодом на стороне SQL-сервера, неведомая счастливым адептам чистого stateless: необходимость тонко сбрасывать entity cache. Т.е. мы знаем, что триггеры обновили счётчики у предков узла, значит закешированные предки уже не валидны.
Блин, вот скажите мне, почему при таком огромном количестве неудобств, stateful настолько распространён и имеет столь широкую инструментальную поддержку. Щас вот смотрю lift — дык он не просто stateful, он даже данные сессий и контексты ajax callbacks в памяти держит. (Это дефолтное поведение "для начинающих", не знаю, что из него можно будет выжать при дальнейшем изучении.) А у меня на VPS памяти 384 метра всего, половина под IPB-форумом товарища.
Здравствуйте, dimgel, Вы писали:
D>Работу по пересчёту остатков регистров (текущих и промежуточных кешированных) после проведения документа — сам бог велел отдать на сторону сервера
*SQL-сервера.
И таким образом, хороший такой кусок бизнес-логики у нас благополучно уплывает из контроллеров в базу. А разработчику приложения остаётся лишь убогий выбор, куда поместить executeStoredProc(): в DocumentController.post(doc) или Document.post(). И пришли мы туда, откуда начали. Похоже, я твоего последнего поста не понял.
И кстати, ещё один аргумент против кода на стороне SQL-сервера: у них у всех диалекты разные.
Здравствуйте, dimgel, Вы писали:
D>Здравствуйте, gandjustas,
D>Ок, только я бы не стал ограничивать использование триггеров поддержкой целостности базы. Взять например, паттерн "документы-регистры", на котором построена 1С-торговля. Работу по пересчёту остатков регистров (текущих и промежуточных кешированных) после проведения документа — сам бог велел отдать на сторону сервера: там ворочаются огромные объёмы данных, а приложению возвращается void.
Неверно. Логика проведения документов обычно нетривиальная, достаточно часто меняется. Держать её в базе — самоубийство.
А само проведение конечно лучше отдать серверу. На клиенте формировать батч sql-команд и заставлять все это выполняться на сервере.
К сожалению современные ORM так не умеют.
D>Но есть ещё одна чисто техническая проблема с кодом на стороне SQL-сервера, неведомая счастливым адептам чистого stateless: необходимость тонко сбрасывать entity cache. Т.е. мы знаем, что триггеры обновили счётчики у предков узла, значит закешированные предки уже не валидны.
А это ты сам себе придумал сложности с кешированием. Используй кеширование вызовов метода и инвалидацию по LastModified.
В случае того же форума реализуется элементарно.
Вообще полновесная ОО-модель плохо работает с кешированием, зачастую заставляя дублировать функции БД в памяти.
D>Блин, вот скажите мне, почему при таком огромном количестве неудобств, stateful настолько распространён и имеет столь широкую инструментальную поддержку.
Где? В real-world stateful не рулит вообще.
D>Щас вот смотрю lift — дык он не просто stateful, он даже данные сессий и контексты ajax callbacks в памяти держит. (Это дефолтное поведение "для начинающих", не знаю, что из него можно будет выжать при дальнейшем изучении.) А у меня на VPS памяти 384 метра всего, половина под IPB-форумом товарища.
Ну не используй левые либы.
Я буквально за полгода разобрался где чего надо использовать, чтобы жизнь была хороша.
Здравствуйте, gandjustas, Вы писали:
G>Неверно. Логика проведения документов обычно нетривиальная, достаточно часто меняется. Держать её в базе — самоубийство.
Не-не-не Дэвид Блэйн! Я ни в коем случае не говорю про логику проведения документов! Только пересчёт остатков после того, как при проведении документа задним числом (кодом BL) были записаны обороты.
Здравствуйте, dimgel, Вы писали:
D>Здравствуйте, gandjustas, Вы писали:
G>>Неверно. Логика проведения документов обычно нетривиальная, достаточно часто меняется. Держать её в базе — самоубийство.
D>Не-не-не Дэвид Блэйн! Я ни в коем случае не говорю про логику проведения документов! Только пересчёт остатков после того, как при проведении документа задним числом (кодом BL) были записаны обороты.
Дык для этого вообще-то индексированные вьюхи есть. Или вообще Integration Services + Analisys Services или аналогичне инструменты в других СУБД.
Здравствуйте, dimgel, Вы писали:
D>Здравствуйте, Юрий Жмеренецкий, Вы писали:
Н>>>>Объект выставляет наружу минимально необходимый контракт (приницип минимизации публичного контракта), все остальное -- внешнее по отношению к нему.
D>>>Даже если это остальное не делает ничего, кроме как делегирует к методам обсновного объекта?
ЮЖ>>Да, но это не имеет отношения к минимизации. Зачем плясать от реализации, которая вторична по отношению к контрактам?
D>Хм. Вообще-то этот вопрос правомерно задавать как раз адептам принципа минимизации публичного контракта, поскольку в нём уже прописана определённая зависимость от реализации. Я ничтоже сумняшеся нашпигую класс методами get_X, set_X и прочими, если они мне покажутся там уместными. И наплюю на минимизацию. Или я не так понял?
Под реализаций я имел ввиду приватные члены, в частности "основной объект". Его наличие — это часть реализации. Поэтому он не должен учитываться при обсуждении контракта, т.к. этот объект является производным от него(контракта).
D>UPD: D>Я пропустил: "даже если собственно X у меня отсутствует". Я тут зациклился на счётчиках, поэтому никак не могу вспомнить другие случаи скрытых данных, которые из интерфейса entity не видны.
А откуда видны? Или на самом деле entity реализует два интерфейса?
D>Но в целом я именно что пытаюсь плясать от интерфейса. Во, вспомнил пример: public isPublished() { return whenPublished != null; } Поскольку для whenPublished тоже есть геттер, на лицо избыточность интерфейса. Зато так удобнее.
Членом какой сущности является isPublished? Судя по названию(гадая) это "внешнее" по отношению к объекту свойство (как и whenPublished). Впрочем, пока удобно можно и пользоваться =)
Можно с такой стороны посмотреть: берем какой-нибудь тип, смотрим на все его геттеры. Декартово произведение все значений, которые в совокупности можно получить от этих геттеров — это все возможные состояния нашего объекта. Теперь делим это состояние на несколько, в соответствии с возможностью вызова некоторых функций (это, кстати полезно для тестирования). Например, для контейнера это будут три "области" — пустой, непустой и полный. В пустом не определена операция удаления, в полном вставка, в непустом обе возможны. Что должны делать функции, которые не определены? Обычно они кидают исключения, иногда просто в документации пишут "нельзя и все, сами будете виноваты", либо это приводит к холостому вызову. + добавляют функции, с помощью которых можно узнать в какой "области" находится объект [Замечание: вызов функции вне ее области — это всегда хуже чем отсутствие вызова]. Например, 'Count' — количество элементов в контейнере, с помощью этого одного свойства в принципе можно определить нужную область, но еще необходимо знать максимальное количество. Заполнение контейнера под завязку — чрезвычайно редкая ситуация, поэтому каждый раз перед вставкой проверять на IsFull никто не будет. С Empty другая ситуация — пустым контейнер бывает часто (может быть каждый раз после создания). + Еще один довод: Empty может быть(иногда) реализована эффективнее нежели 'Count == 0'. Это по поводу избыточности.
Вызов whenPublished на самом деле определен только если isPublished == true, поэтому isPublished является образующей двух _разных_ областей состояний в отношении whenPublished, и в одной области вызов неопределен(значение не имеет смысла, тот факт что по нему можно судить о состоянии свойства isPublished — это побочный эффект реализации). Поэтому, как вариант — whenPublished должна кидать исключение если isPublished == false, т.е. null она никогда не должна возвращать. Из-за этого мы бесплатно получаем гарантию того, что кто-нибудь где-нибудь забудет выполнить проверку на null значения полученного от whenPublished (эта проверка становится ненужной). Вот в этом случае оба этих свойства имеют право существовать в интерфейсе одновременно. Иначе — действительно избыточность =)
Здравствуйте, Юрий Жмеренецкий, Вы писали:
ЮЖ>Членом какой сущности является isPublished? Судя по названию(гадая) это "внешнее" по отношению к объекту свойство (как и whenPublished). Впрочем, пока удобно можно и пользоваться =)
Технически, whenPublished находится в таблице, которую мапит объект. Идеологически, это скажем статья в контент сайте, так что приемлемо.
ЮЖ>whenPublished должна кидать исключение если isPublished == false, т.е. null она никогда не должна возвращать.
Яя, майн группенфюрер! Правда, делал я это больше по наитию, спасибо за хорошую формализацию с "областями состояния".
Здравствуйте, dimgel, Вы писали:
D>Эх, столкнуть бы тебя сейчас с Мамутом, защищавшим не так давно динамически типизированные языки...
Опять ты все в одну кучу мешаешь. Динамическая типизация и рефлекшен — две большие разницы, хотя проблемы схожи.
Здравствуйте, dimgel, Вы писали:
D>Это бла-бла-бла.
Это просто ты читать не умеешь.. )
D>Я думал, с инкапсуляцией мы выше разобрались.
Ты, как я вижу, еще не разобрался..
D> Тебе нужно объяснять, что торчащие наружу данные являются нарушением инкапсуляции данных?
Для начала я хочу услышать от тебя, что такое инкапсуляция данных и где ты вообще взял этот термин.
А потом хочу услышать зачем ты вообще используешь инкапсуляцию и чего пытаешься этим добиться.
D>Не смущает. Там половина паттернов к ООП вообще не относится.
Я с возрастающим интересом хочу услышать от тебя, что ты понимаешь под ООП..
D>Только что написал.
Где?
D>И это бла-бла-бла.
Ты бы лучше на вопросы отвечал.
Здравствуйте, dimgel, Вы писали:
D>Вот к этому и сводятся все твои аргументы. "Ты дурак и потому объяснять тебе бесполезно."
Я пока еще пытаюсь выяснить с чего начинать объяснения, а ты сопротивляешься.
D>Единственный твой вклад в данное обсуждение — это строгое определение инкапсуляции. Остальные посты — бла-бла-бла и понты.
"Мальчик" (с), ты сначала подумай, каков твой вклад в это обсуждение, потом поговорим.
IB>>По твоему, если паттерн не использует наследования, значит это не ООП паттерн? IB>>Тебя не смущает, что половину GOF в этом случае можно от ООП отлучить? D>Не смущает. Там половина паттернов к ООП вообще не относится. На вскидку, тот же посетитель или стратегия — вещи из мира ФП, которые они прикрутили к ООП.
Тезис собственно такой: единственная существенная польза от ООП, благодаря которой его все используют, — это полиморфизм.
Здесь второй параметр функции walkTree() — это функция, берущая параметром TreeNode и возвращающая Unit (это типа void в scala). Глядя на эту строчку, я думаю, а стоило ли придумывать отдельное название для такой элементарной вещи? Не скажу "классический", но какой-то смутно и до боли знакомый эффект усложнения простых вещей, возникающий при переносе их в чуждое окружение.
2. Фасад. К ООП не привязан: я могу взять и наплодить группу глобальных функций и обозвать её фасадом, спрятав за ними другую группу глобальных функций.
3. Стратегия. Вот тут начинается интересное. В вырожденном случае, когда контракт стратегии состоит из одного метода, получаем точную копию посетителя — обычную суперпозицию функций. А в невырожденном мы автоматически приходим к интерфейсу (или базовому классу), то есть к полиморфизму.
Почему я приравниваю использование ООП-интерфейсов к использованию полиморфизма? Я вижу только две задачи, которые решают ООП-интерфейсы: сокрытие реализации и полиморфизм. Но сокрытие реализации чудненько реализуется и в модульном программировании. В том же C, насколько я помню, был модификатор static, который переводится как "module-visible". Вот вам и контракт, и сокрытие реализации. И не надо ООП-огород городить.
Получится прорва глобальных функций? Нет проблемы, вводим пространства имён. Эта фича не является прерогативой ООП. В XML помнится тоже есть пространства имён. Правда, XML namespaces — это анархия вместо иерархии, в то время как в java и других языках пространства имён строго иерархические и на этом завязаны ограничения доступа (visibility). Но полноценные иерархические имена при желании можно сделать и для не-ООП языка. Сделали же модификатор static в C; да и в C++ namespaces живут сами по себе и вроде бы даже могут быть многокомпонентными (если не ошибаюсь).
Тогда почему даже в программах, где "ООП и не пахнет" (с) мой, народ всё равно усиленно плодит классы, интерфейсы и объекты? Я вижу две причины:
1) Полиморфизм, который может не использоваться в архитектуре программы, активно используется в процессе разработки, а именно при тестировании. Нужно подменить рабочий функционал моками? — опа! опять полиморфизм.
2) Используемые библиотеки. Разработчики современных библиотек стараются делать их максимально гибкими, pluggable, а это достигается использованием паттернов, интенсивно использующих полиморфизм; той же стратегии. (Сейчас я держу в голове стандратные либы java — образец гибкости; для функциональных языков формулировочка аргумента может быть несколько иной.)
Ещё несколько паттернов.
4. Синглтон. Неприятный для моих аргументов контрпример. Но хочу заметить, считается антипаттерном! Часто синглтон используется как корневой объект (Application). Почему не кучка глобальных методов? Во-первых, для унификации, а во-вторых, в skeleton-фреймворках он наследуется из какого-нибудь framework.BasicApplication (в дельфи кажись так было), то есть опять эксплуатируется полиморфизм.
5. Service Provider Interfaces, в т.ч. многоуровневые. Как оформить корневую точку входа — глобальной функцией, методом класса Application или статическим методом (синглтоном) — не принципиально. А дочерние SPI всегда эксплуатируют полиморфизм (иначе паттерн просто теряет смысл).
6. Сериализация. Универсальный рекурсивный алгоритм, использующий рефлексию, опирается на факт существования полиморфного метода getClass().
Вроде бы всё вспомнил, что хотел. Если у кого-нибудь есть пример use-case, опровергающий мои построения, или другие соображения — буду признателен.
Здравствуйте, IB, Вы писали:
IB>одного переноса контроля типов в рантайм достаточно, чтобы не рассматривать этот вариант в серьез.
Я, наверное, торможу.
О каком контроле типов может идти речь, когда sql нетипизирован (мы ведь говорим про персистентность). Когда схема базы может измениться независимо от приложения.
ИМХО, в любом случае, используем ли мапер или все делаем ручками, контроль соответствия базы приложению можно осуществить только тестами.
Здравствуйте, Aikin, Вы писали:
A>ИМХО, в любом случае, используем ли мапер или все делаем ручками, контроль соответствия базы приложению можно осуществить только тестами.
Ну что за бред.
Проконтролироать это можно только на одной машине, никто вам не сможет гарантировать что у пользователя будет база соотвествовть приложению.
Надо или код проверочный включить в само приложение, но это уже будет не тест, или забить на все это дело.
Здравствуйте, gandjustas, Вы писали:
A>>ИМХО, в любом случае, используем ли мапер или все делаем ручками, контроль соответствия базы приложению можно осуществить только тестами. G>Ну что за бред. G>Проконтролироать это можно только на одной машине, никто вам не сможет гарантировать что у пользователя будет база соотвествовть приложению.
Я где-то утверждаю обратное?