Autor: Arkadiusz Kotarski


Wprowadzenie

Prawdopodobnie w każdej aplikacji znajdzie się miejsce dla struktury drzewiastej. Mam tutaj na myśli menu nawigacyjne bądź np. zestaw produktów przygotowanych na sprzedaż w sklepie internetowym. Taką funkcjonalność zapewnia Kompozyt - jest to strukturalny wzorzec projektowy, który upraszcza układ powiązanych ze sobą klas.

Budowanie hierarchi jest niemal nierozłączne z programowaniem, pozwala na łatwiejsze poruszanie się po architekturze aplikacji. Główną zaletą omawianego wzorca jest to, że dzięki niemu mamy możliwość zarządzania grupą obiektów jak jednym z nich. Konsekwencją płynącą z nieużywania Kompozytu może być ciężki do rozbudowy kod. Bez zmiany dotychczasowej architektury aplikacji, dodawanie każdej nowej klasy do istniejącej już struktury może dla nas oznaczać o wiele więcej pracy.

Spójrzcie na diagram obrazujący wzorzec: Wzorzec projektowy - Kompozyt

Problem

Wzorzec kompozyt mógłby się sprawdzić w grze strategicznej. Zaznaczanie i wykonywanie poleceń wielu jednostkom na raz jest nieodłączną mechaniką w grach tego gatunku.

Nie zawsze tak jest, ale w grze strategicznej mogliby występować swego rodzaju dowódcy, wydający rozkazy przypisanej do niego armi. Oznacza to, że mamy doczynienia z hierarchią, ponieważ przywódca w tym przypadku jest klasą CommanderComposite i posiada w swojej kolekcji jednostki czyli reprezentację klasy EntityLeaf. Dzięki takiemu podejściu rozkaz wydajemy tylko dowodzącemu, a on dzięki strukturze drzewiastej przekazuje to samo polecenie do swojej armi. Oczywiście najlepsze we wzorcu kompozyt jest to, że nie blokuje on dostępu do prymitywu czyli EntityLeaf - mamy dalej możliwość wydawania poleceń nawet najbardziej podstawowym jednostkom. Dana architektura może zawierać inne obiekty klasy CommanderComposite, których zachowanie będzie takie samo jak całej hierarchi.

Wykorzystując dany wzorzec w tym konkretnym przypadku możemy bez problemu zaimplementować trochę inne zachowanie dla dowódcy, np. mógłby poruszać się na czele swojej armi, lub mógłby posiadać inną reprezentację graficzną zaznaczenia, co byłoby utrudnione lub bardziej skompikowane do osiągnięcia bez użycia wzorca kompozyt.

Wzorzec projektowy - Kompozyt

Implementacja

// Dowódca
class CommanderComposite : IUnitComponent
{
    // Lista wszystkich dzieci przypisanych do dowódcy
    private List<IUnitComponent> _children = new List<IUnitComponent>();

    // Rozkaz do ruszenia się
    public void Move()
    {
        Console.WriteLine("I'm a commander and i go first");

        foreach (var item in _children)
        {
            item.Move();
        }
    }

    // Metoda dodająca dzieci
    public void AddChild(IUnitComponent entity)
    {
        _children.Add(entity);
    }

    // Metoda usuwająca dzieci
    public void RemoveChild(IUnitComponent entity)
    {
        _children.Remove(entity);
    }

    // Metoda pobierająca dzieci
    public List<IUnitComponent> GetChild()
    {
       return _children;
    }
}

// interface po którym dziedziczy każdy komponent
interface IUnitComponent
{
    void Move();
}

// Konkretna jednostka
class EntityLeaf : IUnitComponent
{
    // Rozkaz do ruszenia się
    public void Move()
    {
        Console.WriteLine("I move after my commander");
    }
}

// Główna klasa w aplikacji konsolowej "Klient"
class Program
{
    static void Main(string[] args)
    {
        // Powołanie do życia dowódcy.
        CommanderComposite commander = new CommanderComposite();

        // Dodanie jednostek do dowódcy
        commander.AddChild(new EntityLeaf());
        commander.AddChild(new EntityLeaf());
        commander.AddChild(new EntityLeaf());
        commander.AddChild(new EntityLeaf());
        commander.AddChild(new EntityLeaf());

        // Wydanie rozkazu do poruszenia
        commander.Move();

        // Zatrzymanie programu
        Console.Read();
    }
}

Podsumowanie

Dzięki wzorcu projektowemu Kompozyt możemy z łatwością sterować wieloma obiektami. Tworzone przez niego struktury są proste do zarządzania oraz rozbudowy, z łatwością możemy poszerzyć hierarchie o nowe komponenty. Nie możemy jednak zapominać o wadach. Przygotowany przez nas kod może stać się zbyt ogólny co nie zawsze jest w pełni oczekiwane i może przyczynić się do różnych problemów. Jednym z nich mogą być trudności w wykonywaniu pewnych operacji na konkretnym typie.