Компоновщик (Composite)

Паттерн Компоновщик (Composite) объединяет группы объектов в древовидную структуру по принципу «часть-целое и позволяет клиенту одинаково работать как с отдельными объектами, так и с группой объектов.

Образно реализацию паттерна можно представить в виде меню, которое имеет различные пункты. Эти пункты могут содержать подменю, в которых, в свою очередь, также имеются пункты. То есть пункт меню служит с одной стороны частью меню, а с другой стороны еще одним меню. В итоге мы однообразно можем работать как с пунктом меню, так и со всем меню в целом.

Когда использовать компоновщик?

  • Когда объекты должны быть реализованы в виде иерархической древовидной структуры
  • Когда клиенты единообразно должны управлять как целыми объектами, так и их составными частями. То есть целое и его части должны реализовать один и тот же интерфейс

С помощью UML шаблон компоновщик можно представить следующим образом:

Паттерн Компоновщик в C# и .NET

Формальное определение паттерна на 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.

И также применяя компоновщик, мы легко можем обойти все узлы древовидной структуры.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *