Course program← Объектно ориентированное программирование
1История ООП
2Объект и Класс
3Базовые концепции ООП
4Принцип проектирования GRASP
5Принцип проектирования SOLID
6Паттерны GOF

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

This lesson is not translated yet – the Russian original is below.

Паттерн 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();
    }
}
// Общий интерфейс компонентов
class Graphic {
public:
    virtual void Draw() const = 0;
    virtual ~Graphic() = default;
};

class Circle : public Graphic {
public:
    void Draw() const override { std::cout << "Рисуем круг" << std::endl; }
};

class Square : public Graphic {
public:
    void Draw() const override { std::cout << "Рисуем квадрат" << std::endl; }
};

// Компоновщик – контейнер, может содержать другие элементы
class CompoundGraphic : public Graphic {
public:
    void Add(std::shared_ptr<Graphic> child) { children_.push_back(std::move(child)); }

    void Draw() const override {
        std::cout << "Рисуем составной объект (группу):" << std::endl;
        for (const auto& child : children_) {
            child->Draw();
        }
    }

private:
    std::vector<std::shared_ptr<Graphic>> children_;
};
// Общий интерфейс компонентов
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 – мощный структурный паттерн, позволяющий строить древовидные структуры и работать с ними единообразно. Он особенно полезен при реализации графических сцен, меню, файловых систем и любых других иерархий объектов.