Pull to refresh

Comments 42

Мне кажется, что Бритва Оккама не одобряет такой подход.
Можно немного раскрыть комментарий? Что в предлагаемом подходе является излишним?
А мне вот кажется, что Бритва Оккама саму себя не одобряет. Статья расказывает, как введением нескольких новых сущностей сэкономить кучу времени и нервов. Так что они вовсе не являются лишними.
А киньте, пожалуйста, в меня ссылочкой, где кто-нибудь из классиков рассказывает про паттерн «Фабрика»? А то я такой не знаю — знаю только абстрактную фабрику и фабричный метод — и считаю, что фабрику вы только что придумали.
Судя по всему, статью вы не читали:

Теперь поговорим о наиболее распространенном шаблоне: фабричный метод или просто фабрика.
Да нет, я статью читал.
Дело в том, что я нахожу это сокращение усложняющим понимание, потому что происходит путаница — фабрика созвучна с абстрактной фабрикой, но обозначает другое понятие, фабричный метод. Поэтому и спросил, а кто из классиков вводит аналогичное сокращение.
В разговорах с коллегами мы обычно употребляем слово «фабрика». В приведенных ссылках тоже используется это слово (то, что нагуглил за пару секунд):
Фабрика 1
Фабрика 2
Строго говоря, лучше, конечно же, использовать понятие «фабричный метод», но сути это никак не меняет.
реализация производящих/создающих функций/методов способом, который Вы использовали (конструкция switch/case внутри порождающей функции) не самый хороший вариант, так как при добавлении нового типа придется исправлять уже готовый и протестированный код, возможно еще и чужой, что так же приведет к необходимости перекомпиляции исправленного и зависимого от него кода… в данном случае гораздо выгоднее использовать внешнюю регистрацию новых типов, которая избавит от всех перечисленных минусов…

p.s. зачем эта вереница макросов в с++ коде? ностальгия по MFC и ATL? :)
1. В подзаголовке «Синглтон, фабрика и прототип» приведен способ, который позволяет избежать switch/case. В принципе, можно было бы описать подход, который бы избегал полностью решал эту проблему, но это выходит за рамки этой статьи. Здесь конкретная реализация может быть любой, начиная от простой на свичах, и заканчивая более сложной, главное — единый интерфейс.
2. Про макросы уже обсуждали в предыдущей статье.
1. Мне кажется такой подход как раз входит в рамки данной темы, учитывая, что его реализация крайне проста, а вот дополнительные обертки над shared_ptr, на мой взгляд несколько избыточные, усложняют чтение и понимание приведенного здесь кода и к теме топика отношения как раз имеют мало… так же стоит добавить, что при использовании порождающих паттернов, хорошим тоном является явный запрет создания объектов типов, которые создает фабрика, я вроде у Вас этого не заметил, хотя возможно был невнимателен… интерфейсы это хорошо, но если мы говорим о них в контексте с++, то всегда стоит помнить о зависимостях в коде, которые влияют на работу кода клиента и время компиляции (привет идиома pimpl), что в большом проекте не последний пункт…
2. Ок, почитаем… :)
Не совсем согласен. Используя чистый shared_ptr невозможно использовать принцип обращения зависимостей. Это усложнение обоснованное, а не просто ради прихоти. Если вы знаете, как можно реализовать похожий функционал проще, то приведите соответствующий пример. Думаю, всем это будет полезно.

По поводу запрета. Если внимательно посмотрите на приведенный код, то все классы-реализации находятся в cpp файле и соответственно недоступны другому коду.

Еще раз повторюсь, что здесь я хотел показать принцип обращения зависимостей на примере порождающих шаблонов проектирования.
что Вы понимаете под «принцип обращения зависимостей»? можете в 2х словах написать? к сожалению из текста статьи это не до конца ясно…

по поводу запрета, понятно… хотя из фрагмента кода с определением интерфейса IShape и его дочерних классов, это не совсем очевидно, но можно догадаться… :)
На тему возвращения указателей из функций, мне этот момент тоже не нравится. По-хорошему, нужно вместо указателей возвращать обёртки определяющие владение/удаление, и никогда не возвращать голые указатели. В качестве обёртки похоже идеальный вариант — unique_ptr с подходящим deleter'ом, но можно и без C++0x выкрутиться. Правда, подозреваю, что на практике никто так делать не будет и это вообще мало кого волнует…
Да, как вариант можно использовать unique_ptr. Однако с ним связана определенная сложность: он обладает move-семантикой и это может приводить к неприятным последствиям. С ним надо проявлять осторожность, в то время как shared_ptr можно без каких-либо проблем копировать. Однако с синглтонами такой фокус не пройдет (имеется в виду использование unique_ptr), т.к. надо шарить данные между различными клиентами.
Да вроде unique_ptr хоть и с move-семантикой, не создаёт таких граблей как auto_ptr — если уж скомпилировался, то будет работать. Хотя ограничений, конечно, добавит. Но имхо даже не суть важно, unique_ptr или какая другая обёртка, важно чтобы владелец был чётко определён.

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

В случае синглтона владение (ownership) обычно не передаётся, а значит и shared_ptr не обязателен. shared_ptr — это не «шарить данные», а шарить владение. Не вижу, что мешает в этом случае для возвращения из функции использовать unique_ptr, например с пустым deleter'ом.
Хотя ваша точка зрения про синглтоны понятна, у вас свой синглтон, со всем что нужно :) и с неопределённым владельцем. Но здесь я скорее поддерживаю критиков из предыдущей статьи — уж больно сложно (+оверхед), а польза неочевидна.
А в чем сложность? Если посмотреть на другие вещи, например реализацию того же shared_ptr или просто std::vector, то там сложность на порядки выше. An — это всего лишь легкая обертка вокруг shared_ptr с вполне понятной семантикой.
Так реализацию shared_ptr не заставляют изучать, просто берёшь и пользуешься. У вас же другой случай, вы не про общепринятый boost рассказываете, и поэтому нужно разбираться со всеми биндингами и прочей шаблонной магией. Но зачем, если людям непонятно в чём бонус?
Как минимум, при обращении к данным shared_ptr упадет в случае отсутствия таковых, в то время как мой подход кидает исключение, что является более безопасным способом. Уже это серьезно отличает от shared_ptr и является очень полезным. Я не говорю о других достоинствах, которые значительно упрощают жизнь. Поэтому использование дополнительного класса более, чем оправдано.
Ну, во-первых, бросание исключения — вопрос спорный (безопасность против производительности), и поэтому лучше это оставлять параметром как упоминалось в комментах к предыдущему топику. Во-вторых, если бы речь шла только об этом, то это ооочень тонкая обёртка, куда проще чем то, что вы предлагаете. И наконец, я говорю о неочевидности для людей преимущества вашего подхода над «обычными» синглтонами, в которых и shared_ptr-то не нужны, не то что его «улучшения».

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

Ну в частности в упомянутом комментарии на SO говорится, что на 32-битном msvc как раз будет оверхед. Кроме того, если даже в случае если нет очевидного оверхеда, оптимизации наверняка идут лесом, а они могут в разы улучшать производительность.

Вообще, имхо бессмысленно спорить на эту тему, всё уже «украдено до нас», дебаты на эту тему так и продолжаются, а значит и серебряной пули нет.
Какого рода дополнительные проверки не считая try/catch, который в минимальном варианте может распологаться на самом верху? Какого рода запреты оптимизаций, можно пример?
Дополнительные проверки, добавляемые компилятором, о том что было брошено исключение. Эти проверки, очевидно, будет располагаться не только на самом верху, но и вообще по всему стеку вплоть до самого низа, т.к. исполнение может быть прервано в любой момент. Насчёт оптимизаций — изначально предлагалось к функциям добавлять спецификации вроде nothrow, которые позволяют компилятору лучше оптимизировать (что конкретно — я не в теме). Правда, насколько я знаю, такого рода спецификации ныне deprecated.
относительно раскрутки стека, она понадобится только тогда, когда будет сгенерировано исключение, которое необходимо генерировать в исключительных ситуациях, при возникновении которых, ни о какой производительности уже речь не идет, так как надо обрабатывать такую ситуацию…
Сама раскрутка стека — да, но сама возможность того, что она понадобится, очевидно, добавляет ограничения.
Вообще, вопрос использовать исключения или нет далеко неочевидный, т.к. при их использовании нужно гарантировать exception-safety всего кода. Гугл в частности поэтому от них отказался, да и вообще дебаты на эту тему не утихают. Поэтому я и говорю, что вопрос спорный. Я тоже предпочитаю исключения и стабильное поведение, но в реальности есть аргументы и в ту и в другую сторону, поэтому нельзя решать за всех (наверное, это одна из причин, почему shared_ptr не бросает исключений, не просто так же они «не догадались»).
Если писать как попало, то исключения использовать вредно. А если использовать единый подход, например, как предложенный в статье, то проблем не будет. Я использую исключения в своих проектах и, поверьте, все работало как часы.
Это просто требует другого стиля программирования, в частности требует практически повсеместного использования RAII. Я не спорю, что можно всё написать правильно, я лишь говорю, что это сделать сложно, и поэтому вопрос неочевидный, а вы мне отвечаете «проблем не будет» и «поверьте» :)
Вы скорее всего опять мне не поверите, но сделать это гораздо проще, чем Вы пытаетесь представить… это просто требует стиля программирования в стиле с++ :)
Ну да, а обсуждения на тему exception safe code по всему интернету это тоже выдумки и профанация :)
«Гораздо проще» чем «сложно» — это как? ;)
у меня иногда создается впечатление, что так и есть… это я про обсуждения на тему exception safe code по всему интернету, ни одного вменяемого довода в пользу не использования исключений я не слышал, весь этот оверхед, который обычно сразу начинают вспоминать просто смешной, он настолько мал, что им можно без всяких сомнений пренебречь…

ну вот вспоминая проекты на с++ в которых я принимал участие, я не помню каких-либо проблем при использовании в качестве обработки ошибок исключений… а было в этих проетах всего полно, и легаси код без исключений на ретвалах, и старые либы а ля zlib, и COM-компоненты, как свои, так и чужие и различные библиотеки а ля boost/loki/atl… и никогда из-за этого никаких проблем не было, все аккуратно оборачивалось соответствующими обертками, которые использовали общий для проекта фрэйворк для обработки ошибок, построенный на исключениях и использовалось в основном коде… естественно для того чтобы все корректно работало, код должен писаться в стиле с++, а не в стиле с with classes как многие любят… :)
Для меня «в стиле с++» звучит как использование «симметричных» RAII-объектов по-максимуму. В этом случае действительно работа с исключениями упрощается, но только до тех пор, пока не окажется, что исключение может быть выброшено в деструкторе… Например, если функция закрытия файла может выбросить исключение, а мы хотим завернуть открытие-закрытие в RAII-обёртку, то выйдет облом :) Кстати, было как-то обсуждение похожей ситуации в java, там нет RAII но аналогичным образом исключения в finally бросать нехорошо, а закрытие файла — бросает.

А ещё всё-таки не всё можно завернуть в такие обёртки. Например, взять какой-нибудь Builder, строящий внешний объект, и в процессе получающий исключение, а т.к. оно не из конструктора, то получим недостроенный объект, а если он допустим регистрировался при этом в каком-нибудь списке может выйти нехорошо…

Вообще, получается любая работа с внешним объектом должна быть транзакционной и обязательно заканчиваться коммитом, что далеко не всегда получится автоматически, даже в стиле с++. А вы как с этим справлялись?
«в стиле с++» это не только RAII, хотя конечно эта концепция одна из самых востребованных в контексте обработки ошибок при помощи исключений, да и не только… все описывать в рамках этой темы смысла нет, есть достаточное количество литературы по этому вопросу…

про исключение в деструкторе, это пример по-большому счету высосанный из пальца, опять же в здравом уме это никто делать не будет, на практике при работе с различными библиотеками я такого ни разу не встречал… java не c++ поэтому тут обсуждать нечего…

приведите пример того, что нельзя обернуть, пример с Builder я не догнал, так же как и с обязательной транзакционностью…
то есть Вы предлагаете забить на обнаружение таких ситуаций? Тогда о каком стабильном поведении может идти речь и откуда берутся непонятные креши?
В целом — мне непонятно, в чем ваша статья отличается от других аналогичных статей, которых тьма на том же codeproject. Про фабрики и С++ — есть книжка Александреску, на которую вы не сослались. Там и про полиморфные и про абстрактные фабрики. Про порождающие паттерны в целом лучше всего написано в книгде Refactoring to patterns. Ее я то же не вижу в списке литературы.
Примеры ваши — toy samples. Они не показательны. Я понимаю, что доступные real life примеры сложно придумать — но в том-то и сложность написания статей по тематике.
Есть еще менее известный паттерн Trader [1], о котором на русском нет статей.
Ну и имхо — лучше с позиции рефакторинга паттерны рассматривать. Мне хочется верить, что это не преумножает pattern-happy программистов.
[1] Product Trader, by Baumer & Riehle in Pattern Languages of Program Design 3, Eds Martin, Riehle, Buschman, 1998, pp 29-46.
В статье ясно сказано, что Данная статья не претендует на полноту изложения таких шаблонов. Моя цель была несколько в другом, о чем я в начале и в конце не раз явно упоминал. Более того, как раз приведенная Вами ссылка [1] содержит все те проблемы, о которых я рассказывал и пытался избежать. И если вы внимательно почитаете выводы, то я рассматривал шаблоны именно с позиции рефакторинга.
Я честное слово не вижу рассказа с позиции рефакторинга. Т.е. я под этим подразумеваю a-la «вот был такой-то код, он решал такую-то задачу. Теперь мы захотели его исправить применив связку таких-то паттернов.»
Про статью я невнятно выразился. Пример статьи я привел не для того чтобы Вы писали про этот конкретный паттерн. А к тому, что есть паттерны которые не так широко афишированы как GoF'ские, но могут быть интересны широкой аудитории. А о паттернах GoF написано настолько много статей, что сложно добиться новизны при написании собственной статьи. Кроме, быть может, подбора интересных примеров использования. Этого я как раз не вижу — только toy samples.
Резюмируя критику — недостаточный список литературы, отсутствие новизны как по содержанию, так и по способу подачи материала.
Хотя это не значит, что я считаю вашу статью не полезной — у нас в сообществе до сих пор паттерны это что-то сложное и космическое. Образовывать массы надо. Но массы пишут больше про особенности реализации, чем про ООД, что и печалит и наводит на мысли.
Бррр… с++ и слово «шаблоны», когда уже общепринятый термин «паттерны проектирования» чтобы не путать template из языка.
Sign up to leave a comment.

Articles

Change theme settings