Composite
Проблем
Да се разработи модел на файлова система.
Файловата система трябва да съдържа файлове и папки. Всеки файл има име и размер. Всяка папка има име и може да съдържа файлове и други папки.
Необходимо е да може да се изчислява размерът както на отделен файл, така и на цяла папка.
Решение без използване на шаблона
Един възможен подход е файловете и папките да се реализират като напълно отделни класове.
public class File {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public int getSize() {
return size;
}
}
import java.util.ArrayList;
import java.util.List;
public class Folder {
private String name;
private List<File> files = new ArrayList<>();
private List<Folder> folders = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void addFile(File file) {
files.add(file);
}
public void addFolder(Folder folder) {
folders.add(folder);
}
public int getSize() {
int totalSize = 0;
for (File file : files) {
totalSize += file.getSize();
}
for (Folder folder : folders) {
totalSize += folder.getSize();
}
return totalSize;
}
}
Недостатъци на решението
При този подход клиентският код трябва да прави разлика между файлове и папки. Папката поддържа отделни колекции и отделни методи за добавяне на файлове и подпапки.
Ако в бъдеще се добави нов тип елемент, например архив, класът Folder ще трябва да бъде променен, за да поддържа новия тип.
Следователно е необходимо решение, което позволява всички елементи във файловата система да бъдат обработвани чрез общ интерфейс.
Шаблонът като решение
Composite организира обектите в дървовидна структура, при която както отделните елементи, така и техните групи реализират общ интерфейс.
По този начин клиентският код работи еднакво както с единичен обект, така и с цяла йерархия от обекти.
Дефиниция
Composite е структурен шаблон за проектиране, който организира обекти в дървовидна структура и позволява отделните обекти и техните композиции да бъдат третирани по еднакъв начин.
В шаблона Composite основните участници са:
- Компонент (Component) – общ интерфейс или абстрактен клас, реализиран от всички елементи в дървото.
- Лист (Leaf) – елемент, който няма наследници и реализира конкретното поведение.
- Композит (Composite) – елемент, който съдържа други компоненти и делегира операциите към тях.
UML диаграма
Примерна реализация
Компонент
public interface FileSystemItem {
String getName();
int getSize();
}
Лист
public class File implements FileSystemItem {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
}
Композит
import java.util.ArrayList;
import java.util.List;
public class Folder implements FileSystemItem {
private String name;
private List<FileSystemItem> items = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileSystemItem item) {
items.add(item);
}
public void remove(FileSystemItem item) {
items.remove(item);
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int totalSize = 0;
for (FileSystemItem item : items) {
totalSize += item.getSize();
}
return totalSize;
}
}
Използване
public class Application {
public static void main(String[] args) {
FileSystemItem notes =
new File("notes.txt", 10);
FileSystemItem report =
new File("report.pdf", 120);
Folder documents =
new Folder("Documents");
documents.add(notes);
documents.add(report);
Folder root =
new Folder("Root");
root.add(documents);
root.add(new File("readme.md", 5));
System.out.println(root.getName());
System.out.println(root.getSize());
}
}
Интерфейсът FileSystemItem представлява общия компонент в дървовидната структура.
Класът File е листо (Leaf). Той не съдържа други елементи и реализира операциите директно.
Класът Folder е композит (Composite). Освен собствените си данни той съдържа колекция от обекти, реализиращи интерфейса FileSystemItem. Това позволява една папка да съдържа както файлове, така и други папки.
При извикване на метода getSize() папката последователно извиква същия метод за всички свои елементи и сумира получения резултат. По този начин клиентският код работи еднакво както с отделен файл, така и с цяла йерархия от папки.
[!IMPORTANT] За разлика от Adapter, Decorator и Bridge, при които референцията към друг обект служи за делегиране на поведение, Composite използва йерархична структура от обекти. Всеки композит съдържа колекция от компоненти, което позволява изграждането на дървовидни структури с произволна дълбочина.
Предимства
- позволява еднаква работа с отделни обекти и техните композиции;
- естествено моделира дървовидни структури;
- улеснява добавянето на нови типове елементи;
- намалява необходимостта от проверки за конкретния тип на обекта;
- подпомага спазването на принципа Open/Closed.
Недостатъци
- затруднява налагането на ограничения върху допустимите елементи в дървото;
- при големи йерархии обработката може да стане по-сложна;
- не е подходящ за структури, които не са йерархични.
Приложение
Composite е подходящ когато:
- се моделират дървовидни структури;
- отделните обекти и групите от обекти трябва да бъдат обработвани по еднакъв начин;
- клиентският код не трябва да прави разлика между единичен обект и композиция от обекти;
- се изграждат файлови системи, графични сцени, менюта, организационни структури и други йерархични модели.