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.
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.