Pull to refresh

Comments 32

спасибо за серию статей.
кстати, новичкам бы помогли пару примеров использования в реальных проектах (например ссылки на файлы в SVN какого-нибудь проекта на sf.net или code.google.com) в конце статьи.
Я думаю это будет затруднительно, ввиду того, что я не часто зачитываюсь исходными кодами различных проектов :)

На самом деле большое количество паттернов можно найти в Java API, туда я и буду периодически редиректить народ.

А по поводу проектов, тут, я могу только сделать предположения о том, в каком проекте какой паттерн используется. Более того, возможно это не конкретный проект а класс систем (как редакторы/конструкторы в данной статье)
будет очень хорошо, потому что при начальном изучении паттернов трудно понять где конкретно их использовать
Примеры использования для всех паттернов проектирования есть в книге от GoF.
Возможно стоит представить обобщенную схему паттерна
В принципе, с помощью комментариев на данной диаграмме я попытался отобразить обобщенную схему паттерна.
Вообще это не правильно предостовлять неподдерживаемые методы (например, IntegerValue#add()). В итоге появляются проверки исключений на пустом месте. В том же JComponent это артефакты из-за наследования от java.awt.Container, тогда как в java.awt.Component таких методов нет. Так что дерево из примера не самое лучшее.
На мой взгляд в интерфейсе Number стоит оставить только один метод — value().
Ну этого сделать нельзя, по той простой причине, что реализуется интерфейс, а значит и все методы интерфейса должны быть переопределены.

Вопрос с другом. В интерфейсе SubExpression должны ли быть методы контейнера или нет? Возможно и не должны, при этом контейнерные реализации должны просто расширять этот интерфейс методами add, remove, get. Это на самом деле второй вариант развития событий. Можно и так и так. Смысл паттерна при этом не теряется. Однако в банде четырех, рекомендуется использовать именно тот вариант который я привел в посте. Все по-честному :)
> Ну этого сделать нельзя, по той простой причине, что реализуется интерфейс, а значит и все методы интерфейса должны быть переопределены.
Я и предлагаю интерфейс поменять.

Вообще согласен, просто бесит, когда из-за небольшой непродуманности в интерфейсе приходится потом проверять множество таких исключений, которых могло бы и не быть. Всё-ж таки блог называется «Совершенный Код».
Результатом выполнения кода будет UnsupportedOperationException в строке
> SubExpression a = new Expression(new IntegerValue(5), new IntegerValue(-2));

:-)
Хотя нет, туплю. Код — нечитабельный.

За каким нужно было IntegerValue и FloatValue унаследовать от SubExpression, это только запутывает, ведь реально эти методы не используются.
Это идея паттерна: единый интерфейс для листовых и составных объектов, что позволяет клиенту их трактовать единым образом. А про контейнерные методы в этом интерфейсе я уже пояснил — habrahabr.ru/blogs/complete_code/85166/#comment_2567703
В коде на продакшене НЕ ДОЛЖНО БЫТЬ throw new UnsupportedOperationException()
Если реализация паттерна требует их наличия — значит что-то с паттерном не так.
Мне кажется, что Вы не совсем поняли предназначение и смысл паттерна.

И да, с паттерном все так :)
Вообще, идея паттерна, конечно, в том, чтобы предоставить единый интерфейс… но это не повод бросать экспешны.

Не говоря уже о том, что придумайте мне реальный сценарий, где надо, чтобы у leaf-nodes обязательно был тот же интерфейс?

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

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

Реальных сценариев я думаю предостаточно в GoF они обозначены.

Я согласен с тем, что обработка исключений достаточно дорогостоящая операция, но не на столько чтобы жертвовать ради этого прозрачной архитектурой системы. К тому-же, на С++, например обработка исключений нет так уж и дорога (недавно был на мастер-классе по оптимизирующим компиляторам от Intel).

Реальных сценариев я думаю предостаточно в GoF они обозначены.
Можете привести хотя бы один?

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

В GoF приведены примеры систем (пользовательский интерфейс, каркас для построения компиляторов RTL) на Smaltalk, которые, я более чем уверен не будут для Вас авторитетны :)

И да. То-есть по Вашему, в правильной архитектуре нет места обработке исключений? Вы слышали про методологию защитного программирования?
Да почти все современные фраемверки отрисковки интерфейса построены на данном паттерне. Более того, явный пример про Swing (JComponent) я Вам привел.
Вы путаете. Я просил пример, где это нужно, а не где это реализовано. Желательно, с объяснением, зачем.

Потому как прекрасно существует Windows Forms, в котором есть IComponent и IContainer.

То-есть по Вашему, в правильной архитектуре нет места обработке исключений?
Ровно наоборот. В правильной архитектуре максимум исключительных ситуаций должен быть обработан. Но это не значит, что штатные операции должны порождать исключительные ситуации.

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

У меня right now есть внешняя система, единственный способ проверить наличие элемента с заданным ключом в которой — это обратиться по этому ключу и получить exception, если элемента нет. Никаких Contains или TryGet.
Но это не значит, что штатные операции должны порождать исключительные ситуации
То-есть по-вашему, исключения, которые выбрасывают реализации FloatValue и IntValue — это штатные исключения? Отнюдь, они сигнализируют программисту о том, что его система интерпретирует древовидную структуру как-то не так :) Иными словами, она пытается обратиться к листу как к составному объекту. И это не ошибка описания структуры. Это ошибка ее использования, ошибка интерпретации контекста, называйте это как хотите. Но виноват в этом — клиент (по отношению к паттерну).

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

Все потому, что клиент (класс Main) правильно работает со структурой.
Я и не спорю об этом. Действительно, надо стремиться к такой «идеальной» архитектуре. Но это не всегда возможно. Точнее сказать, всегда невозможно, по ряду причин. Это и сложность интеграции, сложно коллективной разработки, сложность предметной области наконец. Вы прекрасно понимаете, что все эти патерны (со всеми их недостатками), придумали именно для того, что-бы избежать этих проблем.

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

О чем мы спорили вообще? :)
Да, я верю, что может быть кто-то придумает (да скорее всего уже придумал) более рациональный механизм работы с древовидной структурой, но об этом знает только он и может быть небольшая группа разработчиков.
Угу. Windows.Forms — черезвычайно малоизвестная реализация. И очень маленькая группа разработчиков.

Спорим мы о том, что ваш пример нифига не показывает, что такое паттерн Composite, зачем его применять, и в чем его выгода.
Во-первых, можно ссылку на документацию по интерфейсу IComponent и что там еще.

Во-вторых, пример, очень даже показывает как именно клиент единообразно трактует разные вещи (вызывая value()).

Да причем здесь выгода? Да, есть паттерн. Да, работает с древовидной структурой. Да, работает удобно и прозрачно. Ваше личное дело, будете ли Вы его использовать или будете придумывать свои гомыры.
Во-первых, можно ссылку на документацию по интерфейсу IComponent и что там еще.
IComponent
IContainer

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

Основное определяющее свойство Composite — возможность работать с группой объектов так же, как с одним объектом. То есть, если я могу сказать graph.Paint(), то я могу сказать и graphComposite.Paint() (вместо foreach(graph in graphComposite) graph.Paint;). По сути, подмена коллекции композитом. И все.

Отсюда никак не вытекает, что у graph должны быть методы graphComposite.

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

Я просто слишком часто вынужден объяснять разработчикам, почему нельзя бездумно копировать то, что написано в Великой Библии Четырех.
На самом деле, симбиоз интерфейсов IComponent и IContainer и есть паттерн Composite. Это фактически равносильно тому, чтобы вынести контейнерные методы из единого интерфейса.

Более того, такой вариант я нахожу более приемлемым. С одной стороны мы имеем общий интерфейс — точнее интерфейсы. С другой, избавляемся от двусмысленности в трактовании узлов.

Тут Microsoft поступили пожалуй даже лучше чем проектировщики Swing :)
Ну вот видите? И никаких исключений на пустом месте. Хотя, казалось бы, реализация того же паттерна.

Отсюда и вывод — голову надо применять.
Согласен, но первый вариант тоже имеет право на жизнь :)
Вот только я никогда не видел ситуации, где он был бы оправдан.
Все потому, что клиент (класс Main) правильно работает со структурой.
И он же прекрасно показывает, что терминальным узлам структуры методы удаления и добавления не нужны. Прекрасно бы обошлось Expression {Number value}, при этом полностью сохранив логику.
Как тогда прикажете? Удалить контейнерные методы из SubExpression и написать их только в Expression? Да, это выход. Причем, это второй вариант, предлагаемый в GoF. Однако, как я уже отмечал, там рекомендуется все-таки пользоваться первым, ввиду того, что во втором случае интерфейс таки теряет свою универсальность.
А в чем польза универсального интерфейса? Вот реальная, в конкретном примере?
Sign up to leave a comment.

Articles