Interfejs czy klasa abstrakcyjna?

Projektując system tworzymy abstrakcje. Często mamy wybór: klasa abstrakcyjną czy interfejs? Obie konstrukcje są obietnicą, kontraktem, który później musi zrealizować obiekt. Zatem kiedy lepiej użyć której? Dziś zajmiemy się rozróżnieniem między nimi.


Interfejs jest czystą abstrakcją. Nie ma żadnego wykonywalnego kodu. Można korzystać z wielu interfejsów w jednej klasie. Daje nam namiastkę wielodziedziczenia.

Klasa abstrakcyjna może zachowywać się jak interfejs tzn. mieć tylko abstrakcyjne metody.  Jednak możemy też w niej napisać działający kod. Dziedziczenie jest ograniczone. W większości języków dziedziczymy tylko po jednej klasie naraz.

Większość programistów zna definicje jednego i drugiego. Zapytajmy ich jednak kiedy korzystać z którego rozwiązania. Przeciętny programista zachowuje się wtedy jak student na ustnym egzaminie (bardzo mądrze patrzy i milczy).

Lista pytań

Kiedy więc korzystać z którego? Możemy zdecydować odpowiadając na pytania:

  1. Czy funkcjonalność jest ogólna i pasuje do wielu obiektów? Jeśli tak, użyj interfejsu.
  2. Czy obiekty będą należeć do jednej rodziny lub hierarchii? Jeśli tak, użyj klasy abstrakcyjnej.
  3. Czy obiekty mogą mieć wspólny kod? Jeśli tak, użyj klasy abstrakcyjnej.
  4. Czy obiekty mogą mieć wiele różnych funckjonalności? Jeśli tak, użyj interfejsów gdyż nie można dziedziczyć po wielu klasach.
  5. Czy potrzebne są wspólne niepubliczne metody lub atrybuty? Jeśli tak, użyj klasy abstrakcyjnej.
  6. Czy w przyszłości będziemy dodawać metody? Jeśli tak, lepiej użyj klasy abstrakcyjnej.
  7. Czy kontrakt jest ostateczny i nigdy się nie zmieni? Jeśli tak, być może lepszy jest interfejs.
  8. Jak nasze obiekty będą się miały do tworzonej abstrakcji? Jeśli abstrakcja odpowiada na pytanie czym jest obiekt (np. pies jest zwierzęciem, kot również) to użyjemy klasy abstrakcyjnej. Gdy jednak zadajemy sobie pytanie co robi obiekt (np. pies wydaje dźwięk, podobnie jak radio) to użyjemy interfejsu.

Projektując system najpierw zastanawiam się czy można w tym miejscu użyć interfejsu. Jest bardziej ogólny. Nie blokujemy sobie w przyszłości dziedziczenia po kolejnej klasie. Jeśli jednak okaże się, że musimy powtarzać ten sam kod w wielu klasach warto użyć klasy abstrakcyjnej.

Co ważne – wybór nie jest albo-albo. Możemy z powodzeniem zdefiniować interfejs a następnie stworzyć klasę abstrakcyjną, która go zrealizuje. Np. stworzymy interfejs WydajeDźwięk i użyjemy go w abstrakcyjnej klasie Zwierzę jak również w innej UrządzenieRTV.

Przykłady

W poprzednim artykule o logowaniu w PSR-3 spotkaliśmy się z interfejsem logowania (\Psr\Log\LoggerInterface) oraz z realizującą go klasą abstrakcyjną (Psr\Log\AbstractLogger). To dobry przykład na poprawną implementację programowania obiektowego.

Wzorzec „Metoda Szablonowa” jest bardzo dobrym przykładem wykorzystania klasy abstrakcyjnej.

Opisywany wcześniej wzorzec projektowy Filtr pokazuje siłę interfejsów. W tym wzorcu, jeśli chcielibyśmy filtrować obiekty z różnych rodzin wystarczyłoby aby wszystkie implementowały jeden interfejs. Ten interfejs dawałby nam funkcje, które używamy pobierając dane obiektu.

Standard PSR-7 (który jeszcze jest w fazie propozycji) definiuje interfejsy obiektów do zarządzania nagłówkami HTTP. Nie są to klasy abstrakcyjne bo nie ma potrzeby wymuszać żadnego kodu. Standaryzacja może w całości zadowolić się interfejsem

Na koniec

Obie te abstrakcje są podstawą programowania obiektowego. Dobrze jest poznać i zrozumieć je dogłębnie. Spotkamy się z nimi nie tylko na rozmowie kwalifikacyjnej. Projektując system dobra decyzja może nam dodać lub oszczędzić kłopotów w przyszłości.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *