Projektowanie i wytwarzanie aplikacji internetowych

część 6

Sebastian Wyrwał

Dotychczasowe rozważania obejmowały zarówno teorię, jak i
przegląd pewnych popularnych technologii i rozwiązań. Przypomnijmy, iż omówiono
dotychczas model COM, architekturę CORBA, specyfikację CGI – prezentując
odpowiednie przykłady na ich zastosowanie. W zakresie zagadnień teoretycznych
omówiono model RM-ODP oraz protokoły związane z Internetem.

Niniejszy artykuł jest pewnym odejściem od teorii i stanowi
wprowadzenie do bardziej złożonego przykładu budowy aplikacji internetowej.
Prezentowany przykład dotyczy edukacji z zastosowaniem Internetu i jest dość
rozbudowany – tak, aby można było ukazać pewne problemy związane np. z
przekazywaniem obiektów należących do pewnych hierarchii lub wywoływaniem
metod obiektów zdalnych, które znajdują się po stronie klienta przez serwer.
W systemie mają istnieć ponadto różne rodzaje klientów i różne rodzaje usług.
Wymagania dotyczące systemu są niepełne, co ma wymusić zastosowanie rozwiązań,
które są odporne na zmieniające się i nie dospecyfikowane wymagania.
Prezentowany przykład – pomimo pozornie akademickiego zastosowania – zbliża się złożonością do rzeczywistych rozwiązań, nie będąc
tylko i wyłącznie systemem bazodanowym. W artykule są ponadto poruszane pewne
zagadnienia związane z projektowaniem klas interfejsowych, które współpracują
z obiektami zdalnymi. Kluczowym celem tego artykułu jest pokazanie sposobu
wytwarzania systemu rozproszonego opartego o Internet, oraz ukazanie metody
rozwiązywania konkretnych problemów implementacyjnych. Procesy analizy i
projektowania zostały pokazane w dużym uproszczeniu. Uproszczenie to osiągnięto
poprzez ograniczenie się do pokazania artefaktów tych procesów, z pominięciem
szczegółowego ukazania i omówienia wszystkich etapów analizy i
projektowania, na korzyść zagadnień implementacyjnych. Samo projektowanie
obiektowe i język UML są przedstawiane w osobnym cyklu, artykuł ten nie ma
bowiem ukazywać formalnej strony projektowania, ani wytworzenia systemu ściśle
zgodnie z jakimś modelem cyklu życia oprogramowania.

Do budowy aplikacji zostanie wykorzystany język Java,
biblioteka Swing, architektura CORBA oraz (w późniejszym etapie) Servlety. Użycie
tych ostatnich pozwoli korzystać z systemu z wykorzystaniem zwykłej przeglądarki
internetowej. Jeśli chodzi o architekturę CORBA, to zostaną pokazane – w
praktyce – bardziej zaawansowane rozwiązania, takie jak przekazywanie obiektów
należących do hierarchii i związane z tym użycie klas wiążących (ang.
tie classes)
. Narzędzie idlj wchodzące w skład pakietu j2sdk1.4.0
generuje inne pliki potrzebne do implementacji odpowiednio serwera oraz klienta,
co zostanie również omówione w tym artykule. Ponadto implementacja ta oparta
jest o POA (ang. portable object adapter). Do budowy graficznego interfejsu użytkowania
zostanie wykorzystana biblioteka Swing.

Przykładowa aplikacja

We wstępie podano narzędzia oraz technologie, przy użyciu
których zostanie zaimplementowana przykładowa aplikacja. Wybór technologii
został podyktowany przede wszystkim łatwością języka oraz łatwością użycia
architektury Corba oraz tym, iż wiele narzędzi związanych z językiem Java, w
tym pakiet jsdk1.4.0 mogą być używane bezpłatnie i to również do
celów komercyjnych. Język Java jest niezależny od platformy sprzętowej i
systemowej, a obiekty związane z architekturą CORBA mogą istnieć w różnych
systemach. Model COM jest koncepcyjnie znacznie trudniejszy i użycie go
ogranicza się wyłącznie do systemu Windows.

Prezentowana aplikacja rozproszona ma służyć do obsługi
„zdalnej” szkoły bądź uczelni. Problem nie jest aż tak
wyimaginowany jak mogłoby się początkowo wydawać – w Nadrenii Północnej
Westfalii istnieje uniwersytet kształcący korespondencyjnie. Prezentowany
pomysł może być wykorzystany (po rozbudowaniu) do prowadzenia zdalnych kursów
lub szkoleń. System ma działać zarówno w typowej architekturze klient-serwer
z dedykowanym klientem jak i poprzez przeglądarkę internetową. Poniżej
znajduje się opis rzeczywistości takiego systemu, uwzględniający zarówno
wymagania funkcjonalne jak i niefunkcjonalne. Poniższy opis jest w pewnym
stopniu uproszczony (np. wykład prowadzi jeden wykładowca, istnieje jeden
kierunek kształcenia, przypisane na stałe przedmioty bez możliwości ich
wybierania, przedmiot trwa jeden semestr) oraz w pewnym stopniu nie
dospecyfikowany. Jest to jednak jak najbardziej usprawiedliwione, ponieważ, jak
się okaże w dalszej części, cały system ma być w dużym stopniu ramą
pozwalającą na zdalne, rozproszone interakcyjne nauczanie w różnych
dziedzinach.

Kształcenie zdalne

System służy do prowadzenia zdalnej edukacji za pośrednictwem
Internetu i może być wykorzystywany na różnych poziomach kształcenia
i do nauczania różnych przedmiotów. Osoba ucząca się – student – może za jego pośrednictwem, po wcześniejszej rejestracji, uczestniczyć w różnych formach zajęć, co obejmuje zarówno udział w
zdalnych wykładach, ćwiczeniach, zaliczanie kolokwiów, egzaminów oraz
konsultacje. Możliwe jest również pobieranie materiałów pomocniczych.
Dany semestr (poziom kursu) obejmuje różne przedmioty. Do przedmiotu
przypisany jest wykładowca oraz osoby odpowiedzialne za prowadzenie ćwiczeń.
Wykładowca może być równocześnie prowadzącym. System ma pozwalać na
przeprowadzanie kolokwiów i egzaminów. Za kolokwium (egzamin) student
otrzymuje ocenę, a po sprawdzeniu ćwiczeń otrzymuje raport dotyczący
tego, co trzeba jeszcze opanować itp. Przedmiot składa się z wykładów,
a wykład z jednostek. System przechowuje dane o studentach, planach ich
studiów, zaliczeniach i ocenach. Zaliczenia semestru dokonuje kierownik
(dziekan).

Współpraca studenta z systemem może odbywać się w
trybie interakcyjnym lub zwykłym. Pierwszy z trybów dotyczy wykładów,
ćwiczeń, egzaminów konsultacji oraz kolokwiów. Drugi dotyczy
samodzielnej pracy studenta a więc realizacji zadań, pobierania materiałów.
Tryb interakcyjny polega na tym, że w systemie jest równocześnie
zalogowany prowadzący. W trybie zwykłym nie jest konieczne. Tak, więc
tryb interakcyjny odpowiada konwencjonalnemu kontaktowi studenta z prowadzącym
a tryb zwykły pracy własnej. Jeśli zajęcia są typową prezentacją
(wykład, wprowadzenie do ćwiczeń) to przejściem do następnych
jednostek steruje prowadzący. W przypadku kolokwiów i egzaminów oraz części
ćwiczeń, na której zadania rozwiązują studenci, przejściem do
kolejnej jednostki steruje student.

Student: rejestruje się w systemie,
uczestniczy w zdalnych zajęciach, zdaje egzaminy i kolokwia. Uczestnictwo
w zajęciach polega na pobieraniu ich jednostek. Możliwe jest
skorzystanie z pomocy i poproszenie prowadzącego o pomoc, za wyjątkiem
kolokwiów i egzaminów. Student może pobierać materiały pomocnicze
przygotowane przez prowadzącego. Może poprosić prowadzącego o sporządzenie
raportu. Steruje przejściem do kolejnej jednostki.

Prowadzący: sprawdza obecność, zadaje
pytania studentom, prowadzi zajęcia i konsultacje, daje zaliczenia, sporządza
na prośbę studenta raport, który ma być pomocny przy nauce
i wskazywać, czego należy się jeszcze douczyć. Steruje przejściem do
kolejnej jednostki.

Wykładowca: tak jak prowadzący z tym, że może
prowadzić wykład i przeprowadzać egzamin.

Dziekan: dokonuje wpisu na semestr, zalicza semestr, skreśla z listy studentów

Słownik systemu

  • Student – osoba ucząca się za pośrednictwem systemu do kształcenia zdalnego, ma imię, nazwisko,
    PESEL, adres, numer telefonu, adres e-mail, login oraz hasło.
  • Prowadzący – osoba prowadząca
    zdalne zajęcia, udzielająca konsultacji i sprawdzająca kolokwia ma
    login, hasło oraz tytuł i stanowisko.
  • Wykładowca – prowadzący, który
    prowadzi i przygotowuje „zdalny” wykład. Ma nazwisko, imię,
    PESEL, adres, tytuł, adres e-mail. Może przeprowadzić egzamin i
    prowadzić wykład.
  • Semestr – jednostka organizacji
    toku kształcenia.
  • Przedmiot – przedmiot
    nauczania, trwa jeden semestr. Ma nazwę. W skład przedmiotu może
    wchodzić maksymalnie jeden wykład i wiele ćwiczeń.
  • Wykład – wykład z danego
    przedmiotu, do wykładu przypisany jest wykładowca. Wykład ma termin.
    Wykład kończy się egzaminem. Na wykład zapisani są studenci. Wykład
    jest pojęciem ogólnym („Algebra I”)
    a nie realizacją konkretnych zajęć („Wykład z dnia
    01.04”).
  • Ćwiczenia – ćwiczenia
    towarzyszące danemu wykła-dowi, mają prowadzącego i termin. Ćwiczenia
    mają formę (projekt, seminarium, ćwiczenia tablicowe). Na ćwiczenia
    zapisani są studenci. Ćwiczenia są pojęciem ogólnym („Ćwiczenia
    z języka PASCAL”), a nie realizacją konkretnych zajęć.
  • Jednostka – elementarna składowa
    wykładu, ćwiczenia, egzaminu, kolokwium lub konsultacji. Istnieją różne
    rodzaje jednostek mające różną semantykę wykonania. Rodzaje te mogą
    być definiowane i wprowadzane do systemu w trakcie jego eksploatacji.
    Jednostka może generować punktację. Jednostki mogą być grupowane
    tak, aby, możliwy był podział materiału (wykład 1, wykład 2 itp.).
  • Dokumentacja kształcenia – informacje dotyczące uzyskanych zaliczeń, wpisów na semestr oraz inne
    zdefiniowane na poziomie funkcjonalnym informacje.
  • Egzamin – egzamin kończący
    dany wykład przeprowadzany przez wykładowcę, składa się z jednostek
    i posiada kryterium zaliczenia. Posiada datę i godzinę.
  • Kolokwium – egzamin
    przeprowadzany na ćwiczeniach lub wykładzie przeprowadzany przez
    prowadzącego, składa się z jednostek i posiada kryterium zaliczenia.
    Posiada termin.
  • Kryterium zaliczenia – określa
    odwzorowanie liczby punktów na ocenę.
  • Raport – informacja dla
    studenta dotycząca postępów w nauce, może zawierać uwagi lub niewiążącą
    ocenę dla informacji studenta.
  • Rejestracja – Przystąpienie do
    zdalnego kształcenia – obejmuje podpisanie umowy i ewentualne
    wniesienie opłaty.
  • Materiały pomocnicze – skrypty, materiały do ćwiczeń, zadania, kody źródłowe itp. dostępne
    w formie elektronicznej.
  • Dziekan – wykładowca kierujący
    kształceniem zdalnym i dokonujący wpisu na semestr, zaliczenia
    semestru i skreślenia z listy studentów. Zatrudnia wykładowców.
  • Ocena – ma wartość bdb, db,
    dst, ndst. Wiąże się z nią data uzyskania.
  • Zaliczenie semestru – stwierdzone przez dziekana zaliczenie wszystkich przedmiotów i zdanie
    wszystkich egzaminów w danym semestrze, uprawnia do wpisu na następny
    semestr. Jego brak w terminie określonym
    w parametryzacji powoduje skreślenie z listy studentów.
  • Plan kształcenia – odwzorowuje
    numer semestru na zestaw przedmiotów.
  • Podział godzin – określa,
    kiedy odbywają się jakie zajęcia.
  • Wpis na semestr – potwierdzenie
    faktu wpisu na pierwszy lub kolejny semestr kształcenia. Wpis na
    kolejny semestr, za wyjątkiem pierwszego, następuje po zaliczeniu
    semestru poprzedniego. Do danego semestru są przypisane przedmioty.
  • Termin – określa, kiedy
    odbywają się zajęcia („Poniedziałek 14:45”).
  • Tryb – określa, czy student
    wchodzi w bezpośrednią interakcję z prowadzącym (interakcyjny) czy
    nie (zwykły).

System ma być dostępny przy pomocy dedykowanego
klienta, przy czym istnieją różne rodzaje klientów: klient
studencki
, klient prowadzącego, klient służący do zarządzania
sprawami formalnym
(Dziekan). Zarówno studenci jak i prowadzący
mogą być rozproszeni. Czynności dotyczące prezentacji oraz dostęp
do materiałów pomocniczych mogą być dokonywane za pomocą zwykłej
przeglądarki WWW. Każdorazowe użycie systemu wymaga logowania zarówno
po stronie prowadzącego jak i studenta. Równocześnie może odbywać
się wiele wykładów i ćwiczeń.


Rys. 1. Przykładowa struktura systemu do zdalnego kształcenia

Nie wnikając w szczegóły metodologii obiektowych, z powyższego
opisu wynika, iż w systemie istnieją dwie podstawowe hierarchie klas. Pierwsza
z nich to osoby: Student, Prowadzący, Wykładowca, Dziekan.
Druga to jednostki (składające się na wykłady itp.), nie jest ona
jednak zdefiniowania na tym etapie projektowania. Różne jednostki mogą
wykonywać różne czynności np. wypisać napis, pobierać napis, wyświetlać
rysunek, prezentację itp. Możliwość definiowania nowych typów jednostek
jest bardzo istotna, choćby dlatego, iż zdalne kształcenie może dotyczyć
zarówno języka obcego, matematyki jak i programowania. Powoduje to, że
potrzebne są różne formy prezentacji i interakcji z użytkownikiem. Trudno
jest jednak na etapie analizy określić choćby skończony ich zestaw. Można
za to powiedzieć, że jednostka jest pewnym samodzielnym bytem, który może być
wykonany, z czym z kolei może wiązać się wygenerowanie pewnej punktacji.
Jednostka może pobierać dane od użytkownika oraz z systemu. Jest ona
odpowiedzialna za wyświetlenie, pobranie lub odtworzenie danych. W przypadku,
kiedy ma ona generować punkty (czyli wtedy, gdy wchodzi w skład egzaminu lub
kolokwium), musi dysponować mechanizmem oceniania. Oczywiście ocenianie takie
może wymagać interakcji z prowadzącym – musi się ona jednak odbywać w
ramach tej samej jednostki. Preferowana jest sytuacja, kiedy jednostki są tak
przygotowane, aby mogły same, bez pomocy prowadzącego dokonywać oceny. Oczywiście
nie jest to możliwe we wszystkich rodzajach kształcenia. Jeśli chodzi o
relacje asocjacji, to całe kształcenie składa się z semestrów a semestr z
przedmiotów. Przedmiot obejmuje wykład oraz ćwiczenia. Obie formy składają
się z omówionych poprzednio jednostek, podobnie jak egzaminy i kolokwia. Główna
trudność przy implementacji takiego systemu wiąże się z określeniem
bazowej klasy dla jednostek i ich klasy interfejsowej oraz z przekazywaniem
obiektów należących do złożonych hierarchii. Poniżej znajduje się opis różnych
grup klas w systemie:

Student, Prowadzący, Wykładowca, Dziekan

Ze słownika systemu można wyodrębnić następujące klasy
odpowiadające osobom uczestniczącym w przedsięwzięciu: Student, Prowadzący,
Wykładowca, Dziekan.
Ponieważ pewne dane są wspólne dla powyższych
encji, sensowne jest wprowadzenie bazowej klasy Osoba. Klasa ta ma następujące
atrybuty, które wynikają bezpośrednio ze słownika systemu: imię,
nazwisko, PESEL, adres, numer telefonu, adres, e-mail, login, hasło.
Wartości
tych atrybutów powinny być przekazywane podczas tworzenia obiektu, czyli w
konstruktorze klasy. Klasa ta ma jedną metodę logowanie, która służy
do sprawdzenia, czy dana osoba ma uprawnienia do zalogowania się w systemie.

Przedmiot, Wykład, Ćwiczenia, Egzamin, Kolokwium

Klasa Przedmiot grupuje wykład z ćwiczeniami.
Przedmiot ma nazwę i znajduje się na określonym semestrze. Przedmiot może
mieć zero lub jeden wykładów oraz zero lub wiele ćwiczeń. Ponieważ pewne
cechy wykładu oraz ćwiczenia są wspólne (prowadzący) sensowne jest
wprowadzenie abstrakcyjnej klasy Zajęcia, która ma następujące
atrybuty: termin, prowadzącego, semestr i składa się
z jednostek. Klasa Jednostka zapewnia grupowanie materiału
(„wykład z 13 maja”, „kolokwium I”). Warunek, aby wykład
prowadził prowadzący będący wykładowcą jest realizowany w ten sposób, że
przy przypisaniu wykładowi prowadzącego sprawdza się, czy jest on wykładowcą.
Taki sposób realizacji wymagania nosi nazwę realizacji na poziomie
funkcjonalnym. Warto zauważyć, iż klasy Wykład, Ćwiczenia, Egzamin,
Kolokwium są pewnymi klasami zbiorczymi agregującymi jednostki i służącymi
do ich pobierania. Student rozwiązując kolokwium pobiera i realizuje kolejne
jego jednostki, po czym generowana jest ocena. Kryterium zaliczenia
jest również realizowane funkcjonalnie – osoba, która opracowuje
kolokwium lub egzamin określa, ile punktów trzeba zdobyć na daną ocenę.
Diagram klas przedstawiający związki zachodzące pomiędzy opisywanymi w tym
punkcie klasami (zapisany w języku UML1) znajduje się na rysunku 2.


Rys. 2. Diagram klas przedstawiający organizację zajęć

Odzwierciedla on odpowiedni fragment opisu rzeczywistości i
słownika systemu – jest on niestety dość skomplikowany. Organizację
zajęć można nieco „spłaszczyć” traktując kolokwia i egzaminy
(a przy okazji konsultacje) jako oddzielne typy zajęć. Diagram całego systemu
uwzględniający postulowane przed chwilą „przeróbki”
przedstawiono na rysunku 3. Diagram ten jest bardziej czytelny od tego na
rysunku 2, z tym, że nie gwarantuje on, że w skład przedmiotu wchodzi tylko
jeden wykład. Ograniczenie takie można jednak wprowadzić później – także na poziomie funkcjonalnym. Warto już na tym etapie rozważań nakreślić
główne decyzje projektowe oraz ewentualne trudności pojawiające się przy
projektowaniu i implementacji:

  • System jest otwarty od strony klasy Jednostka – powoduje to, iż metody tej klasy muszą być bardzo starannie
    zaprojektowane i przetestowane.
  • Klasy dziedziczące z klasy Jednostka muszą w
    podobny sposób realizować interakcję z użytkownikiem i przy pomocy
    takiego samego interfejsu.
  • Za prezentację jednostek po stronie klienta
    odpowiedzialna ma być klasa JPanelPrezentacji.
  • Klasa Jednostka (i jej klasy pochodne) mają znajdować
    się po stronie serwera a odpowiadające im klasy interfejsowe po stronie
    klienta.


Rys. 3. Diagram klas całego systemu

Wymagania takie powodują, że system będzie po części
rozwijany od dołu (and. Bottom – up) do góry. Jak już powiedziano, główny
nacisk projektowy powinien zostać położny na klasę Jednostka, która ma
odpowiadać za podstawową formę interakcji z użytkownikiem,
oraz na klasę
JPanelPrezentacji która jest jej klasą interfejsową.
Gdy obiekt pierwszej
z klas wchodzi w skład wykładu, odpowiada on kolejnemu krokowi prezentacji;
gdy wchodzi w skład ćwiczeń, może zarówno wyświetlać jak i pobierać
pewne informacje – przykładem może być zadanie studentowi pytania i
oczekiwanie na jego odpowiedź. Oczywiście ograniczenie działania sytemu do wyświetlania
tekstu i pobierania odpowiedzi byłoby poza obowiązującymi standardami.
Prezentacja może przybierać różne formy np. wyświetlenie obrazka,
odtworzenie pliku muzycznego itd. Forma reakcji użytkownika może być także
zróżnicowana – np. wpisanie tekstu, naciśnięcie klawisza. Dodatkowo, w
przypadku, gdy jednostka wchodzi w zakres egzaminu lub kolokwium, punkty za
odpowiedzi studenta są sumowane. Można przyjąć, iż jedna jednostka
odpowiada interakcji studenta z prowadzącym (bezpośredniej: „niech mi
Pan powie…” lub pośredniej, w postaci pytania na egzaminie2).
Warto więc zdefiniować jej podstawową funkcjonalność:

  • Wykonanie jednostki,
  • Ocena odpowiedzi studenta w obrębie tej jednostki,
  • Reakcja na błędną odpowiedź (powtarzamy aż do
    uzyskania poprawnej odpowiedzi, np. gdy uczymy, lub idziemy dalej, np.
    podczas egzaminu),
  • Sposób wykonywania jednostki (czy ma od razu przechodzić
    do następnej jednostki, czy oczekiwać na reakcję – umożliwia to
    łączenie jednostek i tworzenie większych struktur),
  • Akacja wykonywana przy przejściu do następnej
    jednostki,
  • Akcja wykonywana na początku po przejściu z poprzedniej
    jednostki,
  • Uzyskanie pomocy (np. podpowiedzi, pytanie do prowadzącego),
  • Serializacja danych (np. zapis do pliku i odczyt z
    pliku),
  • Określenie maksymalnej ilości punktów za daną
    jednostkę.

Poniżej znajduje się wstępna wersja szkieletu klasy Jednostka;
z punktu widzenia projektowania istotne są tylko następujące cechy: zestaw
publicznych składowych klasy; fakt, że klasa do prezentacji używać będzie
komponentu typu JPanelPrezentacji oraz to, iż wszystkie klasy dziedziczące z
tej klasy będą miały dokładnie taki sam publiczny interfejs.

import java.lang.System;
import java.util.Vector;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.io.*;
import java.lang.*;
public class Jednostka implements Runnable, Externalizable{
        private String _aName;
        private int _maxPunktow;
        private Student _student;
        private JPanelPrezentacji _textPane;
public Jednostka() {
}
public Jednostka(String aName){
            _aName = new String(aName);
}
public void MaxPunktow(int m){
        _maxPunktow = m;
}
public void run(){
}
public int Ocen(){
        return 0;
}
public int BlednaReakcjaUzytkownika(){
        return 0;
}
public int CzyCzekacNaNastepna(){
        return 1; // określa, czy jednostka ma czekać na naciśnięcie przycisku -
//przejście do następnej
}
String WezNazwe(){
        return _aName;
}
void UstawStudenta(Student student){
       
_student = student;
}
public void NaZakonczenie(){
}
public void NaPoczatek(){
}
public void writeExternal(ObjectOutput out) throws IOException{
}
public void Pomoc(){
}
public void readExternal(ObjectInput in)throws IOException{
}
public void ustawObszarRoboczy(JTextPane textPane){
        _textPane = textPane;
    }
}

Powyższa klasa jest pewną „przymiarką”, a mówiąc
ściśle – ma ona taką postać jak w systemie, który nie jest
rozproszony. Należy mieć świadomość tego, iż w systemie rozproszonym
obiekty tej (a właściwie dziedziczącej z niej) klasy znajdować się będą
po stronie serwera, natomiast obiekt klasy JPanelPrezentacji znajdować się będzie
po stronie klienta, co pokazano na rysunku 4. Aby uczynić zadość teorii, należy
przyznać, iż klasa odpowiedzialna za prezentację jest typowo techniczna i
wprowadzono ją na diagramie dlatego, że przyjęto pewne uproszczenia. Klasy
interfejsowe innych klas nie zostały uwzględnione na diagramie.


Rys. 4. Realizacja prezentacji

O POA słów kilka

Architektura CORBA została już omówiona3, niemniej
jednak postęp techniczny (a mówiąc ściślej wprowadzenie pakietu j2sdk1.4)wymaga
uaktualnienia pewnych informacji. W wersji tej używa się już POA (ang.
Portale Object Adapter), który pozwala na przenośność implementacji obiektów
pomiędzy różnymi ORB’ami. Powoduje to, iż narzędzie idlj
generuje nieco inne pliki, inaczej też implantuje się obiekt zdalny po stronie
serwera (w poprzedniej wersji dziedziczyło się z klasy _X_ImplBase.java,
wygenerowanej dla interfejsu X przez to narzędzie). Ponieważ odcinek
ten jest typowo praktyczny, zagadnienia te zostaną potraktowane w podobny sposób.
Dla poniższego interfejsu (plik: Test.idl):

interface Test{
          long f(in string a, out string b);
};

zostaną wygenerowane następujące pliki:

  • Po stronie serwera (wywołanie idlj -fserver
    test.idl)
    : Test.java, TestOperations.java, TestPOA.java

  • Po stronie klienta (wywołanie idlj -fclient
    test.idl):
    Test.java, TestOperations.java, TestHolder.java,
    TestHelper.java,_TestStub.java.

Implementacja obiektu zdalnego, który implantuje
funkcjonalność po stronie serwera polega na dziedziczeniu z klasy TestPOA,
co przedstawiono poniżej:

import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

public class MyImpl extends TestPOA {
       
public int f (String a, org.omg.CORBA.StringHolder b){
                   
System.out.println("a="+a);
                   
b.value = new java.lang.String("ala ma kota");
        return 1;
    }
}

Powyższa klasa (MyImpl) dziedziczy z klasy TestPOA i
implementuje metodę f z interfejsu Test. Jest to możliwe, gdyż klasa
bazowa implementuje interfejs TestOperations:

public abstract class TestPOA extends org.omg.PortableServer.Servant
implements TestOperations,org.omg.CORBA.portable.InvokeHandler,

który z kolei jest odpowiednikiem interfejsu zdefiniowanego
w pliku test.idl w języku Java. Warto zauważyć, że typowi „string”
przekazywanemu do metody (in) odpowiada typ „String” w języku
Java; temu samemu typowi, ale przekazywanemu z metody (przez zmienną),
odpowiada specjalny typ org.omg.CORBA.StringHolder. Obiekt tego typu jest jednak
tworzony po stronie wywołującej – w przykładowym kodzie tworzony jest
obiekt „zwykłej” klasy String, który następnie jest przypisywany
atrybutowi value obiektu b (klasy org.omg.CORBA.StringHolder). Klasa serwera
wygląda następująco:

import java.io.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.PortableServer.POA;

public class Serv{
        public static void main(String args[]){
        try{
               
ORB orb=ORB.init(args,null);
        POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
               
rootpoa.the_POAManager().activate();
               
MyImpl rej = new MyImpl();
               
org.omg.CORBA.Object ref = rootpoa.servant_to_reference(rej);
               
Test href = TestHelper.narrow(ref);
               
org.omg.CORBA.Object objRef =
               
orb.resolve_initial_references("NameService");
               
NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
               
String name = " Test101";
               
NameComponent path[] = ncRef.to_name( name );
               
ncRef.rebind(path, href);
               
System.out.println("Serwer działa ...");
               
orb.run();
        }catch (Exception e){
               
e.printStackTrace(System.out);
        }
    }
}

Działanie serwera zostanie przedstawione w telegraficznym
skrócie:

  1. Zainicjowanie lokalnego obiektu ORB’a,
  2. Pobieranie referencji do usługi RootPOA,
  3. Aktywacja zarządcy POA tak, aby rozpoczął on
    przetwarzanie żądań,
  4. Utworzenie obiektu „zdalnego”, który
    implementuje funkcjonalność po stronie serwera,
  5. Pobranie referencji (zgodnej z architekturą CORBA)
    skojarzonej ze stworzonym w poprzednim punkcie obiekcie,
  6. Pobranie referencji do usługi (serwera) nazw,
  7. Dowiązanie referencji do obiektu implantującego
    funkcjonalność z nazwą usługi (Test101) lub mówiąc inaczej,
    rejestracja tego obiektu w serwerze nazw.

import java.io.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;
public class Kli {
static Test test;
        public static void main(String args[]){
               
try{
        ORB orb = ORB.init(args, null);
        org.omg.CORBA.StringHolder s=new org.omg.CORBA.StringHolder("");
        org.omg.CORBA.Object objRef =
               
orb.resolve_initial_references("NameService");
        NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
        String name = "Test101";
        test = TestHelper.narrow(ncRef.resolve_str(name));
        System.out.println(test.f("aaa",s));
        System.out.println("s = "+s.value);
        }catch(Exception e){
        System.out.println("Błąd: "+e);
        e.printStackTrace(System.out);
        }
    }
}

Działanie klienta jest następujące:

  1. Zaincjowanie ORB’a,
  2. Utworzenie obiektu typu StringHolder dla wartości
    zwracanej z metody,
  3. Pobranie referencji do usługi nazw,
  4. Pobranie referencji do obiektu zdalnego,
  5. Wywołanie metod na rzecz referencji do obiektu zdalnego.

Delegacja i użycie klas wiążących

Niezależnie od tego, czy obiekt dostarczający funkcjonalności
po stronie serwera, jest zaimplementowany z użyciem POA, klasa która go
definiuje, dziedziczy z pewnej klasy wygenerowanej przez narzędzie idlj.
Przy prezentowanych akademickich przykładach nie stanowi to żadnego
problemu. W zastosowaniach praktycznych, gdy zachodzi potrzeba, aby klasa
dostarczająca funkcjonalności dziedziczyła z innej klasy, pojawia się
problem wynikający z tego, iż w języku Java nie ma dziedziczenia
wielokrotnego. W projektowanym systemie problem ten dotyczy, np. klas Student,
Prowadzący, Wykładowca, Dziekan,
jako że wszystkie dziedziczą pośrednio
lub bezpośrednio z klasy Osoba. Pozornie, podobna sytuacja dotyczyć będzie
klas dziedziczących z klasy Jednostka; różnica pomiędzy tymi
hierarchiami klas jest jednak bardzo duża. Różne osoby „robią” różne
rzeczy, co ma być rozróżniane – mają zatem dość znacznie różniące
się zestawy metod. Nie można osoby traktować jak interfejsu. Dziekan robi co
innego, niż student. „Podciąganie” metod do klasy bazowej i
tworzenie jednego interfejsu byłoby tutaj dużym błędem projektowym.
Odmiennie wygląda sprawa klas dziedziczących z klasy jednostka – jednym z założeń projektowych jest to, aby z różnych rodzajów jednostek
korzystało się identycznie. Mamy tutaj ewidentnie do czynienia z interfejsem.
Rozwiązaniem problemu, który pojawia się w hierarchii osób, jest delegacja.
Ponieważ jest ono ogólne i pojawia się w bardzo wielu miejscach (o ile nie
wszędzie), warto przedstawić je na rysunku:


Rys. 5. Delegacja

Istota rozwiązania polega na tym, że obiekt klasy A
przekazuje wywołania metod do obiektu klasy B tzn:

class A{
B b = new B();
        public int f(int a){
        return b.f(a);
        }
        public void g(){
                   
b.g();
        }
}

Klasą, która „coś robi” jest w rzeczywistości
klasa B. Zauważmy, że dzięki takiemu zabiegowi można mieć dwie,
niezależne od siebie hierarchie klas. Klasa A może dziedziczyć np. z klasy
XPOA, klasa B może dziedziczyć z dowolnej innej klasy. Dla przykładu z
poprzedniego rozdziału, jeżeli przy generowaniu plików potrzebnych do
implementacji serwera użyje się opcji -fserverTIE, zostanie
utworzona dodatkowa klasa TestPOATie.java, która dziedziczy z klasy
TestPOA.java. Przy zastosowaniu delegacji, klasa implementująca funkcjonalność
po stronie serwera nie dziedziczy z klasy TestPOA, ale implementuje interfejs
TestOperations; „reszta”4 tej klasy pozostaje bez zmian:

public class MyImpl implements TestOperations {
        public int f (String a, org.omg.CORBA.StringHolder b){
               
System.out.println("a="+a);
               
b.value = new java.lang.String("ala ma kota");
        return 1;
    }
}

Zmiany w klasie Serv (w trzech wytłuszczonych liniach)
polegają na tym, iż tworzony jest obiekt implementujący funkcjonalność po
stronie serwera (klasy MyImpl), który jest następnie rzutowany do referencji
do interfejsu TestOperations. Obiekt ten jest używany jako argument w
konstruktorze klasy wiążącej (TestPOATie) i jest później używany zamiast
obiektu implementującego funkcjonalność z poprzedniego rozdziału.

public class Serv{
        public static void main(String args[]){
        try{
        ORB orb=ORB.init(args,null);
            POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
            rootpoa.the_POAManager().activate();
   
        
TestOperations rej = new MyImpl();
            TestPOATie del = new TestPOATie(rej);
            org.omg.CORBA.Object ref = rootpoa.servant_to_reference(del);
           
Test href = TestHelper.narrow(ref);
            org.omg.CORBA.Object objRef =
            orb.resolve_initial_references("NameService");
            NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
        String name = "Test101";
            NameComponent path[] = ncRef.to_name( name );
        ncRef.rebind(path, href);
            System.out.println("Serwer działa ...");
        orb.run();
        }catch (Exception e){
            e.printStackTrace(System.out);
        }
    }
}

Obsługa zdarzeń a CORBA

Dotychczas rozważane przykłady użycia tej architektury zakładają,
iż interakcja jest zawsze inicjowana od strony klienta. Założenie takie nie
sprawdza się niestety w projektowanym systemie. Z opisu rzeczywistości wynika,
iż przejściem do następnych jednostek może sterować prowadzący. Obiekt
reprezentujący jednostkę (klasy Jednostka) przetwarza się po stronie serwera,
ale jego obiekt interfejsowy (klasy JPanelPrezentacji) znajduje się po stronie
klienta (rysunek 4). Inicjatorem interakcji jest więc w takim wypadku serwer.
Rozwiązanie, którym klient odpytywałby serwer o zmiany jego stanu (a
konkretnie o obiekt klasy Jednostka) w ogóle nie wchodzi w grę. Zanim
zostanie zaprezentowane konkretne rozwiązanie, należy przeanalizować rozwiązanie
oparte na wzorcu projektowym MVC (ang. Model View Controller), które jest
stosowane również w języku Java do obsługi zdarzeń. Idea jest prosta, lecz
stanowi niezwykle silne rozwiązanie. Obiekt nasłuchujący, który jest
zainteresowany stanem drugiego, musi się w tym drugim zarejestrować. Obiekt będący
źródłem zdarzenia „wie” wówczas, jakie obiekty ma powiadomić o
zdarzeniu. Na rysunku 5 obiekt Z jest źródłem zdarzeń, na które nasłuchują
obiekty N1 i N2. Linią przerywaną zaznaczono fakt, iż obiekty się w przeszłości
zarejestrowały, linią ciągłą zaś przedstawiono powiadomienie o zdarzeniu,
które właśnie zaszło.


Rys. 6. Wzorzec MVC

W projektowanym systemie jedynym miejscem, gdzie
powiadamianie o zdarzeniach będzie musiało być realizowane zdalnie a więc
przy użyciu architektury CORBA, jest jak wspomniano współpraca pomiędzy
klasami dziedziczącymi z klasy Jednostka a obiektami JPanelPrezentacji.
Ponieważ interfejs publiczny pierwszej z klas jest już zaprojektowany, trzeba
zastanowić się nad interfejsem drugiej z klas. Autor jest zdania, iż trudno
to uczynić w całkowitym oderwaniu od implementacji. Znacznie łatwiej jest
przeanalizować komponent, który będzie w rzeczywistości stosowany do
realizacji prezentacji a następnie wyspecyfikować interfejs. Kolejnym krokiem
będzie rozważenie, jakie obiekty mogą być potrzebne do prezentacji. Wstępna
analiza pokazuje, iż do prowadzenia prezentacji mogą być potrzebne różne
pliki: graficzne, muzyczne oraz łańcuchy znaków. Komponentem z biblioteki
Swing, który bardzo dobrze nadaje się zarówno do prezentacji tekstów jak i różnego
rodzaju plików, a nawet kontrolek (tj. przycisków itp.) jest klasa JTextPane.
Należy sobie zdać sprawę z tego, iż np. wyświetlanie tabelki powinno być
realizowane lokalnie w tym sensie, że serwer przesyła informację, iż należy
narysować tabelkę o danych parametrach z określonymi danymi (lub np. tabelkę,
do której użytkownik wprowadzi dane). Wspomniane dane, potrzebne do tego celu,
są oczywiście również przesyłane, jednak sam proces rysowania jest
realizowany lokalnie, a nie zdalnie. Również komponenty wstawiane (np.
etykiety) do obiektu interfejsowego – powinny być tworzone lokalnie.
Sprawę komponentów należy przeanalizować w tym miejscu bardziej dokładnie:
jeśli po stronie klienta ma być wyświetlony ekran przedstawiony schematycznie
na rysunku 7:


Rys. 7. Przykład prezentacji jednostki

to do obiektu, który realizuje interfejs, należy wstawić
kolejno: tekst, przejście do nowej linii (też tekst), komponent pozwalający
na wprowadzenie tekstu (edycyjny), tekst, przejście do nowej linii, tekst,
komponent edycyjny, tekst, itd. Do klienta należy przesłać zatem: polecenie
wyłania tekstu wraz z tekstem do wyświetlenia, polecenie wyświetlenia
komponentu edycyjnego itd. a odesłać należy (po wprowadzeniu odpowiedzi przez
użytkownika) to, co zostało wpisane przez klienta, czyli tablicę łańcuchów.
Można oczywiście zadać pytanie: dlaczego do klienta nie należy przesłać
wzorców i po jego stronie sprawdzić czy to, co wprowadził użytkownik jest
poprawne. Postępowanie takie byłoby w pełni uzasadnione dla skończonej i ściśle
określonej hierarchii jednostek. Jest jednak niemożliwe do zastosowania (lub
przynajmniej trudne), jeśli zestaw jednostek ma być nieokreślony na etapie
projektowania (co wynika z planowanej elastyczności systemu). Aby uzyskać dużą
uniwersalność systemu, zastosowany w systemie komponent edycyjny (JPanelPrezentacji)
może tylko nieznacznie ograniczyć bardzo duże możliwości klasy JTextPane. Mówiąc
innymi słowy, zastosowany przez nas komponent ma być wyłącznie adapterem,
dostosowującym komponent biblioteki Swing do istniejących wymagań związanych
z komunikacją i wygodą jej realizacji.

c.d.n.

Literatura

1 Opis notacji języka UML znajduje się w
artykule o projektowaniu systemów informatycznych.

2 Wykład też traktujemy jako interakcję – z
tym, że od studenta nie są wymagane odpowiedzi.

3 W numerze 20-tym

4 Importy pominięto dla oszczędności miejsca.