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 диаграма

Composite

Примерна реализация

Компонент

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 е подходящ когато:

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

This site uses Just the Docs, a documentation theme for Jekyll.