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

W poprzednich 2 artykułach na temat dziedziczenia oraz polimorfizmu starałem Ci się przedstawić podstawy programowania obiektowego, mówiłem Ci o tym, że temat polimorfizmu zostanie jeszcze rozwinięty, ponieważ w abstrakcji również mamy do czynienia z polimorfizmem. Także, tym razem poruszę temat abstrakcji, jest to kolejny temat, niezwykle ważny w programowaniu obiektowym, którego znajomość pomoże Ci znaleźć pracę jako młodszy programista C#/.NET. W C# do zastosowania abstrakcji stosujemy klasy abstrakcyjne oraz interfejsy. W tym artykule przybliżę Ci właśnie te zagadnienia.

Co to jest abstrakcja w programowaniu obiektowym?


Co to jest abstrakcja i do czego potrzebujemy abstrakcji?


Abstrakcję najlepiej zrozumieć na przykładzie. Załóżmy, że masz odtwarzacz DVD. Wszyscy wiedzą, jak wygląda taki odtwarzacz DVD, to znaczy jak taki odtwarzacz wygląda z zewnątrz. Jednak każdy taki odtwarzacz ma w środku jakąś skomplikowaną logikę, ale na zewnątrz ma tylko kilka przycisków, które są udostępnianie użytkownikom. Zazwyczaj są to przyciski start, stop, power itp., dzięki którym użytkownicy komunikują się z tym odtwarzaczem. Konsumentów takiego odtwarzacza zazwyczaj wcale nie interesuje to, co się dzieje w środku. Ich interesuje tylko to, jak włączyć, wyłączyć, zatrzymać taki odtwarzacz, no może jeszcze kilka innych funkcji :) I to jest właśnie abstrakcja. To znaczy, nie interesują nas szczegóły danej implementacji, interesuje nas tylko to, co jest nam udostępniane na zewnątrz. Mamy jakiś kontrakt i wiemy, że wszystkie implementacje muszą zawierać dane funkcje, bez informowania konsumentów o ich szczegółach.


Przykład w programowaniu


Jeżeli w programowaniu współpracujemy na przykład z bazą danych to mamy jakiś kontrakt, który mówi nam o tym, że każda klasa powinna spełniać dane kryteria. To znaczy implementować funkcję na przykład dodawania danych do bazy danych, usuwania, edytowania, czytania danych itp., nie interesuje nas konkretna implementacja. Załóżmy, że nasza klasa implementuje ten kontrakt i współpracuje z bazą danych MSSQL. Jeżeli abstrakcja została dobrze zaprojektowana, to nie jesteśmy zależni tylko od tej jednej bazy danych, w każdej chwili możemy zamienić naszą implementacją na współpracę z inną bazą danych, na przykład w PostgreSQL, Firebird itd. i o to właśnie chodzi, nie chcemy uzależniać się od danego silnika, danej implementacji. Dzięki temu nasze aplikacje mają luźne powiązania i są bardziej elastyczne i rozszerzalne. Co więcej, wprowadzenie abstrakcji sprawia, że nasz kod może być łatwiej testowalny. Jak już wspomniałem, w programowaniu możemy zastosować abstrakcję poprzez klasy abstrakcyjne oraz interfejsy. Mówimy o tym, że każdy obiekt wykonuje jakieś zadania bez ujawniania implementacji szczegółów.


Czym jest klasa abstrakcyjna?


Dzięki klasom abstrakcyjnym możemy wprowadzić do naszej klasy pewien poziom abstrakcji. Klasa abstrakcyjna jest to klasa, która powstała dlatego by być klasą bazową dla innych klas. Klasy takie, jak również metody muszą zostać oznaczone słowem kluczowym abstract. Nie można utworzyć obiektu klasy abstrakcyjnej. Klasa ta może zawierać same deklaracje metod, a także definicje metod, to znaczy implementacje.


Przykład klasy abstrakcyjnej


W ostatnim artykule na temat polimorfizmu. Pokazałem Ci pewną hierarchię dziedziczenia, gdzie klasą bazową była metoda Shape. To znaczy:

public class Shape
{
    public virtual void Draw()
    {
        Console.WriteLine("Draw Base");
    }
}

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 Shape(),
            new Square(),
            new Triangle()
        };

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

//OUTPUT
//Draw Base
//Draw Square
//Draw Triangle

W takiej hierarchii jest jednak kilka mankamentów. To znaczy mamy tutaj klasę Shape (kształt) i możemy stworzyć instancję takiej klasy, ale czy obiekt tej klasy ma jakiś sens? Klasa ta również ma metodę Draw, ale czy można narysować kształt? Wiemy jak narysować kwadrat, trójkąt, ale jak narysować kształt? No właśnie, dlatego ta klasa powinna być abstrakcyjna, to znaczy zostać oznaczona poprzez abstract. Zróbmy zatem kilka poprawek w tym kodzie.

public abstract class Shape
{
    public abstract 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 Shape(), //ERROR! - Cannot create an instance of the abstract class or interface 'Shape'
            new Square(),
            new Triangle()
        };

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

Dzięki słowu kluczowemu abstract klasa Shape stała się klasą abstrakcyjną, nie możemy utworzyć instancji tej klasy i to między innymi również nam chodziło. Metoda Draw została również oznaczona poprzez słowo kluczowe jako metoda abstrakcyjna, została zdefiniowana sama deklaracja tej metody, a co za tym idzie, mamy pewność, że każda klasa pochodna musi zaimplementować własną implementację tej metody. Metody abstrakcyjne nadpisujemy w klasach pochodnych słowem kluczowym override. Także metody abstrakcyjne są z natury wirtualne i zapewniają zachowanie polimorficzne, o tym właśnie, był poprzedni artykuł. Musisz pamiętać, że metody abstrakcyjne mogą zostać utworzone tylko w klasie abstrakcyjnej i zostać napisane w klasie pochodnej poprzez użycie override. Ponadto w klasie abstrakcyjnej mogą również zostać zaimplementowane metody, które nie są abstrakcyjne, wówczas muszą zawierać implementacje. Także takie klasy możemy stosować wtedy, gdy chcemy wprowadzić jakieś wspólne zachowanie, ale chcemy mieć pewność, że każda klasa pochodna zdefiniuje sobie taką metodę.


Czym jest interfejs?


Interfejsy w odróżnieniu od klas abstrakcyjnych umożliwiają całkowite oddzielenie szczegółów implementacji od udostępnianych elementów. Przed C# 8, interfejsy mogły zawierać same deklaracje metod, nie mogły zawierać ich definicji - ta definicja musiała znaleźć się w klasie implementującej interfejs. Od wersji C# 8, również interfejsy mogą zawierać domyślną definicję metod. W odróżnieniu od klas tutaj używamy słowa kluczowego interface. Konwencja jest taka, że do nazwy dodajemy przedrostek I, to znaczy w naszym przypadku może to być IShape. Często dodaje się również do nazwy interfejsu przyrostek able, to znaczy, że coś jest zdolne do czegoś, na przykład IMoveable.

public interface IShape
{
    void Draw();
}


Czym się różni klasa abstrakcyjna od interfejsu?


Jest to bardzo popularne, często zadawane pytanie na rozmowach kwalifikacyjnych na stanowisko młodszego programisty .NET (więcej). Najważniejsze różnice między klasą abstrakcyjną, a interfejsem to:
  • W deklaracji interfejsu używamy słowa kluczowego interface, a w klasie abstrakcyjnej abstract class.
  • Klasa może implementować dowolną ilość interfejsów, a dziedziczyć może tylko po 1 klasie abstrakcyjnej.
  • Składowe interfejsu nie mogą zostać oznaczone atrybutami dostępu (zawsze niejawnie jest to public), a w klasie abstrakcyjnej mogą zostać oznaczone atrybuty dostępu.
  • W interfejsach 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.


PODSUMOWANIE:


W tym artykule poznałeś kolejne niezwykle ważne zagadnienie z punktu widzenia programowania obiektowe. Pokazałem Ci, czym jest abstrakcja, jakie są zalety stosowania abstrakcji, jak możesz zaimplementować abstrakcję w C#. Wiesz już, czym są interfejsy, klasy abstrakcyjne, metody abstrakcyjne oraz wiesz, jakie są różnice między nimi. W kolejnym tygodniu pokaże Ci 4 filar programowania obiektowego, będzie to hermetyzacja danych. Do zobaczenia za tydzień.

Poprzedni artykuł - Co To Jest Polimorfizm w Programowaniu Obiektowym?.
Następny artykuł - Co To Jest Hermetyzacja w Programowaniu Obiektowym?.
Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
Programista C#/.NET. Specjalizuje się w ASP.NET Core, ASP.NET MVC, ASP.NET Web API, Blazor, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Komentarze (2)
Patryk
PATRYK, 4 kwietnia 2023 14:04
W jednym fragmencie artykułu w przedostatnim słowie jest błąd (zamiast "deklaracje" powinno być "definicje"): (...) Przed C# 8, interfejsy mogły zawierać same deklaracje metod, nie mogły zawierać ich definicji - ta definicja musiała znaleźć się w klasie implementującej interfejs. Od wersji C# 8, również interfejsy mogą zawierać domyślną >>deklarację<< metod. Pozdrawiam
Kazimierz Szpin
KAZIMIERZ SZPIN, 4 kwietnia 2023 17:56
Cześć @PATRYK. Dzięki za informacje - masz rację :)
Dodaj komentarz

Wyszukiwarka

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