Паттерн Composite (Компоновщик) используется для представления иерархических древовидных структур, где отдельные объекты и их контейнеры обрабатываются единообразно. Он позволяет работать с группами объектов так же, как и с отдельными элементами.
Когда применять Composite
- Когда нужно представить часть и целое в виде иерархии (например, структура меню, файловая система).
- Когда необходимо, чтобы клиент одинаково работал с отдельными объектами и их композициями.
- Когда важно упростить работу с древовидными структурами данных.
PlantUML-диаграмма
// Общий интерфейс компонентов
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 — мощный структурный паттерн, позволяющий строить древовидные структуры и работать с ними единообразно. Он особенно полезен при реализации графических сцен, меню, файловых систем и любых других иерархий объектов.