Najkrótsza wskazówka#

Wybierz abstrakcyjną klasę, gdy chcesz modelować wspólny typ z dzielonym stanem, implementacją lub ograniczeniami dziedziczenia. Wybierz interfejs, gdy najważniejszy jest kontrakt zachowania, który mogą spełniać także klasy z różnych hierarchii. Oba mechanizmy mogą współistnieć: klasa może dziedziczyć po klasie abstrakcyjnej i realizować jeden lub więcej interfejsów.

Klasy Karta i Przelew dziedziczą po abstrakcyjnej klasie MetodaPlatnosci oraz realizują interfejs Autoryzowalna.
Abstrakcyjna klasa grupuje wspólną bazę, a interfejs opisuje kontrakt zachowania.

MetodaPlatnosci grupuje wspólną tożsamość, dane i operację, a Autoryzowalna opisuje kontrakt, który obie klasy spełniają. To przykład modelowy, nie recepta projektowa: jeśli karta i przelew nie mają wspólnego znaczenia lub zmieniają się niezależnie, dzielenie ich przez bazową klasę może być nieuzasadnione.

Co wnosi klasa abstrakcyjna#

Klasa abstrakcyjna jest klasyfikatorem, którego nie instancjonuje się bezpośrednio w danym modelu. Może definiować atrybuty, operacje, wspólne zachowanie, niezmienniki i punkty rozszerzeń dla podklas. W diagramie nazwa klasy abstrakcyjnej bywa zapisywana kursywą albo oznaczona właściwością {abstract}; szczegóły prezentacji zależą od narzędzia.

Używaj jej, gdy podtypy rzeczywiście są szczególnymi rodzajami wspólnego konceptu i dzielą część jego stanu lub implementacji. Abstrakcyjna klasa może narzucać wspólny kontrakt podtypom, lecz sama hierarchia nie gwarantuje, że podtypy są wymienne — ich zachowanie nadal powinno spełniać oczekiwania wobec typu ogólnego.

Co wnosi interfejs#

Interfejs specyfikuje zachowanie oferowane przez realizatorów. Podkreśla możliwości lub zobowiązania, nie wspólną reprezentację stanu. W UML interfejs może mieć operacje i inne właściwości dopuszczone przez specyfikację; konkretna zawartość zależy od modelu. Klasa realizuje interfejs relacją realizacji, zapisywaną przerywaną linią z pustym trójkątem skierowanym do interfejsu.

Jedna klasa może realizować kilka niezależnych kontraktów, a podobny interfejs mogą realizować klasy niepowiązane generalizacją. To pozwala klientom zależeć od wymaganej możliwości, na przykład Autoryzowalna, zamiast od konkretnego typu płatności.

Różnice między UML a językiem programowania#

Diagram UML opisuje model niezależny od konkretnej składni języka, chyba że jawnie określono platformę. Języki różnią się liczbą dozwolonych klas bazowych, wielodziedziczeniem, implementacją domyślną, widocznością i ograniczeniami interfejsów. Nie zakładaj, że ograniczenia jednego języka są regułami UML.

Jeśli diagram ma być odwzorowany w kodzie, dodaj założenie o języku lub profilu. Weryfikuj, czy mapowanie zachowuje zamierzone relacje, szczególnie gdy UML pokazuje kilka nadklas albo właściwości interfejsu, których język docelowy nie obsługuje bez dodatkowych konstrukcji.

Kryteria wyboru#

  • Wspólna tożsamość i stan: przemawiają za abstrakcyjną klasą, jeśli podtypy reprezentują ten sam rodzaj pojęcia.
  • Wspólne zachowanie lub szkielet algorytmu: może uzasadniać bazową klasę, o ile wspólna implementacja jest rzeczywistą odpowiedzialnością.
  • Niezależna możliwość lub kontrakt: przemawia za interfejsem, szczególnie gdy realizatorzy są z różnych hierarchii.
  • Wiele niezależnych ról: interfejsy mogą modelować je osobno; nie buduj głębokiej hierarchii klas tylko po to, by współdzielić etykiety.
  • Brak wspólnego znaczenia domenowego: nie twórz klasy abstrakcyjnej wyłącznie po to, by uniknąć kilku podobnych pól.
  • Ograniczenia platformy: sprawdź, czy język i narzędzie docelowe obsługują wybrane odwzorowanie.

Oba mechanizmy razem#

Klasa może dziedziczyć wspólną implementację z abstrakcyjnego typu i jednocześnie deklarować niezależne kontrakty. Przykładowo różne metody płatności mogą współdzielić identyfikator i etap rozliczenia, ale tylko część z nich obsługuje autoryzację wstępną.

Uważaj na nadmiar hierarchii. Jeśli zmiana jednego typu bazowego wymusza niepowiązane zmiany w wielu klasach, interfejs lub kompozycja może lepiej odzwierciedlać odpowiedzialności. Decyzja powinna wynikać z semantyki oraz stabilności kontraktów, nie z mechanicznej zasady „zawsze preferuj X”.

Typowe błędy#

  • Interfejs uznany za klasę bez implementacji. Kluczową rolą interfejsu jest jawny kontrakt, nie tylko brak kodu.
  • Abstrakcyjna klasa dla przypadkowego współdzielenia pól. Wspólna struktura powinna mieć uzasadnienie w modelu.
  • Interfejs traktowany jako wspólne przechowywanie stanu. Standardowa rola interfejsu koncentruje się na specyfikacji zachowania.
  • Realizacja pomylona z generalizacją. Implementowanie kontraktu to inna relacja niż bycie podtypem klasy.
  • Założenia języka przeniesione na UML. UML i platforma implementacyjna mają różne reguły.
  • Przesadna abstrakcja. Nie twórz wspólnej bazy, jeśli nie upraszcza ona pojęć ani zachowania.
  • Hierarchia uznana za dowód zamienności. Podtypy muszą nadal spełniać oczekiwania kontraktu bazowego.

Podsumowanie#

Abstrakcyjna klasa grupuje wspólny typ, stan i potencjalne zachowanie; interfejs definiuje kontrakt, który mogą realizować różne klasy. Wybór zależy od semantyki, wspólnej odpowiedzialności i potrzeb klienta. Oba mechanizmy można łączyć, jeśli model pokazuje odrębne role.