Pracownik firmy jest sprzedawcą lub inżynierem. Siedziba firmy jest jej centralą lub oddziałem. Kontrahent jest klientem lub dostawcą. To tylko kilka przykładów często spotykanej sytuacji, gdy dany obiekt pełni wobec innych obiektów pewne role. Zobaczmy, w jaki sposób można modelować role na diagramach klas.
Modelowanie ról wydaje się być zagadnieniem prostym i intuicyjnym. A jednak powoduje wśród analityków i projektantów sporo nieporozumień, których efektem są diagramy klas niepoprawnie odzwierciedlające rzeczywistość biznesową. Modelując role rzadko bowiem odpowiadamy sobie na dwa zasadnicze pytania:
- czy obiekt może pełnić tylko jedną rolę w danym czasie, czy może ich pełnić kilka jednocześnie?
- czy obiekt może zmienić pełnioną rolę, czy przez całe swoje życie występuje w jednej roli?
W zależności od odpowiedzi na te pytania, powinniśmy wybrać odpowiedni sposób modelowania ról. A mamy do wyboru kilka technik. Omówimy je na przykładzie firmy produkcyjnej, która prowadzi bazę swoich kontrahentów. Kontrahent może kupować towary od naszej firmy -- jest wtedy nazywany klientem. Może także dostarczać naszej firmie surowce -- jest wtedy dostawcą.
Role jako podklasy
Role często intuicyjnie modelujemy jako podklasy, wychodząc z założenia, że klient i dostawca są szczególnymi przypadkami kontrahenta. Tworzymy wówczas model taki jak na rysunku 1. Model ten ma dwa bardzo istotne ograniczenia, wynikające z samej istoty obiektowości:
- każdy obiekt może być obiektem tylko jednej klasy,
- jeśli utworzymy obiekt danej klasy, to nie może on w trakcie swego życia zmienić tożsamości i zawsze już będzie obiektem tej klasy.
Rysunek 1. Modelowanie ról przy pomocy podklas.
W naszym przykładzie oznacza to, że nasi dostawcy nie mogą być obsługiwani jako klienci, zaś klienci nie mogą być jednocześnie dostawcami. Nie można bowiem utworzyć obiektu, który jest jednocześnie obiektem klasy Klient i Dostawca. Co więcej, nawet jeśli zaprzestaniemy współpracy z pewnym dostawcą, to do momentu usunięcia go z bazy kontrahentów nie możemy obsługiwać go jako klienta. Jest bowiem reprezentowany przez obiekt klasy Dostawca i nie może się przeistoczyć w obiekt klasy Klient.
W literaturze spotyka się próby obejścia drugiego ograniczenia, opierające się na spostrzeżeniu, że przecież obiekt klasy Dostawca można usunąć, tworząc w jego miejsce obiekt klasy Klient. Jednak obchodzenie ograniczenia w ten sposób jest rozwiązaniem bardzo nieeleganckim i zaciemniającym model systemu. Wymaga tworzenia nowego obiektu, kopiowania atrybutów i usuwania starego obiektu. Jeśli chcemy, aby obiekt mógł zmieniać swoją rolę w trakcie swego życia, wystarczy zastosować jeden z modeli omówionych w dalszej części artykułu.
Nie znaczy to bynajmniej, że wykorzystanie podklas do modelowania ról jest zawsze błędem. Wszystko zależy od tego, jak odpowiemy na pytanie, czy obiekt może mieć w swoim życiu wiele ról, czy tylko jedną. Jeśli świadomie przyjmiemy wspomniane dwa ograniczenia, to model wykorzystujący podklasy znakomicie spełni swoje zadanie.
W naszym przykładzie sensowne wydaje się jednak założenie, że dostawca może być jednocześnie klientem (na przykład dostawca wycieraczek dla firmy produkującej samochody może jednocześnie kupować samochody od tej firmy). Dlatego model wykorzystujący podklasy w naszym przykładzie nie jest poprawny.
Role jako związki
Najprostszym sposobem obejścia wspomnianych ograniczeń związanych z dziedziczeniem jest zamodelowanie ról po prostu jako relacji pomiędzy klasami. Nazwy ról możemy zapisać na końcach relacji, przy klasie, która pełni daną rolę. Takie rozwiązanie jest przedstawione na rysunku 2.
Rysunek 2. Modelowanie ról jako relacji.
Zgodnie z tym modelem jeden kontrahent może być jednocześnie klientem i dostawcą -- wystarczy, że powiążemy go relacjami zarówno z towarami, które kupuje, jak i z surowcami, które dostarcza.
Trudno sobie wyobrazić bardziej eleganckie rozwiązanie. Jest proste -- nie wymaga użycia dodatkowych klas -- a jak wiadomo prostota jest bardzo pożądaną cechą modeli informatycznych. Ma tylko jedną wadę: nie pozwala przypisać atrybutów do poszczególnych ról. Role są bowiem relacjami, a te nie mogą mieć atrybutów.
Z tym ograniczeniem można jednak sobie łatwo poradzić. Wystarczy ?przeciąć? relacje na pół, wstawiając dodatkowe klasy reprezentujące role. W klasach tych umieszczamy atrybuty, które chcielibyśmy przypisać do ról. Ponieważ obiekty tych klas są ściśle zależne od obiektu głównego (w naszym przykładzie kontrahenta), między klasą główną a klasami reprezentującymi role możemy zastosować kompozycję. Takie rozwiązanie jest przedstawione na rysunku 3.
Rysunek 3. Modelowanie ról przy pomocy dodatkowych klas i kompozycji.
Kontrahenta, który jest dostawcą, poznamy po tym, że z obiektem reprezentującym tego kontrahenta jest związany obiekt klasy Dostawca. Nic nie stoi oczywiście na przeszkodzie, aby ten sam kontrahent był jednocześnie klientem i miał stowarzyszony obiekt klasy Klient.
Bardzo podobne rozwiązanie można uzyskać stosując klasy asocjacyjne (ang. association classes). Klasa asocjacyjna jest związana z relacją między dwoma innymi klasami. Dla każdego egzemplarza tej relacji jest tworzony osobny obiekt klasy asocjacyjnej. Jest to więc technika pozwalająca pośrednio przypisać atrybuty do relacji (poprzez ich umieszczenie w klasie asocjacyjnej).
Rysunek 4. Modelowanie ról przy pomocy klas asocjacyjnych.
Rozwiązanie wykorzystujące klasy asocjacyjne jest przedstawione na rysunku 4. Różni się ono od rozwiązania z rysunku 3. -- poza zastosowaną notacją -- tylko jednym drobnym szczegółem. Wg rysunku 3., kontrahent dostarczający wiele różnych surowców ma przypisany tylko jeden obiekt klasy Dostawca, wspólny dla wszystkich dostarczanych przez niego surowców. Czas zapłaty za dostawę jest więc określony tylko raz dla każdego dostawcy. Natomiast zgodnie z rysunkiem 4., osobny obiekt klasy asocjacyjnej Dostawca jest tworzony dla każdego surowca dostarczanego przez dostawcę. Zatem czas zapłaty za dostawę trzeba określić osobno dla każdego surowca dostarczanego przez danego dostawcę. Analogiczna różnica pomiędzy tymi dwoma modelami dotyczy klasy Klient i jej atrybutu rabat.
W następnym odcinku
Rozwiązania przedstawione na rysunkach 3. i 4. pozwalają na utworzenie kontrahenta, który nie ma przypisanej żadnej roli. W kolejnym artykule zobaczymy, jak można sobie radzić, gdy obiekt musi mieć przynajmniej jedną rolę. Poznamy także kolejne, niekiedy bardzo zaskakujące techniki modelowania ról.
Szymon Zioło
Artykuł został opublikowany w Software Developer's Journal nr 5/2009.
Komentarze