Projektowanie systemów informatycznych (część 5)

Sebastian Wyrwał

Poprzedni odcinek stanowił wprowadzenie do języka UML,
pokazano w nim rodowód tego języka i podstawowe pojęcia w nim występujące.
Przypomnijmy, iż model składa się z różnych widoków – które z kolei
skupiają się na różnych aspektach systemu. Różne widoki są przydatne dla
różnych kategorii osób uczestniczących w przedsięwzięciu programistycznym.
W skład widoków wchodzą diagramy. Istnieją diagramy: przypadków użycia
(ang. Use case diagram), opisujące widzianą z zewnątrz funkcjonalność
systemu, diagramy klas, ukazujące statyczną strukturę systemu, diagramy
obiektów, opisujące strukturę systemu w danej chwili, diagramy behawioralne,
opisujące zachowanie systemu oraz diagramy implementacyjne, opisujące fizyczną
strukturę systemu lub organizację kodu źródłowego systemu. Do diagramów
behawioralnych zalicza się diagramy stanów. Opisują one zachowanie klasy (lub
całego systemu). Diagramy aktywności obrazują realizację przypadków użycia
(także operacji lub interakcji). Bardzo ważne są też diagramy interakcji, które
pozwalają na zapisanie współdziałania ze sobą obiektów. Najczęściej w
projektach występują diagramy przypadków użycia, klas i interakcji. W firmie
nie zawsze jest bowiem czas na tworzenie szczegółowej dokumentacji
technicznej. Największe problemy występują przy określaniu funkcjonalności
systemu. Nie każdy system łatwo jest testować w sposób, w jaki będzie on
„normalnie” używany. Może to bowiem wymagać dużych nakładów czasowych
związanych z wpisywaniem dużych ilości danych. Celowe jest dostarczenie rozwiązań
do automatycznego testowania, generowania pewnych raportów testowych, symulujących
być może współpracę z użytkownikiem.

Wyobraźmy sobie bardzo starannie wykonany projekt, w którym
nie ujęto funkcji związanych z testowaniem systemu. Funkcje te nie są oczywiście
widoczne dla użytkownika, ale mają kluczowe znaczenie. W momencie fazy testów
całego systemu okazuje się, że trzeba „z palca” dopisać szereg funkcji
testowych – co rozpoczyna „łatanie” systemu, lub – mówiąc bardziej
formalnie – programowanie przyrostowe lub co gorsze, odkrywcze.

W tym odcinku zostaną przedstawione pewne problemy związane
z projektowaniem systemu do obsługi przychodni lekarskiej z użyciem, rzecz
jasna, języka UML. Warto w tym miejscu dodać, iż projektując system dla
przychodni lekarskiej powinno się przeanalizować regulacje prawne np. ustawę
o zawodzie lekarza z dnia 5 grudnia 1996. Taka analiza pozwala na „wyłapanie”
błędów i nieścisłości w opisie rzeczywistości. Poza tym, niecelowe jest
cytowanie ustawy lub kilku ustaw w tymże opisie. Ustawą należy się posiłkować
tworząc taki system, który można skonfigurować tak, aby zamodelować
zmieniające regulacje prawne. Ma to szczególne znaczenie np. w systemach płacowych,
gdzie często zmieniają się przepisy, które w dodatku mają wpływ na
algorytmy naliczania wynagrodzeń itp. Artykuł opisuje jak tworzyć system, który
będzie elastyczny w użyciu. Ma to szczególne znaczenie wtedy, gdy wymagania
stawiane systemowi mogą ulegać częstym zmianom lub są niedospecyfikowane.
Trzeba pamiętać, że sytuacja taka ma miejsce w większości komercyjnych
projektów. Umiejętność wyspecyfikowania (ostatecznej) wersji systemu, choć
bardzo w inżynierii oprogramowania ceniona, występuje w praktyce niezbyt często.
Warto ponadto mieć świadomość częstych zmian regulacji prawnych.

Projektowanie – analiza opisu rzeczywistości

Aby dokonać „pierwszej przymiarki” do określenia klas,
jakie będą występowały w systemie, należy przeczytać opis rzeczywistości
wraz ze słownikiem i wyodrębnić rzeczowniki. Klasami mogą więc być:
przychodnia, lekarz, pacjent, dokumentacja, recepta, USG itp. W obrębie wyodrębnionych
klas należy jednak dokonać pewnych „cięć”. System, w którym jest 100
klas, jest dużym systemem. Tak więc dojrzały projekt powinien być do pewnego
stopnia ascetyczny. Dla projektowanego systemu można wyróżnić dwie główne
klasy:

  • pacjent
  • lekarz.

Jest to uzasadnione, ponieważ pielęgniarki nie mają (na
poziomie przychodni) specjalności. Działanie przychodni, to w większości
interakcje lekarza z pacjentem: pacjent rejestruje się do lekarza, ten ostatni
przeprowadza u niego badania, wystawia zaświadczenia itd. Ponadto każdy
pacjent ma lekarza prowadzącego co powoduje, że między pacjentem a lekarzem
występuje specyficzne powiązanie, lekarz również podejmuje decyzje
merytoryczne, wystawia zaświadczenia itp. Nie ma kluczowego znaczenia, kto
dokonuje rejestracji lub robi zastrzyk. Warto oczywiście aby w systemie były
takie informacje, np. na wypadek skargi, powikłań lub popełnienia błędu.

W poprzednim odcinku napisano, iż lekarz specjalista jest
szczególnym przypadkiem lekarza. Jest to typowy przykład dziedziczenia. Rozwiązaniem,
które nasuwa się samo, jest klasa lekarz i podklasy odpowiadające lekarzom różnych
specjalności. Sprawdza się ono na etapie nauki projektowania obiektowego czy
notacji związanej z językiem UML; nie jest niestety odpowiednie dla
rzeczywistego systemu informatycznego.

W profesjonalnym projekcie należy rozważyć jednak rozwiązania
alternatywne, zwłaszcza, że jeden lekarz może mieć różne specjalności. W
obrębie specjalności mogą istnieć podspecjalności itd. Poza tym dobrą
praktyką jest budowanie systemu tak, aby można było wprowadzać nowe
specjalności, jako że medycyna ewoluuje. Poniżej przedstawiono kilka
potencjalnych rozwiązań:

  1. Hierarchia lekarzy
  2. Jedna klasa lekarz i hierarchia specjalności
  3. Jedna klasa lekarz i jedna klasa specjalność
  4. Jedna klasa lekarz, obiekt tej klasy posiada zbiór
    uprawnień (badań), które może on wykonywać.

W rozwiązaniu pierwszym (rysunek 1) mamy bazową klasę
lekarz i dziedziczące z niej klasy np. gastrolog, kardiolog itp. Rozwiązanie
takie powoduje, że nie ma możliwości wprowadzania nowych specjalności. (W języku
Java można dynamicznie ładować klasy, więc problem nie jest aż tak dotkliwy
w przypadku tego języka implementacji, nie rozwiązuje jednak problemu
posiadania przez lekarza kilku specjalności lub podspecjalności). Rozwiązanie
takie zakłada domyślnie, iż jeden lekarz ma dokładnie jedną specjalność
– założenie takie nie jest jednak poparte opisem rzeczywistości. Przyjęcie
takiego założenia byłoby ewidentnie błędną interpretacją opisu
rzeczywistości. Potwierdza to praktyka, ponieważ jeden lekarz może mieć
kilka specjalności np. „kardiolog gastrolog”.


Rysunek 1.
Hierarchia lekarzy

W drugim rozwiązaniu (rysunek 2) istnieje tylko jedna klasa
lekarz i różne specjalności np. pediatra, chirurg. W takiej sytuacji
„lekarz ogólny” lub „lekarz medycyny” są pewnymi (sztucznie
stworzonymi) specjalizacjami. W „naturalnym” przypadku, kiedy każdej
specjalności odpowiada jedna klasa, aktualny pozostaje problem dynamicznego
definiowania specjalności. Dodatkowo, rozwiązanie takie nie zmniejsza liczby
klas w systemie. Rozwiązuje ono natomiast problem posiadania kilku specjalności
przez jednego lekarza. Można bowiem obiekt reprezentujący lekarza powiązać z
kilkoma obiektami odpowiadającymi specjalnościom.


Rysunek 2.
Lekarz – hierarchia specjalności

W rozwiązaniu trzecim (rysunek 3) zaprezentowano odejście
od automatycznego używania generalizacji „gdzie tylko można” i
zaproponowano rozwiązanie, w którym specjalność jest jedną klasą, której
obiekty odpowiadają różnym specjalnościom. Łatwo zauważyć, iż hierarchię
klas zastąpiono asocjacją dwóch klas. Jeżeli specjalności są wyrażone na
poziomie obiektów, to można je dynamicznie dodawać i modyfikować. Spostrzeżenie
to dotyczy w ogólności całego systemu – obiekty odpowiadają dynamicznemu
obrazowi systemu. Ceną, którą się za to płaci jest funkcjonalne sprawdzanie
legalności pewnych operacji. To, co jest określane na poziomie obiektów, może
być w ogólności definiowane dynamicznie. Rzeczy określane na poziomie klas
pozostają niezmienne, ponieważ odpowiadają statycznemu obrazowi systemu.


Rysunek 3.
Specjalności jako obiekty klasy
specjalność

W czwartym rozwiązaniu (rysunek 4), dla każdego lekarza
definiuje się zbiór uprawnień – nie ma wtedy konieczności wprowadzania
specjalności na tym poziomie działania systemu. Specjalność jest realizowana
na poziomie funkcjonalnym. Można zapytać jak się to odbywa. Odpowiedź jest
prosta: specjalność jest pewnym zbiorem uprawnień – co jest rozwiązaniem
elastycznym. Pozwala ono na łatwe dostosowanie systemu do zmieniającej się
rzeczywistości. Definiowanie uprawnień nie jest w żadnym wypadku sztuczne lub
nadmiarowe – lekarz histopatolog nie leczy pacjentów. Uprawnienia nie muszą
się jednak ograniczać do tych, które wynikają z ustawy o zawodzie lekarza
(lekarz może posiadać umiejętności z węższych dziedzin medycyny lub
udzielać określonych świadczeń, np. lekarz-stażysta ma ograniczone
uprawnienia) ale mogą mieć związek z organizacją pracy w przychodni.

Rozwiązanie czwarte traktuje tak samo specjalizację, jak i
uprawnienie np. do badania kierowców, co jest korzystne. Problem pojawia się
jednak wtedy, gdy trzeba wyszukać np. wszystkich lekarzy kardiologów – ich
uprawnienia mogą być bardzo podobne. Problem ten rozwiązuje podejście, w którym
uprawnienia mogą tworzyć hierarchię. Podejście to stanowi połączenie rozwiązań
trzeciego oraz czwartego. Lekarz posiada zbiór uprawnień – z tym, że
uprawnienia mogą mieć poduprawnienia. W takiej hierarchicznej strukturze
specjalność jest traktowana jako jedno uprawnienie, które posiada
poduprawnienia. Uprawnienia tworzą hierarchiczną strukturę (nie mylić z
hierarchią klas). Zaletami takiego podejścia jest spójne traktowanie
uprawnienia i specjalności, prostota i elegancja (tylko dwie klasy). Oczywiście,
z uprawnieniami mogą wiązać się pewne daty (np. ważności uprawnienia), które
są nadmiarowe dla specjalności ale nie jest zbyt duża niedogodność.


Rysunek 4.
Lekarz – uprawnienie

Hierarchię osób w systemie można dalej spłaszczać,
rezygnując nawet z klas lekarz, pielęgniarka itp. Nie można tego jednak robić
automatycznie, ponieważ personel biurowy to ewidentnie nie to samo co lekarz.
Całkowite spłaszczenie hierarchii klas powodowałoby zupełną nieczytelność
projektu. Postępowanie takie można odnieść do sytuacji, kiedy w języku
programowania nie ma typów. Jest to możliwe, ale może powodować różne
problemy. Jeśli chodzi o personel biurowy można zauważyć, co nie dotyczy
projektowanego systemu, iż zastosowanie rozwiązania analogicznego do
wymienionego w punkcie piątym powyższych rozważań, może przynieść bardzo
duże korzyści. Zamiast wprowadzać klasy sekretarka, kadrowa, pracownik płac
itp., możliwe jest zastosowanie jednej klasy pracownik biurowy i określanie
dla niej uprawnień i obowiązków. Jest to rozwiązanie elastyczne, ponieważ
nie ma nadmiaru danych. W wypadku kiedy dana osoba pracuje na kilku etatach,
zachodzi potrzeba czasowego nadania pewnych uprawnień lub obowiązków –
podobnie, gdy ulega zmianie zakres obowiązków na danym stanowisku.


Rysunek 5.
Lekarz – hierarchia uprawnień wyrażona
na poziomie obiektów

Niedoświadczeni projektanci często definiują zbyt dużo
klas. Trzeba pamiętać, że muszą istnieć wyraźne przesłanki aby dany byt z
opisu rzeczywistości „przetrwał” jako klasa kolejne iteracje projektowe. Z
drugiej strony, nie należy tworzyć projektu zdegenerowanego do zbyt małej
liczby klas. Pacjent i lekarz są pojęciami zbyt odległymi, aby łączyć je w
jedną klasę. Jak najbardziej poprawne jest wprowadzenie klasy osoba, która
łączy wspólne cechy lekarza i pacjenta. Łatwo zauważyć, że zarówno
lekarz jak i personel biurowy, są pracownikami. Prowadzi to do następującej
hierarchii, przestawionej na rysunku 6.


Rysunek 6. Przykładowe osoby w systemie

Podstawowe informacje (imię, nazwisko itd.) są wspólne dla
różnych osób, dlatego wprowadzenie klasy osoba jest celowe. Z klasy tej
dziedziczą bezpośrednio tylko dwie klasy: Pacjent oraz Pracownik. Można
jeszcze zastanowić się, czy nie należy wyodrębnić jeszcze jednej klasy, która
odpowiada personelowi „w białym fartuchu”. Jedyny argument jaki za tym
przemawia to taki , że ma on bezpośredni kontakt z pacjentem. Pielęgniarka
nigdy nie jest lekarzem itp. Może za to istnieć lekarz specjalizujący się w
analizie medycznej ale można to wyrazić za pomocą specjalności.

Pacjent jest leczony przez jednego lekarza (prowadzącego),
inni lekarze go konsultują w miarę potrzeby. Aby wygodnie nie wprowadzać zbyt
wielu danych przy dodawaniu nowego lekarza do systemu, można użyć wzorca
projektowego Prototype [3] w ten sposób, iż dla wszystkich aktualnie istniejących
specjalności (podspecjalności) mamy zdefiniowane obiekty – szablony, które
są klonowane.


Rysunek 7.
Rejestr specjalności

Rejestr specjalności (rysunek 7) jest zatem kolekcją obiektów
klasy lekarz, nie są one jednak do końca zdefiniowane. Tak więc prototypowy
obiekt ma zdefiniowane tylko te atrybuty, które są wspólne dla tych obiektów,
do których tworzenia posłuży. Dla przedstawionego tutaj problemu będą to właśnie
zbiory uprawnień (definiujące również specjalności). Nie ma żadnego sensu
określać dla obiektów prototypów nazwisk lub imion. W momencie, gdy do
systemu trzeba wprowadzić zatrudnianego właśnie lekarza, zostaje wywołana
metoda nowy_lekarz klasy Rejestr_specjalności. Wprowadzając dane lekarza należy
określić obiekt prototyp, który zostanie sklonowany. Jeśli ów prototyp nie
istnieje, należy go zdefiniować. Zastosowanie tego wzorca projektowego zaburza
typowe podejście do relacji klasa – obiekt. W ujęciu tradycyjnym, klasa
definiuje właściwości obiektów. Tutaj właściwości obiektu są definiowane
przez obiekt-prototyp. Zauważmy, że hierarchię klas zastępuje się wtedy
zbiorem obiektów-prototypów. Klasa musi oczywiście pozwalać na elastyczne
definiowanie właściwości obiektów. Poniższy kod ilustruje omawiane rozwiązanie.
Zgodnie z tym, co zostało powiedziane we wstępie, wprowadzono pewne bardzo
proste funkcje służące do testowania rozwiązania. Ich działanie ogranicza
się do wypisania wartości atrybutów obiektu klasy. Nie uwzględniono ich też
w zaprezentowanych powyżej diagramach UML. W rzeczywistym projekcie należy (na
pewnym poziomie) wprowadzić je do diagramów. Uprawnienie posiada nazwę i
pod_uprawnienia. Te ostatnie są przechowywane w wektorze _dzieci.

import java.util.*;
public class Uprawnienie {

    Vector _dzieci = new Vector();
    String _nazwa = null;

    Uprawnienie(String nazwa){
            _nazwa = nazwa;
    }

    void dodajDziecko(Uprawnienie u){
            _dzieci.add(u);
    }

    public void pisz(){
           
System.out.println(" "+_nazwa);
            for (int i = 0 ;i<_dzieci.size();i++)
                   
((Uprawnienie)_dzieci.elementAt(i)).pisz();
    }
}

Klasa Lekarz została wyposażona w metody służące do zarządzania
uprawnieniami:

  • dodajUprawnienie – służy do dodawania uprawnienia

  • ustawUprawnienia – służy do zdefiniowania zbioru
    uprawnień

  • usunUprawnienie – służy do usunięcia uprawnienia

W większości przypadków zarządzanie uprawnieniami odbywa
się na poziomie obiektów prototypów. Trzeba jednak pamiętać, iż są to
takie same obiekty jak te, które reprezentują lekarzy w systemie. Jedyna różnica
polega na ich odmiennym użyciu. Z punktu widzenia prezentowanego rozwiązania,
istotna jest metoda klonuj. Kopiuje ona do nowo utworzonego obiektu te atrybuty,
które mają mieć takie same wartości jak w prototypie. W prezentowanym
przypadku są to wyłącznie uprawnienia.

import java.util.*;

public class Lekarz {
    Vector uprawnienia = null;
    String _nazwisko = null;
    String _imie = null;
    String _NIP = null;
    String _PESEL = null;
    String _Adres = null;
    String _numerTelefonu = null;
    String _numerTelefonuKomorkowego = null;

    public Lekarz(){
    }

    public void ustawDane(String nazwisko, String imie, String NIP, String PESEL,
String Adres, String numerTelefonu, String numerTelefonuKomorko- wego){

    _nazwisko = nazwisko;
    _imie = imie;
    _NIP = NIP;
    _PESEL = PESEL;
    _Adres = Adres;
    _numerTelefonu = numerTelefonu;
    _numerTelefonuKomorkowego = numerTelefonuKomorkowego;
    }

    public void dodajUprawnienie(Uprawnienie u){
           
uprawnienia.add(u);
    }

    public void ustawUprawnienia(Vector vc){
            uprawnienia = vc;
    }

    public void usunUprawnienie(Uprawnienie u){
    }

    public Lekarz klonuj(){
            Lekarz l = new Lekarz();
            l.uprawnienia = (Vector)this.uprawnienia.clone();
            return l;
    }

    void pisz(){
           
System.out.println(_nazwisko);
           
System.out.println(_imie);
           
System.out.println(_NIP);
           
System.out.println(_PESEL);
           
System.out.println(_Adres);
           
System.out.println(_numerTelefonu);
           
System.out.println(_numerTelefonuKomorkowego);

    System.out.println("posiada następujące uprawnienia");

    for (int i = 0;i<uprawnienia.size();i++){
            ((Uprawnienie)uprawnienia.elementAt(i)).pisz();
            }
    }

}

Rejestr specjalności przechowuje prototypy obiektów oraz pozwala na
identyfikację prototypów przy pomocy nazw. Jest to bardzo silne rozwiązanie,
polegające na dynamicznym definiowaniu typów w czasie wykonania. Powiązanie
nazwy z prototypem uzyskano dzięki zastosowaniu słownika (Hashtable).

import java.util.*;
public class Rejestr_specjalnosci {

        Hashtable prototypy = new Hashtable();

        Lekarz nowy_lekarz(String nazwa_prototypu){
        Lekarz p = (Lekarz)prototypy.get(nazwa_prototypu);
               
Lekarz l = p.klonuj();
               
return l;
        }

        void definiuj_prototyp(String nazwa_prototypu, Vector uprawnienia){
               
Lekarz l = new Lekarz();
               
l.ustawUprawnienia(uprawnienia);
               
prototypy.put(nazwa_prototypu,l);

        }

        void pisz(){
               
System.out.println(prototypy.toString());
        }

}

Rejestracja nowego prototypu lekarza polega na określeniu
jego nazwy i zbioru jego uprawnień. W rzeczywistym systemie, poza wektorem
uprawnień można określać inne właściwości, np. czas pracy itd. Metoda
definiuj_prototyp tworzy nowy obiekt klasy Lekarz, przypisuje mu uprawnienie i
zapisuje go do słownika. Zastosowanie słownika pozwala na identyfikowanie
obiektu poprzez nazwę. Metoda nowy_lekarz służy do tworzenia obiektów klasy
Lekarz w systemie. Jej działanie polega na pobraniu obiektu-prototypu ze słownika
i sklonowaniu go (metoda klonuj).

Poniżej ukazano wykorzystanie zaprezentowanego wcześniej
rozwiązania. Utworzono dwie specjalności identyfikowane przez łańcuchy:

  • lekarz medycyny pracy,
  • stomatolog.

Specjalność (definiowana w systemie jako uprawnienie)
„lekarz medycyny pracy” posiada uprawnienie: badania okresowe.

import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) {

    Uprawnienie badaniaOkresowe = new Uprawnienie("badania okresowe");
    Uprawnienie lekarzMedycynyPracy = new Uprawnienie("lekarz medycyny pracy");
    lekarzMedycynyPracy.dodajDziecko(badaniaOkresowe);

    Uprawnienie lekarzStomatolog = new Uprawnienie("stomatolog");

    Rejestr_specjalnosci rejestrSpecjalnosci = new Rejestr_specjalnosci();

    Vector uprawnieniaLekarzaMedycynyPracy = new Vector();
    uprawnieniaLekarzaMedycynyPracy.add(lekarzMedycynyPracy);

    rejestrSpecjalnosci.definiuj_prototyp("lekarz medycyny
pracy",uprawnieniaLekarzaMedycynyPracy);

    Vector uprawnieniaStomatologa = new Vector();
    uprawnieniaStomatologa.add(lekarzStomatolog);
   
rejestrSpecjalnosci.definiuj_prototyp("stomatolog",uprawnieniaStomatologa);

    Lekarz nowyLekarz = rejestrSpecjalnosci.nowy_lekarz("lekarz medycyny
pracy");
    nowyLekarz.ustawDane("Kowalski","Jan","1234567","1234567","ul.
Karmelkowa","1234567","0600 123 456");

    nowyLekarz.pisz();

           
rejestrSpecjalnosci.pisz();
    }

}

Warto w tym miejscu dodać, iż dynamiczne ładowanie kodu w
języku java nie rozwiązuje omawianego problemu w zadowalającym stopniu. Może
oczywiście istnieć otwarta hierarchia specjalności lekarzy, ale przy
pojawieniu się nowej trzebaby u producenta systemu zamawiać nową klasę. (Nie
pozbawione racji będzie twierdzenie, że sytuacja taka może być
„korzystna” dla wytwórców oprogramowania, gdyż generuje dodatkowe
zlecenia. Nie powinno się jednak tego wykorzystywać – projektowanie systemów
w ten sposób jest jak najbardziej naganne.)

Definiowanie czynności

Definiowanie uprawnień nie miałoby sensu, gdyby nie wiązały
się one z akcjami. Akcje na poziomie systemu odpowiadają metodom na niższym
poziomie abstrakcji. Stosunkowo łatwo można ujednolicić sygnatury tych metod.
Można wtedy zastosować (nieco zmodyfikowany) wzorzec projektowy Strategy [3].
Pozwala on na zdefiniowanie rodziny algorytmów, z których można wybierać w
czasie wykonania ten, który zostanie użyty. Książkowym przykładem może być
sortowanie. Można zbudować hierarchię klas, w której każda implementuje różne
algorytmy sortowania. W systemie do obsługi przychodni lekarskiej można w ten
sposób zdefiniować różne czynności podejmowane przez lekarzy oraz powiązać
te czynności z uprawnieniami, niezbędnymi do ich wykonania. (Niekoniecznie
tymi, które wynikają z ustawy o zawodzie lekarza. W elastycznym systemie można
zdefiniować, iż USG wykonuje pewna grupa lekarzy, dyżury lekarzy ustala
kierownik itd.) Jeśli uprawnienia nie zostałyby powiązane z konkretnymi
algorytmami, byłyby słabym mechanizmem. Niech ogólna czynność wykonywana
przez lekarza będzie nazywana usługą medyczną. Można zbudować następującą
hierarchię klas, co pokazano na rysunku 8. Dla uproszczenia, na razie pominięto
sprawdzanie uprawnień.


Rysunek 8.
Usługi medyczne

Każda czynność wykonywana przez lekarza (dotycząca pośrednio
lub bezpośrednio pacjenta) realizowana jest przez wywołanie metody wykonaj
odpowiedniej klasy. Wzorzec musi być zmodyfikowany w ten sposób, że jednym z
argumentów metody wykonaj klasy usługa medyczna musi być pewien
identyfikator, identyfikujący algorytm. Jeżeli ma być wykonane badanie USG,
to wywołując metodę wykonaj na rzecz obiektu klasy usluga_medyczna przekazuje
się np. łańcuch „USG”. Ustalenie, co będzie tym identyfikatorem, ma
znaczenie drugorzędne i może być przeprowadzone na etapie bliższym
implementacji. Przykładowy (pseudo)kod może wyglądać następująco:

class usluga_medyczna{
        public void wykonaj(String id, Vector parametry){
               
usluga_medyczna u = null;

               
if (id.equals("USG")){
                       
u = new USG();

                               
u.wykonaj(parametry);
                               
return;
               
}
               
if (id.equals("Badanie kierowców")){
                               
u = new Badanie_kierowcow();
                               
u.wykonaj(parametry);
                               
return;
               
}
               
...
}
}

W ciele metody wykonaj klasy usluga_medyczna, w zależności
od identyfikatora id tworzony jest obiekt odpowiedniej klasy. Jeśli
identyfikator ma wartość „USG”, to tworzy się obiekt klasy USG. Następnie
wywołuje się metodę wykonaj tego obiektu. Rozwiązanie w takiej postaci nie
jest jeszcze zbyt elastyczne, dlatego można użyć słownika (Hashtable):

class usluga_medyczna{

        private static Hashtable rejestr_uslug = new Hashtable();

        public static rejestruj_usulge(String nazwa, usuluga_medyczna u){
        rejestr_uslug.put(nazwa,u);

        }

public void wykonaj(String id, Vector parametry){

usluga_medyczna u = (usluga_medyczna)rejestr_uslug.get(id);

        u.wykonaj(parametry);

}
}

Żadne klonowanie nie jest potrzebne, ponieważ klasy służą
wyłącznie do enkapsulacji metod-algorytmów. Wystarczy więc, aby w systemie
istniały pojedyncze instancje tych obiektów.

Można tutaj ponowić zarzut o nieelastyczne projektowanie i
zarzucić autorowi pewną niekonsekwencję. Jest ona jednak pozorna. Pierwszy
problem, który pojawił się w przypadku lekarzy wynikał stąd, iż dany
lekarz może mieć kilka specjalności. Nie dotyczy on jednak usług. Jeden
algorytm odpowiada dokładnie jednej usłudze. Założenie takie jest możliwe
ponieważ: usługa nie stanowi opisu postępowania dla lekarza (to nie jest
system szkolący lekarzy lub wspomagający diagnozowanie), usługa jest tworem
niezdefiniowanym w opisie rzeczywistości – można więc przyjąć dość
elastyczne założenia. Sensowne wydaje się być wyodrębnienie podstawowych
typów usług takich, jak np. badanie, wydanie zaświadczenia itp. a następnie
definiowanie „szczegółów” na poziomie obiektów. Właściwości obiektów
można definiować np. przy pomocy pewnego rodzaju języka opisu. Język taki
należy zdefiniować stosownie do zakresu informacji opisujących usługę.
Rozwiązanie to jest bliskie wytwarzaniu oprogramowania, które jest oparte o
rodziny produktów. Różnica jest taka, jak pomiędzy kompilatorem języka i
interpreterem. W klasycznym podejściu, opartym o koncepcję rodzin, tworzymy
narzędzia służące do generowania konkretnego systemu na podstawie opisu tego
systemu. Ów opis jest wyrażony w specyficznym języku, który pozwala na
opisanie dowolnego systemu należącego do danej rodziny. Może to być rodzina
systemów kadrowo-płacowych, systemów do obsługi przychodni itp. Autor widzi
jeszcze inną możliwość – zdefiniowanie jak najbardziej uniwersalnego
systemu – ramy, który będzie następnie konfigurowany. Łatwo zauważyć, iż
rozwiązanie takie pozwala na modyfikowanie zachowania się systemu, bez
potrzeby jego powtórnego wytwarzania lub komplikacji. W klasycznym podejściu
opartym o koncepcje rodzin, nie ma takiej możliwości. Na podstawie definicji
generuje się system (który w pewnym stopniu może być konfigurowalny), ale
nie ma możliwości przedefiniowania działającego i zainstalowanego już
systemu. Zastosowanie rozwiązania proponowanego przez autora wiąże się, co
oczywiste, z pewnym spowolnieniem działania systemu. Nie musi mieć to jednak
znaczenia dla wszystkich systemów. Wiadomo, że podejście takie nie sprawdzi
się w odniesieniu do systemów czasu rzeczywistego (np. kontroli lotów) ale
dla systemów takich, jak system obsługi przychodni, może być do przyjęcia.

Podsumowując rozważania podjęte w tym odcinku można
powiedzieć, że system informatyczny powinien być projektowany tak, aby był
jak najbardziej elastyczny. Z tym stwierdzeniem wiąże się następne mówiące
o tym, iż akademickie (książkowe) przykłady nie mogą zostać użyte w
rzeczywistym systemie. Mając do wyboru prostotę rozwiązania i elastyczność
systemu należy wybrać elastyczność. Początkującym projektantom wydaje się
(zwłaszcza, że jest to poparte przykładami w literaturze) iż cechy systemu
należy określać na poziomie klas. Trzeba jednak pamiętać, że cecha systemu
„wszyta” w diagram klas pozostaje niezmienna w trakcie życia całego
systemu. Oczywiście zdefiniowanie właściwości systemu na poziomie obiektów
jest bardziej pracochłonne. Postępowanie takie bardzo się jednak opłaca. Jeżeli
projekt systemu nie zakłada żadnej elastyczności, to jakikolwiek błąd
generuje bardzo duże zmiany w projekcie i – co za tym idzie – bardzo duże
koszty. Jeżeli system jest projektowany tak, aby był zorientowany na zmiany,
to w przypadku ich zaistnienia trzeba po prostu dokonać zmian w konfiguracji
systemu. Oczywiście wymóg elastyczności nie może być mylony z niestarannym
projektowaniem. Można nawet powiedzieć, że im bardziej staranny system, tym
bardziej dokładnie musi być projektowany.

Literatura

[1] J. Rumbaugh, I. Jacobson, G. Booch The Unified
Modelling Language Reference Manual
Addison-Wesley 1999
[2] OMG Unified Modeling Language Spcyfication OMG
January 1999 (UML 1.3al)
[3] E. Gamma R.Helm R. Jahnson J. Vissides Design patterns Addison-Wessley
1998