Blog Dla Młodszych Programistów C#/.NET

Dzisiaj nadszedł czas na kolejny bardzo ważny temat w programowaniu obiektowym, który musi zrozumieć każda osoba, która chce zostać młodszym programistą .NET. Z artykułu dowiesz się, czym jest polimorfizm w programowaniu obiektowym, oczywiście postaram Ci się to wytłumaczyć na przykładach w C#. Polimorfizm jest bardzo związany z dziedziczeniem, także, jeżeli jeszcze nie wiesz, czym jest dziedziczenie, to najlepiej wróć do poprzedniego artykułu na blogu, to znaczy: Dziedziczenie w programowaniu obiektowym.

Co to jest polimorfizm w programowaniu obiektowym?


Czym jest polimorfizm?


Polimorfizm (wielopostaciowość) to jedno z podstawowych założeń dotyczących programowania obiektowego. Wielopostaciowość, to znaczy zapisywanie jednej funkcji (metody) pod różnymi postaciami. To znaczy, dzięki niemu możemy obsługiwać różne typy w różny sposób, bez znajomości tych typów. Polimorfizm można podzielić na 2 grupy, polimorfizm statyczny, a także polimorfizm dynamiczny.


Polimorfizm statyczny


W C# to przeciążanie funkcji oraz operatorów. Chodzi o to, że w 1 klasie może istnieć dużo metod o takiej samej nazwie, lecz różniących się tylko parametrami. Polimorfizm statyczny zachodzi podczas kompilacji programu, to która metoda zostanie wybrana, zależy od ilości, a także typu przekazanych do metody argumentów. Warto wspomnieć, że nie można przeciążyć metody, która różni się tylko zwracanym typem.


Polimorfizm dynamiczny


w C# jest powiązany z funkcjami wirtualnymi i abstrakcyjnymi, jest to tak zwane przesłanianie (nadpisywanie) funkcji. Idea polimorfizmu (dynamicznego) kręci się wokół dziedziczenia klas. Aby przesłonić metodę w klasie bazowej, należy użyć słowa kluczowego virtual, a w klasie podrzędnej override. Zazwyczaj, jeżeli ktoś mówi o polimorfizmie, to ma na myśli właśnie polimorfizm dynamiczny. Przykłady w tym artykule, również będą dotyczyły polimorfizmu dynamicznego.


Zadanie w C#


W ostatnim artykule opisałem Ci, na czym polega dziedziczenie. Polimorfizm (dynamiczny) jest ściśle powiązany właśnie z dziedziczeniem. Spójrz na poniższy przykład, na którym została przedstawiona hierarchia dziedziczenia kilku klas.

public class Shape
{
        
}

public class Square : Shape
{

}

public class Triangle : Shape
{

}

public class Program
{
    static void Main(string[] args)
    {
        var shapes = new List<Shape>()
        {
            new Square(),
            new Triangle()
        };
    }
}

Mamy tutaj klasę Shape, po której dziedziczą klasy Square oraz Triangle. Załóżmy, że mamy za zadanie napisać program, który będzie rysował różne kształty figur, które będą w podanej liście shapes.


Złe rozwiązanie (bez polimorfizmu)


Na początek zacznijmy od złego rozwiązania, to znaczy jak mógłby wyglądać taki program bez zastosowania polimorfizmu.

public class Shape
{
        
}

public class Square : Shape
{
    public void DrawSquare()
    {
        Console.WriteLine("Draw Square");
    }
}

public class Triangle : Shape
{
    public void DrawTriangle()
    {
        Console.WriteLine("Draw Triangle");
    }
}


public class Program
{
    static void Main(string[] args)
    {
        var shapes = new List<Shape>()
        {
            new Square(),
            new Triangle()
        };

        foreach (var shape in shapes)
        {
            if (shape is Square)
                (shape as Square).DrawSquare();
            else if (shape is Triangle)
                (shape as Triangle).DrawTriangle();
        }
    }
}

//OUTPUT
//Draw Square
//Draw Triangle

Co prawda wynik jest prawidłowy, robi to, co potrzebujemy, ale takie rozwiązanie jest bardzo problematyczne, wprowadza duży bałagan w kodzie. Jeżeli w przyszłości będzie trzeba dodać nową figurę, którą również będziemy chcieli rysować, to metoda Main będzie wtedy musiała zostać zmodyfikowana i potrzebne będą kolejne warunki sprawdzające jakiego typu jest dany obiekt.


Prawidłowe rozwiązanie z zastosowaniem polimorfizmu


Jak w takim razie powinno się prawidłowo zaprojektować te klasy? Należy skorzystać z polimorfizmu.

public class Shape
{
    public virtual void Draw()
    {
    }
}

public class Square : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Draw Square");
    }
}

public class Triangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("Draw Triangle");
    }
}


public class Program
{
    static void Main(string[] args)
    {
        var shapes = new List<Shape>()
        {
            new Square(),
            new Triangle()
        };

        foreach (var shape in shapes)
        {
             shape.Draw();
        }
    }
}

//OUTPUT
//Draw Square
//Draw Triangle

W powyższym przykładzie zastosowaliśmy polimorfizm. Jak widzisz, tym razem w metodzie Main nie musimy już sprawdzać typu obiektu. Jeżeli dojdzie jakaś nowa klasa do naszej aplikacji, to również metoda Main nie będzie musiała zostać zmieniona. A teraz spróbuje Ci wyjaśnić, to co zmieniliśmy. Po pierwsze dodaliśmy do klasy Shape nową metodę Draw, jak widzisz, jest to metoda wirtualna, to znaczy oznaczona słowem kluczowym virtual. Gdy oznaczamy metodę jako virtual, to umożliwiamy nadpisanie tej metody klasie podrzędnej (dziedziczącej). Aby w klasie podrzędnej nadpisać taką metodę, musimy użyć słowa kluczowego override, dzięki któremu właśnie nadpisujemy metodę o tej samej definicji własną implementacją. Dzięki temu klasy Square oraz Triangle mają własną nadpisaną implementację metody Draw. Dlatego w metodzie Main, iterując po liście Shapes, za każdym razem, bez sprawdzanie typu, wywoływana jest oczekiwana implementacja. Polimorfizm jest jeszcze bardzo powiązany z klasami i metodami abstrakcyjnymi, dlatego w kolejnym artykule poruszę właśnie ten temat.


PODSUMOWANIE:


Polimorfizm jest to bardzo ważny filar programowania obiektowego, bez którego znajomości, ciężko Ci będzie znaleźć pracę jako młodszy programista C#/.NET. Pokazałem Ci, jak dzieli się polimorfizm oraz jakie ma zastosowanie. Najważniejsze, żebyś zapamiętał słowa kluczowe virtual (do oznaczenia metod, które mogą zostać przesłonięte w klasie pochodnej) oraz override (do oznaczenia metod przesłoniętych). Jeżeli masz jakieś pytania co do tego artykułu, to napisz do mnie maila, lub zostaw komentarz. W kolejnym tygodniu poruszymy temat abstrakcji w programowaniu obiektowym, rozwinę również wtedy temat polimorfizmu.

Poprzedni artykuł - Co To Jest Dziedziczenie w Programowaniu Obiektowym?.
Następny artykuł - Co To Jest Abstrakcja w Programowaniu Obiektowym?.
Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
CTO & Founder - FindSolution.pl
Programista C#/.NET. Specjalizuje się w Blazor, ASP.NET Core, ASP.NET MVC, ASP.NET Web API, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Komentarze (11)
Sebolek66
SEBOLEK66, poniedziałek, 18 maja 2020 20:48
Jak dla mnie bajka. Świetny blog.
Kazimierz Szpin
KAZIMIERZ SZPIN, niedziela, 7 czerwca 2020 09:45
@SEBOLEK, dzięki :)
Michał
MICHAŁ, niedziela, 18 października 2020 21:32
Bardzo pożyteczne dzięki
Kazimierz Szpin
KAZIMIERZ SZPIN, poniedziałek, 19 października 2020 22:01
Cześć @MICHAŁ. Cieszę się, że artykuł jest dla Ciebie wartościowy. Dzięki za Twoją opinię :)
Lukasz
LUKASZ, piątek, 6 listopada 2020 15:25
Szukam odpowiedzi na pytanie, czym różni się polimorfizm do stosowania interfejsów i abstrakcji...
Programista2300brutto
PROGRAMISTA2300BRUTTO, poniedziałek, 4 stycznia 2021 02:12
Nie zrozumiałem
Kazimierz Szpin
KAZIMIERZ SZPIN, poniedziałek, 4 stycznia 2021 19:23
Cześć @LUKASZ. Jest kilka różnic :) Po pierwsze klasa może implementować dowolną ilość interfejsów, ale dziedziczyć może tylko po 1 klasie abstrakcyjnej. Składowe interfejsu nie mogą zostać oznaczone atrybutami dostępu (domyślnie ustawiony jest public), a w klasie abstrakcyjnej mogą zostać oznaczone atrybuty dostępu. W interfejsie nie można deklarować pól, a w klasie abstrakcyjnej można to robić. Interfejs nie może mieć konstruktora, a w klasie abstrakcyjnej może być implementacja konstruktora domyślnego. Kiedyś metody interfejsu nie mogły mieć ciała metody, tylko same deklaracje, ale zostało to zmienione w C# w wersji 8. Od tej wersji metody interfejsu również mogą mieć domyślną implementację.
Kazimierz Szpin
KAZIMIERZ SZPIN, poniedziałek, 4 stycznia 2021 19:24
Cześć @PROGRAMISTA2300BRUTTO. Ze zrozumiem czego konkretnie masz problem? :)
Nieźle
NIEŹLE, piątek, 22 stycznia 2021 21:13
Cześć, super wytłumaczenie! Teoria teorią, ale anty-przykład dał mi dużo większe zrozumienie przydatności tematu niż jakikolwiek inne podejście. Propsy PS. Jestem tu pierwszy raz i nie mogę nie zauważyć, że strona jest bardzo estetyczna.
Kazimierz Szpin
KAZIMIERZ SZPIN, poniedziałek, 25 stycznia 2021 07:14
@NIEŹLE, dzięki. Cieszę się, że mogłem pomóc. Zapraszam do pozostałych artykułów o podstawach programowania obiektowego :)
Miłosz
MIŁOSZ, piątek, 16 lipca 2021 14:26
Czy każda klasa pierwotna jest klasą abstrakcyjną?
Dodaj komentarz

Wyszukiwarka

© Copyright 2024 modestprogrammer.pl. Wszelkie prawa zastrzeżone. Regulamin. Polityka prywatności. Design by Kazimierz Szpin