Составной шаблон — это очень мощный шаблон проектирования, который вы регулярно используете для манипулирования группой вещей через один и тот же интерфейс, чем одна вещь. При этом вам не нужно различать случаи в единственном и множественном числе, что часто упрощает ваш дизайн.
Тем не менее, есть случаи, когда вы склонны использовать шаблон Composite, но интерфейс ваших объектов не совсем подходит. Не бойтесь, некоторые простые рефакторинги для сигнатур методов могут сделать ваши интерфейсы дружественными к композитам, потому что это того стоит.
Всегда начинай с примеров
Представьте себе интерфейс для финансового инструмента с геттером в его валюте:
public interface Instrument { Currency getCurrency(); }
Этот интерфейс подходит для отдельного инструмента, однако он не масштабируется для группы инструментов (составной шаблон), потому что соответствующий метод получения в составном классе будет выглядеть (обратите внимание, что возвращаемый тип теперь является коллекцией):
public class CompositeInstrument { // list of instruments... public Set getCurrencies() {...} }
Мы должны признать, что каждый инструмент в составном инструменте может иметь различную валюту, следовательно, составной может быть мультивалютным, следовательно, тип возврата сбора. Это нарушает цель составного шаблона, который заключается в унификации интерфейсов для одного и нескольких элементов. Если мы на этом остановимся, то теперь мы должны различать один инструмент и составной инструмент, и мы должны различать это на каждом сайте вызовов. Я не доволен этим.
Брутальный подход
Брутальный подход заключается в обобщении исходного интерфейса, чтобы он работал для составного случая:
public interface Instrument { Set getCurrencies() ; }
Этот интерфейс теперь работает как для отдельного, так и для составного случая, но за счет того, что всегда приходится иметь дело с коллекцией в качестве возвращаемого значения. На самом деле я не уверен, что мы упростили наш дизайн с помощью этого подхода: если составной случай не используется так часто, мы даже усложнили дизайн для небольшой выгоды, потому что возвращаемый тип коллекции всегда идет своим путем, требуя цикл каждый раз, когда он вызывается.
Хитрость для улучшения — просто выяснить, для чего действительно используется наш интерфейс Геттер на начальном интерфейсе только показывает, что мы не думали о фактическом использовании раньше, другими словами, он показывает проектное решение «по умолчанию» или его отсутствие.
Преврати это в логический метод
Очень часто этот вид геттера в основном используется для проверки того, имеет ли инструмент (одиночный или составной) какое-либо отношение к данной валюте, например, чтобы проверить, является ли инструмент приемлемым для экрана в долларах США или торгуемым трейдером, который только предоставлено право торговать в евро.
В этом случае вы можете преобразовать метод в другой метод выявления намерений, который принимает параметр и возвращает логическое значение:
public interface Instrument { boolean isCurrency(Currency currency); }
Этот интерфейс остается простым, он ближе к нашим потребностям и, кроме того, теперь он масштабируется для использования с составным инструментом, поскольку результат для составного инструмента может быть получен из каждого результата для каждого отдельного инструмента и оператора AND:
public class CompositeInstrument { // list of instruments... public boolean isCurrency(Currency currency) { boolean result; // for each instrument, result &= isCurrency(currency); return result; } }
Что-то делать с Fold
Как показано выше, проблема все в возвращаемом значении. Обобщая логическую и их логическую логику из предыдущего примера (‘& =’), общая хитрость для Composite-friendly-интерфейса заключается в определении методов, которые возвращают тип, который легко свернуть при последовательном выполнении. Например, хитрость заключается в том, чтобы объединить («свернуть») логический результат нескольких вызовов в один логический результат. Обычно вы делаете это с AND или OR для логических типов.
Если возвращаемый тип является коллекцией, вы можете объединить результаты, используя addAll (…), если это имеет смысл для операции.
Технически это легко сделать, когда возвращаемый тип закрывается под операцией ( магмой ), то есть когда результат какой-либо операции имеет тот же тип, что и операнд , точно так же, как ‘boolean1 AND boolean2 ‘ также является логическим значением.
Это, очевидно, относится к булевой и их булевой логике, но также к числам и их арифметике, операциям с коллекциями и их наборами, строкам и их конкатенации, а также ко многим другим типам, включая ваши собственные классы, как Эрик Эванс предлагает вам одобрить «Закрытие операций »В своей книге« Доменно-управляемый дизайн .
Преврати это в пустой метод
Хотя это невозможно в нашем предыдущем примере, методы void очень хорошо работают с шаблоном Composite: возвращать нечего, нет необходимости объединять или сворачивать что-либо:
public class CompositeFunction { List functions = ...; public void apply(...) { // for each function, function.apply(...); } }
Продолжение прохождения стиля
Последний прием, который поможет с шаблоном Composite, — это использование стиля передачи продолжения путем передачи объекта продолжения в качестве параметра методу. Затем метод устанавливает в него свой результат вместо использования возвращаемого значения.
Например, чтобы выполнить поиск по каждому узлу дерева, вы можете использовать продолжение следующим образом:
public class SearchResults { public void addResult(Node node){ // append to list of results...} public List getResults() { // return list of results...} } public class Node { List children = ...; public void search(SarchResults sr) { //... if (found){ sr.addResult(this); } // for each child, child.search(sr); } }
Передавая продолжение в качестве аргумента методу, продолжение заботится о множественности, и метод теперь хорошо подходит для составного паттерна. Вы можете считать, что продолжение действительно заключает в один объект процесс складывания результата каждого вызова, и, конечно, продолжение является изменяемым.
Этот стиль немного усложняет интерфейс метода, но также предлагает преимущество единственного распределения одного экземпляра продолжения для каждого вызова.
Одно слово об исключениях
Методы, которые могут генерировать исключения (даже непроверенные исключения), могут усложнить использование в композите. Чтобы иметь дело с исключениями внутри цикла, который вызывает каждого потомка, вы можете просто сгенерировать первое возникшее исключение за счет отказа от цикла. Альтернативой является сбор каждого захваченного исключения в коллекцию, а затем создание сложного исключения вокруг коллекции после завершения цикла. В некоторых других случаях составной цикл также может быть удобным местом для фактической обработки исключений, таких как полное ведение журнала, в одном центральном месте.
В заключение
Мы видели некоторые приемы, позволяющие настроить сигнатуру ваших методов так, чтобы они хорошо работали с шаблоном Composite, обычно путем некоторого сворачивания возвращаемого типа. В свою очередь, вам не нужно делать различие вручную между одним и несколькими, и один единственный интерфейс может использоваться гораздо чаще; Именно с такими деталями вы можете сделать свой дизайн простым и готовым к любым новым вызовам.
Следуй за мной по щебетать ! Кредиты: фотографии от меня, кроме сборочной линии от BUICK REGAL (Flickr)
От http://cyrille.martraire.com/2011/04/composite-friendly-interfaces/