Kompozyt

Wzorce projektowe

  |   8 min czytania

Zastosowanie

Kompozyt (ang. Composite) (wzorzec strukturalny) umożliwia składanie obiektów w strukturę drzewiastą, dzięki czemu ich widoczność oraz reprezentacja może się sprowadzać do jednego obiektu. Klient zamiast wielu obiektów operuje na jednym, a żądana na nim operacja wykonuje się dla każdego jego potomka. Struktura składa się z prymitywnych obiektów - liści (Leaf) oraz rozszerzonych - kompozytów (Composite), które przypominają gałezię drzewa. Użycie wzorca Kompozyt upraszcza kod klienta, usprawnia zarządzanie grupą obiektów oraz ułatwia dodanie nowego rodzaju komponentów.

Ograniczenia

Projektując strukturę drzewiastą należy mieć na uwadzę, że powstała hierarchia klas może być zbyt ogólna. Wykonanie operacji w danym momencie tylko na określonej grupie obiektów spośród całej struktury jest mocno utrudnione.

Użycie

Wzorzec Kompozyt stosowany jest w miejscach, gdzie grupa podobnych obiektów o wspólnym zachowaniu może zostać ujęta w strukturze drzewiastej. Warto pamiętać, że sercem działania wzorca Kompozyt jest wykonywania operacji na zbiorze obiektów, a nie sama ich reprezentacja w postaci struktury drzewa i właśnie w takim kontekście powinien być używany.

Implementacja

Interfejs Component zawiera metody wspólne dla wszystkich elementów w kolekcji. Każdy element należący do struktury drzewiastej musi implementować interfejs Component. Elementy typu Composite mogą zawierać potomków, natomiast obiekty typu Leaf są ostatnimi w hierarchii elementami i nie posiadają potomków.

Kompozyt diagram

Poniższy listing przedstawia implementację wzorca Kompozyt dla jednej klasy rodzica Composite oraz jednej klasy dziecka Leaf.

public class Composite implements Component {

    private List<Component> childrens;
    //other fields

    public Composite() {
        childrens = new ArrayList();
    }

    public void addChild(Component component) {
        childrens.add(component);
    }

    public void removeChild(Component component) {
        childrens.remove(component);
    }

    @Override
    public void operation() {
        //do action
        for(Component children : childrens) {
            children.operation();
        }
    }

    //other methods
}

public class Leaf implements Component {

    //some fields

    @Override
    public void operation() {
        //do action
    }

    //other methods
}

//other Leaf class in the same way

interface Component {

    void operation();
}

Tworzenie struktury drzewiastej i podejmowanie na niej wspólnych działań może przebiegać następująco.

//create components
Composite composite1 = new Composite();
Composite composite2 = new Composite();
Leaf leaf1 = new Leaf();
Leaf leaf1 = new Leaf();
Leaf leaf1 = new Leaf();

//create tree structure
composite2.addChild(leaf2);
composite2.addChild(leaf3);
composite1.addChild(leaf1);
composite1.addChild(composite2);

//do some action
composite1.action(); //do action for every child in composite1

Przykład

Sklep internetowy wykorzystuje aplikację Storehouse do zarządzania procesem kompletowania zamówienia klienta. W zależności od rodzaju zamówionych towarów Item są one pakowane do specjalnie przeznaczonych opakowań Box (reklamówki, torby, skrzynie itp). Towary z magazynu trafiają do opakowań, a następnie do sektorów w strefie dostarczeń. Gdy zamówienie jest skompletowane zostaje umieszczone w zbiorczym kontenerze i wysłane do klienta. Aplikacja odnotowuje ruch towarów oraz na podstawie zawartości opakowań generuje pełne rozliczenie. Poniższy listing przedstawia implementacje aplikacji z użyciem wzorca Kompozyt.

public class Box implements Item {

    private String name;
    private String sector;
    private Status status;
    private List<Item> items;

    public Box(String name) {
        this.name = name;
        this.sector = "main";
        this.status = Status.MAGAZINE;
        this.items = new ArrayList();
    }

    @Override
    public String getInfo() {
        String info = name + " contains: ";
        for(Item item : items)
            info = info + item.getInfo() + "\n";
        return info;
    }

    @Override
    public double getPrice() {
        double price = 0;
        for(Item item : items)
            price += item.getPrice();
        return price;
    }

    @Override
    public void changeStatus(Status status) {
        this.status = status;
        for(Item item : items)
            item.changeStatus(status);
    }

    public void addItem(Item item) {
        items.add(item);
    }

    public void removeItem(Item item) {
        item.changeStatus(Status.MAGAZINE);
        items.remove(item);
    }

    public void changeSector(String sector) {
        this.sector = sector;
    }

    public String getSector() {
        return sector;
    }
}

public class Product implements Item {

    private String name;
    private Status status;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
        this.status = Status.MAGAZINE;
    }

    @Override
    public String getInfo() {
        return name;
    }

    @Override
    public double getPrice() {
        return price;
    }

    @Override
    public void changeStatus(Status status) {
        this.status = status;
    }
}

interface Item {

    String getInfo();
    void getPrice();
    void changeStatus(Status status);
}

Pracownicy magazynu otrzymują kompletują otrzymane zamówienie oraz odnotowują stan poszczególnych produktów.

//workers colect the order
//small items in bag
Box bag = new Box("Bag");
Item item1 = new Item("Bulb", 10.0);
Item item2 = new Item("Towel", 5.00);
bag.addItem(item1);
bag.addItem(item2);

//fragile products in box
Box box = new Box("Box");
Item item3 = new Item("Wine glass", 8.00);
Item item4 = new Item("Wine glass", 8.00);
Item item5 = new Item("Jug", 13.00);
box.addItem(item3);
box.addItem(item4);
box.addItem(item5);

//small and fragile items moved to deliver zone
bag.changeSector("A1");
bag.changeStatus(Status.READY);
box.changeSector("B2");
box.changeStatus(Status.READY);

//client canceled one item
box.removeItem(item4);

//order is ready to deliver
//workers get boxes from sectors
String bagSector = bag.getSector();
String boxSector = box.getSector();
Box container = new Box("Container");
container.addItem(bag);
container.addItem(box);

//prepare recipe
String orderInfo = container.getInfo();
double orderPrice = container.getPrice();

//deliver order to client
container.changeStatus(Status.DELIVER);

Biblioteki

Przykładem wykorzystania wzorca Kompozyt w Androidzie są widoki - obiekty typu View oraz ViewGroup. Ich reprezentacja w plikach layout przypomina strukturę drzewiastą, a podjęta akcja dla widoku ViewGroup dotyczy wszystkich jego dzieci zarówno typu View jak i ViewGroup.