Comments 15
я правильно понял, что если коротко:
Extract Interface + Extract Class?
btw, А в листьях лучше, наверно, NullObject смотрелся-бы, в виде пустой иммутбл синглтон коллекции.
дабы ексепшены не кидать.
Extract Interface + Extract Class?
btw, А в листьях лучше, наверно, NullObject смотрелся-бы, в виде пустой иммутбл синглтон коллекции.
дабы ексепшены не кидать.
Да, правильно. А я вроде так и сделал: неизменяемая синглтон-коллекция, получаемая через свойство ComponentCollections.EmptyCollection. Эксепшены кидаются только при попытке изменить коллекцию (вызвать Add, Remove, Clear и т.п.) Если вы обращаетесь к Count или GetEnumerator, то с Leaf все работает так же, как и с Composite-объектами.
тьфублин, листья проморгал. сорри
Если полностью закончить выделение класса — вынести children из Menu и инкапсулировать его в отдельный класс, то разделение бизнес логики (Display) и дерева будет полным. (в UML классы разделены, а в коде — нет)
Если полностью закончить выделение класса — вынести children из Menu и инкапсулировать его в отдельный класс, то разделение бизнес логики (Display) и дерева будет полным. (в UML классы разделены, а в коде — нет)
хотя, если код только для примера, то всё гуд :)
«Этим свойством можно и не пользоваться: тогда при попытке изменить коллекцию дочерних элементов для листового компонента будет выбрасываться исключение NotSupportedException. Таким образом, мы не теряем прозрачность интерфейса для всех компонентов — основное преимущества паттерна «Компоновщик», — и в то же время получаем простой способ определить, могут ли у любого выбранного компонента быть дочерние элементы.»
Composite реализуется ровно наоборот: свойства и поведение дочерних элементов протягиваются до компоновщика, а не все элементы в дереве реализуют свойства и поведение компоновщика (диаграмма в GoF это хорошо показывает).
Вы решали задачу «как работать с элементом, думая, что он дерево». Composite решает задачу «как работать с деревом, думая, что оно — элемент».
Composite реализуется ровно наоборот: свойства и поведение дочерних элементов протягиваются до компоновщика, а не все элементы в дереве реализуют свойства и поведение компоновщика (диаграмма в GoF это хорошо показывает).
Вы решали задачу «как работать с элементом, думая, что он дерево». Composite решает задачу «как работать с деревом, думая, что оно — элемент».
Соответственно, в вашем примере должно быть не IMenuItem: IComponent, а IComponent: IMenuItem.
Так вы и работаете с деревом как с элементом, вызываете Display для элемента — выводится дерево, этот аспект компоновщика я не изменял. IComponent введен для того, чтобы частично вынести код из IMenuItem и других реализаций в некоторую общую мини-библиотеку, которая не будет изменяться для различных иерархий. Вас возможно смутил метод GetDescendants(), он позволяет работать с элементом, думая, что он дерево, но это лишь дополнительная функциональность, которая автоматически реализуется для любого наследника IComponent.
Понимаете ли, все то, что вы понаписали, не имеет никакого отношения к Composite.
Не понимаю, почему? Свойства и поведения дочерних элементов по-прежнему протягиваются до компоновщика, как вы и написали, просто это реализуется в том же классе для бизнес-логики (Display, Name) и в отдельном классе для структуры (Add, Remove). Классы объединяются через композицию, но объединение происходит уже в композитном классе Menu. Я в примере использовал стандартный List и как бы автоматически получил реализацию интерфейса структуры. А вы попробуйте передать свой класс TChildrenCollection, реализующий Add, Remove и GetChild, функциональность будет 1-в-1 по GoF.
Потому что Composite — это когда коллекция реализует интерфейс элемента. Все. И для этого используется банальное решение с выделением интерфейса, его навязыванием композитному элементу, и реализации (причем реализация каждый раз своя, она специфична для задачи).
Весь понаписанный вами код не нужен для паттерна Composite.
Весь понаписанный вами код не нужен для паттерна Composite.
Из GoF. «Паттерн Composite. Назначение: компонует объекты в древовидные структуры для представления иерархий часть-целое. Позволяет клиентам единообразно трактовать индивидуальные и составные объекты… Ключом к паттерну компоновщик является абстрактный класс, который представляет одновременно и примитивы, и контейнеры».
Для того, что я изложил в посте, верны все 3 утверждения, так что это компоновщик. Нужно ли писать весь мой код, чтобы реализовать компоновщик? Нет, совсем не нужно. Во многих случаях этот код избыточен. В чем смысл написанного мной кода? В том, что написали его 1 раз (имею ввиду IComponent, итераторы и NullObject-коллекции) и в дальнейшем используете его, реализуя конкретную функциональность в каждом конкретном случае свою (и для структуры, и для бизнес-логики вы реализуете свое в каждой иерархии IMenuItem!)
В чем преимущества предложенного мною подхода: для множества схожих задач можно выделить общий интерфейс иерархии и реализовать NullObject-коллекцию для него 1 раз. Плюс вы получаете некоторый готовый функционал (итераторы) через методы-расширения. Этот мой подход довольно банален и ничего нового и выдающегося из себя не представляет.
Для того, что я изложил в посте, верны все 3 утверждения, так что это компоновщик. Нужно ли писать весь мой код, чтобы реализовать компоновщик? Нет, совсем не нужно. Во многих случаях этот код избыточен. В чем смысл написанного мной кода? В том, что написали его 1 раз (имею ввиду IComponent, итераторы и NullObject-коллекции) и в дальнейшем используете его, реализуя конкретную функциональность в каждом конкретном случае свою (и для структуры, и для бизнес-логики вы реализуете свое в каждой иерархии IMenuItem!)
В чем преимущества предложенного мною подхода: для множества схожих задач можно выделить общий интерфейс иерархии и реализовать NullObject-коллекцию для него 1 раз. Плюс вы получаете некоторый готовый функционал (итераторы) через методы-расширения. Этот мой подход довольно банален и ничего нового и выдающегося из себя не представляет.
«В том, что написали его 1 раз (имею ввиду IComponent, итераторы и NullObject-коллекции) и в дальнейшем используете его, реализуя конкретную функциональность в каждом конкретном случае свою (и для структуры, и для бизнес-логики вы реализуете свое в каждой иерархии IMenuItem!)»
… и при этом получаем кучу семантического мусора, который нам не нужен. В этом основная беда.
«Объясню иначе. Все, что я сделал — взял паттерн компоновщик и применил к нему другие паттерны (ну и адаптировал к реалиям C#). Это усложняет структуру, но дает больше гибкости. Получить не-компоновщик при этом я не мог.»
Вы могли получить больше, чем компоновщик, что сделало название вашей статьи совершенно неуместным. Называйся она «как написать универсальную структуру для хранения иерархий», вопросов бы не было.
… и при этом получаем кучу семантического мусора, который нам не нужен. В этом основная беда.
«Объясню иначе. Все, что я сделал — взял паттерн компоновщик и применил к нему другие паттерны (ну и адаптировал к реалиям C#). Это усложняет структуру, но дает больше гибкости. Получить не-компоновщик при этом я не мог.»
Вы могли получить больше, чем компоновщик, что сделало название вашей статьи совершенно неуместным. Называйся она «как написать универсальную структуру для хранения иерархий», вопросов бы не было.
Объясню иначе. Все, что я сделал — взял паттерн компоновщик и применил к нему другие паттерны (ну и адаптировал к реалиям C#). Это усложняет структуру, но дает больше гибкости. Получить не-компоновщик при этом я не мог.
Sign up to leave a comment.
Как усовершенствовать реализацию Компоновщика в .NET