Паттерн Компоновщик (Composite) объединяет группы объектов в древовидную структуру по принципу "часть-целое и позволяет клиенту одинаково работать как с отдельными объектами, так и с группой объектов.
Образно реализацию паттерна можно представить в виде меню, которое имеет различные пункты. Эти пункты могут содержать подменю, в которых, в свою очередь, также имеются пункты. То есть пункт меню служит с одной стороны частью меню, а с другой стороны еще одним меню. В итоге мы однообразно можем работать как с пунктом меню, так и со всем меню в целом.
Когда объекты должны быть реализованы в виде иерархической древовидной структуры
Когда клиенты единообразно должны управлять как целыми объектами, так и их составными частями. То есть целое и его части должны реализовать один и тот же интерфейс
С помощью UML шаблон компоновщик можно представить следующим образом:
Формальное определение паттерна на C# могло бы выглядеть так:
class Client { public void Main() { Component root = new Composite("Root"); Component leaf = new Leaf("Leaf"); Composite subtree = new Composite("Subtree"); root.Add(leaf); root.Add(subtree); root.Display(); } } abstract class Component { protected string name; public Component(string name) { this.name = name; } public abstract void Display(); public abstract void Add(Component c); public abstract void Remove(Component c); } class Composite : Component { List<Component> children = new List<Component>(); public Composite(string name) : base(name) {} public override void Add(Component component) { children.Add(component); } public override void Remove(Component component) { children.Remove(component); } public override void Display() { Console.WriteLine(name); foreach (Component component in children) { component.Display(); } } } class Leaf : Component { public Leaf(string name) : base(name) {} public override void Display() { Console.WriteLine(name); } public override void Add(Component component) { throw new NotImplementedException(); } public override void Remove(Component component) { throw new NotImplementedException(); } }
Component: определяет интерфейс для всех компонентов в древовидной структуре
Composite: представляет компонент, который может содержать другие компоненты и реализует механизм для их добавления и удаления
Leaf: представляет отдельный компонент, который не может содержать другие компоненты
Client: клиент, который использует компоненты
Рассмотрим простейший пример. Допустим, нам надо создать объект файловой системы. Файловую систему составляют папки и файлы. Каждая папка также может включать в себя папки и файлы. То есть получается древовидная иерархическая структура, где с вложенными папками нам надо работать также, как и с папками, которые их содержат. Для реализации данной задачи и воспользуемся паттерном Компоновщик:
class Program { static void Main(string[] args) { Component fileSystem = new Directory("Файловая система"); // определяем новый диск Component diskC = new Directory("Диск С"); // новые файлы Component pngFile = new File("12345.png"); Component docxFile = new File("Document.docx"); // добавляем файлы на диск С diskC.Add(pngFile); diskC.Add(docxFile); // добавляем диск С в файловую систему fileSystem.Add(diskC); // выводим все данные fileSystem.Print(); Console.WriteLine(); // удаляем с диска С файл diskC.Remove(pngFile); // создаем новую папку Component docsFolder = new Directory("Мои Документы"); // добавляем в нее файлы Component txtFile = new File("readme.txt"); Component csFile = new File("Program.cs"); docsFolder.Add(txtFile); docsFolder.Add(csFile); diskC.Add(docsFolder); fileSystem.Print(); Console.Read(); } } abstract class Component { protected string name; public Component(string name) { this.name = name; } public virtual void Add(Component component){} public virtual void Remove(Component component) { } public virtual void Print() { Console.WriteLine(name); } } class Directory :Component { private List<Component> components = new List<Component>(); public Directory(string name) : base(name) { } public override void Add(Component component) { components.Add(component); } public override void Remove(Component component) { components.Remove(component); } public override void Print() { Console.WriteLine("Узел " + name); Console.WriteLine("Подузлы:"); for(int i=0; i<components.Count;i++) { components[i].Print(); } } } class File : Component { public File(string name) : base(name) {} }
В итоге подобная система обладает неплохой гибкостью: если мы захотим добавить новый вид компонентов, нам достаточно унаследовать новый класс от Component.
И также применяя компоновщик, мы легко можем обойти все узлы древовидной структуры.