Projektowanie systemów informatycznych

cz. VIII

Sebastian Wyrwał

Bliżej implementacji

W poprzednich odcinkach omówiono problemy, którymi zajmuje
się inżynieria oprogramowania, oraz najpopularniejsze metodologie
projektowania oprogramowania – metody strukturalne, obiektowe oraz
formalne. Sama znajomość metod oraz języków modelowania i projektowania nie
wystarcza do tego, aby być dobrym projektantem i projektować elastyczne
systemy informatyczne. Bardzo duże znaczenie ma doświadczenie nabyte w trakcie
realizacji różnych projektów. Nikt nie stanie się profesjonalnym
projektantem po przeczytaniu jednej książki.

Wiele systemów ma pewne wspólne cechy – takie jak np.
graficzny interfejs użytkownika i musi wykonywać pewne bardzo podobne czynności
– jak na przykład zarządzanie zasobami. Ten odcinek jest poświęcony
zagadnieniom automatycznego generowania interfejsu użytkownika. Ma on również
stanowić próbę odpowiedzi na pytanie: jak wytwarzać systemy informatyczne,
aby obniżyć koszty. W tych rozważaniach ważną rolę odgrywają wzorce
projektowe; wspominano o nich wielokrotnie zarówno w tym cyklu, jak i w cyklu
poświęconym projektowaniu i wytwarzaniu aplikacji internetowych.

Coraz większą popularność zdobywa również programowanie
ekstremalne, które jest zorientowane na użytkowników i zakłada, mówiąc w
dużym uproszczeniu, tworzenie testów przed wytworzeniem właściwych klas użytkowych
(problemom testowania poświęcony był poprzedni odcinek cyklu).

Warto pamiętać, iż żaden rzeczywisty projekt (pominąwszy
projekty realizowane przez studentów na uczelni) nie kończy się na poziomie
projektu logicznego – czyli takiego, w którym występują wyłącznie
byty znajdujące się w opisie rzeczywistości. Chociaż upraszczając, można
powiedzieć, że rola np. serwerów aplikacji sprowadza się do tego, aby
programista nie musiał implementować „wszystkiego od początku”, a
mógł skupić się bardziej na istocie swego rozwiązania, korzystając z różnych
usług, to jednak samo ograniczenie się do klas implementujących model
biznesowy nie jest możliwe. Model biznesowy żyje w logice biznesowej –
warstwa ta zapewnia mechanizmy do zarządzania obiektami tego modelu.

Wspólne cechy systemu

Możliwość wyodrębnienia pewnych wspólnych cech pozwala
na zdefiniowanie uniwersalnej ramy dla bardzo szerokiej klasy systemów lub
bibliotek, które silnie wspomagają to zadanie. W tym cyklu wielokrotnie
przewijało się pojęcie ramy, ale tylko w kontekście pewnej rodziny zastosowań.
Przyglądając się systemowi, czy to bankowemu, czy edukacyjnemu, można odnieść
wrażenie, iż na pewnym poziomie abstrakcji wszystko jest bardzo proste.
Podczas implementacji często pojawiają się problemy, z których istnienia
wcześniej projektanci nie zdawali sobie sprawy. Profesjonalne projektowanie połączone
z zastosowaniem odpowiedniego modelu cyklu życia oprogramowania ma, co prawda,
za zadanie wyeliminować jego „łatanie”, ale w praktyce, sprawy często
wyglądają inaczej. Można spróbować pokusić się o oszacowanie nakładu
pracy na różne kategorie bytów w systemie. Aby to zrobić, należy najpierw
wyodrębnić owe kategorie:

  • Interfejs użytkownika
    – Walidacja i weryfikacja danych wprowadzonych
    przez użytkownika,
    – Lokalizacja interfejsu użytkownika dla użytkowników
    posługujących się różnymi językami,
    – Prezentacja danych z systemu

  • Model biznesowy

  • Logika biznesowa
    – Zarządzenie zasobami
    – Tworzenie obiektów
    – Obsługa sytuacji wyjątkowych
    – Logowanie i wylogowywanie do/z systemu
    – Sprawdzanie uprawnień użytkowników
    – Parametryzacja i konfiguracja systemu
    – Wejście/wyjście i serializacja obiektów

  • Warstwa danych

  • Procedury testowe

  • Program instalacyjny

  • System pomocy

Tabela 1. Typowe rozwiązania niektórych zagadnień

Zagadnienie Rozwiązanie
Wytwarzanie graficznego interfejsu użytkownika Środowiska programistyczne do natychmiastowego
tworzenia aplikacji (RAD)

Generowanie formatek na podstawie definicji
klas biznesowych w trakcie działania programu

Generowanie kodu formatek na podstawie klas
biznesowych

Weryfikacja danych Wyrażenia regularne
Zarządzanie zasobami Menedżer zasobów
Tworzenie obiektów Fabryki obiektów, wzorzec projektowy Prototype
Parametryzacja systemu Koncepcje wywodzące się z wytwarzania rodzin programów
Serializacja obiektów W języku Java obiekty muszą implementować odpowiednie interfejsy
Mapowanie na model relacyjny/Warstwa danych Hibernate (zostanie omówione w następnym odcinku)

Poniżej dokonano analizy pewnego niewielkiego komercyjnego
systemu informatycznego:

  • W systemie tym są 23 klasy związane z interfejsem
    użytkownika, co daje w sumie ponad 4000 linii kodu źródłowego,

  • 16 klas implementujących logikę biznesową, w tym
    jedna fabryka obiektów i jeden słownik, w sumie blisko 2500 linii kodu.

  • 20 klas implementujących model biznesowy, w sumie
    blisko 3500 linii. Jedna klasa implementująca wejście/wyjście (ponad 350 linii).

  • Trzy klasy implementujące prosty model danych
    (prawie 400 linii).

  • Inny kod – niecałe 1000 linii.

Cały system ma więc 12 000 linii kodu – jest to
niewielki system. Nie jest to ani system bazodanowy – nie używa motoru
bazy danych i przechowuje dane w plikach – ani nie jest to aplikacja
internetowa. Nawet orientacyjne dane pozwalają stwierdzić, że:

  • Graficzny interfejs użytkownika to 30% całego
    systemu,

  • Logika biznesowa to 20% systemu

  • Model biznesowy to 30% systemu

  • Inny kod – 20% (we/wy, konfiguracja itp.)

Warto przeanalizować, choć pobieżnie jeszcze jeden, tym
razem typowo bazodanowy komercyjny system, który jest aplikacją internetową:

  • Graficzny interfejs użytkownika – ponad 50
    klas

  • Klasy pomocnicze – 10

  • Fabryka obiektów – 1 klasa

  • Zarządzanie zasobami – 4 klasy

  • Klasy modelu biznesowego – nieco więcej niż 10

  • Klasy związane z przesyłaniem przez sieć – 1

  • Obsługa wyjątków – blisko 10 klas.

Widać z tego, że klasy związane z interfejsem użytkownika
stanowią ponad 50% systemu, klasy implementujące model biznesowy tylko około
10% procent całego systemu.

Z przedstawionych danych można wyciągnąć następujące
wnioski – skoro klasy implementujące model biznesowy stanowią dość
niewielki procent systemu (dla rozważanych przykładów odpowiednio 30% i 10%
systemu), to projekt logiczny stanowi równie niewielką część właściwego
projektu. Bardzo znaczne nakłady związane z wytworzeniem systemu są potrzebne
do wytworzenia graficznego interfejsu użytkownika. Dość znaczne nakłady związane
są również z wytworzeniem różnych klas pomocniczych, czy to z warstwy
logiki biznesowej, czy też związanych z zarządzaniem zasobami.

Odpowiada to intuicyjnemu odczuciu, że z pozoru system wygląda
bardzo prosto…

Nasuwa się pytanie: co zrobić, aby wytworzenie systemu było
prostsze i tańsze? Można odpowiedzieć, że im więcej kodu można wygenerować
automatycznie lub powtórnie użyć, tym system będzie tańszy, a jego
wytworzenie szybsze. Można również powiedzieć, że gdyby kod generować na
podstawie opisu rzeczywistości tak, jak lansują to niektóre metodologie np.
Lyee, to prawdopodobnie można by w ten sposób uzyskać niewielki fragment
systemu; chyba, że reszta mogła by być generowana automatycznie na podstawie
owego rdzenia. Należy wyjaśnić, iż metodologia Lyee opracowana przez Japończyków,
eliminuje całkowicie wg jej autorów, konieczność programowania. Aplikacja
jest generowana automatycznie, na podstawie wymagań jej stawianych. Zaliczyć
do nich można definicje formatek i wydruków, dane w systemie oraz warunki i
zależności pomiędzy danymi. Wg autorów, błędy w programie mogą wystąpić
jako skutek błędów w specyfikacji.

Dlatego tak ważne wydaje się być zastosowanie ram. Zakładając,
iż przeciętny developer jest w stanie opracować 300 linii kodu miesięcznie
(wliczając w to projektowanie i testowanie), niewielki projekt będący
pierwszym przykładem powinien zostać zrealizowany w ciągu 30 miesięcy. Tylko
nieco ponad 8 miesięcy zajmie wytworzenie warstwy biznesowej, wynikającej z
opisu rzeczywistości. Jaki jest zatem koszt wytworzenia przykładowego systemu?
Ograniczając kalkulację wyłącznie do kosztów osobo-miesiąca programisty i
zakładając, że całkowite koszty pracy programisty wynoszą 3 000 zł miesięcznie,
z prostej kalkulacji wynika, iż wytworzenie systemu kosztuje 90 000 zł. Z
czego 27 000 zł przypada na graficzny interfejs użytkownika, 18 000 na logikę
biznesową, 27 000 zł na model biznesowy. Oczywiście rzeczywisty koszt
wytworzenia systemu jest wyższy – trzeba uwzględnić urlopy, absencje
chorobowe oraz wiele innych kosztów stałych.

Co można ulepszyć

Na pierwszy ogień powinien pójść interfejs użytkownika,
a później wszystkie klasy pomocnicze. Między tymi klasami jest jednak
zasadnicza różnica – interfejs użytkownika jest dość silnie uzależniony
od konkretnej aplikacji lub rodziny aplikacji. Klasy pomocnicze mogą być tak
zaprojektowane, aby mogły być zastosowane w szerokiej rodzinie aplikacji.
Analizując prostą aplikację do wspomagania sprzedaży, można stwierdzić, że
jej funkcjonalność jest następująca:

  • prowadzenie rejestru klientów,

  • wystawianie faktur VAT i rachunków,

  • drukowanie etykiet na paczki,

  • monitorowanie dłużników.

Dodatkowym wymaganiem może być monitorowanie stanu magazynu
dla danego produktu. Samo wyodrębnienie klas należących do modelu biznesowego
nie nastręcza problemów:

  • Klient,

  • Faktura,

  • Rachunek,

  • Sprzedaż,

  • Towar,

  • Magazyn,

  • DaneFirmy.

Wiadomo, iż klient ma nazwę bądź imię i nazwisko, adres,
NIP, PESEL. Faktura VAT to dane o firmie sprzedającej, kupującej i lista
sprzedaży oraz pewne wyliczone na tej podstawie informacje.

Sprzedaż to wpis na fakturze VAT, dotyczący konkretnego
towaru, określający dane tego towaru, oraz ilość jaka została sprzedana. Na
tej podstawie można wyliczyć kwotę, itd.

Klasa towar mogłaby wyglądać następująco:

public class Towar{
public String _symbol, _nazwa;
public String _cena;
public Integer _stan;
Vat stopa_vat;
}

class Sprzedaz{
    Towar _towar;
    Integer _ilosc;
}

Dysponując ordynacją podatkową i rozporządzeniami regulującymi
wygląd faktury VAT oraz kilkoma przykładowymi fakturami, klasy z warstwy
biznesowej można zaprojektować i napisać w ciągu zaledwie kilku godzin
pracy. W trakcie pracy okazuje się jednak, że praktycznie każda klasa z
modelu biznesowego ma swoją klasę interfejsową. Zaprojektowanie samego
interfejsu graficznego może być przyspieszone przez użycie pewnych narzędzi.
Ręcznie trzeba na ogół jednak napisać obsługę zdarzeń i wymianę danych z
formatki do klasy biznesowej. System taki – jeśli ma być prosty –
będzie sam zarządzał danymi, nie korzystając z żadnego motoru bazy danych.
Trudno więc sobie wyobrazić aby korzystał z takich, czy innych narzędzi do
raportowania.

Celowe mogło by być napisanie klasy generującej
automatycznie interfejs użytkownika, na podstawie definicji pól w klasie z
modelu biznesowego. W przypadku języka Java nie jest do tego konieczna analiza
leksykalna kodu. W tym języku istnieje możliwość programowego pobrania pól
klasy.

Można wyróżnić różne koncepcje automatycznego tworzenia
i generowania interfejsu użytkownika na podstawie definicji klasy:

  1. Pobrać publiczne pola klasy i na tej podstawie
    automatycznie tworzyć klasę interfejsową (Java)

  2. Zastosować wzorzec projektowy, który powiąże zmienne z
    kontrolkami do ich edycji.

  3. Zamiast tworzyć obiekty, można generować kod klasy i
    zapisywać go do pliku.

Trzeba jedynie odpowiedzieć na pytanie: jak dostarczyć
informacje na temat sposobu prezentacji danych takie, jak nazwa pola edycyjnego,
typ kontrolki, wymiar itp. Można tutaj zastosować:

  1. Opis w dokumencie (XML),

  2. Parametryzację obiektów klas służących do
    prezentacji,

  3. Stworzenie rozbudowanej hierarchii klas (pole _nazwa klasy
    Nazwa, pole _cena klasy Cena dla przykładowej klasy Towar). Powoduje to znaczny
    przyrost ilości klas, ale zapewnia jednolitą prezentację podobnych danych np.
    kwot na różnych formatkach.

Tutaj nasuwa się pewna dygresja – szkoda, iż w języku
programowania nie można dodać własnego słowa kluczowego do deklaracji pola;
dla klasy towar mogło by to wyglądać następująco:

public class Towar{
public String Edit _symbol, _nazwa;
public String Edit _cena;
public Integer _stan;
public List Vat stopa_vat;
}

Informacje te można w pewien sposób „przemycić”
– popularne środowiska programistyczne (np. Eclipse) mają możliwość
automatycznego generowania metod get_X i set_X dla składowych publicznych, co
wygląda następująco:

public class Towar {
    public String _symbol, _nazwa;
    public String _cena;
    public Integer _stan;
    public VAT stopa_vat;

   
public String get_cena() {
        return _cena;
    }

    public String get_nazwa() {
        return _nazwa;
    }
    public Integer get_stan() {
        return _stan;
    }
    public String get_symbol() {
        return _symbol;
    }
    public VAT getStopa_vat() {
        return stopa_vat;
    }
   
public void set_cena(String string) {
        _cena = string;
    }

    public void set_nazwa(String string) {
        _nazwa = string;
    }
    public void set_stan(Integer integer) {
        _stan = integer;
    }
    public void set_symbol(String string) {
        _symbol = string;
    }
    public void setStopa_vat(VAT vat) {
        stopa_vat = vat;
    }
}

Nic nie stoi na przeszkodzie, aby metody te zmodyfikować
tak, by niosły informacje o tym, jakie kontrolki mają być użyte, tzn.

public String get_cena(JTextField jtextField) {
    return _cena;
}

Wzorzec projektowy GuiVariable – definicja problemu

Ponieważ opracowanie wzorca projektowego jest ciekawym
zagadnieniem, zostanie ono zaprezentowane bliżej. W katalogu wzorców
projektowych [1], dla każdego zaprezentowanego tam wzorca podane są pewne
informacje m.in.:

  • Cel,

  • Powód, dla którego zdefiniowano wzorzec,

  • Zastosowanie,

  • Składowe,

  • Sposób współpracy,

  • Konsekwencje użycia,

  • Implementacja,

  • Gdzie zastosowano wzorzec.

Cel: Umożliwić automatyczne generowanie interfejsu użytkownika
dla danej klasy użytkowej na podstawie jej kodu źródłowego.

Powód: Jak wykazuje analiza istniejących projektów,
znaczne nakłady związane są z realizacją graficznego interfejsu użytkownika.
Należy zaprojektować rozwiązanie pozwalające zautomatyzować ten proces,
poprzez tworzenie interfejsu w czasie wykonania aplikacji, lub/i wygenerowanie
interfejsu użytkownika.

Wprawka – proste rozwiązanie

Zanim zostanie pokazana propozycja rozwiązania, zostanie
zaprezentowana pewna wprawka; stanowić ona będzie przypomnienie dwóch
prostych wzorców, jak również prezentację pewnych bardzo przydatnych cech języka
Java.

  • Dynamiczne pobieranie informacji o polach i
    metodach:

import java.lang.reflect.Field;

import java.lang.reflect.Method;


public class Main {

    public static void main(String[] args) {
   
Field fields[] = Towar.class.getFields();
    System.out.println("fields");
    for (int i = 0;i<fields.length;i++){
        System.out.println(fields[i].toString());
    }

    System.out.println("nmethods");
   
Method methods[] = Towar.class.getMethods();

    for (int i = 0;i<methods.length;i++){
       
System.out.println(methods[i].toString());
    }

    Towar towar = new Towar();
    Field _cena = fields[2];
    try{
        _cena.set(towar,"10");
    }
    catch(IllegalAccessException e){
        e.printStackTrace();
    }

    System.out.println("towar.cena= "+towar._cena);

    }

}

Powyższy program pobiera informacje o polach oraz metodach
klasy towar. Informacja związana z polem klasy znajduje się w obiekcie
klasy typu Field, a informacja dotycząca metody w obiekcie klasy Method. Do
przypisania konkretnej wartości danemu polu obiektu można użyć metody set
klasy Field; pierwszym jej argumentem jest obiekt, drugim wartość, jaka ma być
przypisana temu polu. W zaprezentowanym przykładzie polu _cena obiektu towar
jest przypisana wartość „10”.

Poniżej zaprezentowano przykładowy wynik działania powyższego
programu:

fields

public java.lang.String Towar._symbol

public java.lang.String Towar._nazwa

public java.lang.String Towar._cena

public java.lang.Integer Towar._stan

public VAT Towar.stopa_vat

 

methods

public java.lang.String Towar.get_cena()

public java.lang.String Towar.get_nazwa()

public java.lang.Integer Towar.get_stan()

public java.lang.String Towar.get_symbol()

public VAT Towar.getStopa_vat()

public void Towar.set_cena(java.lang.String)

public void Towar.set_nazwa(java.lang.String)

public void Towar.set_stan(java.lang.Integer)

public void Towar.set_symbol(java.lang.String)

public void Towar.setStopa_vat(VAT)

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final void java.lang.Object.wait() throws
java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws
java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws
java.lang.InterruptedEx-ception

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

towar.cena= 10

Na analogicznej zasadzie można wywoływać metody obiektu każdej
klasy.

Poniżej zaprezentowano prosty dialog, który pozwala na
edycję pól typu String dowolnej klasy. Kwestia rozszerzenia rozwiązania na
inne typy pól nie jest skomplikowana. Argumentem wywołania konstruktora jest
obiekt, dla którego ma być dynamicznie stworzony interfejs użytkownika. Przed
stworzeniem pól edycyjnych pobierane są opisy pól obiektu. W pętli
sprawdzany jest typ pola; jeśli pole jest typu String, to tworzy się dla niego
tekstowe pole edycyjne. Pole edycyjne inicjuje się wartością odpowiadającego
mu pola obiektu. Po wciśnięciu przycisku, polom obiektu przypisuje się wartości
odpowiadających im pól edycyjnych formatki. W podanym przykładzie ograniczono
się do pól typu String – rozszerzenie rozwiązania na inne typy nie jest
skomplikowane. Dla porządku warto wspomnieć, iż w przypadku błędnego odwołania
do pobranego dynamicznie pola, zostanie wygenerowany wyjątek
Ille-galAccessException.

import java.awt.event.MouseAdapter;

import java.awt.event.MouseEvent;

import java.lang.reflect.Field;

import
javax.swing.BoxLayout;

import javax.swing.JButton;

import javax.swing.JDialog;

import javax.swing.JFrame;

import javax.swing.JLabel;

import javax.swing.JTextField;


public class EditDialog extends JDialog {

    Field fields[];
    JTextField controls[] =new JTextField[20];

    Object obj;

    public EditDialog(Object object){
        super((JFrame)null,true);

    obj = object;

    getContentPane().setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));

    this.setSize(200,350);

   
fields=object.getClass().getFields();

    for
(int i = 0;i<fields.length;i++){
    JTextField jtf = new JTextField();
    Field field = fields[i];
    if (field.getType() == String.class){

    controls[i]= jtf;
    getContentPane().add(new JLabel(field.getName()+":"));
    getContentPane().add(jtf);

    try{
        jtf.setText((String)field.get(object));
    }catch(IllegalAccessException e){
        e.printStackTrace();

        }
    }
}

JButton submitButton = new JButton();
getContentPane().add(submitButton);

submitButton.addMouseListener(new MouseAdapter(){
    public void mousePressed(MouseEvent evt){
        submitPressed();

    }

});

}

private void submitPressed(){

    for (int i = 0;i<fields.length;i++){
        Field field = fields[i];
        JTextField component = controls[i];
         if (field.getType() == String.class){
        try{
   
         field.set(obj,component.getText());

        }catch(IllegalAccessException e){
           
e.printStackTrace();
    }

        }
        }
        dispose();
    }

}

Aby zastosować własne nazwy pól edycyjnych, można użyć
tablicy haszującej. Zaprezentowane rozwiązanie sprawdza się w języku Java,
gdzie – jak już powiedziano – można dynamicznie pobierać pola i
metody klasy; nie może być jednak wykorzystane tak prosto w języku C++. Poza
tym, nie zapewnia ono walidacji wprowadzonych wartości.

Można natomiast przy pomocy wzorca projektowego Prototype
[1] zapewnić zastosowanie różnych kontrolek dla różnych typów danych. Można
tutaj wykorzystać następującą strategię:

  • Sprawdź, czy dla danej nazwy atrybutu zdefiniowano
    kontrolkę

  • Jeśli tak – użyj jej

  • Sprawdź, czy dla typu atrybutu zdefiniowano
    kontrolkę

  • Jeśli tak – użyj jej

  • Jeśli nie – użyj kontrolki domyślnej.

Wzorzec projektowy GuiVariable – bardziej zaawansowane rozwiązanie

Prezentowany pomysł polega na tym, aby zmienną połączyć
z kontrolką, która służy do edytowania jej wartości. Można powiedzieć, że
koncepcja jest podobna do wzorca projektowego Obserwator (ang. Observer), który
opisano w literaturze [1].

Wzorzec ten stanowi podstawę architektury
model-widok-kontroler (MVC), która została zastosowana w bibliotece MFC firmy
Microsoft – Visual C++.

Wzorzec ten stanowi również podstawę obsługi zdarzeń w języku
Java.

Proponowane poniżej rozwiązanie zakłada powiązanie każdej
zmiennej, którą użytkownik może edytować z kontrolką do tego służącą.
Sama idea jest bardzo prosta, trzeba natomiast pamiętać o różnych sposobach
prezentacji różnych typów danych i o różnej konwersji. Chodzi o to, by
zamiast używać klasy String przy definicji pola nazwisko, używać
innej klasy, która dostarczy mechanizmów potrzebnych do edycji tego pola.
Najprościej byłoby wprowadzić nową klasę:

public class GuiObject extends Object{
Variable editor=null;
...
}

Dla konkretnego typu zmiennej należałoby z powyższej klasy
dziedziczyć i dostarczyć odpowiednich mechanizmów edycyjnych. Niestety, z
wielu klas, które pozostają w polu zainteresowania projektantów omawianego
rozwiązania, nie można (w języku Java) dziedziczyć, ponieważ zdeklarowano
je ze specyfikatorem final, ze względu na efektywność implementacji.
Trzeba więc tworzyć klasy, które zawierają składowe odpowiednich typów. Jeżeli
jednak nie będziemy się bezpośrednio odwoływać do pól klasy, używając
zamiast tego odpowiednich metod, różnica będzie niezauważalna.

Rdzeń powyższego rozwiązania tworzą:

  • interfejs GUI,

  • klasa GuiObject,

  • klasa VariableEditor,

  • klasa BiznesDialog.

Pierwsza z klas jest korzeniem hierarchii zmiennych, druga
– odpowiadających im komponentów edycyjnych. Ostatnia z klas jest
„automatycznym” dialogiem, który służy do edytowania zmiennych.

Wzorzec został przedstawiony na rysunku 1; widać, że w
jego obrębie występują dwie hierarchie klas – pierwsza odpowiada klasom
„GUI”, druga zaś ich edytorom. Klasa biznesowa, która ma mieć
„automatyczny” interfejs użytkownika, musi implementować interfejs
GUI. Przykładowa klasa biznesowa – Towar – nie należy do żadnej
hierarchii, w związku z użyciem wzorca. Taka przynależność – wydawać
by się mogło, że uzasadniona – byłaby w rzeczywistości głęboko
szkodliwa. Mogłaby ona uniemożliwiać programiście tworzenie hierarchii
obiektów biznesowych.


Rys. 1.
Wzorzec projektowy GuiVariable

Na rysunku 2 przedstawiono diagram sekwencji, który w
uproszczeniu opisuje działanie wzorca.


Rys. 2.
Diagram sekwencji wzorca GuiVariable

Klasa biznesowa sama dostarcza mechanizmów do definiowania
swoich atrybutów. W ciele metody define tworzony jest automatyczny
dialog, argumentami jego konstruktora są: nazwa dialogu i atrybuty „GUI”.
Dialog w swoim konstruktorze pobiera nazwy swoich tekstowych pól edycyjnych.
Pola edycyjne są inicjowane wartościami odpowiednich atrybutów. Po przyciśnięciu
przycisku zatwierdzającego, atrybutom przypisywane są wartości wprowadzone do
odpowiadających im pól edycyjnych.

public interface GUI {
    public void define();
}
 
public abstract class GuiObject {
    public VariableEditor editor;
    String name;
 
    public GuiObject(String nameIn){
        name = nameIn;
    }

    public VariableEditor getVariableEditor(){
   
     return
editor;

    }
 
    public String getName(){
        return name;
    }
    }
 
public abstract class VariableEditor {
    GuiObject guiObject = null;
 
    public VariableEditor(GuiObject guiObjectIn){
       
guiObject = guiObjectIn;
   
}
 
   
public abstract boolean
get();
   
public abstract boolean set();
 
    public abstract JComponent getComponent();
}
 
 
public class GuiString extends GuiObject{
 
    public String variable = null;
 
    public GuiString(String nameIn){
    
   

super(nameIn);
       
editor = new StringEditor(this);
       
variable = new String();
   
}
}
 
public class StringEditor extends VariableEditor {
 
    private JTextField jtf;
 
    public StringEditor(GuiObject guiObject){
       
super(guiObject);
   
}
 
 
    public boolean get() {
       
String variable = ((GuiString)guiObject).variable;
   
if (jtf!=null){
   
jtf.setText(variable);
   
    
return true;

   
}
   
    
return false;

   
}
 
    public JComponent getComponent() {
        
if (jtf==null){
           
jtf = new JTextField();
       
}
       
get();
        
return jtf;
}
public boolean set() {
 
    if (jtf!=null){
        
   
String variable = jtf.getText();
        
   
((GuiString)guiObject).variable=variable;
   
        
return true;

           
}
   
 
   

return false;

   
}
 
}
 
public class Towar implements GUI{
   
public GuiString _symbol, _nazwa;
   
public GuiString _cena;
 
    public Towar(){
       
_symbol = new GuiString("Symbol");
       
_nazwa = new GuiString("Nazwa");
       
_cena = new GuiString("Cena");
   
}
 
    public String get_cena() {
        
return _cena.variable;
   
}
 
    public String get_nazwa() {
        
return _nazwa.variable;
   
}
 
    public String get_symbol() {
        
return _symbol.variable;
   
}
 
    public void set_nazwa(String string) {
       
_nazwa.variable = string;
   
}

   
public void set_symbol(String string) {
       
_symbol.variable = string;
   
}

   
public void define(){
       
BiznesDialog biznesDialog = new BiznesDialog("Towar",new
GuiString[]{_nazwa,_symbol,_cena});

       
biznesDialog.show();
   
}
}
public class BiznesDialog extends JDialog {
 
    GuiObject guiObjects[] = null;
 
    public BiznesDialog(String name,GuiObject guiObjectsIn[]){
            
   

super((JFrame)null,true);
 
        guiObjects = guiObjectsIn;
 
        getContentPane().setLayout(new BoxLayout(this.getContentPane(),BoxLayout.Y_AXIS));
 
            
this.setSize(200,350);
       
this.setTitle(name);
 
      Container container = getContentPane();
 
          
for
(int i=0;i<guiObjects.length;i++){
               
GuiObject object = guiObjects[i];
               
container.add(new Jlabel(object.getName()));
               
container.add(object.getVariableEditor().getComponent());
          
}
 
       JButton submitButton = new
JButton();

        
  container.add(submitButton);
 
       submitButton.addMouseListener(new
MouseAdapter(){

                    
public void mousePressed(MouseEvent evt){
                              
submitPressed();
         
}});
 
         
}
 
    private void submitPressed(){
 
   
for
(int i=0;i<guiObjects.length;i++){
           
GuiObject object = guiObjects[i];
           
object.getVariableEditor().set();
   
}
   
dispose();
   
}
 
}
public class Main {
 
    public static void main(String[] args) {
   
 
   
Towar towar = new Towar();
 
    towar.set_nazwa("Ploug Magazine");
    towar.set_symbol("PL1");
    towar.define();
    System.out.println("towar._nazwa="+towar.get_nazwa()
+"ntowar._symbol="+towar.get_symbol());
 
    }
 
}

Oczywiście zaprezentowane idee dotyczące automatycznego
generowania zasobów stanowią punkt wyjścia dla komercyjnych zastosowań. W
literaturze mówi się o użyciu języka XML do definiowania interfejsu użytkownika.
Główny problem dotyczy dostarczenia informacji o rozłożeniu poszczególnych
kontrolek oraz o prezentacji bardziej złożonych struktur. Zaprezentowaną ideę
należy oddzielić od projektowania interfejsu metodą „przeciągnij i upuść”
– należy ją rozumieć jako rozwiązanie pozwalające skupić się na
projektowaniu klas użytkowych. Klasy, które implementują uniwersalne dialogi
i formatki, mogą być przerobione tak, aby generowały kod odpowiednich dialogów
w języku Java. W takim przypadku można zbudować bibliotekę do automatycznego
generowania interfejsu użytkownika. Wygenerowany interfejs można poprawić ręcznie
tak, aby miał odpowiedni wygląd i był ergonomiczny. Zdaniem autora,
zautomatyzowanie całego procesu może przyczynić się do uzyskania
ujednoliconego wyglądu interfejsu użytkownika.

O zasobach słów kilka

Jak już powiedziano, każda aplikacja zarządza zasobami
– mogą to być np. ikony, łańcuchy znaków, itp. Dobrze jest różne
zasoby „przechowywać” w jednej zbiorczej klasie:

public class ResourceManager {

    Hashtable resources = new Hashtable();

    public void registerImageIcon(ImageIcon imageIcon,String key){
        resources.put(key,imageIcon);
    }

    public ImageIcon getImageIcon(String key){
         return (ImageIcon)resources.get(key);
    }

    public void registerString(String string, String key){
        resources.put(key,string);
    }

    public String getString(String key){
         return (String)resources.get(key);
    }

   
public static void main(String args[]){
        ResourceManager resourceManager = new
ResourceManager();
        resourceManager.registerString("Dane Pacjentów","dp1");
        resourceManager.registerString("Dane Osobowe","do1");
        System.out.println("do1="+resourceManager.getString("do1"));
    }

}

Pokazany powyżej zarządca zasobów może przechowywać
obiekty klas ImageIcon i String. Zaprezentowaną definicję można oczywiście
rozszerzyć tak, aby przechowywać obiekty różnych klas. Warto zauważyć
istotną różnicę pomiędzy prezentowanym rozwiązaniem, a wzorcem projektowym
Prototype. Zarządca przechowuje obiekty, ale ich nie klonuje. Jeśli
trzeba sklonować obiekt (np. gdy ikona ma jednocześnie występować w kilku
miejscach) można to zrobić „poza” wzorcem. Wzorzec projektowy Prototype
stosuje się wtedy, kiedy zachodzi potrzeba utworzenia obiektu na podstawie
istniejącego obiektu. W omawianym w poprzednich odcinkach przykładzie, wzorzec
mógł być użyty do definiowania różnych specjalności lekarzy.

Literatura

[1] Gamma at al., Design Patterns, Addinson-Wesley
1995
[2] D. Astels, G. Miller, M. Novak, Extreme Programming,
Helion 2002
[3] http://www.catena.co.jp/english/e-contents/elyee/what_lyee.htm