Паттерн проектирования «Компоновщик» / «Composite» (2025)

Почитать описание других паттернов.

Проблема

Предоставить клиенту единообразный доступ к листовым и составным элементам древовидной структуры.

Описание

Существует большое количество программных систем, в которых так или иначе применяются древовидные структуры объектов. В большинстве случаев, это всевозможные конструкторы/редакторы, которые позволяют собрать что-то большое (составное) из чего-то более мелкого (листового). При этом, клиент трактует и большое и мелкое как одно и тоже, а система должна различать составные и листовые объекты соответственно.

Не только программы-конструкторы используют данный паттерн (а я надеюсь, что используют). Ярким примером подобной древовидной структуры является пользовательский интерфейс (GUI). Действительно, типичное окно пользовательского интерфейса — контейнер для более простых виджетов — панелей, кнопок, полей воода и т.д., причем, панель, в свою очередь тоже являются контейнерными объектами и так вплоть до элементарных листовых объектов. Ярким примером использования данного паттерна в этом контексте является библиотека отрисовки интерфейса Swing (я имею ввиду класс javax.swing.JComponent).

Итак, вернемся к поставленной проблеме — предоставить клиенту единообразный интерфейс к листовым и составным элементам древовидной структуры. Очевидно, что для решения данной проблемы необходимо завести общий интерфейс, который будет описывать и элементарные и составные объекты. Более того, т.к. интерфейс описывает и составные объекты, он должен содержать контейнерные методы — add, remove, get для добавления, удаления и получения объекта из контейнера. Причем, данные методы должны быть параметризованы тем самым общим интерфейсом. Таким образом, автоматически появляется возможность добавлять в контейнер не только элементарные объекты но и другие контейнеры.

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

Практическая задача

Напишем простейший сумматор выражений с применением паттерна «Компоновщик». Сумматор не должен заниматься разбором выражений, он должен лишь описывать и реализовывать древовидную структуру для более удобного вычисления выражений.

Я специально не стал брать классические примеры с конструкторами или пользовательским интерфейсом, чтобы у читателя не осталось впечатлений об узкой направленности паттерна.

Диаграмма классов

Для начала несколько комментариев. В данном примере я несколько иначе трактую понятие контейнера. Скажем так, я спроецировал классическое понятие контейнера на предметную область — вычисление выражений. Мой «контейнер» ведет себя несколько иначе, чем классический. Вместо метода remove(), у SubExpression есть метод sub(), который на самом деле и делает удаление из контейнера, но только по-своему. Ввиду того, что это все-таки сумматор, метод sub() аналогично add() добавляет подвыражение в контейнер, но с противоположным знаком, тем самым реализуя вычитание.

Паттерн проектирования «Компоновщик» / «Composite» (1)

Рассмотрим диаграмму. Интерфейс подвыражения (SubExpression) описывает единообразный интерфейс для всех объектов древовидной структуры, которых, к слову сказать, не много — целые числа (IntegetValue), вещественные числа (FloatValue) и выражения (Expression). Очевидно, что все числа — листовые объекты и контейнерных методов они не реализуют, а выражение как раз наоборот — контейнер.

Реализация на Java

В реализации нет кода класса FloatValue.

// Единообразный интерфейс доступа к листовым и контейнерным объектам
public interface SubExpression {

public Number value();

public void add(SubExpression expr);
public void sub(SubExpression expr);
public SubExpression getSubExpression(int index);
}// Лист - целое число
public class IntegerValue implements SubExpression {

private Integer value;

public IntegerValue(Integer value) {
this.value = value;
}

@Override

public void add(SubExpression expr) {
throw new UnsupportedOperationException();
}

@Override

public SubExpression getSubExpression(int index) {
throw new UnsupportedOperationException();
}

@Override

public void sub(SubExpression expr) {
throw new UnsupportedOperationException();
}

@Override

public Number value() {
return value;
}
}

import java.util.

ArrayList;
import java.util.List;// Выражение - контейнер
public class Expression implements SubExpression {

private List<SubExpression> exprs;

public Expression(SubExpression ... exprs) {
this.exprs = new ArrayList<SubExpression>();
for (SubExpression expr: exprs) {
this.exprs.add(expr);
}
}

@Override
public void add(SubExpression expr) {
exprs.add(expr);
}

@Override
public void sub(SubExpression expr) {
if (expr instanceof IntegerValue) {
exprs.add(new IntegerValue(-1*expr.value().intValue()));
} else {
exprs.add(new FloatValue(-1*expr.value().floatValue()));
}

}

@Override

public SubExpression getSubExpression(int index) {
return exprs.get(index);
}

@Override

public Number value() {
Number result = new Float(0);

for (SubExpression expr: exprs) {
result = result.floatValue() + expr.value().floatValue();
}

return result;
}
}// Использование
public class Main {public static void main(String[] args) {
// Вычислим выражение - 20 - (5-2) - (11+6)
// Приведем к следующему виду 20 - a - b
SubExpression expr = new Expression();

SubExpression a =

new Expression(new IntegerValue(5), new IntegerValue(-2));
SubExpression b = new Expression(new IntegerValue(11), new IntegerValue(6));

expr.add(new IntegerValue(20));
expr.sub(a);
expr.sub(b);

System.out.println(expr.value());
}
}

* This source code was highlighted with Source Code Highlighter.

Надеюсь, у меня получилось донести до Вас идею паттерна.

Паттерн проектирования «Компоновщик» / «Composite» (2025)

References

Top Articles
Latest Posts
Recommended Articles
Article information

Author: Arielle Torp

Last Updated:

Views: 5465

Rating: 4 / 5 (61 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Arielle Torp

Birthday: 1997-09-20

Address: 87313 Erdman Vista, North Dustinborough, WA 37563

Phone: +97216742823598

Job: Central Technology Officer

Hobby: Taekwondo, Macrame, Foreign language learning, Kite flying, Cooking, Skiing, Computer programming

Introduction: My name is Arielle Torp, I am a comfortable, kind, zealous, lovely, jolly, colorful, adventurous person who loves writing and wants to share my knowledge and understanding with you.