Przejdź do głównej zawartości

[OOP] Ma czym polega wzorzec projektowy Kompozyt?


Wzorzec projektowy Kompozyt pozwala nam na używanie w jednolity sposób zarówno pojedynczych obiektów, jak i całych ich grup.

Kompozyt należy do strukturalnych wzorców projektowych. Więcej o rodzajach wzorców można poczytać tutaj [under construction].

Rozważmy program, którego zadaniem jest rysowanie na ekranie obiektów. Możemy zdecydować, czy narysuje się kwadrat, kółko, albo też obrazek w dowolnym formacie. Chcemy też by użytkownik miał możliwość narysowania całej grupy obiektów - utworzonej przez niego kompozycji kwadratów, kółek itp.

Zacznijmy pisać program. Chcemy, żeby każdy obiekt, który może zostać narysowany na ekranie, implementował interfejs IDrawable. Utwórzmy ten interfejs oraz kilka klas go implementujących.

    interface IDrawable
    {
        void Draw();
    }

    class Circle : IDrawable
    {
        public void Draw()
        {
            Console.WriteLine("Rysuję kółko");
        }
    }

    class Square : IDrawable
    {
        public void Draw()
        {
            Console.WriteLine("Rysuję kwadrat");
        }
    }

    class Picture : IDrawable
    {
        public void Draw()
        {
            Console.WriteLine("Rysuję obrazek");
        }
    }

Świetnie. A zatem możemy napisać coś takiego:

            var drawables = new List<IDrawable>
            {
                new Circle(),
                new Square(),
                new Square(),
                new Picture()
            };

            foreach(var drawable in drawables)
            {
                drawable.Draw();
            }

Na razie każdy z obiektów jest traktowany indywidualnie. Chcemy jednak doprowadzić program do postaci, w której użytkownik będzie mógł rysować nie tylko pojedyncze obiekty, ale i całe ich kompozycje. Możemy to sobie wyobrazić na przykład tak:

            var drawables = new List<IDrawable>
            {
                new Circle(),
                new IDrawable[] {new Circle(), new Square(), new Circle() },
                new Square(),
                new Picture()
            };

Taki kod oczywiście nie skompiluje się - drugi element w liście nie należy do typu IDrawable. Musimy utworzyć klasę, która reprezentować będzie kompozyt obiektów do rysowania.

Klasa ta będzie miała dwie kluczowe cechy:

  • będzie zawierać w sobie kolekcję obiektów typu IDrawable
  • sama będzie implementować interfejs IDrawable - byśmy mogli traktować ją tak samo, jak pojedyncze obiekty, i na przykład użyć w pętli foreach
Zobaczmy:

    class CompositeDrawing : IDrawable
    {
        private readonly List<IDrawable> _components = new List<IDrawable>();

        public void Add(IDrawable drawable)
        {
            _components.Add(drawable);
        }

        public void AddRange(params IDrawable[] drawables)
        {
            _components.AddRange(drawables);
        }

        public void Remove(IDrawable drawable)
        {
            _components.Remove(drawable);
        }

        public void Draw()
        {
            foreach(var component in _components)
            {
                component.Draw();
            }
        }
    }

Klasa ta posiada kolekcję obiektów IDrawable i obsługujące ją metody, zaś sama implementuje metodę Draw iterując po obiektach z tej kolekcji. Dzięki temu możemy teraz traktować w jednolity sposób zarówno pojedyncze obiekty IDrawable, jak i całe ich kompozycje:

            var compositeDrawing = new CompositeDrawing();
            compositeDrawing.Add(new Square());
            compositeDrawing.Add(new Circle());
            compositeDrawing.Add(new Square());

            var drawables = new List<IDrawable>
            {
                new Circle(),
                compositeDrawing,
                new Square(),
                new Picture()
            };
            
            foreach (var drawable in drawables)
            {
                drawable.Draw();
            }

Nic nie stoi na przeszkodzie, by kompozyt zawierał w sobie inne kompozyty:


            var compositeDrawing = new CompositeDrawing();
            compositeDrawing.Add(new Square());
            compositeDrawing.Add(new Circle());

            var innerCompositeDrawing = new CompositeDrawing();
            innerCompositeDrawing.Add(new Picture());
            innerCompositeDrawing.Add(new Square());

            compositeDrawing.Add(innerCompositeDrawing);
            
            var drawables = new List<IDrawable>
            {
                new Circle(),
                compositeDrawing,
                new Square(),
                new Picture()
            };
            
            foreach (var drawable in drawables)
            {
                drawable.Draw();
            }

Dzięki wzorcowi kompozyt utworzyliśmy jednolity interfejs do używania zarówno pojedynczych obiektów, jak i całych ich kompozycji.

Wzorzec kompozyt bywa bardzo przydatny. Inne przykładowe zastosowania to:

  • w programie graficznym możemy zaznaczyć pojedynczy obiekt i go powiększyć/pomniejszyć. Chcemy mieć taki sam interfejs i zachowanie dla zaznaczenia całej grupy obiektów.
  • w programie zawierającym dane pracowników możemy wygenerować PITa tą samą komendą zarówno dla pojedynczej osoby, jak i dla grupy osób
W poście tym korzystałam z:
  • https://www.udemy.com/design-patterns-csharp-dotnet
  • http://www.rafaljankowski.pl/wzorce-projektowe/wzorce-10-kompozyt/
  • https://stackoverflow.com/questions/5334353/when-should-i-use-composite-design-pattern

Komentarze