Kompozyt (Composite)

Posted on by 0 comment

Pod rozważania weźmiemy strukturalny wzorzec projektowy (strukturalne wzorce projektowe odpowiadają za regulację powiązań między klasami), którym jest Kompozyt (Composite). Warto, choć pobieżnie znać ten wzorzec, ponieważ jest dość często używany w programowaniu.

Kompozyt

Nazwa wzorca naturalnie nawiązuje do jego budowy, efektem zastosowania wzorca jest struktura drzewiasta obiektów np.

Pacjent
--Przegląd
--Leczenie Zęba
----Borowanie
----Wypełnienie
----Szlifowanie

Wzorzec ten stosujemy, gdy grupę obiektów należących do pewnego typu, możemy w pewnych aspektach traktować tak jak pojedynczy obiekt. Ułatwia nam to manipulację dużą liczbą obiektów, zamiast odnosić się do nich pojedynczo, możemy wywołać akcję na całej grupie.

Diagram klas

Kompozyt (composite) wzorzec projektowy

Kompozyt (composite) wzorzec projektowy

Gdy mówimy o wzorcu kompozyt, możemy dokonać jego dysocjacji na poszczególne składowe:

  • Liść (Leaf) – reprezentuje prymitywny obiekt nie posiadający potomków,
  • Kompozyt (Composite) – reprezentuje grupę obiektów, składającą się z „liści”, implementuje akcje interfejsu Komponent,
  • Komponent (Component) –  interfejs, który implementują obiekty, definiuje ich domyślne zachowanie,
  • Klient – operuje na obiektach zawartych  w układzie.

Kod źródłowy

Zastosujmy ten wzorzec w gabinecie stomatologicznym 🙂 Załóżmy, że pani stomatolog, po zakończonej wizycie, chce wydrukować klientowi zestawienie wykonanych operacji oraz ich koszt.

Dostarczmy interfejs Komponent, który będzie narzucał podstawową implementację naszym klasom pochodnym, moglibyśmy tutaj użyć także klasy abstrakcyjnej, kwestia w wyborze należy do programisty, należy pamiętać, że klasa abstrakcyjna daje nam troszkę większą swobodę implementacji (może zawierać pola, konstruktory, ciała metod itp.) :

interface Komponent
{
    int Cena { get; set; }
    string NazwaWyswietlana { get; set; }

    void Dodaj(Komponent ob);
    void Usun(Komponent ob);
    void Wyswietl(int przesuniecie);
}

Widzimy tutaj trzy metody oraz dwie właściwości charakteryzujące naszą klasę.

W kolejnym kroku zaaranżujemy dwie klasy należace do grupy Component, jedna z nich będzie przechowywała operacje potrzebne do usunięcia ubytku na powłoce zęba (UsuniecieUbytku), druga będzie składowała wszystkie czynności wykonane podczas wizyty u pani dentystki (Wizyta):

class Wizyta : Komponent
    {
        public int Cena { get; set; }
        public string NazwaWyswietlana { get; set; }
        private List<Komponent> _dzieci = new List<Komponent>();

        public Wizyta(string nazwa)
        {
            NazwaWyswietlana = nazwa;
        }

        public void Dodaj(Komponent ob)
        {
            _dzieci.Add(ob);
            Cena += ob.Cena;
        }

        public void Usun(Komponent ob)
        {
            _dzieci.Remove(ob);
            Cena -= ob.Cena;
        }

        public void Wyswietl(int przesuniecie)
        {
            Console.WriteLine(new String('-', przesuniecie) + String.Format("{0} : Cena {1} zł",NazwaWyswietlana, Cena));

            foreach (Komponent ob in _dzieci)
            {
                ob.Wyswietl(przesuniecie + 2);
            }
        }
    }

    class UsuniecieUbytku : Komponent
    {
        public int Cena { get; set; }
        public string NazwaWyswietlana { get; set; }
        private List<Komponent> _dzieci = new List<Komponent>();

        public UsuniecieUbytku(string nazwa)
        {
            NazwaWyswietlana = nazwa;
        }

        public void Dodaj(Komponent ob)
        {
            _dzieci.Add(ob);
            Cena += ob.Cena;
        }

        public void Usun(Komponent ob)
        {
            _dzieci.Remove(ob);
            Cena -= ob.Cena;
        }

        public void Wyswietl(int przesuniecie)
        {
            Console.WriteLine(new String('-', przesuniecie) + String.Format("{0} : Cena {1} zł", NazwaWyswietlana, Cena));

            foreach (Komponent ob in _dzieci)
            {
                ob.Wyswietl(przesuniecie + 2);
            }
        }
    }

Obie klasy implementują założenia zawarte w interfejsie . Właściwość Cena przechowuje koszt wykonania czynności w podzbiorze, we właściwości NazwaWyswietlana zawarta jest wyświetlana nazwa obiektu. Wartym uwagi elementem jest Lista _dzieci oraz metody na niej operujące, która przechowuje operacje składające się na tę czynność. Ostatnim składnikiem klasy jest funkcja Wyswietl() odpowiedzialna, za „wydrukowanie” zestawienia.

Kolejnymi składowymi są elementy „liście”, które są dla nas swego rodzaju operacjami atomowymi. Można by się zastanowić, czy nie utworzyć jednej klasy Lisc i poprzez properties NazwaWyswietlana definiować jej rodzaj, jednak uznałem, że przecież klasa Przeglad mogłaby posiadać dodatkowe funkcjonalności, których nie musimy definiować na potrzeby przykładu. Ponadto jest to dobry moment na ukazanie, że obiekty typu „Liść” nie muszą być jednego typu, a muszą być spójne pod względem określonego zachowania, po którym mamy korzyść z ich zgrupowania:

class Przeglad : Komponent
    {
        public int Cena { get; set; }
        public string NazwaWyswietlana { get; set; }

        public Przeglad(string nazwa)
        {
            NazwaWyswietlana = nazwa;
            Cena = 40;
        }

        public void Dodaj(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Usun(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Wyswietl(int przesuniecie)
        {
            Console.WriteLine(new String('-', przesuniecie) + String.Format("{0} : Cena {1} zł", NazwaWyswietlana, Cena));
        }
    }
    
    class Narzedzie1 : Komponent
    {
        public int Cena { get; set; }
        public string NazwaWyswietlana { get; set; }

        public Narzedzie1(string nazwa)
        {
            NazwaWyswietlana = nazwa;
            Cena = 30;
        }

        public void Dodaj(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Usun(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Wyswietl(int przesuniecie)
        {
            Console.WriteLine(new String('-', przesuniecie) + String.Format("{0} : Cena {1} zł", NazwaWyswietlana, Cena));
        }
    }
    
    class Plombowanie : Komponent
    {
        public int Cena { get; set; }
        public string NazwaWyswietlana { get; set; }

        public Plombowanie(string nazwa)
        {
            NazwaWyswietlana = nazwa;
            Cena = 50;
        }

        public void Dodaj(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Usun(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Wyswietl(int przesuniecie)
        {
            Console.WriteLine(new String('-', przesuniecie) + String.Format("{0} : Cena {1} zł", NazwaWyswietlana, Cena));
        }
    }

    class Szlifowanie : Komponent
    {
        public int Cena { get; set; }
        public string NazwaWyswietlana { get; set; }

        public Szlifowanie(string nazwa)
        {
            NazwaWyswietlana = nazwa;
            Cena = 30;
        }

        public void Dodaj(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Usun(Komponent ob)
        {
            Console.WriteLine("Niedostępne dla tego elementu.");
        }

        public void Wyswietl(int przesuniecie)
        {
            Console.WriteLine(new String('-', przesuniecie) + String.Format("{0} : Cena {1} zł", NazwaWyswietlana, Cena));
        }
    }

Jak widzimy w elementach „prostych” nie ma Listy przechowującej kolekcji obiektów, ponieważ pełnią one w naszym przykładzie rolę typów atomowych. Wynikiem tego są bezużyteczne metody Dodaj() oraz Usuń().

Pozostało nam sprawdzenie jak nasz projekt zachowuje się w praktyce:

    class Program
    {
        static void Main(string[] args)
        {
            Wizyta pacjent1 = new Wizyta("Pacjent Adam");
            pacjent1.Dodaj(new Przeglad("Przegląd"));

            UsuniecieUbytku usuniecieUbytku = new UsuniecieUbytku("Usunięcie ubytku");
            usuniecieUbytku.Dodaj(new Narzedzie1("Użycie narzędzia 1"));
            usuniecieUbytku.Dodaj(new Plombowanie("Plombowanie"));
            usuniecieUbytku.Dodaj(new Szlifowanie("Szlifowanie"));

            pacjent1.Dodaj(usuniecieUbytku);

            pacjent1.Wyswietl(2);
        }
    }

Argumentami konstruktorów są nazwy wyświetlane metod.

Wynik naszych działań, który ukazał się na konsoli:

--Pacjent Adam : Cena 150 zł
----Przegląd : Cena 40 zł
----Usunięcie ubytku : Cena 110
------Użycie narzędzia 1 : Cena
------Plombowanie : Cena 50 zł
------Szlifowanie : Cena 30 zł

Podsumowanie

Reasumując kompozyt to wzorzec reprezentujący strukturalną grupę wzorców projektowych. Dzięki jego implementacji ułatwiamy pracę na zbiorze podobnych pod jakimś względem obiektów, nie tracąc jednocześnie dostępu do pojedynczych składowych, które stanowią grupę. Daje to wrażenie, że odwołujemy się do pojedynczego obiektu, gdy faktycznie działamy na rodzinie obiektów.

Mam nadzieję, że po przeczytaniu tego artykułu implementacja omawianego wzorca, będzie tylko przyjemnością.

Wpis należy do serii postów o wzorcach projektowych.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *