Przejdź do głównej zawartości

[C#] Jakie są różnice między klasą abstrakcyjną a interfejsem?


Zastosowanie:
Klasa abstrakcyjna reprezentuje pewien bazowy koncept - zbyt ogólny, by można było mówić o utworzeniu instancji, jednak na tyle szczegółowy, by można było w nim zawrzeć pewnie bazowe zachowania.

Przykładem klasy abstrakcyjnej mogłaby być klasa BoardGame, reprezentująca grę planszową. Nie możemy w nią zagrać - gra planszowa jest zbyt ogólnym konceptem. Może być jednak dobrą podstawą do dziedziczenia dla konkretnych gier: SettlersOfCatan, GameOfThrones, czy TwilightImperium. Każda gra planszowa, niezależnie od rodzaju, posiada pewien ustalony schemat rozgrywki. Najpierw rozkładamy planszę i pionki, potem toczy się właściwa gra, a na koniec dokonujemy jakiegoś podsumowania punktów i wyboru zwycięzcy. Skoro jest to logika wspólna dla każdej gry planszowej, powinna zawrzeć się w klasie abstrakcyjnej. Dopiero poszczególne klasy dziedziczące bedą uszczegóławiać, jak należy rozłożyć planszę, jak prowadzić rozgrywkę i jak wybierać zwycięzcę.


    abstract class BoardGame
    {
        public void Play()
        {
            PrepareGame();
            Run();
            CalculatePoints();
        }

        protected abstract void PrepareGame();
        protected abstract void Run();
        protected abstract void CalculatePoints();
    }

    class SettlersOfCatan : BoardGame
    {
        protected override void PrepareGame()
        {
            //ułóż planszę z sześciokątów i postaw pierwsze wioski
        }

        protected override void Run()
        {
            //handluj z innymi graczami by się rozbudować
        }

        protected override void CalculatePoints()
        {
            //podlicz punkty za wioski, miasta i karty specjalne
        }
    }

    class GameOfThrones : BoardGame
    {
        protected override void PrepareGame()
        {
            //rozłóż jednostki każdego rodu
        }

        protected override void Run()
        {
            //napadaj wrogów i wbijaj sojusznikom nóż w plecy
        }

        protected override void CalculatePoints()
        {
            //wygraj, jeśli twoje zamki i twierdze są warte odpowiednią ilość punktów
        }
    }

Dzięki zastosowaniu takiego dziedziczenia w klasach dziedziczących nie powielamy wspólnej logiki - a więc specyficznej kolejności wykonywania czynności w grze planszowej. Klasy te zajmują się tylko tym, co faktycznie jest specyficzne tylko dla nich - a nie dla planszowek ogółem. Nawiasem mówiąc - zastosowaliśmy tutaj wzorzec projektowy Template Method o którym można poczytać tutaj.

Interfejs jest rodzajem zobowiązania lub kontraktu. Jeśli jakaś klasa implementuje interfejs, to deklaruje tym samym, że potrafi dokonać jakiejś czynności. Interfejs w żaden sposób nie zobowiązuje jej do trzymania się konkretnej implementacji - wymaga tylko, by klasa posiadała metodę o danej sygnaturze.

Przykładem mógłby być interfejs IFlyable, wspólny dla wszystkiego, co potrafi latać. Interfejs ten mógłby być realizowany przez tak niezwiązane ze sobą obiekty jak samolot, ptak czy liść na wietrze. Mimo, że są od siebie zupełnie różne, od każdego z nich możemy oczekiwać umiejętności latania.

Innym przykładem może być wbudowany w .NET interfejs IEnumerable. Wyobraźmy sobie, że chcemy użyć klasy wypisującej słowa ze zbioru na ekran. Klasy tej w żaden sposób nie obchodzi jak zaimplementowana jest zbiór słów - czy to lista, słownik, mapa czy tablica. Klasa ta chce mieć tylko możliwość przejść po tej kolekcji za pomocą pętli foreach, i dla każdego iterowanego elementu zawołać metodę wypisującą do konsoli.


    public interface IFlyable
    {
        void Fly();
    }

    public class Bird : IFlyable
    {
        public void Fly()
        {
            //machaj skrzydłami
        }
    }

    public class Plane : IFlyable
    {
        public void Fly()
        {
            //odpal silniki 
        }
    }

    public class LeafInTheWind : IFlyable
    {
        public void Fly()
        {
            //daj się ponieść
        }
    }

W skrócie: klasa abstrakcyjna mówi czym coś jest. Interfejs mówi co coś robi.
Jeśli myślisz o kilku klasach i masz w głowie jeden rzeczownik na ich określenie, to prawdopodobnie chcesz użyć klasy abstrakcyjnej. Jeśli zaś do głowy przychodzi ci czasownik opisujący ich czynności - lepiej zastosuj interfejs.

W kodzie:
  • Klasa abstrakcyjna może posiadać konstruktor, interfejs nie może.
  • Klasa abstrakcyjna może mieć nieabstrakcyjne metody - czyli metody z ciałem. Interfejs nie może.
  • Klasa abstrakcyjna może posiadać pola. Interfejs nie może.
  • Możemy dziedziczyć tylko z jednej klasy (abstrakcyjnej czy zwykłej). Możemy implementować wiele interfejsów.
  • W klasie abstrakcyjnej możemy uzywać modyfikatorów dostepu - private, public, protected itp. W interfejsie nie możemy - wszytskie metody są domyślnie public. Dopisanie słowa "public" wprost powoduje błąd kompilacji.

Komentarze

Prześlij komentarz