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

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

Когда применять Composite

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

PlantUML-диаграмма

UML диаграмма Composite

// Общий интерфейс компонентов
public abstract class Graphic
{
    public abstract void Draw();

    public virtual void Add(Graphic graphic) { }
    public virtual void Remove(Graphic graphic) { }
}

// Лист — простой элемент (не содержит других)
public class Circle : Graphic
{
    public override void Draw()
    {
        Console.WriteLine("Рисуем круг");
    }
}

public class Square : Graphic
{
    public override void Draw()
    {
        Console.WriteLine("Рисуем квадрат");
    }
}

// Компоновщик — контейнер, может содержать другие элементы
public class CompoundGraphic : Graphic
{
    private readonly List<Graphic> _children = new List<Graphic>();

    public override void Add(Graphic graphic)
    {
        _children.Add(graphic);
    }

    public override void Remove(Graphic graphic)
    {
        _children.Remove(graphic);
    }

    public override void Draw()
    {
        Console.WriteLine("Рисуем составной объект (группу):");
        foreach (var child in _children)
        {
            child.Draw();
        }
    }
}

// Клиентский код
class Program
{
    static void Main()
    {
        var circle = new Circle();
        var square = new Square();

        var group = new CompoundGraphic();
        group.Add(circle);
        group.Add(square);

        var mainGroup = new CompoundGraphic();
        mainGroup.Add(group);
        mainGroup.Add(new Circle());

        mainGroup.Draw();
    }
}
// Общий интерфейс компонентов
type Graphic interface {
	Draw()
}

// Листья — простые элементы
type Circle struct{}

func (Circle) Draw() { fmt.Println("Рисуем круг") }

type Square struct{}

func (Square) Draw() { fmt.Println("Рисуем квадрат") }

// Компоновщик — контейнер. Пустых Add/Remove у листьев нет:
// метод есть только там, где он осмыслен.
type CompoundGraphic struct {
	children []Graphic
}

func (g *CompoundGraphic) Add(child Graphic) {
	g.children = append(g.children, child)
}

func (g *CompoundGraphic) Draw() {
	fmt.Println("Рисуем составной объект (группу):")
	for _, child := range g.children {
		child.Draw()
	}
}

func main() {
	group := &CompoundGraphic{}
	group.Add(Circle{})
	group.Add(Square{})

	mainGroup := &CompoundGraphic{}
	mainGroup.Add(group)
	mainGroup.Add(Circle{})

	mainGroup.Draw()
}
from abc import ABC, abstractmethod


class Graphic(ABC):
    @abstractmethod
    def draw(self) -> None: ...

    def add(self, graphic: "Graphic") -> None: ...

    def remove(self, graphic: "Graphic") -> None: ...


class Circle(Graphic):
    def draw(self) -> None:
        print("Рисуем круг")


class Square(Graphic):
    def draw(self) -> None:
        print("Рисуем квадрат")


class CompoundGraphic(Graphic):
    def __init__(self) -> None:
        self._children: list[Graphic] = []

    def add(self, graphic: Graphic) -> None:
        self._children.append(graphic)

    def remove(self, graphic: Graphic) -> None:
        self._children.remove(graphic)

    def draw(self) -> None:
        print("Рисуем составной объект (группу):")
        for child in self._children:
            child.draw()


group = CompoundGraphic()
group.add(Circle())
group.add(Square())

main_group = CompoundGraphic()
main_group.add(group)
main_group.add(Circle())

main_group.draw()
abstract class Graphic {
    abstract draw(): void;

    add(graphic: Graphic): void {}
    remove(graphic: Graphic): void {}
}

class Circle extends Graphic {
    draw(): void {
        console.log("Рисуем круг");
    }
}

class Square extends Graphic {
    draw(): void {
        console.log("Рисуем квадрат");
    }
}

class CompoundGraphic extends Graphic {
    private readonly children: Graphic[] = [];

    add(graphic: Graphic): void {
        this.children.push(graphic);
    }

    remove(graphic: Graphic): void {
        const i = this.children.indexOf(graphic);
        if (i >= 0) this.children.splice(i, 1);
    }

    draw(): void {
        console.log("Рисуем составной объект (группу):");
        for (const child of this.children) child.draw();
    }
}

const group = new CompoundGraphic();
group.add(new Circle());
group.add(new Square());

const mainGroup = new CompoundGraphic();
mainGroup.add(group);
mainGroup.add(new Circle());

mainGroup.draw();
import java.util.ArrayList;
import java.util.List;

abstract class Graphic {
    abstract void draw();

    void add(Graphic graphic) { }
    void remove(Graphic graphic) { }
}

class Circle extends Graphic {
    void draw() {
        System.out.println("Рисуем круг");
    }
}

class Square extends Graphic {
    void draw() {
        System.out.println("Рисуем квадрат");
    }
}

class CompoundGraphic extends Graphic {
    private final List<Graphic> children = new ArrayList<>();

    @Override
    void add(Graphic graphic) {
        children.add(graphic);
    }

    @Override
    void remove(Graphic graphic) {
        children.remove(graphic);
    }

    @Override
    void draw() {
        System.out.println("Рисуем составной объект (группу):");
        for (Graphic child : children) {
            child.draw();
        }
    }
}

public class Program {
    public static void main(String[] args) {
        CompoundGraphic group = new CompoundGraphic();
        group.add(new Circle());
        group.add(new Square());

        CompoundGraphic mainGroup = new CompoundGraphic();
        mainGroup.add(group);
        mainGroup.add(new Circle());

        mainGroup.draw();
    }
}
sealed interface Graphic {
    fun draw()
}

class Circle : Graphic {
    override fun draw() = println("Рисуем круг")
}

class Square : Graphic {
    override fun draw() = println("Рисуем квадрат")
}

// Пустых add/remove у листьев нет: они объявлены только у контейнера.
class CompoundGraphic : Graphic {
    private val children = mutableListOf<Graphic>()

    fun add(graphic: Graphic) {
        children += graphic
    }

    fun remove(graphic: Graphic) {
        children -= graphic
    }

    override fun draw() {
        println("Рисуем составной объект (группу):")
        children.forEach { it.draw() }
    }
}

fun main() {
    val group = CompoundGraphic().apply {
        add(Circle())
        add(Square())
    }

    val mainGroup = CompoundGraphic().apply {
        add(group)
        add(Circle())
    }

    mainGroup.draw()
}

Плюсы и минусы

Плюсы Минусы
Упрощает работу с иерархическими структурами Может затруднить введение ограничений на состав объектов
Позволяет клиенту одинаково работать с отдельными элементами и их группами Усложняет отладку при глубокой вложенности
Следует принципу открытости/закрытости (OCP) Требует дополнительных классов и абстракций

Заключение

Composite — мощный структурный паттерн, позволяющий строить древовидные структуры и работать с ними единообразно. Он особенно полезен при реализации графических сцен, меню, файловых систем и любых других иерархий объектов.