84

SDJ_03_2010_PL

Embed Size (px)

DESCRIPTION

Software Developers Jurnal

Citation preview

Page 1: SDJ_03_2010_PL
Page 2: SDJ_03_2010_PL

��������������������������������������������������������������������

�����������������������������������������������������������������������������

�����������������������������������������������������������������������������

�������������������������������������������������������������������������������

����������������������������

���������������������������������������������������������������������������

��������������������������������������������������������������������������������

������������������

�����������������������������������������������������������������������������������

��������������������������������������������������������������������������������

���������������������������������������������������������������������������������

������������������������������������������������������������������������������

���������������������������������������������

�����������������������������������������������������������������������������������������������

Page 3: SDJ_03_2010_PL

��������������������������������������������������������������������

�����������������������������������������������������������������������������

�����������������������������������������������������������������������������

�������������������������������������������������������������������������������

����������������������������

���������������������������������������������������������������������������

��������������������������������������������������������������������������������

������������������

�����������������������������������������������������������������������������������

��������������������������������������������������������������������������������

���������������������������������������������������������������������������������

������������������������������������������������������������������������������

���������������������������������������������

�����������������������������������������������������������������������������������������������

Page 4: SDJ_03_2010_PL

4

SPIS TREŚCI

SZTUCZNA INTELIGENCJA20 Paradygmat programowania CLP – Metody rozwiązywania trudnych problemów kombinatorycznychŁukasz MazurWysoka efektywność metod CLP jest rezultatem wykorzystania procedur propagacji ograniczeń oraz dystrybucji zmiennych, w celu poszukiwania rozwiązań spełniających wszystkie przyjęte ograniczenia. Procesy te realizowane są w sposób klasyczny, jak i rozproszony, dając bardzo dobre rezultaty obliczeniowe.

BEZPIECZEŃSTWO32 Niezawodność systemów informatycznychAndrzej OlchawaNieustanny postęp technologiczny, zwłaszcza ten dotyczący świata IT, sprawia, że mamy do czynienia z globalną komputery-zacją oraz informatyzacją, która z dnia na dzień zatacza coraz to szersze kręgi. Postęp służyć powinien globalnemu dobru, jednak wraz postępem pojawiają się coraz to nowe problemy oraz pułap-ki, których nie sposób traktować z przymrużeniem oka.

WARSZTATY36 Hibernate Search API – Mechanizm wyszukiwania pełnotekstowego w HibernateŁukasz AntoniakAplikacje bazodanowe stanowią obecnie znaczący odsetekoprogramowania tworzonego na zlecenie prywatnych firm, jak i ogromnych korporacji. Większość aplikacji realizuje warstwę do-stępu do danych za pomocą relacyjnie zorientowanej implemen-tacji bazy danych (ang. Relational Database Management Sys-tem). Wybór ten ogranicza swobodę przeszukiwania zawartych informacji do zbioru ściśle sformalizowanych zapytań udostęp-nianych przez aplikację. Artykuł ten wprowadza w zagadnienia przeszukiwania pełnotekstowego oferowanego przez Hibernate Search oraz Apache Lucene.

44 Spring.NET – uniwersalny spinacz. Wprowadzenie do konfiguracji fabryki obiektów.Piotr WyczółkowskiSpring to bardzo wygodne i szeroko konfigurowalne narzędzie pozwalające spinać ze sobą poszczególne obiekty, jak i integro-wać całe warstwy aplikacji.

15 Opis DVD

BIBLIOTEKA MIESIĄCA6 Boost String Algorithms – Eleganckie i efektywne przetwarzanie napisów w języku C++Rafał KociszCzy próbowałeś kiedyś budować zaawansowane narzędzia do przetwarzania tekstu, bazując na funkcjonalności klasy std::

string? Jeśli tak, to założę się, że nie wspominasz zbyt dobrze tego doświadczenia. Podstawowe udogodnienia związane z przetwa-rzaniem napisów w C++ są, delikatnie mówiąc... mało wygodne. Na szczęście – istnieje alternatywa!

PROGRAMOWANIE C++12 Tworzenie kopii obiektów – Wzorzec prototypuRobert NowakKopiowanie obiektów, czyli tworzenie duplikatów, przechowu-jących te same informacje bez niszczenia oryginału, jest jedną z podstawowych operacji, które wykorzystujemy w programowa-niu. Artykuł opisuje tę czynność, analizując techniki wspierające proces tworzenia kopii w języku C++.

PROGRAMOWANIE JAVA16 Przewodnik po SCJP – czyli certyfikat z Javy - część 3Krzysztof Rychlicki - KiciorProces zdobywania certyfikatów, potwierdzających umiejętno-ści z różnych dziedzin wiedzy, stał się jednym z ważniejszych elementów osobistego rozwoju. Proces ten ma miejsce również w branży IT; certyfikaty dla programistów (Java lub .NET), admini-stratorów czy sieciowców (Cisco) można coraz częściej odnaleźć w CV osób starających się o pracę, zwłaszcza w owianym złą sła-wą kryzysie gospodarczym.

03/2010 (183)

4 03/2010

Page 5: SDJ_03_2010_PL

www.sdjournal.org 5www.sdjournal.org

APLIKACJE BIZNESOWE56 SOA – Tworzenie serwisów wspomagających proces integracjiPaweł PietraszTworzenie rozwiązań integracyjnych to nie trend, ale wymóg sta-wiany przed projektantami systemów informatycznych. Coraz bar-dziej złożone procesy biznesowe wymagają od nas projektowania rozwiązań dotykających coraz to większej ilości systemów, które w przeszłości często nie były projektowane w sposób zapewniający łatwą możliwość integracji.

PRACA W ZESPOLE66 Audyt techniczny – Czyli jak sprawdzić jakość prac dostawcy IT? Karolina ZmitrowiczInstytucja zlecająca realizację prac IT z definicji pragnie, by jakość tych prac była jak najlepsza, a wymagania dotyczące produkcji systemu oraz samego produktu spełnione na odpowiednim po-ziomie. Zapewnienia i deklaracje dostawcy to jedno – a uzyska-nie konkretnych, obiektywnych dowodów spełnienia wymagań to co innego.

EFEKTYWNOŚĆ PRACY74 Klient, który wie czego chce – Czyli sztuka zadawania pytańMichał Bartyzel, Mariusz SieraczkiewiczJeśli zdarza Ci się spotykać z osobami nietechnicznymi i musisz pozyskiwać od nich konkretne informacje, aby móc sprawnie im-plementować swoje zadania, to ten artykuł jest dla Ciebie. Skupi-liśmy się w nim na technice zadawania pytań, która ogólne infor-macje pomaga przekuć na mierzalne konkrety.

FELIETON62 Certyfikacja i co dalej?Oliwia ŁączyńskaObecna sytuacja na rynku pracy oraz otwarcie rynków europej-skich dla polskich pracowników powoduje, że coraz częściej za-stanawiamy się nad sposobem poniesienia własnych kwalifi-kacji. Szukamy metod, które są postrzegane jako wartościowe i uznawane nie tylko przez polskich pracodawców i manage-rów. Rozwiązaniem coraz częściej wybieranym przez Programi-stów zajmujących się Java są certyfikacje SUNa ze względu na ich uniwersalność.

Miesięcznik Software Developer’s Journal (12 numerów w roku)jest wydawany przez Software Press Sp. z o.o. SK

Redaktor naczelny: Łukasz Łopuszański [email protected]

Projekt okładki: Agnieszka Marchocka

Skład i łamanie: Tomasz Kostro www.studiopoligraficzne.com

Kierownik produkcji: Andrzej Kuca [email protected]

Dział produkcji i kolportażu: Alina Stebakow [email protected]

Nakład: 6 000 egz.

Adres korespondencyjny:Software Press Sp. z o.o. SK,

ul. Bokserska 1, 02-682 Warszawa, Polskatel. +48 22 427 36 91, fax +48 22 224 24 59

www.sdjournal.org [email protected]

Dział reklamy: [email protected]

Obsługa prenumeraty: EuroPress Polska [email protected]

Dołączoną do magazynu płytę CD przetestowano programem AntiVirenKit firmy G DATA Software Sp. z o.o.

Redakcja dokłada wszelkich starań, by publikowane w piśmie i na towarzyszących mu nośnikach informacje i programy były poprawne, jednakże nie bierze odpowiedzialności za efekty

wykorzystania ich; nie gwarantuje także poprawnego działania programów shareware, freeware i public domain.

Uszkodzone podczas wysyłki płyty wymienia redakcja.

Wszystkie znaki firmowe zawarte w piśmie są własności odpowiednich firm.

Zostały użyte wyłącznie w celach informacyjnych.

Redakcja używa systemu automatycznego składu

Osoby zainteresowane współpracą prosimy o kontakt:[email protected]

Druk: Artdruk www.artdruk.com

Wysokość nakładu obejmuje również dodruki. Redakcja nie udziela pomocy technicznej w instalowaniu i użytkowaniu

programów zamieszczonych na płycie CD-ROM dostarczonej razem z pismem.

Sprzedaż aktualnych lub archiwalnych numerów pisma po innej cenie niż wydrukowana na okładce – bez zgody wydawcy – jest działaniem na jego szkodę i skutkuje odpowiedzialnością

sądową.

5

Osoby zainteresowane współpracą prosimy o kontakt:

Page 6: SDJ_03_2010_PL

03/20106

Biblioteka miesiącaProgramowanie w języku C++

www.sdjournal.org 7

Standardowe udogodnienia biblioteki języka C++, dedykowane przetwarza-niu napisów, pozostawiają wiele do ży-

czenia. Wielu programistów uważa, że klasa std::string to jedna z większych wpadek twórców standardu tego języka. Szablon std::basic_string jest przykładem klasy o roz-dmuchanym, niespójnym interfejsie, mało in-tuicyjnej w użyciu, udającej kontener STL, ale nie będącej nim do końca i nieelastycznej. Je-śli nie wierzysz, to spróbuj przy pomocy std::basic_string wykonać operację przycię-cia zbędnych znaków (np. spacji) z początku i końca napisu (ang. trimming)...

Oczywiście, da się to zrobić – po uprzed-nim zapoznaniu się z takimi metodami klasy std::string jak find_first_not_of, find_last_not_of oraz erase (patrz: Listing 1). Ale czy nie można by zrobić tego prościej...? A co w sytuacji, kiedy nasz napis reprezento-wany jest jako wektor char'ów?

Zanim załamiesz ręce, poczekaj! Na szczę-ście – jest...

...światełko w tunelu...

...w postaci biblioteki Boost String Algori-thms. Biblioteka ta oferuje uogólnione (ang. generic) implementacje algorytmów działają-

cych na napisach, których brakuje w biblio-tece standardowej języka C++. Algorytmy te można sklasyfikować w ramach kilku pod-stawowych grup: operacje przycinania napi-sów (ang. trimming), operacje konwersji zna-ków (ang. case conversion) oraz funkcje służące do wyszukiwania, zamiany i usuwania ciągów znaków (ang. search, replace, erase).

Boost String Algorithms skonstruowa-na jest w sposób, który nie ogranicza jej do współpracy z jednym, z góry narzuconym ty-

pem kontenera (np. std::string). W kon-tekście tej biblioteki pojęcie napis oznacza sekwencję znaków przechowywanych w do-wolnym kontenerze. Podobnie, pod pojęciem znak niekoniecznie muszą kryć się typy char i wchar_t.

Prezentowana biblioteka jest częścią pakie-tu Boost; jako że składa się ona z samych pli-ków nagłówkowych, jej konfiguracja jest bar-dzo prosta: wystarczy pouczyć kompilator, gdzie ma szukać nagłówków.

Więcej informacji na temat konfiguracji bi-blioteki znajdziesz w ramce Szybki Start.

Pierwszy przykładZanim rozważymy szczegółowo możliwości biblioteki Boost String Algorithms, spójrzmy na krótki, prosty przykład jej użycia (patrz: Listing 2).

Boost String Algorithms

Czy próbowałeś kiedyś budować zaawansowane narzędzia do przetwarzania tekstu, bazując na funkcjonalności klasy std::string? Jeśli tak, to założę się, że nie wspominasz zbyt dobrze tego doświadczenia. Podstawowe udogodnienia związane z przetwarzaniem napisów w C++ są, delikatnie mówiąc... mało wygodne. Na szczęście – istnieje alternatywa!

Dowiesz się:• Co oferuje biblioteka Boost String Algori-

thms i dlaczego warto jej używać;• Jak można wykorzystać ją w praktyce.

Powinieneś wiedzieć:• Jak programować w języku C++;• Podstawowa znajomość biblioteki STL.

Poziom trudności

Eleganckie i efektywne przetwarzanie napisów w języku C++

Listing 1. Implementacja operacji przycięcia zbędnych znaków (np. spacji) z początku i końca napisu, bazująca na funkcjonalności std::string

void trim( string& str )

{

string::size_type pos = str.find_last_not_of( ' ' );

if (pos != string::npos)

{

str.erase( pos + 1 );

pos = str.find_first_not_of( ' ' );

if ( pos != string::npos )

{

str.erase( 0, pos );

}

}

else

{

str.erase( str.begin(), str.end() );

}

}

Page 7: SDJ_03_2010_PL

03/20106

Biblioteka miesiącaProgramowanie w języku C++

www.sdjournal.org 7

Zadanie, które ma zrealizować nasz przy-kład, jest bardzo proste: chcemy usunąć białe znaki z początku i końca napisu, a następnie stworzyć nowy napis będący kopią poprzed-niego, z tą różnicą, że wszystkie litery z orygi-nalnego napisu zamienione będą na małe. Bo-ost String Algorithms pozwala zrealizować to zadanie za pomocą dwóch prostych, intuicyj-nych funkcji. Morał z tego przykładu jest na-stępujący: nie mając pod ręką odpowiedniej bi-blioteki, musielibyśmy funkcje do przycinania i konwersji zaimplementować sami. Niby pro-sta rzecz, aczkolwiek nie do końca. Jeśli two-rzony przez nas kod miałby być kodem pro-dukcyjnym, to należałoby poważnie zastano-wić się nad takimi aspektami jak: poprawność (szczegółowe rozważenie wszystkich przypad-ków użycia), wydajność, rozszerzalność, ob-sługa błędów itd. Trzeba by zapewne zapro-jektować i zaimplementować zestaw testów modułowych. Krótko mówiąc, trywialne zda-nie, które chcielibyśmy rozwiązać w kilka se-kund, urasta nam nagle do rozmiarów tworze-nia własnej biblioteki – co już trywialnym za-daniem nie jest.

Dla odmiany, Boost String Algorithms da-je nam dokładnie to, czego potrzebujemy: proste, dobrze przetestowane, wydajne im-plementacje operacji na napisach. Wystar-czy dołączyć właściwy nagłówek (#include <boost/algorithm/string.hpp>) i po kłopo-cie. Aż chciałoby się zacytować motto użyt-kowników języka Perl: niechaj proste rzeczy po-zostaną proste.

KonwencjeKażda biblioteka narzuca pewne zasady doty-czące korzystania z niej. Podobnie jest i w przy-

padku Boost String Algorithms. Patrząc na przykład z Listingu 2, da się zauważyć kilka podstawowych konwencji:

• Przekazywanie argumentów: w odróżnie-niu od STL Boost String Algorithms prefe-ruje przekazywanie kontenera do funkcji

Listing 2. Prosty przykład użycia biblioteki Boost String Algorithms

#include <boost/algorithm/string.hpp>

#include <iostream>

using namespace std;

using namespace boost;

int main()

{

string str1(" Hello WOrLd! ");

cout << '[' << str1 << ']' << endl;

trim( str1 );

cout << '[' << str1 << ']' << endl;

string str2 = to_lower_copy( str1

);

cout << '[' << str2 << ']' << endl;

return 0;

}

Szybki startAby rozpocząć pracę z Boost String Algorithms musimy zainstalować pakiet bibliotek Bo-ost. Pakiet ten można pobrać pod adresem http://www.boost.org. Przeważająca część biblio-tek wchodzących w skład tego pakietu jest zakodowana w plikach nagłówkowych i z tej racji można ich używać od ręki. Tak właśnie jest w przypadku biblioteki prezentowanej w niniej-szym artykule. Jeśli używasz kompilatora z rodziny Microsoft Visual Studio to wystarczy we właściwościach projektu (ang. Properties), w zakładce Con�guration Properties \ C/C++ \ Ge-neral odpowiednio ustawić pole Additional Include Directories (patrz: Rysunek 1). Ja de�niuję sobie zawsze zmienną środowiskową %BOOST_HOME%, która wskazuje na miejsce (katalog), w którym zainstalowany jest pakiet Boost. Na Rysunku 1 pokazane jest jak odwołuję się do tej zmiennej. Oczywiście można tutaj podać również względną (w odniesieniu do katalogu, w którym znajduje się projekt) bądź bezwzględną ścieżkę wskazującą na to miejsce.

Trochę więcej zachodu jest ze skon�gurowaniem biblioteki Boost Regex. Nie jest stano-wi ona co prawda meritum niniejszego artykułu, aczkolwiek – z racji tego, iż korzystam z niej w kilku prezentowanych tu przykładach – pozwolę sobie napisać klika słów na temat jej in-stalacji. Boost Regex jest jednym z nielicznych komponentów Boost, które wymagają od-dzielnego zbudowania. Na szczęście, pakiet posiada swój własny system budowania źró-deł – w dużej mierze niezależny od platformy. Aby skorzystać z tego systemu potrzebny jest programu bjam. Link do strony na której można pobrać ten program (oczywiście za dar-mo) znajduje się pod adresem http://www.boost.org/doc/libs/1_41_0/more/getting_started/windows.html. Pod tym samym adresem znajduje się również bardzo szczegółowa instrukcja budowania źródeł pakietu Boost. Proces kompilacji całej biblioteki jest czasochłonny i wy-maga sporej ilości wolnego miejsca na dysku. Na szczęście istnieje możliwość zbudowania wybranych komponentów. W celu zbudowania biblioteki Boost Regex w katalogu głównym biblioteki (tam gdzie znajduje się plik Jamroot) należy wydać polecenie:

bjam --with-regex

Zanim to uczynimy, należy upewnić się, czy program bjam.exe znajduje się w ścieżce wykona-nia (zmienna środowiskowa PATH) oraz czy z poziomu konsoli dostępne są narzędzia dewelo-perskie Microsoft Visual Studio C++ (aby uzyskać dostęp do tych narzędzi należy uruchomić plik vcvars32.bat znajdujący się w podkatalogu bin, w miejscu gdzie zainstalowano wspomnia-ne środowisko).

Podana wyżej komenda spowoduje zbudowanie biblioteki Boost Regex i zainstalowa-nie jej w katalogu %BOOST_HOME%\bin.v2. Uruchamiając przykłady zamieszczone w niniej-szym artykule należy pamiętać o tym aby skon�gurować program łączący (ang. Linker) tak aby używał skompilowanej biblioteki Boost Regex. W przypadku korzystania z pakietu Mi-crosoft Visual Studio wystarczy we właściwościach projektu (ang. Properties) w zakładce Con-�guration Properties \ Linker \ General ustawić odpowiednio pole Additional Library Directories, tak aby podana tam ścieżka wskazywała na miejsce, w którym znajduje się plik ze skompilo-waną biblioteką.

Rysunek 1. Kon�guracja narzędzia Microsoft Visual Studio przy pracy z biblioteką Boost String Algorithms

Page 8: SDJ_03_2010_PL

03/20108

Biblioteka miesiącaProgramowanie w języku C++

www.sdjournal.org 9

w postaci pojedynczego argumentu. Kon-wencja STL (przekazywanie pary iterato-rów) daje oczywiście wielką elastyczność, aczkolwiek wiąże się z pewnymi ograni-czaniami, między innymi z brakiem moż-liwości składania wywołań kilku funkcji czy z mniejszą czytelnością kodu. Złotym środkiem stosowanym przez Boost String Algorithms jest inna biblioteka z pakie-tu Boost: Range Library. Biblioteka ta jest zbiorem konceptów i narzędzi użytko-

wych ułatwiających implementację algo-rytmów uogólnionych i w tym przypadku idealnie spełnia swoją rolę.

• Modyfikowanie argumentów: w przy-kładzie pokazanym na Listingu 2 widzi-my, że przy przetwarzaniu napisów cza-sami występuje potrzeba modyfikacji ar-gumentów przekazywanych do funkcji, a czasami chcielibyśmy zachować sobie kopię oryginalnego napisu. Biblioteka Boost String Algorithms respektuje oby-

dwie potrzeby i udostępnia swoje algo-rytmy zarówno w wersjach modyfikują-cych argumenty, jak i kopiujących nowy wynik (te ostatnie można rozpoznać po końcówce _ copy w nazwie funkcji).

• Nazewnictwo: w tym przypadku Boost String Algorithms trzyma się konwencji narzuconej przez bibliotekę standardową. W związku z tym nazwy algorytmów pi-sane są małymi literami, zaś słowa w na-zwie rozdzielone są znakiem podkreśle-nia. Oprócz końcówki _ copy, w przy-padku funkcji zwracających wynikowy napis, pojawia się jeszcze przedrostek i, występujący wtedy, gdy dany algorytm działa w sposób niezależny od wielkości znaków (np.: ifind _ first()). Podobnie postfiks _ if zarezerwowany jest dla algo-rytmów wykorzystujących predykaty de-finiowane przez użytkownika.

Operacje przycinaniaGratuluję Ci Drogi Czytelniku! Szczęśliwie przebrnąłeś przez wprowadzenie do tematu! Czas przejść do konkretów. Na początek omó-wimy operacje przycinania.

Idea tego typu operacji jest prosta: chodzi o to, aby usunąć z napisu zbędne znaki znaj-dujące się na jego początku i/lub końcu. Mogą być to wcięcia w kodzie źródłowym, znaki no-wego wiersza w kolejnych liniach pliku teksto-wego czy pomyłkowo wpisane przez użytkow-nika spacje w polu formularza.

Boost String Algorithms oferuje nam trzy al-gorytmy odpowiadające za przycinanie:

• trim _ left() – przycina napis z lewej strony;

• trim _ right() – przycina napis z prawej strony;

• trim() – przycina napis z obydwu stron.

Każdy z tych algorytmów występuje w czte-rech wariantach: modyfikującym swój argu-ment, kopiującym wynikowy napis, z predy-katem i bez predykatu. Dla przykładu, z al-gorytmem trim _ left() skojarzone są nastę-pujące cztery szablony funkcji:

• trim _ left _ copy _ if(),• trim _ left _ if(),• trim _ left _ copy(),• trim _ left().

Na Listingu 3 przedstawiony jest przykład wykorzystania operacji przycinania nie ko-rzystających z predykatów.

W komentarzach obok wywołań makra DUMP przedstawione są wartości poszczególnych na-pisów po wykonywaniu kolejnych operacji.

Pozostają jeszcze operacje przycinania ko-rzystające z predykatów. Te omówię w kolej-nym punkcie.

Listing 3. Przykład użycia algorytmów przycinania

#include <boost/algorithm/string.hpp>

#include <iostream>

using namespace std;

using namespace boost;

#define DUMP( str ) cout << #str << " == \"" << str << "\"" << endl;

int main()

{

string str1 = " hello world! ";

string str2 = trim_left_copy( str1 );

DUMP( str2 ); // str2 == "hello world! "

string str3 = trim_right_copy( str1 );

DUMP( str3 ); // str3 == " hello world!"

trim( str1 );

DUMP( str1 ); // str1 == "hello world!"

}

Listing 4. Przykład użycia algorytmów przycinania z wykorzystaniem predykatów

#include <boost/algorithm/string.hpp>

#include <iostream>

using namespace std;

using namespace boost;

#define DUMP( str ) cout << #str << " == \"" << str << "\"" << endl;

int main()

{

string str1( "3254832746 hello world!" );

string str2( " 54 32 46 hello world! $$$+-+- " );

string str3( "hello world! +-11$$%76 " );

trim_left_if( str1, is_digit() || is_space() );

DUMP( str1 ); // str1 == "hello world!"

trim_if( str2, is_digit() || is_space() || is_any_of( "+-%$" ) );

DUMP( str2 ); // str2 == "hello world!"

trim_right_if( str3, is_digit() || is_space() || is_any_of( "+-%$" ) );

DUMP( str3 ); // str3 == "hello world!"

}

Page 9: SDJ_03_2010_PL

03/20108

Biblioteka miesiącaProgramowanie w języku C++

www.sdjournal.org 9

Predykaty i klasyfikacjaGłównym zadaniem predykatu, w kontekście biblioteki Boost String Algorithms, jest spraw-dzenie, czy zadany napis spełnia określony wa-runek. Prezentowana biblioteka oferuje szereg podstawowych predykatów (wszystkie one są zdefiniowane w nagłówku boost/algorithm/string/predicate.hpp). Dla przykładu, predykat istarts_with() weryfikuje, czy napis podany jako pierwszy argument rozpoczyna się od cią-gu znaków przekazanego jako argument drugi. Dodatkowo, przedrostek i na początku nazwy predykatu sugeruje, iż w tym przypadku roz-miary liter w obydwu napisach nie będą bra-ne pod uwagę.

Predykaty są bardzo użytecznym narzę-dziem w bibliotece Boost String Algorithms, gdyż można przekazywać je do algorytmów po to, aby dostosować ich działanie do indywidu-alnych potrzeb użytkownika. Co więcej, moż-na je łączyć ze sobą za pomocą operatorów lo-gicznych. Rozważmy przykład przedstawiony na Listingu 4.

Widać tutaj, na czym polega siła predyka-tów: w elegancki i prosty sposób jesteśmy w sta-nie dopasować funkcjonalność algorytmów z rodziny trim do naszych potrzeb. W roz-ważanym tu przypadku predykat złożony jest z trzech klasyfikatorów. Zadaniem klasy-fikatora jest sprawdzenie, czy wszystkie znaki w określonym napisie należą do określonej kla-sy (np. czy są to cyfry bądź znaki alfabetu). Peł-na lista klasyfikatorów dostępnych w ramach Boost String Algorithms znajduje się w nagłów-ku boost/algorithm/string/classification.hpp.

W kolejnych punktach niniejszego artyku-łu będę powracał jeszcze do tematu predyka-tów, pokazując konkretne przykłady ich zasto-sowania.

Operacje wyszukiwaniaBiblioteka Boost String Algorithms udostęp-nia cały szereg algorytmów pozwalających znajdować zadane ciągi znaków w napisach:

• find _ first() – znajduje pierwsze wystą-pienie napisu w wejściowym ciągu;

• find _ last() – znajduje ostatnie wystą-pienie napisu w wejściowym ciągu;

• find _ nth() – znajduje n-te wystąpienie napisu w wejściowym ciągu (licząc od zera);

• find _ head() – znajduje początkowy fragment napisu o zadanej długości;

• find _ tail() – znajduje końcowy frag-ment napisu o zadanej długości;

• find _ token() – znajduje pierwszy pasu-jący token w napisie;

• find _ regex() – znajduje fragment napi-su pasujący do zadanego wyrażenia regu-larnego;

• find() – uogólniony algorytm wyszuki-wania.

Listing 5. Przykład użycia algorytmu wyszukiwania

#include <boost/algorithm/string.hpp>

#include <boost/assign.hpp>

#include <boost/foreach.hpp>

#include <iostream>

using namespace std;

using namespace boost;

int main()

{

vector< char > text = boost::assign::list_of

('H')('e')('l')('l')('o')(',')(' ')

('w')('o')('r')('l')('d')('!');

iterator_range< vector< char >::iterator > result

= find_last( text, "world" );

if ( result )

{

to_upper( result );

}

BOOST_FOREACH( char ch, text )

{

cout << ch;

}

cout << endl;

}

Listing 6. Wyszukiwanie frazy na podstawie wyrażenia regularnego

#include <boost/algorithm/string.hpp>

// Dołączamy odpowiedni nagłówek, aby móc skorzystać z find_regex().

#include <boost/algorithm/string/regex.hpp>

#include <iostream>

using namespace std;

using namespace boost;

int main()

{

// Wyrażenie regularne opisujące wzorzec adresu e-mail.

const static boost::regex email_regex(

"[\\w.%-]+"

"@"

"[A-Za-z0-9.-]+"

"\\.[A-Za-z]{2,4}" );

// Tekst zawierający adres e-mail.

string text("my email adress is [email protected]");

// Wyszukiwanie adresu e-mail w napisie.

iterator_range< string::iterator > result = find_regex( text, email_regex );

// Jeśli wyszukiwanie się powidło, to zamieniamy wszystkie litery w

// wyszukanym ciągu na małe.

if ( result ) to_lower( result );

// Wyświetlamy wyszukany ciąg na standardowym wyjściu.

cout << "Found: \"";

copy( result.begin(), result.end(), ostream_iterator< char >( cout ) );

cout << "\"" << endl;

}

Page 10: SDJ_03_2010_PL

03/201010

Biblioteka miesiącaProgramowanie w języku C++

www.sdjournal.org 11

Dodatkowo, pierwsze trzy algorytmy wy-stępują w wariantach ignorujących wielkość znaków (np. ifind _ first()). Przykład wyko-rzystania operacji wyszukiwania przedsta-wiony jest na Listingu 5.

Na wspomnianym przykładzie daje się za-obserwować kilka ciekawych szczegółów. Po pierwsze, tym razem zdecydowaliśmy się użyć wektora znaków (std::vector< char >) ja-ko reprezentacji napisu. Fakt, iż prezentowa-ny przykład kompiluje się i działa bez żadne-go problemu, pokazuje dobitnie, jak bardzo elastyczną biblioteką jest Boost String Algo-rithms. Po drugie, warto zauważyć, iż funk-cja find_last() zwraca jako rezultat zakres iteratorów (iterator_range), który wskazu-je na znaleziony ciąg znaków. Obiekty typu iterator_range posiadają operator konwer-sji do typu bool, dzięki czemu można uży-wać ich w instrukcjach warunkowych. Ponie-waż wszystkie algorytmy z Boost String Algo-rithms działają na zakresach, rezultat wyszuki-wania można przekazywać do innych funkcji z tej biblioteki. W naszym przykładzie, obiekt zwrócony z find_last() przekazujemy do al-gorytmu to_upper(), skutkiem czego będzie zmodyfikowanie znalezionego ciągu znaków (zamiana na duże litery).

W przypadku operacji wyszukiwania warto zwrócić uwagę na dwa algorytmy udostępniane przez Boost String Algorithms: find_regex() oraz find_token(). Pierwsza z nich pozwala wyszukiwać określone frazy w napisie na pod-stawie wyrażenia regularnego (z wykorzysta-niem biblioteki Boost Regex), druga zaś – wy-szukiwać tzw. tokeny, czyli znaki spełniające pe-wien predykat. Proste przykłady wykorzystania tych funkcji (wraz z komentarzami) znajdują się odpowiednio na Listingu 6 i 7.

Przy wyszukiwaniu fraz w tekście często po-jawia się potrzeba znalezienia wszystkich jej wy-stąpień, a nie tylko pierwszego. Boost String Al-gorithms ułatwia swoim użytkownikom wyko-nanie tego zadania, oferujące tzw. iterator wy-szukiwania (ang. find iterator). Rozważmy frag-ment kodu przedstawiony na Listingu 8.

Prezentowany fragment kodu ma za zada-nie wyszukać we fragmencie tekstu wszyst-kie adresy e-mail. W tym celu wykorzystamy, tak jak w przykładzie z Listingu 6, wyrażenie regularne. Tym razem jednak użyjemy iterato-ra wyszukiwania. Iterator ten jest tworzony za pomocą wywołania specjalnej funkcji:

make_find_iterator( text, regex_finder(

email_regex ) );

Funkcja ta przyjmuje dwa argumenty: na-pis (zakres), który chcemy przeszukiwać, oraz obiekt szukacza (ang. finder). W na-szym przypadku korzystamy z szukacza ty-pu regex _ finder, ale oczywiście Boost String Algorithms udostępnia również inne

Listing 7. Wyszukiwanie znaku spełniającego zadany predykat

#include <boost/algorithm/string.hpp>

#include <iostream>

using namespace std;

using namespace boost;

int main()

{

string text("int delta = b*b - 4*a*c");

// Szukamy znaku będącego cyfrą.

iterator_range< string::iterator > result

= find_token( text, is_digit() );

cout << "Found: \"";

copy( result.begin(), result.end(),

ostream_iterator< char >( cout ) );

cout << "\"" << endl;

}

Listing 8. Wyszukiwanie frazy na podstawie wyrażenia regularnego

#include <boost/algorithm/string.hpp>

// Dołączamy odpowiedni nagłówek, aby móc skorzystać z find_regex().

#include <boost/algorithm/string/regex.hpp>

#include <iostream>

using namespace std;

using namespace boost;

int main()

{

const static boost::regex email_regex(

"[\\w.%-]+"

"@"

"[A-Za-z0-9.-]+"

"\\.[A-Za-z]{2,4}" );

string text( "You can reach me at [email protected] or at"

"[email protected]. In case of any technical"

"problems contact with [email protected]." );

vector< string > emails;

typedef find_iterator< string::iterator > string_find_iterator;

for ( string_find_iterator it =

make_find_iterator( text, regex_finder(email_regex) );

it != string_find_iterator(); ++it )

{

emails.push_back( copy_range< std::string >( *it ) );

}

BOOST_FOREACH( const string& email, emails ) cout << email << endl;

}

Page 11: SDJ_03_2010_PL

03/201010

Biblioteka miesiącaProgramowanie w języku C++

www.sdjournal.org 11

opcje w tym zakresie. Stworzony w ten spo-sób iterator wyszukiwania w kolejnych kro-kach wskazywał będzie na zakresy opisujące znalezione frazy. Proponuję w tym momencie poeksperymentować przez moment z kodem przedstawionym na Listingu 8.W następnym podpunkcie rozważymy:

Operacje zamiany i usuwaniaOperacja zamiany (ang. replace) wiąże się nie-odłącznie z operacją wyszukiwania. Nie ma chyba ani jednego nowoczesnego edytora tek-stu, który nie oferowałby opcji wyszukaj i za-mień (ang. search and replace).

Rodzina algorytmów zamiany dostępnych w ramach biblioteki Boost String Algorithms jest całkiem obszerna i bardzo podobna do ze-stawu funkcji wyszukujących. Aby się o tym przekonać, spójrzmy na Listing 9.

Prawda, że wygląda to znajomo? Różnice w stosunku do algorytmów z rodziny find są następujące:

• w przypadku zamiany możemy określić zakres, który pojawi się jako zamiennik wyszukanego ciągu;

• algorytmy zamiany występują również w wersjach niemodyfikujących argument (oznaczone postfiksem _ copy).

Szczególnym przypadkiem zamiany jest usu-wanie. Boost String Algorithms oferuje dla każdej funkcji z rodziny replace jej zamien-nik służący do usuwania. Dla przykładu, od-powiednikiem funkcji replace _ first jest erase _ first.

W przypadku gdy chcemy zamienić wszyst-kie wystąpienia zadanego ciągu za jednym zamachem, możemy skorzystać z funkcji replace_all() (nie ma potrzeby używać tu-taj żadnego iteratora, jak w przypadku algo-rytmu find()). Dla przykładu, kropki wystę-pujące w adresie e-mail w przykładzie z Listin-gu 9 moglibyśmy zamienić jednym wywoła-niem funkcji:

replace_all( email, ".", "[dot]");

Dociekliwi Czytelnicy zastanawiają się za-pewne, w jaki sposób biblioteka obsługuje modyfikacje ciągów, na których wykonywane są operacje zamiany. Dzieje się to za pośred-nictwem tzw. cech sekwencji (ang. sequence traits), na podstawie których Boost String Al-gorithms potrafi wybrać najbardziej efektyw-ny schemat postępowania dla zadanego ciągu. Więcej na ten temat można znaleźć w doku-mentacji biblioteki (patrz: ramka W sieci).

PodsumowanieBiblioteka Boost String Algorithms to niewąt-pliwie ciekawe i użyteczne rozwiązanie. Jeśli programujesz w C++ i przynajmniej od cza-su do czasu zdarza Ci się wykonywać operacje na napisach, to zapoznaj się z tą biblioteką ko-niecznie! Gwarantuję Ci, że trafi ona na stałe

do Twojej podręcznej skrzynki narzędziowej. Główne zalety biblioteki to:

• prostota i spójność interfejsu;• lekkość i prostota konfiguracji (cała bi-

blioteka zaimplementowana jest w pli-kach nagłówkowych);

• elastyczność i rozszerzalność;• zgodność z filozofią STL;• ortogonalność (biblioteka oferuje wie-

le małych, niezależnych narzędzi, które można łatwo łączyć w celu realizacji bar-dziej złożonych operacji).

Reasumując: polecam tę bibliotekę każde-mu programiście tworzącemu aplikacje w ję-zyku C++!

Aha! Jeszcze jedno: żądnych wiedzy Czy-telników zapraszam do przestudiowania do-kumentacji biblioteki dostępnej w Internecie (patrz: ramka W Sieci) – można tam znaleźć jeszcze sporo dodatkowych ciekawostek, któ-re warto poznać.

Listing 9. Przykład użycia algorytmów zamiany

#include <boost/algorithm/string.hpp>

#include <iostream>

#define DUMP( str ) cout << #str << " == \"" << str << "\"" << endl;

using namespace std;

using namespace boost;

int main()

{

string email( "[email protected]" );

replace_first( email, ".", "[dot]" );

DUMP( email ); // email == "rafal[dot][email protected]"

replace_last( email, ".", "[dot]" );

DUMP( email ); // email == "rafal[dot]kocisz@gmail[dot]com"

replace_nth( email, "@", 0, "[at]" );

DUMP( email ); // email == "rafal[dot]kocisz[at]gmail[dot]com"

}

RAFAŁ KOCISZPracuje na stanowisku Dyrektora Techniczne-go w �rmie Gamelion, wchodzącej w skład Grupy BLStream. Rafał specjalizuje się w technologiach związanych z produkcją oprogramowania na platformy mobilne, ze szczególnym naciskiem na tworzenie gier. Grupa BLStream powstała, by efek-tywniej wykorzystywać potencjał dwóch szybko rozwijających się producentów oprogramowania – BLStream i Gamelion. Firmy wchodzące w skład grupy specjalizują się w wytwarzaniu oprogramo-wania dla klientów korporacyjnych, w rozwiąza-niach mobilnych oraz produkcji i testowaniu gier. Kontakt z autorem: [email protected]

W Sieci

• http://www.boost.org/doc/libs/1_41_0/doc/html/string_algo.html – strona domowa biblio-teki Boost String Algorithms;

• http://www.boost.org – strona domowa pakietu bibliotek Boost;• http://www.boost.org/doc/libs/1_41_0/doc/html/string_algo/quickref.html – tu znajdziesz

przegląd wszystkich algorytmów dostępnych w ramach Boost String Algorithms.

LicencjaPakiet bibliotek Boost (a co za tymi idzie również i biblioteka Boost String Algorithms) jest udo-stępniany na licencji Boost Software License 1.0. Pełny tekst tej licencji (w języku angielskim) jest dostępny pod adresem http://www.boost.org/LICENSE_1_0.txt. Licencja przewiduje możli-wość wykorzystywania biblioteki bez uiszczania żadnych opłat, tak w otwartych, jak i w komer-cyjnych projektach. Używanie Boost nie pociąga również potrzeby publikowania kodów źró-dłowych docelowego produktu. Jedynym narzuconym wymogiem jest nakaz dołączania tek-stu licencji biblioteki do wszystkich kopii stworzonego przy jej pomocy oprogramowania.

Page 12: SDJ_03_2010_PL

3/201012

Programowanie C++Wzorzec prototypu

www.sdjournal.org 13

Kopiowanie obiektów jest operacją wykonywaną bardzo często: prze-kazując argumenty przez wartość,

zwracając wyniki obliczeń, przechowując elementy w kontenerach i w wielu innych sytuacjach są tworzone kopie. Jeżeli obiekt jest dostępny pośrednio, na przykład przez wskaźnik, to można wykonać kopię wskaźni-ka (lub innego uchwytu) albo całego obiek-tu. W związku z tym możemy wyróżnić trzy rodzaje kopiowania: kopiowanie płytkie, gdy kopiujemy uchwyty (wskaźniki), kopiowa-nie głębokie, gdy tworzymy kopię obiektu, oraz kopiowanie leniwe, które łączy kopiowa-nie płytkie i głębokie. Do demonstracji tych technik będziemy używali klasy Foo pokaza-nej na Listingu 1.

Kopią płytką nazywa się kopiowanie jedy-nie obiektów pośredniczących, wskaźników, referencji, uchwytów itp. Kopia taka jest tworzona szybko, ponieważ wskaźnik lub in-ny obiekt pośredniczący jest zazwyczaj ma-łym obiektem. Po wykonaniu płytkiej kopii ten sam obiekt jest dostępny z wielu miejsc, obiekt wskazywany nie jest kopiowany, zmia-na jego stanu będzie widoczna we wszystkich kopiach. Głęboka kopia oznacza rzeczywiste

kopiowanie obiektów wskazywanych. Two-rzenie takiej kopii zajmuje więcej czasu i zaso-bów, ale obiekt i kopia są od siebie niezależne. Zmiany obiektu nie mają wpływu na kopię. Na Rysunku 1 pokazano zawartość wskaźni-ków po wykonaniu płytkiej i głębokiej kopii, przykład kodu zawiera Listing 1.

Kopiowanie opóźnioneKopiowanie opóźnione lub leniwe wyko-rzystuje obie strategie kopiowania opisa-

ne powyżej. Na początku wykonujemy ko-pię płytką, która jest przeprowadzana szyb-ko i umożliwia poprawne odczytywanie in-formacji przechowywanych w zarządzanym obiekcie. Przy próbie modyfikacji obiektu badamy, czy obiekt jest wskazywany przez jeden, czy przez kilka wskaźników. Jeże-li istnieje tylko jeden wskaźnik, to mody-fikacja odbywa się na zarządzanym obiek-cie, natomiast jeżeli wskaźników jest wię-cej, wykonuje się głęboką kopię wskazywa-nego obiektu i modyfikuje się tę kopię. Leni-we kopiowanie umożliwia więc optymalne połączenie obu strategii, a ceną jest koniecz-ność przechowywania dodatkowej składo-wej, która pozwala rozstrzygnąć, czy nale-ży robić głęboką kopię. Składową tą jest licz-nik odniesień lub flaga. Można pozbyć się tej składowej, tworząc głęboką kopię obiek-tu za każdym razem, gdy wołana jest opera-

Tworzenie kopii obiektów

Kopiowanie obiektów, czyli tworzenie duplikatów, przechowujących te same informacje bez niszczenia oryginału, jest jedną z podstawowych operacji, które wykorzystujemy w programowaniu. Artykuł opisuje tę czynność, analizując techniki wspierające proces tworzenia kopii w języku C++.

Dowiesz się:• Co to jest leniwe kopiowanie;• Co to jest wzorzec prototypu;• Jak stworzyć fabrykę prototypów.

Powinieneś wiedzieć:• Jak pisać proste programy w C++;• Co to jest dziedziczenie i funkcje wirtualne;• Co to są szablony.

Poziom trudności

Wzorzec prototypu

Listing 1. Tworzenie kopii płytkiej i głębokiej

class Foo { //klasa pomocnicza

public:

Foo() : i_(0) {}

int get() const { return i_; }

void set(int i) { i_ = i; }

};

Foo* shellCopy(Foo* f) { //płytka kopia

return f; //wskaźniki pokazują na ten sam obiekt

}

Foo* deepCopy(Foo* f){ //głęboka kopia

return new Foo(*f); //wskaźniki pokazują na różne obiekty

}

Foo* p1 = new Foo();

Foo* p2 = shellCopy(p1); //płytka kopia

Foo* p3 = deepCopy(p1); //głęboka kopia

p1->set(2); //zmiana obiektu wskazywanego przez p1

assert( p2->get() == 2 ); //obiekt wskazywany przez p2 został zmieniony

assert( p3->get() == 1 ); //obiekt wskazywany przez p3 nie został zmieniony

Page 13: SDJ_03_2010_PL

3/201012

Programowanie C++Wzorzec prototypu

www.sdjournal.org 13

cja modyfikująca, ale wtedy wiele kopii jest zbędnych.

Przykład leniwego kopiowania został pokazany na Listingu 2. Przedstawiona tam klasa wykorzystuje sprytne wskaźni-ki boost::shared_ptr, które zostały omó-wione w SDJ 11/2009. Sprytne wskaźniki to szablony, które pozwalają automatycznie usuwać obiekt utworzony na stercie, prze-chowują one i aktualizują licznik odnie-sień do wskazywanego obiektu. Szablony te wspierają tworzenie leniwej kopii, dostar-czają metodę unique, która pokazuje, czy zarządzany obiekt jest wskazywany przez jeden, czy więcej wskaźników. Metoda ta jest wykorzystana w klasie LazyFoo do roz-strzygania, czy można modyfikować bieżący obiekt, czy raczej należy zrobić kopię.

Tworząc kopię głęboką obiektu tymcza-sowego, który będzie usunięty po zakoń-czeniu operacji kopiowania, można wyko-nać kopię płytką i nie usuwać tego obiek-tu, co przypomina przeniesienie właściciela obiektu. Taki mechanizm dla wskaźników dostarcza std::auto_ptr (SDJ 11/2009), w ogólnym przypadku wymaga on wspar-cia w języku. Takie wsparcie będzie dostar-czone w nowym standardzie C++200x po-przez referencję do r-wartości, co pozwoli na implementację różnych konstruktorów kopiujących. Używając konstruktora ko-piującego do r-wartości, będzie można prze-nieść zawartość obiektu, unikniemy wtedy zbędnej kopii.

Szybkie kopiowanie głębokieDla pewnych typów obiektów kopia głęboka może być wykonana bez użycia konstrukto-ra kopiującego za pomocą operacji kopiują-cych fragmenty pamięci. Obiekty, które bę-dą w ten sposób kopiowane, nie mogą mieć składowych, które są wskaźnikami, bo wska-zywane przez te składowe obiekty także bę-dą musiały być kopiowane przy tworze-niu kopii głębokiej. Informacji o tym, czy obiekt może być kopiowany za pomocą ko-piowania bajtów, dostarcza klasa cech (trejt) has_trivial_copy , który jest dostarcza-ny przez bibliotekę boost::traits. Funk-

cja fastDeepCopy, pokazana na Listingu 3, wykorzystuje dodatkowy argument, który jest tworzony w czasie kompilacji na podsta-wie informacji o typie. Jego wartość nie jest istotna, natomiast typ pozwala wybrać od-powiednią funkcję kopiującą. Jeżeli obiekty mogą być kopiowane za pomocą funkcji ko-piującej fragmenty pamięci, to jest ona wo-łana, w przeciwnym wypadku woła się kon-struktor kopiujący. Technika trejtów została opisana w SDJ 11/2009.

Wzorzec prototypuJeżeli posługujemy się wskaźnikiem lub re-ferencją do klasy bazowej, to możemy wy-konać jedynie płytką kopię. Kopia głęboka jest niedostępna, ponieważ przy tworzeniu obiektu należy podać konkretny typ (patrz

SDJ 2/2010), a my dysponujemy tylko ty-pem interfejsu. Rzeczywisty typ obiektu może być inny.

Wzorzec prototypu, nazywany też wir-tualnym konstruktorem, opisany w książce ,,Wzorce projektowe'' przez „bandę czworga'' (Gamma, Helm, Johnson, Vlissides), pozwa-la na tworzenie głębokiej kopii w takich przy-padkach. Pomysł polega na przeniesieniu od-powiedzialności za tworzenie obiektów do klas konkretnych, wykorzystując mechanizm funkcji wirtualnych. Klasa bazowa dostarcza metody czysto wirtualnej, która jest nadpi-sywana w klasach konkretnych (gdzie znany jest typ), więc można utworzyć głęboką kopię obiektu. Przykład pokazano na Listingu 4, klasa bazowa Figure dostarcza metody czy-sto wirtualnej clone. Metoda ta jest nadpisy-

Listing 2. Leniwa kopia z użyciem boost::shared_ptr

class LazyFoo { //przechowuje leniwą kopię obiektu typu Foo (Listing 1)

public:

LazyFoo(int i) : ptr_(new Foo(i) ) {}

LazyFoo(const LazyFoo& l) : ptr_(l.ptr_) {}

int get() const { return ptr_->get(); }

void set(int i) { //metoda zmienia stan obiektu

if(ptr_.unique() ) { ptr_->set(i); } //bada czy istnieje konieczność

tworzenia kopii

else { ptr_ = PFoo(new Foo(i) ); }

}

private:

typedef shared_ptr<Foo> PFoo;

PFoo ptr_;

};

Listing 3. Wykorzystanie klasy cech do wyboru algorytmu kopiowania

template<typename T> //kopiowanie za pomocą memcpy

T* doFastDeepCopy(const T* element, true_type) {

char* mem = new char[sizeof(T)]; //przydziela pamięć

memcpy(mem, element, sizeof(T));

return reinterpret_cast<T*>(mem);//zwraca obiekt odpowiedniego typu

}

template<typename T> //woła konstruktor kopiujący

T* doFastDeepCopy(const T* element, false_type) {

return new T(*element);

}

template<class T> //algorytm tworzenia kopii wykorzystuje trejty

T* fastDeepCopy(const T* element) {

return doFastDeepCopy(element, has_trivial_copy<T>() ); //tworzy dodatkowy

argument

}

Rysunek 1. Kopia płytka i głęboka dla obiektów dostępnych pośrednio

Szybki startAby uruchomić przedstawione przykła-dy, należy mieć dostęp do kompilatora C++ oraz edytora tekstu. Niektóre przy-kłady korzystają z udogodnień dostar-czanych przez biblioteki boost, warun-kiem ich uruchomienia jest instalacja bi-bliotek boost (w wersji 1.36 lub nowszej). Na wydrukach pominięto dołączanie od-powiednich nagłówków oraz udostęp-nianie przestrzeni nazw, pełne źródła do-łączono jako materiały pomocnicze.

Page 14: SDJ_03_2010_PL

3/201014

Programowanie C++

wana w klasach konkretnych, jeżeli ją będzie-my wołali, to będzie tworzona głęboka kopia obiektu o odpowiednim typie.

Jeżeli jest dostępny wirtualny konstruktor, możemy tworzyć głęboką kopię, wykorzy-stując interfejs klasy bazowej, wołając meto-dę clone(). Listing 4 zawiera przykład, któ-ry tworzy głęboką kopię kolekcji figur i wyko-rzystuje przedstawioną technikę.

Fabryka prototypówWzorzec prototypu możemy wykorzystać w fabryce, która będzie dostarczała obiek-tów danego typu, nazywanej fabryką pro-totypów. Fabryki są to klasy pośredniczą-ce w tworzeniu nowych obiektów, jeden z rodzajów fabryk został omówiony w SDJ 2/2010. Fabryka prototypów przechowu-je obiekty wzorcowe, które będą kopiowa-ne, jeżeli użytkownik zleci utworzenie no-wego obiektu. Fabryka taka pozwala two-rzyć obiektów różnych typów na podstawie identyfikatora, ponadto możemy nadać róż-ne identyfikatory obiektom tego samego ty-pu różniącym się stanem. Fabryki prototy-pów zazwyczaj zużywają więcej zasobów niż fabryki obiektów, konieczne jest przechowy-wanie obiektów wzorcowych, na podstawie których będą tworzone kopie. Przykład ta-kiej fabryki pokazano na Listingu 5.

Fabryki prototypów pozwalają wygodnie tworzyć obiekty z danej hierarchii klas, wy-magają, aby w tej hierarchii był implemento-wany wzorzec prototypu. Dodatkowym kosz-tem tego rodzaju fabryki jest używanie me-chanizmu późnego wiązania (funkcje wirtu-alne), więc obiekty muszą zawierać wskaźnik na tablicę funkcji wirtualnych, wołanie meto-dy clone() odbywa się pośrednio.

PodsumowaniePrzedstawione techniki związane z tworze-niem kopii są powszechnie stosowane w róż-nych językach programowania. Ich znajo-mość pozwala na tworzenie płytkiej lub głę-bokiej kopii w zależności od potrzeb.

Listing 4. Wzorzec prototypu

class Figure {//klasa bazowa

public:

virtual Figure* clone() const = 0;//wirtualny konstruktor

virtual ~Figure(){}

};

class Square : public Figure {//klasa konkretna

public:

Square(int size) : size_(size) {}

Square(const Square& sq) : size_(sq.size_) {}

Figure* clone() const { return new Square(*this); } //tworzy głęboką kopię

private:

int size_;

};

class Circle : public Figure {//klasa konkretna

public:

Circle(int r) : r_(r) {}

Circle(const Circle& c) : r_(c.r_) {}

Figure* clone() const { return new Circle(*this); }//tworzy głęboką kopię

private:

int r_;

};

//przykład użycia wirtualnego konstruktora do tworzenia głębokiej kopii obiektów

typedef vector<Figure*> Figures;

Figures figures;//kolekcja figur, która będzie kopiowana

figures.push_back(new Square(2) );

figures.push_back(new Circle(3) );

figures.push_back(new Circle(1) );

Figures copy; //głęboka kopia kolekcji figur

for(Figures::const_iterator ii = figures.begin(); ii != figures.end(); ++ii)

copy.push_back( (*ii)->clone() );//wykorzystuje wzorzec prototypu

Listing 5. Fabryka prototypów

class FigCloneFactory { //fabryka prototypów dla hierarchii figur

public:

int registerFig(Figure* prototype, int id) { //rejestruje nowy obiekt oraz jego

identyfikator

prototypes_.insert( make_pair(id, prototype) );

}

Figure* create(int id) {//tworzy obiekt danego typu i w danym stanie

map<int, Figure*>::const_iterator i = prototypes_.find(id);

if(i ! = prototypes_.end() ) //jeżeli znalazł odpowiedni wpis

return prototypes_.find(id)->second->clone(); //wzorzec prototypu

return 0L; //zwraca nullptr jeżeli nie znalazł prototypu

}

private:

map<int, Figure*> prototypes_;//przechowuje obiekty wzorcowe

};

W Sieci

• http://www.boost.org – dokumentacja bibliotek boost;

• http://www.open-std.org – dokumenty opisujące nowy standard C++.

Więcej w książceZagadnienia dotyczące współcześnie stosowanych technik w języku C++, wzorce projekto-we, programowanie generyczne, prawidłowe zarządzanie zasobami przy stosowaniu wy-jątków, programowanie wielowątkowe, ilustrowane przykładami stosowanymi w bibliotece standardowej i bibliotekach boost, zostały opisane w książce ,,Średnio zaawansowane pro-gramowanie w C++'', która ukaże się niebawem.

ROBERT NOWAKAdiunkt w Zakładzie Sztucznej Inteligencji Insty-tutu Systemów Elektronicznych Politechniki War-szawskiej, zainteresowany tworzeniem aplikacji bioinformatycznych oraz zarządzania ryzykiem. Programuje w C++ od ponad 10 lat.Kontakt z autorem:[email protected]

Page 15: SDJ_03_2010_PL

15

Opis DVD

Jeśli nie możesz odczytać zawartości płyty DVD, a nie jest ona uszkodzona mechanicznie, sprawdź ją na co najmniej dwóch napędach DVD. W razie problemów z płytą, prosimy pisać pod adres: [email protected]

Redakcja nie udziela pomocy

technicznej w instalowaniu i użytkowaniuprogramów

zamieszczonych na płytach DVD-ROM dostarczonych razem z pismem.

Programowanie w języku Java

Od Witaj świecie do aplikacji korporacyjnych. Aplikacje internetowe – Aplikacja zarządzania konferencjami

Szósty odcinek serii to przykład zastosowania dotychczas zdo-bytych umiejętności do stworzenia aplikacji internetowej. Ce-lem działań będzie zrealizowanie fragmentu funkcjonalności aplikacji do zarządzania konferencjami – tworzenia, przeglą-dania i aktualizacji konferencji.

Pierwszym krokiem będzie stworzenie stron nawigacyjnych z odpowiednimi pozycjami menu, używając do tego podstawo-wych konstrukcji HTML i JSP.

Następnie zostanie stworzony serwlet zajmujący się dyna-miczną częścią aplikacji, której zadaniem będzie interpreta-cja danych otrzymanych w żądaniu od użytkownika oraz wy-korzystaniu ich do zmian systemu. Przedstawione zostaną ele-menty komunikacji między sewletami a stronami JSP, zastoso-

wania klasy ServletContext i interfejsu ServletContextListener. Ponadto zostanie przedstawiony sposób organizacji logiki inter-fejsu użytkownika oraz logiki biznesowej na przykładzie klasy służącej do trwałego zapisywania danych.

W efekcie powstanie działający fragment systemu z zastosowa-niem mechanizmów serwletów i JSP.

Archiwum Linux+ 2009

Page 16: SDJ_03_2010_PL

03/201016

Programowanie JavaCertyfikat SCJP

www.sdjournal.org 17

W trzeciej części cyklu artykułów, poświęconego przygotowaniom do egzaminu Sun Certified Java

Programmer for Java SE 6, zajmiemy się kolej-nym zagadnieniem – API Contents, czyli głów-nie (acz nie tylko) funkcjonalnością J2SE.

W trakcie omawiania niniejszego zagadnie-nia zwrócimy uwagę na kilka zasadniczych po-dzagadnień:

• Klasy opakowujące typy prymityw-ne (m.in. Integer, Character, Byte) i związane z nimi operacje automatyczne-go pakowania/rozpakowania;

• Operacje na łańcuchach znaków i podobne;• Operacje na plikach z wykorzystaniem

klas czytających/zapisujących (Reader/Writer i pochodne);

• Dostosowywanie informacji (teksto-wych, liczbowych, dat) do ustawień regionalnych – klasy DateFormat,

NumberFormat, Locale;• Wykorzystywanie wyrażeń regularnych

w praktyce.

Podobnie jak w poprzednich artykułach, nie będziemy skupiać się na całych zagadnie-

niach (podstawy omówimy jedynie w przy-padku asercji); kluczowe są przeróżne sztuczki i ruczki, za pomocą których twórcy egzaminu chcą utrudnić programistom zdanie go.

Typy prymitywne a sprawa klasProgramowanie w Javie wiąże się nieodłącznie z wykorzystywaniem obiektów. Trudno zna-leźć program, który ich nie używa. Jak już zosta-ło wcześniej napisane, w Javie posługujemy się referencjami do obiektów, a nie obiektami sa-mymi w sobie. Oznacza to, że dowolna zmien-na nie przechowuje obiektu, a jedynie odniesie-nie (wskaźnik) do niego. Gwoli ścisłości trzeba jednak zaznaczyć, że nie wszystkie zmienne są powiązane z obiektami! W przypadku typów prymitywnych (int, char, byte, boolean, float, double, short, long) zmienne przechowują war-tości tych typów, a nie referencje do nich. Teoria teorią, ale jak to stwierdzenie ma się do codzien-nej pracy programisty?

Zasada jest prosta. Z formalnego punktu wi-dzenia we wszystkich metodach, które przyj-mują jako parametr(y) obiekt(y), przekaza-nie zmiennej typu prymitywnego to błąd. Ko-nieczne jest zastosowanie obiektu specjalnej kla-sy (innej dla każdego typu prymitywnego), któ-ry „opakuje” zmienną prymitywną. Jako pełno-prawny obiekt będzie można go przekazać do żądanych metod.

O jakich metodach mowa? Chociażby o naj-prostszych operacjach związanych z kolekcja-mi (więcej na ich temat w jednym z kolejnych artykułów):

List lista = new List();

lista.add(new Integer(3));

Ten krótki fragment kodu prezentuje działa-nie klasy opakowującej (z ang. wrapper) dla ty-pu int. Wartość typu prymitywnego (3) zo-stała przekazana do konstruktora tej klasy. No-wo utworzony obiekt możemy bez przeszkód dodać do listy.

Na podobnej zasadzie funkcjonują pozo-stałe klasy opakowujące. Tabela 1 zawiera spis wszystkich typów prymitywnych i odpowiada-jących im klas.

Wiemy już, jak zamienić wartość typu pry-mitywnego na obiekt, a co z operacją odwrotną? Wystarczy skorzystać z metody intValue(), aby uzyskać wartość prymitywną:

Integer obiekt = (Integer)lista.get(0);

int liczba = obiekt.intValue();

Przedstawione powyżej rozwiązania są jak najbardziej poprawne. Już w tym momen-cie wiem, że niejeden Czytelnik zdążył zapro-testować – „Przecież wywołanie rodzaju li-sta.add(3); działa! O co tutaj chodzi?”.

Problem wynika, jak to zwykle w takich sy-tuacjach, z powodów historycznych. Powyższe rozwiązanie jest jedynym poprawnym rozwią-zaniem w Javie do wersji 1.4 włącznie. Od Javy 5 wprowadzono mechanizm autoopakowania (ang. autoboxing), który odpowiada za automa-tyczne opakowywanie wartości prymitywnych, jak i ich powrotną konwersję:

lista.add(5);

int liczba = (int)lista.get(0);

Rzutowanie jest niezbędne do wykonania kon-wersji z typu Object (nasza przykładowa lista nie jest generyczna), jednak pośrednia kon-wersja do typu Integer dokonuje się automa-tycznie.

Przewodnik po SCJPProces zdobywania certyfikatów, potwierdzających umiejętności z różnych dziedzin wiedzy, stał się jednym z ważniejszych elementów osobistego rozwoju. Proces ten ma miejsce również w branży IT; certyfikaty dla programistów (Java lub .NET), administratorów czy sieciowców (Cisco) można coraz częściej odnaleźć w CV osób starających się o pracę, zwłaszcza w owianym złą sławą kryzysie gospodarczym.

Dowiesz się:• Jakie klasy uczestniczą w procesie i18n;• Jakie klasy wejścia/wyjścia musisz znać do

egzaminu;• Na czym polega mechanizm autoopakowy-

wania.

Powinieneś wiedzieć:• Jak skompilować i uruchomić programy

w Javie;• Jakie są podstawy składni języka;• Jakie są ogólne zasady korzystania z API

w Javie.

Poziom trudności

Czyli certyfikat z Javy – część 3

Page 17: SDJ_03_2010_PL

03/201016

Programowanie JavaCertyfikat SCJP

www.sdjournal.org 17

Na szczęście zadania związane z klasami opa-kowującymi nie są trudne; część z nich (związa-na z przeciążaniem metod) została omówiona w pierwszej części kursu (SDJ 11/09). Przede wszystkim, musisz pamiętać dokładnie nazwy klas. Trzeba uważać, aby nazwy klas opakowują-cych nie pomyliły Ci się z nazwami typów.

Łańcuchowe szaleństwoKrótki opis klas opakowujących z poprzednie-go ustępu ułatwia zrozumienie działania typów prymitywnych w obiektowym świecie. Nie wspomniałem w nim jednak o jednej z klas, któ-ra chyba najczęściej jest zaliczana do typów pry-mitywnych w Javie. Mowa o klasie String.

Klasa String odpowiada za przechowywanie tekstów – zbiorów znaków. Klasa ta otrzymała (jako jedyna) od twórców Javy specjalne przywi-leje – możemy w jej przypadku korzystać z ope-ratorów + i = (co nie jest możliwe w przypadku innych klas, bo przeciążanie operatorów jako ta-kie w Javie nie występuje). Początkujący progra-mista, widząc taki zapis:

String s = "tekst";

s = s + "inny";

może istotnie pomylić klasę String i uznać ją za typ prymitywny. To jednak nie koniec pro-blemów – z klasą String wiąże się wiele cie-kawych zagadnień, które mogą znaleźć się na Twoim egzaminie.

Zaczniemy z wysokiego c – zajmiemy się kwestią modyfikowalności, inaczej zwaną zmiennością (z ang. mutability). Cechą charak-terystyczną łańcuchów jest ich niezmienność. Oznacza to, że w raz utworzonym łańcuchu nie możesz zmienić np. drugiego znaku. Co więcej, nie możesz także dołączać innych łańcuchów na początku, ani na końcu już istniejącego łań-cucha. To zdanie jest sprzeczne z powyższym przykładem, więc spieszę z wyjaśnieniem – drugi wiersz (z operacją konkatenacji – złącza-nia łańcuchów) nie zmienia obiektu tekst, tylko tworzy zupełnie nowy obiekt, zawierający tekst tekstinny. Nie zapominaj, że zmienna s jest jedy-nie referencją! Po wykonaniu pierwszej instruk-cji wskazuje ona na jeden obiekt, a po wykona-niu drugiej – już na zupełnie inny!

W tym momencie dochodzimy do często spotykanego na egzaminach pytania – na pod-stawie fragmentu kodu musisz określić, ile obiektów typu String zostało w nim utworzo-nych. Odpowiedź na to pytanie dla poprzednie-

go fragmentu brzmi: 3 (dwa z nich zostały wyja-śnione, a trzeci musisz znaleźć samodzielnie).

Na zakończenie tego podzagadnienia nie mógłbym pominąć kwestii puli łańcuchów (ang. string pool). Dzięki niezmienności łańcu-chów możemy osiągnąć pewne korzyści. Załóż-my, że tworzysz łańcuch za pomocą bezpośred-niego przypisania:

String s = "tekst";

Java w tym momencie dodaje do owej puli wy-żej wymieniony literał tekstowy. Jeśli w dal-szym fragmencie kodu ten sam literał pojawi się ponownie:

String s2 = "tekst";

to zamiast tworzyć nowy obiekt, JVM skorzy-sta z obiektu istniejącego już w puli! Nie daj się zatem zwieść – wywołanie dwóch instruk-cji w obrębie jednego bloku kodu spowoduje utworzenie tylko jednego obiektu! Jednak już utworzenie łańcucha znaków za pomocą kon-struktora:

String s3 = new String("tekst");

spowoduje utworzenie nowego obiektu.

Metody klasy StringNie będziemy w tym miejscu dogłębnie oma-wiać metod klasy String. Opis działania moż-na zobaczyć w dokumentacji. Warto jednak zwrócić uwagę na kilka szczegółów, dotyczą-cych wszystkich metod:

• Opanuj arytmetykę łańcuchową, czyli wy-liczanie znaku na danej pozycji, a także uwzględnianie w obliczeniach długości łań-cuchów. Nie jest to skomplikowane, jednak w bardziej złożonych przykładach określa-nie podłańcuchów może zająć trochę czasu.

• Zapoznaj się z metodą intern(). W prak-tyce jest ona wykorzystywana niezwykle rzadko, jednak na egzaminie może poja-wić się w kontekście zagadnienia opisane-go w poprzednim akapicie.

• Zwróć uwagę, że drugim parametrem me-tody substring() jest indeks ostatniego znaku, a nie liczba znaków do pobrania!

• W ramach ćwiczeń dotyczących arytme-tyki łańcuchowej uwzględnij intensywne użycie metod substring() i replace().

StringBuilder i StringBuffer – tacy sami, ale nie ci sami...Przy przetwarzaniu względnie dużej ilości in-formacji z użyciem klasy String mogą wystąpić problemy związane z wydajnością. Załóżmy, że w jakimś programie podczas wczytywania da-nych cyklicznie dołączamy znaki do łańcucha. Oznacza to, że każde dołączenie tworzy nowy łańcuch, a jednocześnie przeznacza do usunię-cia stary. Co za tym idzie – jeśli w swoim pro-gramie chcesz intensywnie modyfikować i two-rzyć nowe łańcuchy, absolutnie nie powinieneś używać do tego celu klasy String.

Z powyższego kłopotu wybawią Cię kla-sy StringBuilder i StringBuffer. Obie kla-sy mają identyczną funkcjonalność, która spro-wadza się do możliwości dynamicznego dołą-czania, usuwania i modyfikacji znaków w ob-rębie wewnętrznego bufora. Różnią je dwie cechy – klasa StringBuffer jest synchronizo-wana (dzięki czemu dostęp do obiektu tej kla-sy przez różne wątki nie spowoduje powstania bezsensownych danych), a StringBuilder nie (dzięki czemu działa ona nieznacznie szybciej); ponadto klasa StringBuilder została wprowa-dzona dopiero w Javie 5.

Funkcjonalność obu klas stanowi połączenie niektórych możliwości klasy String (np. meto-dy substring(), length(), indexOf()) z ope-racjami dołączania (append()), wstawiania (in-sert()) i usuwania (delete()) znaków. Zwróć uwagę, że te trzy metody, wraz z kilkoma po-dobnymi metodami tej klasy, zwracają zmody-fikowany obiekt. Dzięki temu możesz tworzyć (nomen omen) łańcuchy wywołań:

StringBuffer sb = new StringBuffer();

sb.append("tekst").append("

inny").delete(1, 2);

Konwersję do zwykłego łańcucha znaków uzy-skuje się za pomocą metody toString(). Na egzaminie podchwytliwymi mogą okazać się pytania związane z arytmetyką łańcuchów – zwróć uwagę, że w metodzie delete() (i kilku podobnych) indeks początkowy jest wliczany do wykonywanej operacji (inclusive), a indeks końcowy – nie (exclusive)!

Tabela 1. Typy prymitywne i odpowiadające im klasy opakowujące

Nazwa typu prymitywnego

Klasa opakowująca

int Integer

boolean Boolean

byte Byte

char Character

short Short

long Long

�oat Float

double Double

Listing 1. Schemat wyświetlania sformatowanej daty

// Wyświetla aktualną datę w najbardziej rozbudowanym formacie obowiązującym w USA

Locale l = new Locale("en","US");

Calendar c = Calendar.getInstance(l);

DateFormat df = DateFormat.getDateInstance(DateFormat.FULL, l);

System.out.println(df.format(c.getTime()));

Page 18: SDJ_03_2010_PL

03/201018

Programowanie JavaCertyfikat SCJP

www.sdjournal.org 19

Operacje wejścia/wyjściaTen niezwykle rozległy temat na egzaminie jest (na szczęście) poruszany w dość ograniczony sposób. Musisz znać klasy File, FileWriter, FileReader, BufferedReader, BufferedWriter i PrintWriter. Krótki opis klas znajdziesz w Ta-beli 2., jednak to nie on w rozwiązywaniu zadań jest najważniejszy. W przypadku tych klas nale-ży wiedzieć, w jaki sposób można wiązać ze so-bą obiekty tych klas, aby uzyskać obiekt o żąda-nej funkcjonalności. Jednym słowem – wzorzec projektowy dekorator w pełnej krasie.

Zarówno strumienie, jak i pozostałe klasy wejścia/wyjścia były tworzone zgodnie z zasad-mi działania dekoratora. W dużym skrócie, de-korator opakowuje oryginalny obiekt, zachowu-jąc jego funkcjonalność, a dodatkowo wzbogaca-jąc ją o nowe możliwości. Na przykład, zwykły obiekt typu FileReader umożliwia odczyt da-nych w postaci bajtów, ale udekorowany za po-mocą obiektu klasy BufferedReader pozwala na odczyt całych wierszy tekstu:

BufferedReader br = new BufferedReader

(new FileReader("plik.txt"));

Oczywiście każda z klas dokładnie określa, ja-kie obiekty można przekazać w konstruktorze. Jednym z najczęściej spotykanych typów zadań egzaminacyjnych jest wybór poprawnych kon-struktorów dla danej klasy. W przypadku klasy BufferedReader możemy skorzystać jedynie z klas pochodzących od klasy Reader.

Same operacje zapisu i odczytu nie są trak-towane w specjalny sposób. Inaczej ma się sytu-acja z operacjami na samych plikach, a nie we-wnątrz nich. Musisz więc wiedzieć, jakie możli-wości oferuje klasa File. Standardowo zalecam więc zwracanie uwagi na nazwy metod, przyj-mowane przez nie parametry i typy zwraca-nych obiektów.

Wyświetlanie danychGdy dysponujesz już danymi pobranymi z pli-ku, musisz je w jakiś sposób wyświetlić. Twór-cy pytań egzaminacyjnych o tym nie zapo-mnieli, dzięki czemu ten dość specyficzny te-mat jest traktowany na egzaminie stosunkowo poważnie.

Aby proces formatowania tekstu nie wydał się zbyt prosty, musisz w nim uwzględnić usta-wienia regionalne, specyficzne dla danego kra-ju (a nawet regionu) – za co odpowiada osobna klasa o nazwie Locale. To właśnie ona, w połą-czeniu z klasą DateFormat (w przypadku dat) i NumberFormat (w przypadku liczb i walut), pozwala na pełną kontrolę sposobu wyświetla-nia danych. Do kompletu przyda się odrobina wiedzy na temat klas Calendar i Date; czasa-mi trzeba przecież wykonywać operacje zwią-zane z datami.

Zagadnienie samo w sobie jest dość cie-kawe, jednak nam przyświeca konkretny cel

– zdanie egzaminu. Przede wszystkim, mu-sisz znać ogólne działanie poszczególnych klas. Warto znać także skrót i18n, z angielskiego in-ternationalization (pomiędzy i a n znajduje się 18 liter) – tym mianem określa się wszystkie czynności w trakcie tworzenia oprogramowa-nia, związane z przystosowywaniem progra-mów do różnych języków/ustawień regional-nych. To oczywiście dopiero początek – naj-istotniejszy typ zadań polega na analizie ko-du wykonującego operacje i18n (wskazaniu błędów, określeniu wyświetlanych komuni-katów etc.). Z tego względu ważna jest znajo-mość kluczowych metod wyżej opisanych klas – ich połączenie w logicznie działający kod sta-je się wtedy banalne.

Na początku zazwyczaj jest tworzony obiekt klasy java.util.Locale. To właśnie ten obiekt przechowuje informację na temat kraju i/lub konkretnych ustawień regionalnych. Jest on niezależny od klas formatujących (choć klasy te wykorzystują go dość intensywnie), dlatego warto stworzyć go na początku. Do najważniej-szych składników klasy należą:

• dwuparametrowy konstruktor Locale(String język, String kraj) – język to dwuliterowy kod języka (ISO-639, pisany małymi literami, np. en), a kraj – dwuliterowy kod języka (ISO-3166 pisa-ny wielkimi literami, np. US).

• metoda statyczna getDefault() pobierają-ca domyślne ustawienia na danym kompu-terze.

• szereg statycznych pól, określających usta-wienia dla niektórych krajów (np. FRENCH, GERMAN)

Po utworzeniu ustawień lokalnych musisz po-brać odpowiedni obiekt do formatowania. Za-czniemy od liczb (walut), dlatego będzie to obiekt klasy NumberFormat. W tym przypad-ku najpierw musisz utworzyć obiekt, korzysta-jąc z jednej ze statycznych metod:

• getInstance() – działa jak getNumberIn-stance();

• getNumberInstance() – zwraca obiekt formatujący zwykłe liczby;

Pytania1. Wybierz instrukcje, które nie spowodują błędu kompilacji przy następującej deklaracji:List<Integer> lista = new List<Integer>();a) lista.add(new Integer(3));b) lista.add(3);c) lista.add(new Integer(3).intValue());d) lista.add(new Integer(3).valueOf(3));

2. Które z poniższych wyrażeń zwróci tekst 1232345?a) String s = "12345";s.substring(1, 4).substring(2,3) + "2345");b) new StringBuffer("12345").append("45").replace(3, 5, "23");c) new StringBuffer("54321").insert(2,new StringBuffer("2323").reverse().substring(2)).reverse().append("23").delete(4, 6)

3. Wybierz wywołania, które skompilują się poprawnie:a) BufferedReader br = new BufferedReader(new File("plik.txt"));b) FileReader file = new FileReader(new InputStreamReader("plik.txt"));c) PrintStream ps = new PrintStream("plik.txt"); d) PrintWriter pw = new PrintWriter(new File("plik.txt"));

4. Wybierz poprawny(-e) schemat(y) prowadzący do wyświetlenia sformatowanej daty:a) utworzenie obiektu klasy Locale -> utworzenie obiektu typu Date -> utworzenie obiektu formatującego NumberFormat -> pobranie daty z kalendarza -> wyświetlenie datyb) utworzenie obiektu typu Date -> utworzenie obiektu formatującego DateFormat -> wy-świetlenie datyc) pobranie obiektu typu Calendar -> utworzenie obiektu formatującego DateFormat -> pobranie daty -> wyświetlenie daty

5. Wybierz prawidłowe odpowiedzi:a) Poniższy kod wyświetli tekst 0 abaabc:Pattern p = Pattern.compile("[abc]+");Matcher m = p.matcher("abaabc");while (m.find()) System.out.println(m.start()+" "+m.group());b) Poniższy kod wyświetli tekst 0 abaabc:Pattern p = Pattern.compile("[abc]+");Matcher m = new Matcher(p).matcher("abaabc");while (m.find()) System.out.println(m.start()+" "+m.group());c) Poniższy kod wyświetli tekst 0 0 1 1 2 2 3 3Pattern p = Pattern.compile("\d+\s");Matcher m = p.matcher("0 1 2 3 ");while (m.find()) System.out.println(m.start()+" "+m.group());

Page 19: SDJ_03_2010_PL

03/201018

Programowanie JavaCertyfikat SCJP

www.sdjournal.org 19

• getCurrencyInstance() – zwraca obiekt formatujący z uwzględnieniem konwencji zapisu walut.

Powyższe wersje metod zwracają obiekty do-stosowane do domyślnych ustawień regional-nych – istnieją też warianty tych metod, przyj-mujące obiekt klasy Locale. Po utworzeniu obiektu nie pozostaje Ci nic innego, jak wy-wołać metodę format(), aby wyświetlić liczbę w żądany sposób:

System.out.println(numberFormatter.format(3

000000));

Nieco bardziej skomplikowany schemat obo-wiązuje w przypadku formatowania daty. Po pierwsze, musisz najpierw pobrać datę, a po drugie – utworzenie obiektu formatujące-go dla dat jest bardziej złożone. Zacznijmy od pierwszego etapu.

Uzyskanie obiektu klasy Date, który można sformatować za pomocą klasy DateFormat, jest możliwe na dwa sposoby. Po pierwsze, możesz po prostu utworzyć obiekt:

Date d = new Date();

Obiekt d zawiera datę i czas jego utworzenia. Możesz skorzystać z metod klasy Date; jest jed-nak jedno małe „ale”. Metody te zostały uzna-ne za przestarzałe już w JDK 1.1. Znamieni-ta funkcjonalność tej klasy została zastąpiona przez klasę Calendar. Uzyskanie obiektu kla-sy Date w przypadku tej klasy wymaga wyko-nania dwóch kroków:

• pobrania obiektu klasy Calendar – za po-mocą metody getInstance() dla domyśl-nych ustawień regionalnych lub z parame-trem klasy Locale;

• wywołania metody getTime().

Gdy dysponujesz obiektem do sformatowania, musisz jeszcze utworzyć obiekt formatujący klasy DateFormat. W tym przypadku również możesz skorzystać z metod statycznych:

• getDateInstance(int styl, Locale lokale) – pobiera formater dostosowany do ustawieńregionalnych z określonym stylem daty.

• getDateInstance(int styl) – pobie-ra formater dostosowany do domyślnych

ustawień regionalnych z określonym sty-lem daty.

• getDateInstance() i getInstance() – pobierają formater dostosowany do do-myślnych ustawień regionalnych ze sty-lem DEFAULT – w przypadku metody getDateInstance() i ze stylem SHORT – w przypadku metody getInstance().

Styl daty określa sposób wyświetlania daty i cza-su – do dyspozycji masz zbiór kilku stałych, za-deklarowanych w klasie DateFormat: (DEFAULT, SHORT, FULL, MEDIUM, LONG). Ich dokładne warto-ści zależą od ustawień regionalnych. Warto za-uważyć, że obecnie domyślny styl daty przecho-wuje tę samą wartość, co styl MEDIUM. Jeśli chcesz sformatować także czas, skorzystaj z metod za-wierających dodatkowo słowo Time w nazwie.

Po uzyskaniu obiektu musisz wywołać meto-dę parse(), podając jako argument obiekt daty. Cały kod (dla wariantu z datą) jest przedstawio-ny na Listingu 1.

Cały schemat wyświetlania danych przedsta-wiłem dość drobiazgowo, ponieważ równie dro-biazgowe pytania mogą pojawić się na egzami-nie. Jeśli jakiś element pojawił się w moim omó-wieniu, oznacza to, że ma on znaczenie.

Wyrażenia regularne (nie)?fajne są\?Obsługa wyrażeń regularnych stanowi istotne zagadnienie w chyba każdym szanującym się ję-zyku programowania, dlatego nie mogło jej za-braknąć w Javie, jak również na egzaminie. Wie-dza niezbędna na egzaminie składa się z dwóch podzagadnień: znajomości wybranych wyrażeń regularnych, jak również sprawnego poruszania się wśród klas je obsługujących. Samo omówie-nie wyrażeń regularnych (z uwagi na ograniczo-ny rozmiar artykułu) sprowadzę jedynie do ich wypisania (za java.sun.com):

. (kropka), * (gwiazdka), +, ?, \d, \s, \w, [], ().

Nawiasy są wykorzystywane jedynie do grupo-wania (bez obsługi przechwytywania). Kwan-tyfikatory *, + i ? występują jedynie w wersji za-chłannej. Informacje na temat powyższych in-formacji w Internecie znajdziesz bez liku. Gdy wyrażenia regularne staną się dla Ciebie banal-ne, możesz zacząć z nich korzystać z Javie, ko-rzystając z wielu klas. Zaczniemy od najbar-dziej standardowych – Pattern i Matcher.

Klasa Pattern reprezentuje pojedyncze wyra-żenie regularne. Aby uzyskać obiekt tej klasy, mu-sisz skorzystać ze statycznej metody compile():

Pattern p = Pattern.compile("\s[a-z]+\s");

Powyższe wyrażenie regularne dopasowuje ciąg słów (pisanych małych literami), bez pol-skich znaków, przedzielonych białymi znaka-mi. Po utworzeniu obiektu musisz skorzystać z metody matcher(), aby dopasować podane wyrażenie regularne do danych wejściowych:

Matcher m = p.matcher("test");

Teraz możesz zacząć przeszukiwać dane wej-ściowe, pobierając informacje na temat kolej-nych pasujących do wyrażenia regularne pod-ciągów:

while (m.find()) {

System.out.println(m.start() + " " +

m.group());

}

Metoda start() zwraca indeks początkowy podciągu, który pasuje do wyrażenia, a meto-da group() – cały podciąg. Metoda find() wy-szukuje kolejne podciągi pasujące do wyraże-nia. Dopóki wyszukiwanie będzie przynosiło wyniki – będzie zwracana wartość true.

Powyższy schemat jest jednym z najczęściej sprawdzanych mechanizmów na egzaminie. Warto wiedzieć, że do sprawdzenia pojedyn-czego dopasowania możesz skorzystać z meto-dy matches() obiektu klasy Matcher (utwo-rzonego w taki sam sposób, jak w poprzednim przykładzie):

boolean pasuje = m.matches();

Zagadnienie serializacji i obsługa metody printf(), które w pewnych zestawach ćwi-czeniowych pojawia się w ramach tego dzia-łu, zostanie omówione w jednym z kolejnych artykułów.

W tym momencie nie pozostało Ci nic inne-go, jak rozwiązać zadania załączone do tego ar-tykułu. Powodzenia!

KRZYSZTOF RYCHLICKI KICIORAutor programuje w Javie i .NET. Pisze książki i ar-tykuły na różne tematy związane z programowa-niem. Jest posiadaczem certy�katu SCJP i SCWCD; od kwietnia 2007 do kwietnia 2008 legitymował się tytułem Microsoft MVP w kategorii Visual C#. Kontakt z autorem: [email protected]

Bibliogra�a:

• Sierra K., Bates B. – Sun Certi�ed Pro-grammer for Java 5 Study Guide

Odpowiedzi1. a, b, c. Odpowiedź d jest nieprawidłowa, ponieważ metoda valueOf() jest statyczna, a dodatkowo jako para-metr przyjmuje łańcuch znaków.2. b – pozostałe warianty generują inne wartości (sprawdź jakie, a następnie skompiluj podane wyrażenia, aby potwierdzić wyniki).3. a, c, d – b nie, ponieważ konstruktor klasy FileReader nie przyjmuje jako argumentu obiektu klasy Reader.4. b, c – a nie, ponieważ do formatowania daty nie korzystamy z obiektu typu NumberFormat. Niepotrzebne jest też dwukrotne pobieranie daty, chociaż nie jest to formalnie rzecz biorąc błąd.5. a – b nie, ponieważ klasa Matcher nie zawiera konstruktora. Przykład c jest prawie dobry – problem w tym, że metoda start() będzie zwracać kolejne indeksy zliczane wraz ze spacjami, więc na standardowym wyjściu otrzymasz tekst 0 0 2 1 4 2 6 3.

Page 20: SDJ_03_2010_PL

03/201020

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 21

Systemy klasy CLP (Constraint Logic Programming) są szczególną odmia-ną mechanizmów opartą o paradyg-

mat programowania z ograniczeniami. Me-toda CLP łączy w sobie dwa paradygma-ty programowania: programowanie logicz-ne (Logic Programming) oraz programowa-nie z ograniczeniami (Constraint Program-ming). W przeciwieństwie do imperatyw-nych języków programowania, w których podaje się sekwencje poleceń do wykona-nia, w językach programowania logicznego specyfikuje się zestaw zależności, na pod-stawie których system dedukcyjny próbu-je udowodnić zadane twierdzenie. Z ko-lei programowanie z ograniczeniami pole-ga na podaniu pewnych ograniczeń, które określają właściwości poszukiwanego roz-wiązania. Wyrażają one relacje pomiędzy zmiennymi. Połączenie dwóch przedsta-

wionych metod programowania zaowoco-wało stworzeniem narzędzia (podejścia), które w większości zastosowań jest bardziej wydajne od zwykłego programowania lo-gicznego. Stale przybywa sporo nowej wie-dzy na temat rozwiązywania problemów z ograniczeniami oraz rozwijane są meto-dy optymalizacji trudnych do rozwiązania problemów. Sama technika nie jest do koń-ca zrozumiała przez wielu ludzi i jest przez to mało popularna. Zupełnie odwrotnie ma się to do jej efektywności, która jest wyso-ka. Jak można zauważyć, systemy takie ma-ją wiele możliwych oraz naturalnych do im-plementacji zastosowań. Z powodzeniem można je stosować w rzeczywistych środo-wiskach produkcyjnych, w których są odpo-wiednim rozwiązaniem systemowym.

Różne warianty modelu CLP W wypracowanych dotychczas wariantach istnieje wiele stosowanych odmian tego pa-radygmatu. Możemy wyróżnić podejście klasyczne do rozwiązania problemu: czyli problem CSP (Constraint Satisfaction Pro-blem), jego wersję rozproszoną - DisCSP (Distributed Constraint Satisfaction Pro-

blem) oraz wersję dynamiczną DynCSP (Dynamic CSP - składa się ona ze skończo-nej sekwencji klasycznych problemów CSP (np.: csp(0), csp(1),…, csp(n)), w której każdy podproblem jest inny – np. poprzez zmia-nę ograniczeń w kolejnych krokach). Sku-piają się one na odpowiednim zapisie pro-blemu w celu jego rozwiązania przez sys-tem. System taki musi być odpowiednio wyposażony w mechanizmy oraz algoryt-my heurystyczne, służące do poszukiwa-nia rozwiązania.

Kolejnym obszarem silnie badanym są też poszczególne warianty optymalizacji problemu spełnienia ograniczeń. Warianty takie dla konkretnej odmiany czy podejścia przedstawiają się następująco: CSOP (Con-straint Satisfaction Optymization Problem) – dla klasycznego problemu oraz DCOP (Distributed Constraint Optymization Pro-blem) – dla problemu rozproszonego. Dla-tego celem artykułu jest przegląd obejmu-jący przedstawienie reprezentacji wiedzy w wybranych obszarach zapisu problemu oraz przedstawienie narzędzi. Aby opisy-wane zagadnienia były lepiej zrozumiałe, dołączono także przykłady, odnoszące się do wybranego zapisu problemu.

Rzeczywiste problemy Główne obszary zastosowań opisywanych technik obejmują zadania klasy kombina-torycznej oraz optymalizacji dyskretnej. W szczególności technika ta znajduje zasto-sowanie do sterowania dyskretnymi procesa-mi produkcyjnymi. W tych obszarach opi-sywane narzędzie obliczeniowe jest bardzo przydatne między innymi dla:

Paradygmat programowania CLP

Wysoka efektywność metod CLP jest rezultatem wykorzystania procedur propagacji ograniczeń oraz dystrybucji zmiennych, w celu poszukiwania rozwiązań spełniających wszystkie przyjęte ograniczenia. Procesy te realizowane są w sposób klasyczny, jak i rozproszony, dając bardzo dobre rezultaty obliczeniowe.

Dowiesz się:• O deklaratywnym podejściu do zapisu/

rozwiązywania trudnych problemów decy-zyjnych;

• Informacji o różnych wariantach reprezentacji wiedzy dla problemu spełnienia ograniczeń;

• Jakie rodzaje narzędzi oraz mechanizmów są nieodłączną częścią paradygmatu CLP.

Powinieneś wiedzieć:• O zagadnieniach optymalizacji oraz wybranych

aspektach z obszaru badań operacyjnych;• Podstawowe informacje na temat paradyg-

matów programowania oraz algorytmiki;• Obszary sztucznej inteligencji z zakresu sys-

temów wieloagentowych (MAS).

Poziom trudności

Metody rozwiązywania trudnych problemów kombinatorycznych.

Page 21: SDJ_03_2010_PL

03/201020

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 21

• wspomagania harmonogramowania;• szeregowania zadań, a w szczególności

przydziału zadań i zasobów dla poszcze-gólnych maszyn;

• optymalizacji planu dla projektu.

Rzeczywiste problemy, z jakimi mamy styczność podczas tworzenia dedykowa-nych narzędzi do ich rozwiązywania, są głównie problemami decyzyjnymi. Wy-mienione wyżej zagadnienia należą do pro-blemów optymalizacji dyskretnej z ograni-czeniami.

Tradycyjne podejścia matematyczne do rozwiązania tego typu problemów, czyli znaj-dowania rozwiązań optymalnych, a nawet w niektórych przypadkach tylko dopuszczal-nych, są mało efektywne. Spowodowane jest to tym, iż w takiego rodzaju zadaniach istnie-je tzw. zjawisko eksplozji kombinatorycznej, wynikające z dużej złożoności obliczeniowej zadania. Zadania takie zaliczane są do klasy zadań NP-trudnych.

W proponowanych przez większość ist-niejących na rynku rozwiązań, dla takiego typu zadań, odchodzi się powoli od metod poszukiwania rozwiązania problemu za po-mocą języków imperatywnych na rzecz now-szego narzędzia (podejścia), którym jest de-klaratywny język programowania z ograni-czeniami. Wraz ze swoistymi mechanizma-mi, bazującymi na programowaniu w logice, posiada ono duży potencjał obliczeniowy do wykorzystania.

Klasyczny problem spełnienia ograniczeńProgramowanie w logice z ograniczeniami CLP(Constraint Logic Programming) to dzie-dzina nauki, którą można ulokować pomię-dzy sztuczną inteligencją, badaniami ope-racyjnymi oraz językami programowania. Jest paradygmatem (koncepcją czy też tech-niką) programowania tak jak np. programo-wanie zorientowane obiektowo czy progra-mowanie funkcyjne (które to jest filozofią programowania będącą odmianą progra-mowania deklaratywnego, w której funkcje należą do wartości podstawowych, a nacisk kładzie się na wartościowanie funkcji, a nie na wykonywanie poleceń). Koncepcja ta stosowana jest do modelowania, rozwiązy-wania oraz zapisu problemów, które mogą być dane za pomocą zbioru zmiennych i ich dziedzin, oraz relacji pomiędzy nimi wyra-żonych w postaci ograniczeń. Ograniczenia takie odzwierciedlają zależności pomiędzy zmiennymi problemu. Łatwość modelo-wania problemów decyzyjnych, a w szcze-gólności tych, które posiadają w swojej na-turze ograniczenia, brak przepaści seman-tycznej pomiędzy modelem a metodyką je-go rozwiązywania oraz efektywne metody

poszukiwania rozwiązań, czynią środowi-ska CLP bardzo atrakcyjnym do szybkiego modelowania i rozwiązywania problemów decyzyjnych. Jest to szczególnie przydat-

ne przy budowie dedykowanych systemów wspomagania decyzji, które charakteryzują się specyficznymi właściwościami, struktu-rą czy ograniczeniami.

Rysunek 1. Przykład interpretacji gra�cznej problemu/zadania

���

��

���� ���� ����

��������������

����������������

���������������

�� � � � ��

� � �

Rysunek 2. Graf wartości zmiennych oraz relacje ograniczeń pomiędzy nimi

���

���

���

� �

� �

� �

Rysunek 3. Przeszukiwanie przestrzeni rozwiązań: a) algorytmy standardowe; b) zawężanie dziedziny

Page 22: SDJ_03_2010_PL

03/201022

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 23

De�nicja CSP Podstawowym zagadnieniem reprezentacji wie-dzy w programowaniu w logice z ograniczenia-mi jest tzw. problem spełnienia ograniczeń CSP (Constraint Satisfaction Problem). Klasyczny pro-blem spełnienia ograniczeń zapisujemy w po-staci trójki zdefiniowanej następująco:

( V, D, C )

gdzie:

• V – skończony zbiór zmiennych;• D – związane ze zmiennymi skończone

dziedziny;• (są one odpowiednio dziedzinami dla

poszczególnych zmiennych).• C – skończony zbiór ograniczeń (wię-

zów) spełnianych przez zmienne ze zbio-ru V.

Przykład 1:Przykładowy zapis zadania reprezentujący wiedzę o postawionym problemie może mieć następującą postać:

• V = {x, y, z} – zbiór zmiennych;• D = {x::{0..9}, y::{0..9}, z::{0..9}}

– są to poszczególne dziedziny dla zmiennych;

• C = {x < y, z = x + y, x>=3, z =

8} – jest to zapis zbioru ograniczeń, jakie występują w problemie.

Rozwiązaniem tak zapisanego problemu jest takie przyporządkowanie każdej zmiennej pewnej wartości z jej dziedziny, aby wszyst-kie ograniczenia były spełnione (jednocze-śnie). Inaczej mówiąc, jest to dowolne war-tościowanie zmiennych spełniające wszyst-kie ograniczenia.

Problem CSP jest głównie rozwiązywany przez standardowe w tej dziedzinie algorytmy takie jak: backtracking (nawracanie), dynamicz-ny backtracking, back jumping (odskakiwanie do tyłu) oraz foward checking (sprawdzanie do przodu). Problem ten może być również roz-wiązywany w oparciu o reprezentację grafową metodą sprawdzania spójności łukowej. Spój-ność łukowa sprawdza spełnialność więzów lo-kalnie pomiędzy nieprzypisanymi zmiennymi (czyli zmiennymi, które nie mają jeszcze okre-ślonej wartości). Łuki są spójne, gdy dla warto-ści każdej zmiennej danego węzła istnieje przy zadanym ograniczeniu odpowiednia wartość zmiennej z węzła, do którego została ona przy-pisana. Poniżej podano zapis przykładowego zadania oraz jego interpretacje za pomocą gra-fu (Rysunek 2) :

V = { x, y, z };

D = {{1, 2}, {1, 2}, {1, 2}};

C = { x = y, x<>z, y > z }.

Rysunek 4. Przykład redukcji dziedzin, podczas procesu propagacji w trakcie rozwiązywania zadania

���

� � � � � � � � � �

������������������������������������

����������

Rysunek 5. Przykład problemu, w którym operujemy na zmiennych typu znakowego

�������������������

��������������������������������������

��������������������������

������������������������������������������������������������������������������������

�� ����

����

Rysunek 6. Przykład zbioru agentów (wymieniających się komunikatami) oraz intra-agenta

��� ���

������

���

Page 23: SDJ_03_2010_PL

03/201022

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 23

Na Rysunku 2 przedstawiającym reprezen-tację graficzną zadania graf jest spójny wte-dy, gdy zmienne przyjmują następujące war-tości: x =2, y=2, z=1 (rozwiązanie zadania).

Metoda rozwiązywania bazująca na spraw-dzeniu spójności łuków w grafowej reprezen-tacji problemu nazywana jest AC (Arc Consy-stency).

W metodzie tej istnieje szereg algorytmów wyznaczających rozwiązanie. Podstawowy-mi algorytmami w tej grupie są : AC-1, AC-3, AC-4 oraz jeszcze wiele innych (szeroko opi-sywane w literaturze fachowej), lecz nieuży-wanych tak często jak te wymienione w ar-tykule.

Propagacja i dystrybucja – jako główne mechanizmy CLPWiększość metod rozwiązywania zadania w środowiskach opisywanych w artykule bazu-je na naprzemiennym stosowaniu procedur propagacji (ograniczania dziedzin zmiennych decyzyjnych) nazywanych w skrócie propaga-torami i dystrybucji (ukonkretniania warto-ści zmiennych decyzyjnych w celu wyszuka-nia rozwiązania).

Propagacja to operacja, która polega na sukcesywnym zawężaniu dziedzin zmien-nych zgodnie z semantyką więzów, w celu usunięcia elementów, z których nie można zbudować żadnego rozwiązania. Porządek kroków propagacji nie ma wpływu na wy-nik końcowy.

Propagacja nie jest operacją wystarczają-cą do rozwiązania problemu CSP, zwiększa ona jednak efektywność procesu (znacząco przyśpiesza) poszukiwania rozwiązania do-puszczalnego.

Propagatory są implementowane w śro-dowiskach jako współbieżnie pracujące agenty (w formie osobnych procesów) obli-czeniowe, które starają się zawęzić domeny zmiennych występujących w danym ogra-niczeniu.

Przeważnie implementacja ich wykony-wana jest za pomocą wątków. Jeśli wszyst-kie wartości wszystkich zmiennych speł-niają dane ograniczenie, propagator prze-staje istnieć:

przykłady: x < y oraz x = {3..5} i y ={6..9}

Propagatory wymuszają dodatkowe ograni-czenia (nieproste), czyli powiązania pomię-dzy dwoma lub więcej zmiennymi:

przykłady: a) x < y lub b) x + y = z

Aby przybliżyć efekt działania propagato-rów, przeanalizujmy wcześniej zdefiniowa-ne w przykładzie 1 zadanie:

• V = {x, y, z} – (zbiór zmiennych);

• D = {x::{0..9}, y::{0..9}, z::{0..9}} – (dziedziny dla zmiennych);

• C = {x < y, z = x + y, x > 3, z =

8} – (ograniczenia).

Rysunek 7. Przykładowy początek problemu (dziedziny zmiennych są kompletne)

���

��������������������������������

��������������������������������

��������������������������������

� � � � � � � � � �

��������������

��������������������

�������������������������

���������������������

���

Rysunek 8. Redukcja dziedzin po wymianie komunikatów i uzgodnieniu wartości zmiennych

�����������������������

� � � � � � � � � �

���������������������������������

���������������������������������� ������������������

����������������

�����������������������������

�����

�����������������������

Page 24: SDJ_03_2010_PL

03/201024

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 25

Na Rysunku 4 został przedstawiony przy-kład: efektem działania propagacji dla pierwszego ograniczenia x < y jest wyłą-czenie dwóch wartości z dziedzin zmien-nych: dla x jest to 9, a dla y jest to 0. W na-stępnym etapie są sprawdzane pozostałe ograniczenia.

W większości przypadków propagacja ograniczeń nie prowadzi do rozwiązania. Dlatego programowanie z ograniczeniami jest ściśle związane z dystrybucją połączo-ną z poszukiwaniem. Dystrybucja polega na wprowadzeniu dodatkowego ogranicze-nia (często jest to przyporządkowanie jed-nej wartości do zmiennej, a zadaniem dys-trybucji jest odpowiednie wybranie zmien-nej i wartości).

Kiedy to nastąpi, sprawdzana jest spój-ność poprzez propagację ograniczeń na po-zostałe zmienne. Istnieją wtedy trzy moż-liwości:

• znalezione zostanie rozwiązanie (wszyst-kie zmienne mają po jednej wartości w swojej dziedzinie);

• dziedziny niektórych zmiennych zostaną zawężone, ale jednoznaczne rozwiązanie nie jest wciąż wyznaczone, więc dystrybu-cja jest dokonywana na kolejnej zmiennej;

• dodatkowe ograniczenie jest niespójne z pozostałymi ograniczeniami, więc pro-ces nawrotu jest dokonywany, a wybra-na wartość z dziedziny wybranej zmien-nej jest usuwana.

Ten proces jest dokonywany iteracyjnie i jest nazywany poszukiwaniem. Poszukiwanie jest odpowiedzialne za zatrzymanie po zna-lezieniu pierwszego rozwiązania lub pew-nej liczby rozwiązań lub wszystkich roz-wiązań. Poszukiwanie tworzy tzw. drzewo poszukiwań, gdzie każdy węzeł jest stanem zmiennej.

Podsumowując więc, proces dystrybu-cji prowadzi do utworzenia dwóch lub wię-cej rozłącznych przestrzeni rozwiązań (np. przestrzenie liczb całkowitych dodatnich), wywodzących się z przestrzeni głównej, po-przez wprowadzenie sprzecznych ograni-czeń:

• przykład: podział na dwie rozłączne przestrzenie: x = 1, x <>1

• przykład: podział na trzy rozłączne prze-strzenie: y = 1, y = 2, y > 2

Strategia dystrybucji jest zbiorem reguł określających:

• którą ze zmiennych niezdeterminowa-nych wybrać do dystrybucji;

• w jaki sposób podzielić dziedzinę tej zmiennej na rozłączne podzbiory.Rysunek 9. Rozwiązanie problemu, czyli przyporządkowanie do zmiennych konkretnych wartości

�����������������������

� � � � � � � � � �

��������������������������

Listing 1a. Dane w osobnym pliku

Produkty = { "gas" "chloride" };

Skladniki = { "nitrogen" "hydrogen" "chlorine" };

Zapotrzebowanie = [ [1 3 0] [1 4 1] ];

Zysk = [30 40];

Sklad = [50 180 40];

Listing 1b. Kod programu w OPL Studio

{string} Produkty = ...;

{string} Skladniki = ...;

// deklaracja zmiennych przechowujących dane

float Zapotrzebowanie[Produkty][Skladniki] = ...;

float Zysk[Produkty] = ...;

float Sklad[Skladniki] = ...;

// zmienna decyzyjna (decision variable)

dvar float+ Produkcja[Produkty];

// deklaracja ograniczenia

constraint ograniczenie;

// zdefiniowanie funkcji celu

maximize

sum( p in Produkty )

Zysk[p] * Produkcja[p];

// zadanie ograniczeń

subject to {

forall( s in Skladniki )

ograniczenie =

sum( p in Produkty )

Zapotrzebowanie[p][s] * Produkcja[p] <= Sklad[s];

}

Listing 1c. Wynik programu w OPL Studio

//========================================

/*Final solution with objective = 1800:

Produkcja = [6.6667 40]; */

//========================================

Page 25: SDJ_03_2010_PL

03/201024

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 25

Jest to działanie wystarczające do rozwią-zania problemu CSP, gdyż dziedzinę każdej zmiennej można stopniowo dzielić na coraz mniejsze podzbiory, aż wszystkie zmienne zostaną zdeterminowane do pewnej war-tości. Podział taki jest niekoniecznie jed-nakowo efektywny czasowo. Efektywność może być podnoszona poprzez sterowanie procesem obliczeń, który obejmuje wybór kolejności podstawiania zmiennych decy-zyjnych, wybór kolejnych wartości zmien-nych decyzyjnych czy też liczbę podsta-wień poszczególnych zmiennych. Sterowa-nie zatem sprowadza się do określenia efek-tywnej strategii podstawiania w kontek-ście rozwiązywanego problemu. Jest to bar-dzo istotny aspekt całego procesu, mający wpływ na szybkość uzyskania rozwiązania.

Dziedziny zmiennychDo modelowania problemów decyzyjnych istnieje potrzeba określenia rodzaju zmien-nych, które najlepiej odzwierciedlą zapisywa-ny problem. Jeżeli problem wymaga do mo-delowania zbyt skomplikowanych struktur, należy go sprowadzić do problemu CSP, któ-ry można później zapisać w wybranym środo-wisku w celu rozwiązania go.

W zbiorze ograniczeń wyróżnia się pod-zbiór ograniczeń podstawowych (basic con-straints),

które explicite określają dziedziny zmien-nych, np. ograniczenie x::{2..8} mówi, że wartości, jakie może przyjmować zmienna x, są liczbami całkowitymi ze zbioru [2..8].

Zmienna o dziedzinie jednoelemento-wej nosi nazwę zmiennej zdeterminowanej. W systemach programowania klasy CLP sto-suje się (są możliwe do wykorzystania) stan-dardowe typy dziedzin dla poszczególnych zmiennych, takie jak:

• FD (finite domain) – skończone zbiory nieujemnych liczb całkowitych;

• FS (finite sets) – skończone zbiory, któ-rych elementami są dziedziny FD;

• B (binary set) – zbiór binarny {0, 1};• R (real sets) – skończone zbiory liczb rze-

czywistych wymiernych.• Dodatkowo środowiska CLP umożliwią

także implementacje ograniczeń logicz-nych, symbolicznych, które są niedo-stępne w klasycznych środowiskach pro-gramowania matematycznego. Zmien-ne definiowane są poprzez literały, np.: {czerwony, zielony, niebieski}

(przykład zobrazowany na Rysunku 5).

Problem rozproszonego spełnienia ograniczeńReprezentacja wiedzy dla środowisk roz-proszonych jest ściśle związana z systema-

mi wieloagentowymi MAS (Multi Agents Systems), w których zachodzi zjawisko ko-operacji niezależnych jednostek programo-wych, oddalonych od siebie (również fizycz-nie), działających we wspólnym środowisku. Interakcja pomiędzy poszczególnymi repre-zentantami takiego systemu zachodzi przy pomocy wymiany komunikatów.

Znalezienie rozwiązania może być zatem postrzegane jako osiągnięcie wewnętrznej spójności w zbiorze agentów. To z kolei wa-

runkuje realizację wspólnych celów agentów, która wymaga współpracy pomiędzy agenta-mi i koordynowania ich działań. Zatem ko-operacja w tego typu systemach może być po-strzegana jako problem rozproszonego speł-nienia ograniczeń (Distributed CSP). Problem taki przyjmuje więc następującą postać:

• istnieje zbiór agentów: {1, 2, ... , n};• każdy agent posiada jedną albo wie-

le zmiennych (ma przyporządkowaną

Rysunek 10a. Przykładowa reprezentacja algorytmu działającego w środowisku agentów (iteracyjne ulepszanie)

��

��

��

��

��

�� �������

�������������

����

�������������������������

��������������������������

�����������������������������

���������

�����������������������

������������������������

���������

Rysunek 10b. Przykładowa reprezentacja algorytmu działającego w środowisku agentów (iteracyjne ulepszanie)

�����������������������������������������

����������������������

�������������

����������������������

��������������

����������������������

�������������������������������������������������������������

�������������������������������

����������������������������������

���������

������������������������

�����������������������������

��������������������������������

�����������������������

��������������������������

����������������������������������

�������������������������������

������������������������������

�������������������������������

�������������������������������

����������������

������������

������������

��������

������������

�������������

�������

����������

�����������

����

�����

����������

������������

����������

��������������������������������

Page 26: SDJ_03_2010_PL

03/201026

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 27

część zmiennych poszukiwanego roz-wiązania);

• istnieje inter-agent reprezentujący ogra-niczenia.

De�nicja Distributed CSPW definicji problemu zakłada się, iż w zbio-rze agentów możliwa jest komunikacja i speł-nione są następujące warunki:

• agenty komunikują się poprzez wysyła-nie komunikatów;

• wysyłanie komunikatu możliwe jest wtedy, gdy agent zna adres odbiorcy;

• komunikat dociera do odbiorcy po skoń-czonym losowym czasie;

• dla dowolnej pary agentów porządek od-bioru jest zgodny z porządkiem wysła-nia;

• każdy agent ma tylko częściową wiedzę o problemie.

Zadanie rozproszonego spełnienia ograni-czeń jest rozwiązane w zbiorze agentów wte-

dy, gdy istnieje przyporządkowanie przez agenta takiej wartości do swojej zmiennej, która będzie spełniać zadane ograniczenia. Formalna definicja rozproszonego problemu spełnienia ograniczeń przyjmuje postać po-niżej prezentowanej szóstki:

( A, V, D, C, b, k )

gdzie:

• A – zbiór agentów;• V – skończony zbiór zmiennych, dla któ-

rych szukamy wartości;• D – związane ze zmiennymi skończone

dziedziny (gdzie: );• C – skończony zbiór ograniczeń;• b – relacja przynależności zmiennej do

agenta;• k – relacja posiadania wiedzy o ograni-

czeniu dla zmiennej (agent posiada in-formację o ograniczeniu nałożonym na konkretną zmienną).

Każdy z agentów próbuje przypisać wartość swoim zmiennym, przy czym pomiędzy agentami występują zależności, które muszą być uwzględnione w proponowanych pod-stawieniach. Zakłada się ponadto, iż każda zmienna jest związana z jednym agentem, gdyż przypadek współdzielenia zmiennych przez wielu agentów może być zastąpiony przez wprowadzenie dodatkowych zmien-nych (lokalnie dla agenta) i ograniczeń na równość ich wartości.

Przykładowe zadanie reprezentowane w omawiany sposób możemy zapisać w na-stępującej formie:

A = {a1, a2, a3};

V = {x, y, z};

D = {x::{0..9}, y::{0..9}, z::{0..9}};

C = { c1: x < y, c2: z = x + y, c3: x>=3

} ( inter-agent );

b = { belongs(x, a1), belongs(y, a2), .

. . };

k = { known(c1, a1)}, known(c1, a2)},

known(c2, a1)},

known(c1, a2), .

. . }.

gdzie:

Rysunek 11. Przykład wizualizacji przez system GPS

Listing 2a. Przykład problemu kolorowania mapy Australii 3-ma kolorami

public class LMMapColoring extends Example {

public void modelAustralia() {

System.out.println("\n \n Program to solve coloring problem:

modelAustralia() \n \n");

// deklaracja listy zmiennych oraz magazynu

// przechowującego strukturę całego problemu

store = new Store();

vars = new ArrayList<Variable>();

//(red, green, blue)

//( 0, 1, 2 )

// przypisanie dziedzin do zmiennych

FDV WA = new FDV(store, "WA", 0, 2);

FDV NT = new FDV(store, "NT", 0, 2);

FDV SA = new FDV(store, "SA", 0, 2);

FDV Q = new FDV(store, "Q", 0, 2);

FDV NSW = new FDV(store, "NSW", 0, 2);

FDV V = new FDV(store, "V", 0, 2);

FDV T = new FDV(store, "T", 0, 2);

// dodanie zmiennych do listy

// zmienne reprezentują obszary na mapie Australii

vars.add(WA); vars.add(NT); vars.add(SA);

vars.add(Q); vars.add(NSW); vars.add(V);

vars.add(T);

// zadanie ograniczeń

store.impose(new XneqY(WA,NT));

store.impose(new XneqY(WA,SA));

store.impose(new XneqY(NT,SA));

store.impose(new XneqY(NT,Q));

store.impose(new XneqY(SA,Q));

store.impose(new XneqY(SA,NSW));

store.impose(new XneqY(SA,V));

store.impose(new XneqY(Q,NSW));

store.impose(new XneqY(NSW,V));

store.impose(new XneqY(V,T)); // Tasmania

// kolorowanie startuje dla czerwonego jako pierwszego koloru

store.impose(new XeqC(WA,0));

// pomocniczy zapis problemu do pliku XML

LMUtil.toXML(store, -1, ".", "LMMapColoringModelAustralia.xml");

}//!modelAustralia()

...

Page 27: SDJ_03_2010_PL

03/201026

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 27

• b: relacja belongs(xj, i) oznacza, że agent i ma określić wartość zmiennej xj, każda zmienna xj należy więc do jedne-go i - tego agenta (ta relacja jest reprezen-towana jako belongs( xj, i));

• k: relacja known(pk, j) oznacza, iż agent j zna ograniczenie, czyli predykat pk, (ograniczenia są więc rozproszone mię-dzy agentów).

Przyjmuje się, że zadanie DisCSP jest roz-wiązane w zbiorze agentów, wtedy i tyl-ko wtedy, gdy: w odniesieniu do każdej pary: (agent, zmienna), spełniającej rela-cję belongs(), dokonano podstawienia za zmienną takiej wartości, że w dowolnej pa-rze: (predykat, agent) spełniającej relację known(), predykat przy tym podstawieniu jest prawdziwy, czyli wartość zmiennej speł-nia ograniczenia.

Na Rysunku 8 (graficzna reprezentacja za-dania wcześniej zdefiniowanego) została za-prezentowana propagacja ograniczeń, wy-stępująca po rozesłaniu komunikatów po-między agentami, która zawężała dziedziny zmiennych:

Możliwość rozwiązywania w taki spo-sób problemu ma duże znaczenie prak-tyczne ze względu na powszechne wystę-powanie rozproszonych systemów pro-dukcyjnych w gospodarce. Istotne jest zatem prawidłowe i formalne wyspecyfi-kowanie systemu – jego opis matematycz-ny, uwzględnienie warunków kolejnościo-wych, właściwe odzwierciedlenie ograni-czeń zmiennych.

W wyniku opisu zazwyczaj zostaje zde-finiowane trudne zadanie programowa-nia matematycznego, o dużym rozmia-rze, wymagające zastosowania specyficz-nych technik programowania, z koniecz-ności pozostawiających duży margines dla heurystyk.

Model dynamicznego spełnienia ograniczeń DynCSP Przedstawiony w dalszej części artykułu mo-del zapisu wiedzy jest składnikiem (jednym z wielu konwencji) paradygmatu CLP. Pro-gramowanie w logice z ograniczeniami jest dziedziną nauki, która zawiera się pomię-dzy sztuczną inteligencją, badaniami opera-cyjnymi oraz językami programowania. Jest paradygmatem programowania tak jak pro-gramowanie obiektowe czy też programo-wanie funkcyjne. Podejście takiej reprezen-tacji wiedzy stosowane jest do modelowania oraz zapisu problemów, jakie mogą być da-ne za pomocą zbioru zmiennych: V, ich dzie-dzin: D, oraz relacji pomiędzy nimi, wyra-żonych w postaci ograniczeń: C. Ogranicze-nia te odzwierciedlają zależności pomiędzy zmiennymi.

Łatwość modelowania problemów decy-zyjnych, a w szczególności tych, które po-siadają w swojej naturze ograniczenia, brak przepaści semantycznej pomiędzy modelem a metodyką jego rozwiązywania oraz efek-tywne metody poszukiwania rozwiązań czy-

nią tę koncepcję jako bardzo atrakcyjny spo-sób szybkiego modelowania i rozwiązywa-nia problemów decyzyjnych. Jest to szcze-gólnie ważny aspekt podczas budowy dedy-kowanych systemów wspomagania decyzji, które to charakteryzują się specyficznymi

Rysunek 12. Gra�czna reprezentacja dynamicznego CSP

����� ����������������������������������� �����������������������������������

Listing 2b. Przykład budowy struktury searcha dla rozważanego problemu

. . .

public boolean searchAllAtOnce() {

// zmienne potrzebne do obliczenia czasu przeszukiwania

long T1, T2;

T1 = System.currentTimeMillis();

// wskazanie heurystyki wyboru zmiennej i wartości

SelectChoicePoint select = new SimpleSelect(vars.toArray(new Variable[1]),

new MostConstrainedStatic(), new IndomainMin());

// wywołanie procedury przeszukiwania w głąb

search = new DepthFirstSearch();

search.getSolutionListener().searchAll(true);

search.getSolutionListener().recordSolutions(true);

search.setAssignSolution(true);

boolean result = search.labeling(store, select);

T2 = System.currentTimeMillis();

if (result) {

System.out.println("Number of solutions " + search.getSolutionListener().

solutionsNo());

search.printAllSolutions();

}

else

System.out.println("Failed to find any solution");

System.out.println("\n\t*** Execution time = " + (T2 - T1) + " ms");

return result;

}

...

Listing 2c. Funkcja main() wykonująca program

...

//--------------------------------------

public static void main(String args[]) {

LMMapColoring problem = new LMMapColoring();

problem.modelAustralia();

// uruchomienie przeszukiwania (wszystkie rozwiązania)

if ( problem.searchAllAtOnce() )

System.out.println("Solution(s) found");

}//!main()

}

Page 28: SDJ_03_2010_PL

03/201028

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 29

właściwościami, swoistą strukturą czy rów-nież charakterystycznymi dla siebie ograni-czeniami.

De�nicja Dynamic CSP W teorii optymalizacji przyjmuje się po-dział problemów na statyczne oraz dyna-

miczne. Wedle tego podziału w proble-mach dynamicznych poszukiwana jest funkcja rzeczywista, a w statycznych wek-tor będący rozwiązaniem. Można by się od-nieść w tym stwierdzeniu, iż problemem statycznym jest jedna instancja problemu CSP, w której poszukujemy takiego warto-

ściowania zmiennych, aby spełniło zadane ograniczenia.

Jako dynamiczną odmianę problemu można przyjąć DynCSP (co dalej zosta-ło szerzej opisane), który to jest sekwencją statycznych problemów występujących ko-lejno po sobie. W modelu takim kolejne in-stancje różnią się od siebie zestawem ograni-czeń lub rozmiarem dziedzin dla konkret-nych zmiennych.

Problemy numeryczne, występujące w za-daniach koordynacji, badaniach operacyj-nych, sterowaniu produkcją czy też innych pokrewnych gałęziach przemysłu, mogą być transformowane do problemów (mo-delu) CSP oraz do problemów optymaliza-cji COP (Constraint Optimization Problem). W wielu praktycznych aplikacjach softwa-rowych nie istnieje jeden statyczny problem CSP. W rzeczywistości problemy, które ma-my do rozwiązania, często zmieniają się w czasie. Zmiany mogą wydarzyć się w dwo-jaki sposób:

• poprzez dołączenie do modelu dodatko-wej informacji;

• lub poprzez zmianę charakteru proble-mu, którego próbujemy rozwiązać.

Jako przykład zmiany warunków dane-go problemu podać można działanie sys-temu nawigacji GPS, który musi przeli-czyć (rekalkulować) trasę podczas wystą-pienia sytuacji, gdy samochód nie podą-żył proponowaną wstępnie przez ten sys-tem drogą.

Gdy rozwiązywana jest zmodyfikowa-na wersja problemu CSP , nowy zmienio-ny problem jest często (w większości przy-padków) podobny do problemu poprzed-niego (starego): różni się on tylko kilko-ma ograniczeniami lub zmiennymi. Istnie-je możliwość, iż mała zmiana modelu pro-blemu tworzy większe zmiany w rozwią-zaniu optymalnym, jednak w większości praktycznych przypadków optymalne roz-wiązania podobnych do siebie problemów nie różnią się zbyt wiele od siebie. Przykła-dem może być wspominany system GPS, w sytuacji gdy samochód jedzie prosto za-miast skręcić w lewo, pozycja inicjalizacyj-na jest taka sama w obydwu przypadkach, więc dalej (w następnym kroku) zachodzi konieczność jedynie skręcić w lewo (co wią-że się ze zmianą niewielkiej ilości danych w problemie).

Postać problemów, która często ulega zmianie (zmiana warunków zadania) pod-czas działania systemu – w konwencji pa-radygmatu CLP, nazywa się problemem dy-namicznego spełnienia ograniczeń i oznacza skrótem DynCSP (Dynamic Constraint Satis-faction Problem) dla sekwencji klasycznego

Rysunek 13. Prosty przykład zadania (przed rozwiązaniem)

Rysunek 14. Przykład wizualizacji zadania szeregowania (po znalezieniu rozwiązania)

Page 29: SDJ_03_2010_PL

03/201028

Sztuczna inteligencjaParadygmat programowania CLP

www.sdjournal.org 29

problemu oraz DynCOP dla sekwencji pro-blemu optymalizacji (Dyynamic Constraint Optimization Problem).

Przypominając wcześniej opisywane aspek-ty paradygmatu, podstawowym zagadnie-niem reprezentacji wiedzy w programowa-niu w logice z ograniczeniami jest tzw. pro-blem spełnienia ograniczeń CSP. Dynamicz-ne środowisko jest przedstawiane (widziane) jako sekwencja statycznych problemów klasy CSP (Rysunek 12):

Model matematyczny dla dynamicznego CSP jest więc skończoną sekwencją takich (klasycznych) problemów CSP, przedstawia-jących się w sposób:

<csp(0), csp(1),…, csp(n)>

Każdy z podproblemów csp(i) jest instan-cją problemu DynCSP, gdzie różni się od poprzedniej (lub następnej) poprzez do-danie lub usunięcie niektórych ograni-czeń. Wszystkie możliwe zmiany w danej instancji problemu DynCSP mogą być wy-rażone tylko jako usunięcie lub dodanie ograniczenia w kolejnym punkcie czaso-wym. Rozwiązanie całego dynamicznego CSP implikuje rozwiązanie każdej jego in-stancji (z osobna), występującej w sekwen-cji całego problemu.

Pierwsza składowa problemu csp(0) jest rozwiązana od początku (na podsta-wie zadanych warunków początkowych). Dopuszczalnym jest rozwiązywanie każ-dego elementarnego problemu od warun-ków początkowych i zastosowanie się tak do wszystkich późniejszych instancji całe-go problemu. Jednak takie podejście jest nieefektywne i może być przyczyną niesta-bilności pomiędzy kolejno po sobie nastę-pującymi rozwiązaniami. Z tego powodu podczas rozwiązywania dynamicznego pro-blemu CSP istnieje potrzeba wykorzystania w możliwie największym stopniu rozwią-zań z poprzednich instancji.

Istnieje kilka podejść (metod) do rozwią-zywania dynamicznego problemu CSP. Naj-efektywniejsze z nich skupiają się na algo-rytmie lokalnych zmian LC (local changes), który próbuje naprawić poprzednie rozwią-zanie. Kiedy przypisana wartość do zmien-nej staje się niezgodna (niespójna) z roz-wiązaniem poprzedzającym, wtedy niespój-

na część tego rozwiązania (lokalnie) zostaje modyfikowana, zachowując aktualne przy-pisanie wartości tej zmiennej, aż do uzyska-nia spójności (czyli do przywrócenia stanu, w którym ograniczenia są spełnione). Jeśli jest to niemożliwe, podstawiane są inne war-tości dla rozpatrywanej zmiennej w danej in-stancji problemu.

Generalną zasadą tego podejścia jest cią-głe rozwiązywanie zadania (klasycznego CSP) na podstawie zmian założeń, które pojawiają się w trakcie życia systemu. Isto-tą pamiętania poprzedniego rozwiązania (czyli wszystkich przypisań wartości do zmiennych) jest fakt mniejszego nakładu obliczeń na poprawę niespójnego zadania w kroku następnym.

Przegląd narzędzi – środowisk CP/CLPEfektywność metod CP/CLP jest rezulta-tem wykorzystania procedur propagacji, które pozwalają na wzajemne ograniczanie dziedzin zmiennych decyzyjnych, oraz dys-trybucji zmiennych do zawężonych prze-strzeni dziedzin w celu wyselekcjonowania rozwiązań spełniających wszystkie przyjęte ograniczenia. Zawężanie dziedzin zmien-nych decyzyjnych prowadzi do ogranicze-nia potencjalnej przestrzeni rozwiązań, co w efekcie pozwala znacząco skrócić czas potrzebny na obliczenia. Efektem naprze-miennego stosowania tych dwóch proce-dur jest szybki sposób wyszukiwania roz-wiązań dopuszczalnych bądź wykazanie je-go braku.

W podejściach programowania mate-matycznego ograniczenia np. wynikają-ce z problemu przydziału modelowane są sztucznie, przy użyciu binarnych zmien-nych decyzyjnych o wartościach zerojedyn-kowych. Dlatego ważne jest zastosowanie środowisk, w których modelowanie ograni-czeń jest naturalne. Środowiskami takimi są niewątpliwie środowiska umożliwiające formułowanie programu w sposób dekla-ratywny. Programowanie w logice z ogra-niczeniami (Constraint Logic Programming) jest paradygmatem programowania, który niewątpliwie wspiera to podejście, oferu-jąc szereg strategii poszukiwania rozwią-zania oraz metod optymalizacji. Naturalną

zaletą jest także jago równoległość, co czy-ni bardzo atrakcyjną ofertę do możliwości implementacji systemu jako wariantu roz-proszonego.

Poniżej został przedstawiony podział waż-niejszych środowisk wspierających omawia-ną metodykę:

• Środowiska oparte na językach PRO-LOG – CLP:• Constraint Handling in Prolog —

CHIP.• Język OZ ze środowiskiem MO-

ZART rozwijane m.in. przez na-ukowców z DFKI (Niemieckiego Centrum Badań nad Sztuczną Inte-ligencją) oraz SICS (Szwedzki Insty-tut Informatyki).

• Środowiska oparte na językach niższego rzędu jak np. C++:• ILOG Solver – firmy Ilog Inc., to śro-

dowisko, w którym problemy zapisy-wane są w zbliżonym do języka C++ języku OPL (Optimization Program-ming Language). Wspiera ono roz-wiązywanie klasycznych problemów oraz ich optymalizację.(przykład za-pisu prostego problemu produkcyj-nego w ILOG OPL, przedstawiony został na Listingach: 1a, 1b, 1c).

• Gecode – biblioteka/środowisko do rozwoju systemów opartych o tech-nologię ograniczeń.

• Minion – Solver C++ do rozwiązy-wania problemu CSP.

• Platformy programistyczne dostępne ja-ko narzędzia komercyjne:• SICStus Prolog: (Szwedzki Instytut

Informatyki);• ECLiPSe: początkowo ECRC, IC-

Parc, obecnie rozwijany przez CI-SCO;

• CHIP: ECRC, obecnie rozwijany przez COSYTEC;

• również ILOG Solver (darmowa wersja odnosi się do zastosowań wy-łącznie naukowo/badawczych).

• Platformy służące do symulacji proble-mu jako podejścia rozproszonego:• DisChoco – platforma rozproszone-

go programowania, oparta na języku Java;

Literatura

• Eisenberg C.: Distributed Constraint Satisfaction For Coordinating and Integrating a large-scale, Heterogeneous Enterprise.

• Meseguer P., Gonzalez S.: Open, Interactive and Dynamic CSP.• Rossi F., Van Beek P., Walsh T.: Handbook of Constraint Programming. • Russell N.: Constraint Satisfaction Problems.• Yokoo M., Durfee E. H., Ishida T., Kuwabara K.: The distributed constraint satisfaction pro-

blem: formalization and algorithms.

W Sieci

• http://www.ilog.com/;• http://www.gecode.org/;• http://jacop.osolpro.com;• http://minion.sourceforge.net/;• http://www.emn.fr/x-info/choco-solver;• http://www.sics.se/isl/sicstuswww/site/.

Page 30: SDJ_03_2010_PL

03/201030

Sztuczna inteligencja

• Frodo – framework do symulacji al-gorytmów rozproszonego rozwiązy-wania.

• Platformy typu OpenSource:• Choco – solver w postaci bibliote-

ki Java, który może być używany do nauki, badań, jak również za-stosowany w aplikacjach komercyj-nych;

• JaCoP – efektywna obliczeniowo bi-blioteka programistyczna Javy, im-plementująca paradygmat progra-mowania z ograniczeniami.

(przykład zapisu problemu kolorowania w JaCoP: przedstawiono na Listingu 2a, 2b, 2c).

Wiele opracowanych systemów na tych platformach wykorzystuje heurystyczne algorytmy programowania całkowitolicz-bowego z ograniczeniami. Rozwiązywane przez nie problemy są zazwyczaj wysoce złożone już w przypadku niewielkiej licz-by argumentów problemu (liczba zadań, maszyn, pełnionych funkcji itp.). Dzieje się to głównie ze względu na konieczność uwzględnienia licznych ograniczeń zmien-nych stanu, licznych zmiennych decyzyj-nych oraz warunków odnoszących się do porządku i czasu wystąpienia zdarzeń dys-kretnych (czyli w odpowiednich chwilach czasowych), zachodzących w sformułowa-nym zadaniu.

W istniejących środowiskach CLP sam model jest już programem. Dostępne są do użycia (zaproponowania przez użytkowni-ka środowiska) również odpowiednie wa-rianty metod poszukiwania rozwiązania, będące integralną częścią środowiska CLP, do których należą między innymi: propa-gacja ograniczeń, dystrybucja zmiennych oraz kombinacje szereg algorytmów poszu-kiwania rozwiązania jak backtracking (na-wracanie) czy foward checking (sprawdza-nie do przodu) z możliwością wyboru heu-rystyki kolejności przeszukiwania zmien-nych oraz ich wartości.

Przydatne oprogramowanieProces rozwiązania problemu przy użyciu metody CLP składa się z określenia ograni-czeń, a następnie poszukiwania rozwiąza-nia za pomocą szeregu heurystycznych al-gorytmów poszukiwania, propagacji ogra-niczeń oraz dystrybucji zmiennych. Jest to często słabo dostrzegalne przez użytkow-nika tej technologii (można powiedzieć, iż cała mechanika ukryta jest pod ma-ską), dlatego aby ułatwić naukę o technolo-gii CLP, można wstępnie posłużyć się pro-gramami, które w sposób wizualny przed-stawią krokowo przebieg rozwiązywanego problemu.

Przedstawiany proces jest prezentowany w formie wizualnej za pomocą grafów dla ko-lejnych kroków propagacji ograniczeń, w któ-rych następuje redukcja dziedzin zmiennych (czyli wstępne poszukiwanie właściwej war-tości).

W przykładowych appletach Javy moż-na uzyskać wizualizację sprawdzania spój-ności łuków (AC) (wcześniej opisywanej) dla testowo zdefiniowanych problemów (dostarczonych wraz z programem, np.: Rysunek 14) lub dla problemów utwo-rzonych od początku przez siebie (Rysu-nek 13).

Należy mieć na uwadze także to, iż formu-łując własny problem, wynik wygenerowany przez mechanizm (w trakcie rozwiązywania problemu) musi spełniać wszystkie zadane ograniczenia, o ile jest to możliwe, może wy-stąpić jednak sytuacja nadmiernego związa-nia problemu, co implikuje brak jakiegokol-wiek rozwiązania.

PodsumowanieW artykule autor przedstawił przegląd oraz możliwości reprezentacji wiedzy dla wybra-nych metodyk klasy CP/CLP. Przedstawio-no zapis definiowania problemu wraz z kon-kretnym celem jego zastosowania. Wyszcze-gólniono reprezentację wiedzy dla klasycz-nego problemu spełnienia ograniczeń, jak też jego wariantu rozproszonego oraz dy-namicznego. Realizacja przedstawionych etapów dotyczących mechanizmów wyko-rzystywanych w metodykach CP/CLP pro-wadzi do budowy narzędzi w postaci syste-mów do wspomagania decyzji, które pozwa-lają poszukiwać rozwiązań dla zadań proble-mów decyzyjnych o charakterze kombinato-rycznym.

W praktyce programowanie z ogranicze-niami jest wysoce deklaratywne, co pozwa-la wiele rzeczywistych problemów opisy-wać, używając ograniczeń, które odzwiercie-dlają zależności pomiędzy zmiennymi pro-blemu. Taki sposób specyfikacji problemu może być wprowadzony w wielu tradycyj-nych środowiskach programowania, ale naj-lepszy rezultat jest osiągany, kiedy wykorzy-stujemy paradygmat programowania w logi-ce, który również opiera się na deklaratyw-ności i relacjach.

Opisywany paradygmat może być wy-korzystany w teorii ograniczeń, która jest metodyką usprawniania systemów. Zakła-da się, iż w każdym systemie zbudowa-nym z wielu powiązanych ze sobą dzia-łań znajduje się jedno działanie ograni-czające skuteczność całego systemu. Ce-lem systemu może być maksymalizacja zy-sku. W przedsiębiorstwie może funkcjono-wać wiele procesów, z których jeden limi-tuje jego zysk. Jeżeli występują duże różni-

ce w przepustowości procesów (np. na po-szczególnych maszynach na wydziale), to część z nich jest w pewnym stopniu niewy-korzystana. Pociąga to za sobą dodatkowe koszty. Posługiwanie się techniką opartą (lub wspieraną) o mechanizmy CLP (np.: zastosowaną do planowania potrzeb mate-riałowych MRP) może w znacznym stop-niu ograniczyć koszty przedsiębiorstwa oraz pozwolić uniknąć kłopotów spowodo-wanych brakiem jakichś zasobów na okre-ślony czas.

Rozwinięciem rozważań poruszanych w artykule jest przedstawienie rozproszo-nej wersji problemu spełnienia ograniczeń (DisCSP), która jest implementacją podej-ścia działającego w środowisku (systemie) kooperujących ze sobą agentów, wymienia-jących informacje za pomocą komunikatów (wiadomości).

Metoda ta ma proste i naturalne zastoso-wanie w rzeczywistości, w której wiele pro-cesów posiada rozproszone zasoby. Wystę-puje wtedy potrzeba komunikacji ze sobą oddalonych jednostek wykonawczych w ce-lu wymiany wiedzy o wspólnie rozwiązy-wanym problemie.

Zawarto także zarys przykładowych moż-liwości wykorzystania omawianych technik w środowiskach produkcyjnych, gdzie bar-dzo dobrze radzi sobie podejście dynamicz-ne (DynCSP) (czyli sekwencja kolejno wy-stępujących po sobie klasycznych proble-mów CSP).

Myślę, iż taka forma przedstawiania wie-dzy na temat paradygmatu CLP, który sku-pia się na rozwiązaniu problemu CSP, mo-że w znaczny sposób przybliżyć czytelnikowi korzyści płynące z odpowiedniej formy zapi-su zadania podczas podjęcia się próby imple-mentacji w systemie.

Przedstawiane techniki reprezentowa-nia wiedzy można wykorzystać na etapie formułowania (zapisu) problemu w spo-sób najlepiej oddający rzeczywistość (na-turę zadania). Korzystając z systemów kla-sy: CP/CLP (umożliwiających rozwiązywa-nie tej klasy zadań), jesteśmy w stanie tak sformułowane zadanie rozwiązywać bar-dzo efektywnie czasowo.

ŁUKASZ MAZURDoktorant AGH Kraków, obecnie zajmuje się analizą danych oraz rozwojem hurtowni da-nych, prowadzi także prace badawcze nad al-gorytmami optymalizacji w konwencji para-dygmatu programowania w logice z ograni-czeniami.Kontakt z autorem: [email protected]

Page 31: SDJ_03_2010_PL
Page 32: SDJ_03_2010_PL

03/201032

BezpieczeństwoNiezawodność systemów informatycznych

www.sdjournal.org 33

Zjawisko to dotyczy niemalże wszyst-kich płaszczyzn funkcjonowania spo-łeczeństwa (począwszy od instytucji

naukowych, poprzez przemysł, biznes, komu-nikacje, urzędy) dzięki czemu spotykane jest podczas wykonywania nawet najprostszych czynności w naszym codziennym życiu.

Powszechnie znanym oraz na co dzień sto-sowanym słowem jest: usługa (ang. service), dziś również w świecie IT – np. usługa siecio-wa. Ostatnio popularny model dostarczania usług – Cloud Computing, obejmujący modele usług takich jak SaaS, PaaS czy IaaS, oferuje po-nadto całkowicie przezroczystą warstwę dostę-pu do usługi z punktu widzenia usługobiorcy. Użytkownik (usługobiorca) nie musi wiedzieć, gdzie fizycznie znajduje się dana usługa ani znać szczegółów jej implementacji. W jego zaintere-sowaniu leży przede wszystkim funkcjonalność oraz rezultat dostarczany przez wybraną usłu-gę. Oczywistym jest fakt, że bez względu na typ usługi powinna być w pełni świadczona jej funkcjonalność i jednocześnie w sposób jak naj-bardziej niezawodny oraz bezpieczny.

Projektując system informatyczny, udo-stępniający wiele różnorakich usług, oprócz

funkcjonalności - które dostarczane są czę-sto przez zewnętrzne oprogramowanie (np. typowe usługi sieciowe: HTTP, FTP, SMTP, ostatnio popularny VoD i wiele, wiele in-nych) – musimy zadbać o jak największą ich niezawodność (tudzież dostępność) oraz bezpieczeństwo. Pod uwagę bierzemy aspekty związane z serwerami back-up, re-plikacjami baz danych, jak również samych aplikacji odpowiedzialnych za dostarczanie konkretnych już funkcjonalności.

Budując „niezawodny system informatycz-ny” z uwzględnieniem wszystkich założonych w projekcie aspektów bezpieczeństwa i nieza-wodności, nie sposób pominąć kwestię mo-nitorowania jego poszczególnych komponen-tów w celu zobrazowania aktualnego stanu systemu oraz podejmowania właściwych de-cyzji. Niniejszy artykuł pokrótce opisuje ar-chitekturę wieloagentowego systemu moni-torowania komponentów sieciowych, wcho-dzących w skład systemu informatycznego zorientowanego na usługi.

Architektura wieloagentowego systemu monitorowania sieciPojęcie systemu wieloagentowego jest obec-nie dobrze znane przez ludzi ze środowiska IT. Jak wskazuje typ takiego systemu, składa się on z komunikujących się ze sobą niezależ-nych bytów – Agentów (ang. Agents), reali-zujących jednak ten sam cel. W przypadku

systemu monitorowania komponentów siecio-wych celem agentów będzie dostarczanie in-formacji na temat poszczególnych komponen-tów, potrzebnych do określenia aktualnego sta-nu, w jakim się one znajdują.

Zacząć należy jednak od zdefiniowania struktury sieci, w której działać będą oba syste-my: monitorowany system informatyczny oraz system monitorujący. Do naszego celu doskona-le pasuje struktura trójwarstwowa, w której wy-różniamy poszczególne warstwy (Rysunek 1):

• najniższa – warstwa odpowiedzialna za dostarczanie potrzebnych informacji;

• środkowa – warstwa odpowiedzialna za analizę danych oraz podejmowanie de-cyzji – np. adaptacyjnych, zmiany kon-figuracji etc;

• najwyższa – odpowiedzialna za zobrazo-wanie aktualnego stanu całej struktury systemu monitorowanego – np. konsola administratora.

Taka struktura sieci nie jest oczywiście wy-mogiem. System wieloagentowy jest na tyle elastycznym tworem, że daje zastosować się do wielu rodzajów struktur sieci.

W naszym przypadku (staramy się stworzyć system monitorujący komponenty dostarczają-ce usługi sieciowe), projektując strukturę sieci, należy zadbać o to, aby znalazło się w niej miej-sce przynajmniej dla dwóch najniższych warstw – warstwa monitorująca oraz warstwa analizu-jąca i podejmująca decyzje.

W artykule tym skupimy się na najniższej warstwie struktury, tj. warstwie odpowiedzial-nej za dostarczanie informacji dotyczących kom-ponentów świadczących dane usługi sieciowe. Ponadto opisana zostanie warstwa środkowa, analizująca zebrane dane oraz podejmująca pew-ne decyzje oparte o rezultaty analizy. Warstwa ta

Niezawodność systemów informatycznychNieustanny postęp technologiczny, zwłaszcza ten dotyczący świata IT, sprawia, że mamy do czynienia z globalną komputeryzacją oraz informatyzacją, która z dnia na dzień zatacza coraz to szersze kręgi. Postęp służyć powinien globalnemu dobru, jednak wraz postępem pojawiają się coraz to nowe problemy oraz pułapki, których nie sposób traktować z przymrużeniem oka.

Dowiesz się:• Jak może wyglądać podstawowa struktura

wieloagentowego systemu monitoringu sieci;• Podstawowe zagadnienia związane z bez-

pieczeństwem oraz niezawodnością usług. sieciowych

Powinieneś wiedzieć:• Co to jest usługa sieciowa;• Podstawowe informacje na temat systemów

informatycznych świadczących usługi;• Podstawowe informacje na temat popular-

nych narzędzi monitorujących sieć.

Poziom trudności

Page 33: SDJ_03_2010_PL

03/201032

BezpieczeństwoNiezawodność systemów informatycznych

www.sdjournal.org 33

zostanie potraktowana bardzo ogólnie i jedynie w kontekście warstwy najniższej (analiza oraz podejmowanie decyzji warstwy środkowej są zagadnieniami, które mogłyby stanowić temat na co najmniej jeszcze jeden artykuł), gdyż obie warstwy stanowią nieodzowną całość w opisy-wanym modelu systemu monitoringu.

Jak widać na Rysunku 1, cała sieć (system in-formatyczny dostarczający usługi sieciowe) zo-stała podzielona na swego rodzaju komórki. Każda z nich zawiera kolekcję agentów dostar-czających informacje na temat monitorowanych komponentów sieciowych oraz jednego Agenta Decyzyjnego (ang. DecisionAgent), kolekcjonu-jącego (zapisującego, np. do bazy danych) oraz analizującego informacje, a następnie podejmu-jącego odpowiednie decyzje w oparciu o rezul-taty wykonanej analizy. Agent Decyzyjny jest szczególnym przypadkiem zwykłego agenta, którego funkcjonalność rozszerzona jest o me-tody analizy i podejmowania decyzji. Innymi słowy, jest to centralny komponent każdej z po-szczególnych komórek. Zbiór omawianych ko-mórek, wraz z agentami decyzyjnymi, w cało-ści składają się na środkową warstwę systemu monitoringu. W fizycznym projekcie sieci każ-da komórka mogłaby stanowić osobną podsieć, a agenci znajdować się w różnych przestrzeniach adresowych. Oczywiście jest to tylko propozycja, podział na komórki może być czysto abst rakcyj-ny, a o podziale stanowić będzie jedynie infor-macja o tym, z którymi agentami decyzyjnymi komunikują się agenci dostarczający informa-cje o monitorowanych podzespołach. Po opisie tym można wywnioskować, że nie jest to stan-dardowy model systemu wieloagentowego. Po-szczególni agenci pozyskujący i dostarczający in-formacje o monitorowanych komponentach nie muszą mieć możliwości komunikowania się ze sobą nawzajem. W zasadzie nie muszą nawet wiedzieć o swoim istnieniu. Ich zadaniem jest przede wszystkim zdobycie potrzebnych infor-macji oraz przesłanie ich do agenta centralnego – agenta decyzyjnego, który musi już znać loka-lizację poszczególnych agentów oraz mieć moż-liwość komunikacji z każdym z nich w ramach komórki, do której należy. Ponadto agent decy-zyjny powinien komunikować się ze wszystkimi innymi agentami decyzyjnymi stanowiącymi agentów centralnych pozostałych komórek.

Z tego miejsca wiem już, jak zorganizowana może być struktura monitorowanego systemu

informatycznego oraz jaką rolę odgrywa środ-kowa warstwa systemu monitorującego. Jest to oczywiście bardzo ogólny zarys, bez żadnych konkretnych informacji na temat samej techni-ki kolekcjonowania i analizowania danych oraz podejmowania decyzji na podstawie rezultatów dostarczonych przez algorytmy analizy danych. Opis ten pozwala nam jednak na sporządzenie oraz zaprojektowanie ogólnych założeń syste-mu monitoringu. Na Rysunku 2 przedstawio-ny został ogólny zarys systemu monitorowa-nia wraz z przepływem danych między jego po-szczególnymi komponentami. Wyraźnie widać trójwarstwową strukturę (podobnie jak struk-turę sieci, którą chcemy monitorować), w skład której wchodzą (kolejno od warstwy najniższej): monitorowane hosty (lub urządzenia sieciowe), agent decyzyjny wraz z bazą danych (najczęściej w postaci osobnego hosta lub wielu hostów, na których zainstalowany jest cały system bazoda-nowy) oraz konsola, mająca na celu zobrazowa-nie aktualnego stanu monitorowanego systemu wraz z jego poszczególnymi podzespołami.

Zajmijmy się teraz najniższą warstwą, czy-li monitorowanym hostem. Jak wspomniane było na początku niniejszego artykułu, usługi dostarczane są zazwyczaj poprzez zewnętrzne aplikacje/systemy instalowane na hostach we-wnątrz naszej sieci. Przykładem takich usług mogą być: WebServer, DNS, FTP i wiele innych. Każda z tych usług posiada możliwość logowa-nia zdarzeń. Logi zapisywane są w różnych for-matach oraz w różnych postaciach (np. do pli-ków na dysku lub w formacie Syslog-a), które w prosty sposób możemy pozyskać oraz przeka-zać do dalszej analizy. To właśnie jest główną ro-lą agenta monitorującego dany host. Czym więc jest Agent? W najczęstszej postaci będzie to apli-kacja rezydująca na monitorowanej maszynie lub urządzeniu. Najczęściej, gdyż urządzenia

sieciowe takie jak router czy zarządzalne switch-e mają wbudowane oprogramowanie umoż-liwiające przesyłanie logów w formacje np. sy-slog-a w miejsce ich przeznaczenia. Agent, mają-cy możliwość „nasłuchiwania” na plikach logów lub portach TCP, do których wysyłane są logi z poszczególnych serwisów, przechwytuje oraz przekazuje je poprzez sieć (np. przy pomocy pro-tokołu UDP lub innego, w zależności od polityki bezpieczeństwa) do agenta decyzyjnego.

Jednak logi dostarczane przez serwisy świad-czące usługi na danym hoście są tylko jednym ro-dzajem (z wielu) informacji, jakie będą nam po-trzebne do uzyskania rzeczywistego obrazu sta-nu maszyny (hosta). Innym rodzajem informa-cji, które są niemniej istotne, to dane dotyczące aktualnego zużycia procesora, pamięci czy nawet obciążenia na karcie sieciowej. Dzięki tym infor-macjom możemy monitorować aktualną dostęp-ność poszczególnych usług oraz czas odpowiedzi na żądanie od klienta. Aktualnie jest wiele narzę-dzi potrafiących dostarczyć tego typu informacje, np.: SNORT, Statgrab, SNTP czy WMI, jednak są to tylko przykłady. Tak więc nie zależy nam na wymyślaniu koła raz jeszcze, a jedynie wykorzy-staniu dostępnych dla ogółu narzędzi. Agent re-zydujący na hoście powinien być zaprojektowany w taki sposób, aby dać możliwość pozyskiwania danych z dowolnych systemów zewnętrznych. Cel ten można osiągnąć poprzez założoną z góry modularyzację Agenta, co zresztą przedstawione jest na Rysunku 2 (dodatkowe wtyczki w posta-ci bibliotek – tzw. „sensory” dostarczające lub po-zyskujące informacje o hoście).

Drugą – przedstawioną na Rysunku 2, ro-lą agenta, jest umożliwienie zmiany konfigu-racji hosta. Przykładowo, otrzymując infor-macje o niepoprawnym działaniu danej usłu-gi, możemy nakazać agentowi na reakcję, np. zrestartowanie wadliwie działającej usługi.

Projekt ArmazdArmazd, jest to projekt oparty na licencji opensource (GNU GPL v2). Podstawowym założeniem, jest stworzenie systemu moni-torującego oraz podejmującego decyzje re-kon�guracyjne, w oparciu o analizę zebra-nych danych, na temat monitorowanej sie-ci komputerowej. Jeśli jesteś zainteresowany tą tematyką, polecam stronę projektu: http://hardtechnology.org/armazd.

Rysunek 1. Przykładowa struktura sieci – systemu informatycznego świadczącego usługi sieciowe

������������

������� �������

���������

����������������

������� ����� ������������ ������� �������

����������������

�����������������������

������������

Page 34: SDJ_03_2010_PL

03/201034

Bezpieczeństwo

Moduł ten jest oczywiście mocno ograniczo-ny restrykcjami narzuconymi z góry poprzez przyjętą politykę bezpieczeństwa, gdyż wy-magałoby to nadania odpowiednich upraw-nień agentowi w systemie (wielu administra-torów nie zgadza się na tego typu czynności w sieciach zostawionych pod ich opiekę). Co do samej implementacji agenta powinna być ona przeprowadzona w taki sposób, aby sam agent miał jak najmniejszy wpływ na obcią-żenie hosta, na którym rezyduje. Oznacza to implementację na relatywnie niskim pozio-me (np. C/C++) oraz ewentualne uwzględ-nienie obciążenia powodowanego przez agenta w algorytmach analizy danych.

W ogólności, przepływ danych w systemie monitorującym może wyglądać w następują-cy sposób:

• wysyłanie logów przez serwisy do agentów lub pozyskiwanie danych przez agentów;

• wysyłanie danych przez agentów do agen-ta decyzyjnego;

• odbieranie danych przez agenta decyzyj-nego oraz ich analiza i ewentualne zapisa-nie w bazie danych;

• podejmowanie odpowiednich decyzji w reakcji na dane zdarzenie (jeśli taka jest wymagana);

• wysyłanie informacji o reakcji przez agen-ta decyzyjnego do jednego (bądź wielu) agentów rezydujących na monitoro-wanych maszynach;

• wykonanie polecenia (np. zmieniającego konfigurację usługi) przez agenta rezydu-jącego.

Już na tym etapie widać, jak złożona może być jednostka agenta centralnego oraz jak szeroki wachlarz możliwości daje się zastosować, aby zadbać o jak największą dostępność oraz bez-pieczeństwo usług systemu informatycznego.

Przykład implementacji oraz zastosowanie Opisywane powyżej zagadnienia, nie są tyl-ko kawałkiem – oderwanej od rzeczywisto-ści – teorii. Problem niezawodności usług sie-ciowych jest również jak najbardziej realny. Po-woduje to pojawianie się różnorodnych narzę-dzi mających na celu zmaksymalizowanie bez-pieczeństwa i niezawodności systemów infor-matycznych. Mogą być to na przykład rozpro-szone systemy backup-owe, tworzące oraz za-rządzające kopiami zapasowych, jak również narzędzia monitorujące oraz diagnozujące ca-łą strukturę sieci komputerowej. Przykładem takiego narzędzia, może być ogólnie dostępny system Armazd (ramka: Projekt Armazd). Sys-tem ten, został zaprojektowany oraz zaimple-mentowany zgodnie z zagadnieniami opisany-mi w poprzednim rozdziale. Dzięki temu, po-zwala gromadzić dane z wszystkich komponen-tów sieciowych (takich jak router, switch, hosty świadczące usługi sieciowe czy nawet stacje ro-bocze). Implementacja przeprowadzona została na względnie niskim poziome (języki C/C++). Funkcjonalność systemu, w obecnej fazie roz-woju, sprowadza się do monitorowania kom-ponentów sieciowych, pozyskiwanie interesują-cych nas danych oraz przekazywanie ich w zde-finiowane miejsce przeznaczenia. Projekt doty-czy jednak całego procesu podnoszącego nie-

zawodność systemu informatycznego, którego podstawowymi cechami są:

• monitorowanie komponentów siecio-wych;

• gromadzenie oraz analiza zebranych da-nych;

• podejmowanie decyzji rekonfiguracyj-nych poszczególnych jak również całych grup komponentów sieciowych;

Jest to jednak tylko przykład implementa-cji rozwiązań problemów z rodziny bezpie-czeństwa oraz niezawodności.

Nietrudno natomiast wyobrazić sobie przy-kład zastosowania tego typu systemu monito-ringu. Przykładem może być firma świadczą-ca popularne ostatnio usługi VoD. Stale mo-nitorując np. zużycie procesora, pamięci czy obciążenie na łączu, jesteśmy w stanie szybko i niemalże automatycznie reagować na sytu-acje, gdy którykolwiek z wymienionych współ-czynników jest zbyt wysoki. Jeśli jest duże ob-ciążenie na łączu, automatycznie spada dostęp-ność świadczonych usług, łatwo możemy to wy-kryć i zmienić konfigurację routera. W sytuacji, gdy zużycie procesora znacznie podnosi się po-nad normę, również najczęściej mamy do czy-nienia z drastycznym spadkiem świadczonych usług. W przypadku braku wystarczającej ilo-ści pamięci, narażeni jesteśmy przykładowo na błędne działanie aplikacji odgrywających klu-czową rolę w procesie świadczenia usługi. Tego typu parametrów może jest wiele. Natomiast, wszystkie wymienione sytuacje powodują spad-kiem bezpieczeństwa oraz niezawodności syste-mów informatycznych.

PodsumowanieBezpieczeństwo, niezawodność czy dostęp-ność usług sieciowych to bez wątpienia niezwy-kle ważne zagadnienia dzisiejszego świata IT. Ilość i rodzaj usług oraz liczba usługodawców IT różnego typu wzrasta z dnia na dzień. Wraz z tym postępem technologicznym obserwuje-my wzrost odpowiedzialności, jakie biorą na sie-bie jednostki świadczące dane usługi (od banko-wości po instytucje publiczne takie jak np. urzę-dy czy organy prawa). Dlatego wdrażając syste-my informatyczne zorientowane na usługi, nie sposób pominąć wyżej wymienione zagadnie-nia bezpieczeństwa, na które kładziony jest na-cisk, nieraz równoważny z zapewnieniem funk-cjonalności świadczonych usług.

ANDRZEJ OLCHAWAAutor niniejszego artykułu pracownikiem �rmy Po-wer Media S.A., na stanowisku Software Developer. Interesuje się bezpieczeństwem oraz niezawodno-ścią w kontekście systemów informatycznych. Wię-cej informacji na temat autora czytelnik może zna-leźć, odwiedzając stronę: http://hardtechnology.org.Kontakt z autorem: [email protected] 2. Architektura wieloagentowego systemu monitoringu z podziałem na warstwy

�����������������������

���������������������������������������

��������������

�����������������������������������

����������������������������

�����

��������������������

�������������������������������

��������������������������

��

��������������

Page 35: SDJ_03_2010_PL
Page 36: SDJ_03_2010_PL

03/201036

WarsztatyHibernate Search API

www.sdjournal.org 37

Mechanizm wyszukiwania stanowi podstawową funkcjonalność nie-mal wszystkich aplikacji bazoda-

nowych. Zwyczajowo, jego realizacja opiera się na udostępnieniu prostego formularza reprezentującego dopuszczalne kryteria za-pytania. Na podstawie wypełnionych przez użytkownika pól generowane jest zapytanie SQL, którego wyniki zostają przedstawione w formie tabelki. Wyszukiwanie danych tek-stowych bywa udoskonalone o ignorowanie wielkości znaków (funkcja UPPER) i przeszu-kiwanie fragmentów tekstu (klauzula LIKE oraz słowo kluczowe %). Wciąż jednak użyt-kownik nie posiada możliwości wyszukiwa-nia po synonimach, aproksymacji ewentu-alnych błędów literowych czy też porządko-wania otrzymanych wyników innego niż al-fabetyczne sortowanie. Z pomocą w mini-malizacji owych ograniczeń przychodzi me-chanizm przeszukiwania pełnotekstowego,

którego powszechnie znanym przykładem jest internetowa wyszukiwarka Google. Po-siada ona jedno przyjazne użytkownikowi pole tekstowe stanowiące alternatywę dla skomplikowanych formularzy zawierają-cych logikę biznesową. Ściślej mówiąc, me-chanizm wyszukiwania pełnotekstowego opiera się na wyborze zestawu słów charak-terystycznych dla danego dokumentu, ich optymalnym przechowaniu (np. w posta-ci odwróconego indeksu) oraz efektywnym przeszukiwaniu uwzględniającym na przy-kład synonimy, aproksymację oraz oceniają-cym trafność poszczególnych rekordów wy-nikowych. Z pewnością, Czytelnicy posia-dający umysł ścisły lepiej zrozumieją dzia-łanie wyszukiwarek pełnotekstowych po lekturze kolejnych części artykułu przed-stawiających techniczne aspekty realizacji owego mechanizmu na przykładzie Hiber-nate Search i Apache Lucene.

Architektura Hibernate Search oraz Apache LuceneIdea wyszukiwania pełnotekstowego skła-da się z trzech, ściśle zależnych od siebie operacji. Pierwsza z nich zajmuje się eks-trakcją tekstu oraz wydobyciem charak-

terystycznych dla danego dokumentu in-formacji (wyrazów). W przypadku Apache Lucene zadania te realizują tzw. Analyze-r’y (np. StandardAnalyzer). Każdy Analy-zer składa się z Tokenizer'a i łańcucha fil-trów kolejno przetwarzających wprowadzo-ny tekst. Tokenizer analizuje wejściowy ciąg symboli, a następnie zwraca strumień toke-n’ów – poszczególnych wyrazów – bez bia-łych znaków oraz symboli interpunkcyj-nych. Wynikowy strumień token’ów pod-lega filtracji. W najbardziej powszechnym przypadku operacja ta sprowadza się do za-miany wszystkich liter na małe oraz wyklu-czenia tzw. stop-słów, czyli wyrazów nie-wnoszących żadnej konkretnej informa-cji, np. on, który, dlaczego (patrz Listing 2). Etap ten pozornie wydawać się może rzeczą banalną, lecz stanowi serce całego mechani-zmu. To właśnie Analyzer zapewnia wyszu-kiwanie po synonimach czy też akceptację błędów związanych z zamianą kolejności li-ter. Diagram 1 przedstawia przykładowe działanie prostego Analyzer’a.

Drugą fazą operacji indeksowania do-kumentu jest jego zapisanie w strukturze umożliwiającej późniejsze, łatwe przeszuki-wanie. Apache Lucene wykorzystuje algo-rytm invert indexing. Algorytm ten porów-nać można do tradycyjnego spisu treści. To-ken’y zwrócone przez Analyzer gromadzo-ne są w swego rodzaju tablicy. Każda komór-ka przechowuje pojedynczy wyraz oraz wska-zuje na dokumenty go zawierające. Wspo-mniana struktura danych przetrzymywa-na być może w pamięci operacyjnej, jak i fi-zycznie na dysku twardym w postaci zbio-ru plików. Za przechowywanie indeksu od-

Hibernate Search APIAplikacje bazodanowe stanowią obecnie znaczący odsetek oprogramowania tworzonego na zlecenie prywatnych firm, jak i ogromnych korporacji. Większość aplikacji realizuje warstwę dostępu do danych za pomocą relacyjnie zorientowanej implementacji bazy danych (ang. Relational Database Management System). Wybór ten ogranicza swobodę przeszukiwania zawartych informacji do zbioru ściśle sformalizowanych zapytań udostępnianych przez aplikację. Artykuł ten wprowadza w zagadnienia przeszukiwania pełnotekstowego oferowanego przez Hibernate Search oraz Apache Lucene.

Dowiesz się:• Co to jest i jak działa wyszukiwanie pełno-

tekstowe;• Jak wzbogacić aplikację wykorzystującą Hi-

bernate o nowy mechanizm;• Czy Apache Lucene jest rzeczywiście wydajny

i nadaje się do komercyjnych zastosowań.

Powinieneś wiedzieć:• Podstawy programowania w języku Java;• Podstawy zagadnień baz danych;• Podstawy Hibernate.

Poziom trudności

Mechanizm wyszukiwania pełnotekstowego w Hibernate

Page 37: SDJ_03_2010_PL

03/201036

WarsztatyHibernate Search API

www.sdjournal.org 37

powiedzialne są klasy DirectoryProvider. Hibernate Search oferuje dwie wbudowa-ne implementacje: FSDirectoryProvider oraz RAMDirectoryProvider. Istnieje po-nadto możliwość wskazania własnej imple-mentacji zapisującej indeks np. bezpośred-nio w bazie danych. Celowo wskazałem tutaj przykład bazy danych, gdyż z pewnością wie-lu z Was uważać go będzie za bardzo atrakcyj-ny. Emmanuel Bernard (deweloper Hiberna-te Search oraz autor książki Hibernate Search In Action) wskazuje następujące wady owego rozwiązania:

• Indeks przechowywany jest w obiektach typu BLOB, które w zależności od imple-mentacji konkretnego RDBMS nie są najszybszą strukturą bazodanową;

• Współbieżna edycja indeksu jest nie-możliwa, ze względu na wymóg peł-nej blokady (czytania i pisania) całego indeksu przez każdy proces modyfiku-jący dane.

Z punktu widzenia dużych systemów pro-dukcyjnych najwłaściwszą implemen-tacją DirectoryProvider okazuje się FSDirectoryProvider. W odróżnieniu od RAMDirectoryProvider, jego pojemność jest niemal nieograniczona oraz nieulotna, lokal-ne systemy plików są wystarczająco szyb-kie, aby sprawnie odczytywać dane indeksu; rozwiązanie to jest najlepiej przetestowane; Apache Lucene posiada własny cache (prze-chowywany w pamięci RAM) w celu mini-malizacji liczby odczytów z dysku twarde-go; łatwość wykonywania kopii zapasowych. RAMDirectoryProvider powszechnie wyko-rzystywany jest w implementacji testów jed-nostkowych.

Ostatnią fazą, wchodzącą w skład prze-szukiwania pełnotekstowego, jest udostęp-nienie prostego języka zapytań. Apache Lu-cene oferuje bogaty zestaw meta znaków przedstawionych w Tabeli 1. Przeanalizuj-my przykładową kwerendę: title:(+Star -”Star Wars”)^2 AND content:”Capta?n

Enter*”~10 AND publicationDate:

[19730101 TO 20090801]. Zwrócone rekor-dy powinny zawierać w tytule wyraz Star, lecz nie frazę Star Wars (warunek dwukrot-nie ważniejszy niż kolejne). Ponadto wyrazy, jakie można dopasować do formuły Capta?n oraz Enter*, muszą znajdować się w odstę-pie maksymalnie 10 innych wyrazów znaj-dujących się w atrybucie content. Data pu-blikacji powinna również mieścić się w prze-dziale między pierwszym stycznia 1973 ro-ku a pierwszym sierpnia 2009. Za proces parsowania zapytań wprowadzanych bezpo-średnio przez użytkownika odpowiedzialny jest QueryParser, który przesłonić można własną implementacją. Ciekawi mnie, kie-

dy opracowany zostanie mechanizm inter-pretujący w pełni nieustrukturalizowane, potoczne zapytania.

Uważni Czytelnicy zastanawiają się pew-nie, jaką rolę odgrywa Hibernate w całym opisanym wyżej mechaniźmie. Hiberna-te Search integruje mapowanie relacyjno-

obiektowe Hibernate Core z wyszukiwar-ką pełnotekstową Apache Lucene. Narzę-dzie to zapewnia przezroczysty dla pro-gramisty system indeksowania wyspecyfi-kowanych (przy pomocy adnotacji) atry-butów danej encji. Programista, chcąc za-pisać nowy rekord lub zmodyfikować ist-

Tabela 1. Składnia języka zapytań Apache Lucene (http://lucene.apache.org/java/2_0_0/index.html)

Meta znak Opis działania

[nazwa _ pola]:[zapytanie]

De�niuje nazwę pola oraz zapytanie

AND, + Wynik powinien zawierać wszystkie wyspecy�kowane frazy

OR Wynik powinien zawierać dowolną z wyspecy�kowanych fraz

NOT, - Wynik nie powinien zawierać danej frazy

[min TO max] Wartość liczbowa lub data powinna mieścić się w zadanym prze-dziale

? Zastępuje dowolny pojedynczy znak

* Zastępuje dowolną ilość dowolnych znaków

() Grupowanie wyrażeń

^[liczba] Zwiększa wagę dopasowania danej frazy (liczba całkowita)

"[wyrazy]" De�niuje frazę składającą się z wielu wyrazów

~[liczba] FuzzyQuery (liczba ułamkowa) lub PhraseQuery (liczba całkowita) (opisane w dalszej części artykułu)

Rysunek 1. Luke – narzędzie do przeglądania i mody�kacji indeksu Apache Lucene

Listing 1. Przykładowa kon�guracja fabryki sesji Hibernate

hibernate.dialect = org.hibernate.dialect.DataDirectOracle9Dialect

hbm2ddl.auto = create

hibernate.show_sql = true

hibernate.format_sql = false

hibernate.search.default.directory_provider = org.hibernate.search.store.FSDirecto

ryProvider

hibernate.search.default.indexBase = C:\index\

Page 38: SDJ_03_2010_PL

03/201038

WarsztatyHibernate Search API

www.sdjournal.org 39

niejący, wykonuje standardowe operacje save() i update() na obiekcie Session. Hibernate Search zapewnia automatyczną synchronizację indeksu z bazą danych oraz bardzo szybkie pobieranie samego obiek-tu z bazy przez odwołanie po kluczu głów-nym. Model danych Apache Lucene posia-da strukturę płaską, przez co indeksowa-ne relacje muszą zostać zdenormalizowa-ne, co skutkuje zwiększeniem rozmiaru in-deksu. Konfiguracja Hibernate Search, je-go API, testy wydajnościowe oraz zagadnie-nia optymalizacyjne opisane są w kolejnych częściach artykułu.

Przykładowa aplikacjaCzęść praktyczna artykułu zrealizowa-na została na przykładzie prostej aplikacji przechowującej dane o autorach i wyda-nych przez nich książkach. Celem aplika-cji jest prezentacja możliwie wielu funkcji Hibernate Search oraz testy wydajnościowe owego narzędzia. W dalszej części owej sek-cji nie zamierzam skupiać się na logice biz-nesowej aplikacji, sensowności i przydatno-ści niektórych rozwiązań, lecz na prezenta-cji technologii i maksymalnym jej obciąże-niu podczas testów.

Kon�guracja fabryki sesji oraz mapowań encjiPracę z Hibernate Search należy rozpocząć od konfiguracji obiektu SessionFactory oraz opatrzenia indeksowanych encji odpo-wiednimi adnotacjami. Bardzo podstawo-wą konfigurację SessionFactory przedsta-wia Listing 1.

Parametr hibernate.search.default.directory_provider określa domyślną używa-ną implementację DirectoryProvider’a (sło-wo kluczowe default). Istnieje możliwość wskazania różnych DirectoryProvider’ów dla poszczególnych indeksowanych klas. W tym przypadku słowo kluczowe default zastąpić należy nazwą indeksu (np. edu.lantoniak.hibernate.search.model.BookEn

tity). Drugi parametr hibernate.search.default.indexBase określa miejsce składo-wania indeksu. Ukończywszy konfigurację SessionFactory,programista powinien za-jąć się opatrzeniem indeksowanych encji ad-notacjami Hibernate Search. Kilka przykła-dowych atrybutów z odmienną filozofią in-deksowania przedstawia Listing 2.

Aby klasa brała udział w procesie indek-sowania, powinna zostać opatrzona adnota-cją @Indexed.

Pierwszy atrybut id stanowi klucz głów-ny encji BookEntity. Adnotacją @DocumentId powoduje zapisanie wartości owego pola w in-deksie jako identyfikator rekordu, co umożli-wia Hibernate Core szybkie pobranie odpo-wiedniego obiektu z bazy danych.

Listing 2. Przykład mapowania encji z wykorzystaniem adnotacji Hibernate Search

@Entity

@javax.persistence.SequenceGenerator(name="BOOKS_SEQ_GEN", sequenceName="BOOKS_SEQ")

@Table(name="BOOKS")

@Indexed

@ClassBridge(name="secret", impl=SecretBridge.class, index=Index.UN_TOKENIZED)

@FullTextFilterDef(name="public", impl=PublicFilterFactory.class)

@AnalyzerDef(name="standardBookAnalyzer",

tokenizer=@TokenizerDef(factory=StandardTokenizerFactory.class),

filters={@TokenFilterDef(factory=StandardFilterFactory.class),

@TokenFilterDef(factory=LowerCaseFilterFactory.class),

@TokenFilterDef(factory=StopFilterFactory.class)

})

@Analyzer(definition="standardBookAnalyzer")

public class BookEntity {

...

@Id

@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="BOOKS_SEQ_GEN")

@DocumentId

public Long getId() {

return id;

}

@Column(name="TITLE", nullable=false, length=100)

@Fields({

@Field(index=Index.TOKENIZED),

@Field(name="title_sort", index=Index.UN_TOKENIZED)

})

@Boost(2.0f)

public String getTitle() {

return title;

}

@Column(name="PUBLICATION_DATE", nullable=false)

@Field(index=Index.UN_TOKENIZED)

@DateBridge(resolution=Resolution.DAY)

public Date getPublicationDate() {

return publicationDate;

}

@Column(name="CONTENT", nullable=false)

@Lob

@Field(index=Index.TOKENIZED)

@FieldBridge(impl=PDFStringBridge.class)

public byte[] getContent() {

return content;

}

@ManyToMany(targetEntity=AuthorEntity.class, cascade=CascadeType.ALL)

@JoinTable(name="AUTHORS_BOOKS", joinColumns=@JoinColumn(name="BOOKS_ID"),

inverseJoinColumns=@JoinColumn(name="AUTHORS_ID"))

@IndexedEmbedded(depth=1)

public Set<AuthorEntity> getAuthors() {

return authors;

}

...

}

Page 39: SDJ_03_2010_PL

03/201038

WarsztatyHibernate Search API

www.sdjournal.org 39

Drugi atrybut title przechowuje ty-tuł książki. Wartość zmiennej tej indekso-wana jest dwukrotnie (adnotacje @Fields i @Field) – raz pod kluczem title, drugi raz jako title_sort. W przypadku klucza title, wartość zmiennej rozbita zostanie na poszczególne token’y (index=Index.TO-KENIZED). Dla przykładu, tekst Hiberna-te Search API zostałby podzielony na trzy wyrazy – Hibernate, Search oraz API. Każ-dy z owych token’ów wskazywałby na jedną i tę samą książkę (patrz algorytm invert in-dexing) – przechowywałby jako odwołanie tę samą wartość zmiennej id. Jeśli pragnie-my zaoferować możliwość sortowania wy-ników, potrzebujemy oryginalnej wartości zmiennej title, przetrzymywanej w polu title_sort (index=Index.UN_TOKENIZED). Domyślnie Hibernate Search zwraca listę obiektów posortowanych malejąco po traf-ności dopasowania dokumentu. Atrybut title prezentuje jeszcze jedną przydatną własność określoną przez @Boost. Adno-tacja ta definiuje mnożnik stosowany przy wyliczaniu trafności dopasowania danego dokumentu. Potocznie mówiąc, dopasowa-nie wartości zmiennej title do oczekiwa-nej jest w tym przypadku dwa razy ważniej-sze niż innych atrybutów.

W modelu danych Apache Lucene, war-tości wszystkich zmiennych zapisywane są w formie tekstowej. Nie istnieją typy licz-bowe, boolowskie, daty czy też referencje. Aby zamienić dowolny typ na postać tek-stową, stosowane są tzw. Bridge. Adnotacja @DateBridge transformuje format daty na reprezentację tekstową z określoną dokład-nością. Bridge posiadają także wiele innych zastosowań. Jednym z nich może być roz-różnienie wartości składowanej w indek-sie od tej przetrzymywanej w bazie danych. W przykładowej aplikacji atrybut content klasy BookEntity przechowuje ścieżkę do pliku PDF z daną książką (wartość składo-wana w bazie danych). Aby móc wyszuki-wać książkę po fragmencie jej treści, pole content przechowywane w indeksie zawie-ra zindeksowaną całą treść danej pozycji.Implementacja własnego Bridge przedsta-wia się bardzo prosto. Wystarczy przecią-żyć metodą objectToString() przyjmują-cą dany obiekt, a zwracającą jego tekstową reprezentację. @ClassBridge to szczegól-ny przypadek Bridge umożliwiający gene-rację dodatkowych pól na podstawie atry-butów danej klasy. W zamieszczonym na płycie CD przykładzie każdy dokument BookEntity zawierać będzie pole secret o wartości wygenerowanej przez metodę objectToString() klasy SecretBridge. @ClassBridge są bardzo przydatne w połą-czeniu z filtrami opisanymi w dalszej czę-ści artykułu.

LukeLuke to bardzo pomocna aplikacja umożliwiająca przeglądanie oraz mody�kację indeksu Apache Lucene zapisanego na dysku twardym. Narzędzie składa się z pięciu zakładek, z których pierwsze trzy są szczególnie interesujące dla osoby rozpoczynającej pracę z Hibernate Search. Pierwsza z nich, Overview, pokazuje ogólne dane na temat indeksu, takie jak nazwy pól, liczba przecho-wywanych dokumentów oraz terminów. Zakładka Documents pozwala na proste przeszukiwanie oraz mody�kację poszczególnych wpisów. Najczęściej używaną funkcjonalność Luke zawiera pa-nel Search. Oferuje on możliwość wykonywania zapytań, wybór Analyzer’a, przeglądanie otrzy-manych wyników oraz wyjaśnienie, jak obliczona została trafność danego dokumentu.

Listing 3. Przykład zapytania opartego o wyrażenia regularne

public List<BookEntity> getBooksByTitleRegExp(String regExpTitle) {

Session session = getSession();

FullTextSession fullTextSession = Search.getFullTextSession(session);

RegexQuery regexQuery = new RegexQuery(new Term("title", regExpTitle));

FullTextQuery fullTextQuery = null;

List<BookEntity> list = null;

try {

fullTextQuery = fullTextSession.createFullTextQuery(regexQuery,

BookEntity.class);

list = fullTextQuery.list();

} finally {

releaseSession(session);

}

return list;

}

Listing 4. Przykład de�nicji �ltra oraz skorzystanie z niego podczas wykonywania zapytania

public class PublicFilterFactory {

@Factory

public Filter getPublicFilter() {

Term term = new Term("secret", "false");

Query query = new TermQuery(term);

return new QueryWrapperFilter(query);

}

}

public List<BookEntity> getPublicBooksByTitle(String title) {

Session session = getSession();

FullTextSession fullTextSession = Search.getFullTextSession(session);

FullTextQuery fullTextQuery = null;

List<BookEntity> list = null;

try {

Analyzer analyzer = fullTextSession.getSearchFactory().

getAnalyzer(BookEntity.class);

Query query = MultiFieldQueryParser.parse(new String[] {title},

new String[] {"title"}, analyzer);

fullTextQuery = fullTextSession.createFullTextQuery(query,

BookEntity.class);

fullTextQuery.enableFullTextFilter("public");

Criteria fetchingStrategy =

fullTextSession.createCriteria(BookEntity.class).

setFetchMode("authors", FetchMode.JOIN);

fullTextQuery.setCriteriaQuery(fetchingStrategy);

list = fullTextQuery.list();

} catch (ParseException e) {

log.error("Parse exception: " + e);

} finally {

releaseSession(session);

}

return list;

}

Page 40: SDJ_03_2010_PL

03/201040

WarsztatyHibernate Search API

www.sdjournal.org 41

Fakt, iż Apache Lucene przechowuje da-ne wyłącznie w formie tekstowej, impli-kuje konieczność denormalizacji wszel-kich indeksowanych relacji. W przypad-ku konieczności udostępnienia zapytań o własności enkapsulowane w innej encji, atrybut relacji należy opatrzyć adnotacją @IndexedEmbedded. Pozwala ona określić stopień zagłębienia dalszego rekursywnego indeksowania podrzędnych relacji. Jako wy-nik owego działania dokument BookEntity zawierać będzie wszystkie informacje na te-mat każdego z powiązanych autorów (np.

authors.firstName). Aby indeksowanie relacji działało poprawnie, relacja powin-na być dwukierunkowa, a druga jej strona opatrzona adnotacją @ContainedIn. Dzię-ki temu Hibernate powtórnie indeksu-je dokument encji nadrzędnej po modyfi-kacji (operacji UPDATE, INSERT lub DELETE) powiązanego obiektu podrzędnego. Uwa-ga implementacyjna: mechanizm powtór-nego indeksowania działa poprawnie tyl-ko w przypadku modyfikacji obiektu posia-dającego aktualną referencję do encji nad-rzędnej (patrz plik TestRunnable.java, linie

91 do 94). Należy ponadto zwrócić uwagę, iż indeksowanie relacji znacznie zwiększa rozmiar indeksu.

Ostatnim elementem konfiguracyjnym wykorzystywanym w fazie zapisywania in-deksu jest wybór domyślnego Analyzer’a dla danej klasy, lub w szczególności dla konkretnego atrybutu. Listing 2 przedsta-wia definicję standardowego Analyzer’a, którego działanie zostało opisane na po-czątku poprzedniej sekcji artykułu. Hiber-nate Search udostępnia jednak bardziej zło-żone filtry (wykorzystywane przy defini-cji analizatora) obsługujące wyszukiwanie po synonimach (SynonymFilterFactory), podobieństwie fonetycznym (Phonetic-FilterFactory), rodzinie danego wyrazu (SnowballPorterFilterFactory) czy też akceptujące błędy literowe (NGramFilter-Factory). Konfiguracja Analyzer’a wpły-wa na wielkość indeksu, szybkość jego za-pisu oraz Analyzer konieczny do genera-cji poprawnego zapytania. Dla przykładu, SynonymFilterFactory, na podstawie pła-skiego pliku definiującego synonimy, ge-neruje wszystkie możliwe wyrazy blisko-znaczne danego słowa, a następnie zapisu-je je do pliku indeksu, znacznie zwiększa-jąc jego objętość. Filtr ten stanowi ponadto wyjątek reguły nakazującej stosowanie tego samego Analyzer’a podczas zapisywania in-deksu oraz generacji zapytania. Ze względu na złożoność działania niektórych filtrów, odsyłam zainteresowanych Czytelników do lektury ich dokumentacji oraz książki Hibernate Search In Action. W żadnym wy-padku nie powinno się stosować wszyst-kich wymienionych wcześniej filtrów jed-nocześnie ze względu na czas indeksowania oraz potencjalnie niewielką trafność zwra-canych później wyników. Testowa aplika-cja zawiera przykładowe definicje każdego z wyżej omówionych filtrów. Do dyspozy-cji Czytelnika pozostawiam zamianę uży-wanego Analyzer’a przez klasę BookEntity, uruchomienie aplikacji oraz obserwację różnic w plikach indeksu przeglądanych dzięki Luke.

Implementacja metod pełnotekstowego przeszukiwania danychPosiadając skonfigurowaną fabrykę sesji oraz poszczególne encje, możemy przystą-pić do implementacji mechanizmu wyszu-kiwania. Hibernate Search oferuje kilka ro-dzajów zapytań oraz samego sposobu zwra-cania wyników. Do najczęściej stosowanych zapytań należą TermQuery (wyszukiwanie po pojedynczym atrybucie, umożliwia sto-sowanie meta znaków takich jak ? czy *) oraz MutiFieldQueryParser (analogiczne do TermQuery, lecz pozwalające wyszukiwać po

Listing 5. Stronicowanie zwracanych wyników

public Object[] getAuthorsByNamePaginating(String name, int pageNumber, int window) {

Session session = getSession();

FullTextSession fullTextSession = Search.getFullTextSession(session);

FullTextQuery fullTextQuery = null;

List<AuthorEntity> list = null;

int resultSize = -1;

try {

Analyzer analyzer =

fullTextSession.getSearchFactory().getAnalyzer(AuthorEntity.class);

Query query = MultiFieldQueryParser.parse(new String[] {name, name},

new String[] { "firstName", "lastName" }, analyzer);

fullTextQuery =

fullTextSession.createFullTextQuery(query, AuthorEntity.class);

list = fullTextQuery.setFirstResult((pageNumber - 1) * window).

setMaxResults(window).list();

resultSize = fullTextQuery.getResultSize();

} catch (ParseException e) {

log.error("Parse exception: " + e);

} finally {

releaseSession(session);

}

return new Object[] {resultSize, list};

}

Tabela 2. Pomiar czasów procesu indeksowania dla FSDirectoryProvider

Liczba słów do zindeksowania

Liczba zindek-sowanych termi-nów

Czas indekso-wania BookEnti-ty [ms]

Czas indeksowa-nia AuthorEnti-ty [ms]

Zajętość miej-sca [KB]

1453788 10700 48071 835 320

3261806 15012 100878 1616 588

4585970 18435 142773 2415 820

6916213 21539 179535 3236 980

9301547 26067 226505 4672 1380

Tabela 3. Pomiar czasów procesu indeksowania dla RAMDirectoryProvider

Liczba słów do zindeksowania

Liczba zindek-sowanych termi-nów

Czas indekso-wania BookEnti-ty [ms]

Czas indeksowa-nia AuthorEnti-ty [ms]

Zajętość miej-sca [KB]

1453788 10700 47341 122 320

3261806 15012 98727 167 588

4585970 18435 140013 220 820

6916213 21539 178325 253 980

9301547 26067 218019 365 1380

Page 41: SDJ_03_2010_PL

03/201040

WarsztatyHibernate Search API

www.sdjournal.org 41

zbiorze atrybutów). Jedno z ciekawszych ro-dzajów zapytań to FuzzyQuery. Mechanizm ten wymaga określenia oczekiwanego podo-bieństwa między dwoma wyrazami. W przy-padku różnej skali podobieństwa między ter-mami, lista zwracanych wyników może oka-zać się zupełnie inna. Dla podobieństwa rów-nego 0,1, po wpisaniu wymaganego zwrotu eihrbnate, otrzymamy wyniki zawierające wy-raz Hibernate. Zwiększając wymagane podo-bieństwo do domyślnego 0,5, nie otrzymamy tak rozbieżnych rezultatów, lecz pojedyncza przypadkowa zamiana kolejności liter zwró-ci oczekiwane pozycje. W wykonywaniu za-pytań złożonych z całej frazy bardzo dobrze sprawdza się PhraseQuery. Implementacja ta potrafi dopasowywać wyniki ze zmienioną kolejnością wyrazów. Parametr slop definiu-je odległość między wyrazami (domyślnie 0 – wyrazy występujące w pierwotnie wprowa-dzonej kolejności). Zaawansowani użytkow-nicy wymagać mogą udostępnienia mecha-nizmu wyszukiwania wykorzystującego wy-rażenia regularne. Funkcjonalność tą oferuje biblioteka Apache Lucene Regex, której pro-ste użycie przedstawia Listing 3.

Przykłady stosowania omówionych rodza-jów zapytań znajdują się w kodzie źródło-wym testowej aplikacji dołączonej do czaso-pisma na płycie CD.

Zwracane wyniki zapytań można ponad-to zawężać, stosując bardzo wydajne filtry. Filtry zawierają dodatkowe, predefiniowa-ne dla danej klasy lub atrybutu kryteria wy-szukiwania. Mechanizm ten działa analogicz-nie do znanego tag’u filter-def oraz meto-dy enableFilter ,wchodzących w skład Hi-bernate Core (patrz Listing 4). Testy wydaj-nościowe poszczególnych rodzajów zapytań zaprezentowane zostaną w kolejnej sekcji ar-tykułu.

Istotnym aspektem ze względów wydaj-nościowych staje się sposób zwracania wy-ników danego zapytania. Hibernate Se-arch dostarcza (standardowych dla narzę-dzi rodziny Hibernate) metod list(), uniqueResult(), iterate() oraz scroll(). Funkcja list() to najprostszy i jednocze-śnie najmniej wydajny sposób zwracania du-żej liczby wyników, gdyż wszystkie obiek-ty zaciągane są od razu przy wykonywaniu kwerendy. Problem ten częściowo rozwiązu-je metoda scrollable(). Oferuje ona łatwe w nawigowaniu API oraz ładowanie kon-kretnych pełnych obiektów wraz z doku-mentami Apache Lucene dopiero w przy-padku chęci z nich skorzystania, zmniejsza-jąc przy tym zużycie pamięci i czas ocze-kiwania. Najbardziej wydajnym mechani-zmem zwracania rezultatów okazuje się jed-nak stronicowanie. Algorytm ten polega na wyspecyfikowaniu pozycji pierwszego ele-mentu oraz liczby kolejnych pożądanych re-

kordów (tzw. okna). Stronicowanie minima-lizuje nakład pracy bazy danych oraz redu-kuje wykorzystanie procesora i liczbę odczy-tów z dysku przeprowadzanych przez Apa-che Lucene. Ponadto zmniejszona zostaje liczba danych przesyłanych przez sieć, zuży-cie procesora oraz pamięci aplikacji po stro-nie klienta, a w rezultacie czas odpowiedzi programu. Użytkownicy przeglądają zazwy-czaj jedynie pierwsze sto wyników spośród np. 100000 zwracanych, a dzięki dynamicz-nej wielkości okna liczba ta może zostać dopasowana do indywidualnych potrzeb. Przykładową implementację stronicowania przedstawia Listing 5.

Podsumowując, każdy programista powi-nien dopasować stosowany model zapytań oraz zwracania ich wyników do wymogów biznesowych oraz wydajnościowych konkret-nego projektu.

Testy wydajnościowe oraz optymalizacjaTesty wydajnościowe przeprowadzone zo-stały na domowym komputerze (Intel Core

2 Duo T9300 2,5 GHz, 3 GB RAM, HDD 7200 RPM, Windows XP Professional), na zbiorze pięćdziesięciu książek elektronicz-nych zapisanych w formacie PDF. W sumie zindeksowano 26 tysięcy terminów spośród niespełna 10 milionów wyrazów, co zaję-ło niecałe cztery minuty oraz 1,4 MB prze-strzeni dyskowej. Do ekstrakcji tekstu z ksią-żek PDF użyłem biblioteki PDFBox. Pomiar czasu wykonania poszczególnych metod war-stwy dostępu do danych zrealizowałem za po-mocą framework’u Java Execution Time Me-asurement prezentującego zmierzone wyniki w formie przejrzystej tabeli dostępnej pod ad-resem http://localhost:40000. JETM wymaga konfiguracji jedynie w pliku spring-config.xml, dzięki czemu nie pogarsza przejrzystości ko-du źródłowego. Obciążenie procesora oraz pamięci śledzić można, uruchamiając na-rzędzie JConsole dostarczone wraz z maszy-ną wirtualną firmy Sun Microsystems. Uzy-skane wykresy obciążenia sprzętu oraz czasy wstawiania danych do indeksu dla różnych implementacji DirectoryProvider’a przed-stawiają Tabele 2, 3 oraz Wykresy 2 i 3.

Listing 6. Kon�guracja asynchronicznego wątku Worker'a Hibernate Search

hibernate.search.worker.execution = async

hibernate.search.worker.thread_pool.size = 2

hibernate.search.worker.buffer_queue.max = 50

Listing 7. Kon�guracja automatycznej optymalizacji indeksu

hibernate.search.default.optimizer.transaction_limit.max=50

hibernate.search.default.optimizer.operation_limit.max=100

Listing 8. Ręczna optymalizacja indeksu encji BookEntity

public void optimize() {

Session session = getSession();

FullTextSession fullTextSession = Search.getFullTextSession(session);

fullTextSession.getSearchFactory().optimize(BookEntity.class);

releaseSession(session);

}

Rysunek 2. Wykres obciążenia procesora podczas indeksowania 10 milionów wyrazów

Page 42: SDJ_03_2010_PL

03/201042

WarsztatyHibernate Search API

www.sdjournal.org 43

Wyniki przeprowadzonych testów świadczą o niewielkiej różnicy w cza-sie indeksowania dużych encji między dwoma popularnymi implementacjami DictionaryProvider'ów (kolumna doty-cząca BookEntity). Ponadto, czasy indekso-wania tak dużego zbioru danych wydają się w pełni akceptowalne. W przypadku testów wydajnościowych poszczególnych rodzajów zapytań bardzo duże znaczenie odgrywa sa-ma ich konfiguracja. Dla przykładu, im więk-sza wartość parametru similarity zapytań FuzzyQuery, tym szybciej wykonuje się dana kwerenda. Czasy mogą wahać się nawet od 30 do 140 ms. Analogicznemu zachowaniu podlega PhraseQuery i parametr slop. Pod-czas testów najszybciej wypadły zapytania TermQuery, dla których nie udało mi się prze-kroczyć 0 ms. Czasy wykonania kwerend RegexQuery zależał silnie od samego wyraże-nia regularnego, lecz nawet przy użyciu tzw. wildcard'ów oscylował w granicach 70 ms.

Większość programistów, skończywszy implementacje określonej funkcjonalno-ści systemu, zadaje sobie pytanie, czy nie istnieje możliwość przyspieszenia stworzo-nego oprogramowania. W przypadku me-chanizmów wyszukiwania pełnoteksto-wego optymalizacji podlegać może proces wstawiania oraz wyszukiwania informa-cji. Pierwsze pytanie, jakie powinien po-stawić przed sobą architekt aplikacji, doty-czy konieczności indeksowania wszystkich wskazanych pierwotnie danych. W apre-zentowanym przykładzie należałoby in-deksować jedynie krótkie streszczenie danej książki, a nie całą jej treść. Wymaga-nie to zależy jednak wyłącznie od warun-ków logiki biznesowej i nie będę poświęcał mu więcej czasu.

Optymalizacja indeksowania danychDla aplikacji wykonujących wiele współ-bieżnych operacji modyfikujących da-

ne wąskim gardłem staje się czas wsta-wiania danych do pliku indeksu. Przypo-minam, iż Apache Lucene wymaga cał-kowitej blokady modyfikowanego indek-su oraz nie posiada metody modyfikacji wcześniej wprowadzonych danych. Każ-da zmiana (nawet pojedynczego atrybu-tu) danego rekordu powoduje więc usu-nięcie starego, powiązanego z nim doku-mentu, a następnie wstawienie nowego. Domyślnie, indeksowanie danych odbywa się synchronicznie podczas wykonywania metody commit(), co wymaga oczekiwania na uzyskanie pełnego dostępu do indek-su w przypadku uruchomienia wielu pro-cesów współbieżnie modyfikujących da-ne. Istnieje jednak możliwość wykonywa-nia operacji indeksowania asynchronicz-nie. Dodatkowo programista okręcić mo-że maksymalną liczbę wątków współbież-nie modyfikujących indeks oraz maksy-malną wielkość kolejki. Przykładową kon-figurację obiektu SessionFactory przed-stawia Listing 6.

Podczas wielokrotnego wstawiania i usu-wania danych plik indeksu podlega frag-mentacji. Pozostają w nim nieaktualne do-kumenty, niepotrzebnie zajmujące prze-strzeń dyskową oraz spowalniające wsta-wianie oraz wyszukiwanie danych. De-fragmentacją plików indeksu zajmuje się proces optymalizacji. Proces ten urucho-mić można automatycznie po wykonaniu określonej liczby transakcji, pojedynczych operacji lub też ręcznie dzięki metodzie optimize() (Listing 7 i 8).

Większość tworzonych współcześnie aplikacji posiada trójwarstwową architek-turę klient-serwer. Warstwy dostępu do danych, logiki biznesowej oraz prezenta-cji uruchamiane są na osobnych maszy-nach – komputerze klienckim, serwerze aplikacyjnym oraz serwerze bazy danych. Rysunek 3. Wykres zużycia pamięci podczas indeksowania 10 milionów wyrazów

Listing 9. Zwracanie wyników zapytania specy�kując pożądane atrybuty dokumentu

public List<Object[]> getAuthorsIdAndFirstName(String lastName) {

Session session = getSession();

FullTextSession fullTextSession = Search.getFullTextSession(session);

FullTextQuery fullTextQuery = null;

List<Object[]> list = null;

try {

fullTextQuery = fullTextSession.createFullTextQuery(MultiFieldQueryParser.parse(new String[] {lastName}, new String[] {

"lastName" }, fullTextSession.getSearchFactory().getAnalyzer(AuthorEntity.class)), AuthorEntity.class);

list = fullTextQuery.setProjection(FullTextQuery.ID, "firstName").list();

} catch (ParseException e) {

log.error("Parse exception: " + e);

} finally {

releaseSession(session);

}

return list;

}

Page 43: SDJ_03_2010_PL

03/201042

WarsztatyHibernate Search API

www.sdjournal.org 43

Hibernate Search umożliwia oddelegowa-nie procesu indeksowania do dedykowa-nego serwera. Diagram 5 przedstawia mo-del owej architektury. Komputery klienc-kie posiadają lokalne kopie plików indek-su, na których wykonują operacje wyszuki-wania. Chęć modyfikacji danych zgłaszana jest przez umieszczenie odpowiedniej wia-domości w kolejce JMS serwera. Proces ten odbywa się asynchronicznie, co nie wstrzy-muje pracy komputerów klienckich. Ser-wer odpowiedzialny za indeksowanie da-nych (master) przetwarza żądania klien-tów (slave) oraz umieszcza wynikowe pliki indeksu w udostępnionym folderze siecio-

wym. Komputery klienckie pobierają regu-larnie zmodyfikowane pliki indeksu (np. co godzinę). Jedyną wadą owego rozwiązania wydaje się opóźnienie w propagacji zmo-dyfikowanych danych. W przypadku chęci zapoznania się ze szczegółami konfiguracji master’a oraz slave’ów odsyłam zaintereso-wanego Czytelnika do książki Hibernate Se-arch in Action.

Optymalizacja wyszukiwani danychOptymalizacja procesu wyszukiwania da-nych w dużym stopniu przypomina opty-malizację zapytań SQL. Należy zwrócić uwagę na pobieranie wyłącznie wymaga-

nych atrybutów wchodzących w skład skom-plikowanych encji. Hibernate Search udo-stępnia metodę setProjection() pozwa-lającą na wyspecyfikowanie pól zwracanych przez zapytanie (Listing 9). Wartości atry-butów tych muszą być jednak przechowy-wane zarówno w bazie danych, jak i w pli-kach indeksu (opcja store=Store.YES ad-notacji @Field). Dzięki projekcjom progra-mista unika przesyłania przez sieć nadmia-rowych danych, przechowywania ich w pa-mięci oraz ewentualną niepotrzebną ob-róbkę na terminalu klienckim. Znaczący wzrost wydajności systemu obserwuje się podczas stosowania algorytmu stronicowa-nia w celu zwracania wyników zapytań. Hi-bernate nie pobiera wówczas ogromnej ilo-ści danych, większości których użytkownik i tak nie przejrzy.

PodsumowanieArtykuł ten zaprezentował podstawy me-chanizmu wyszukiwania pełnotekstowe-go opartego na Hibernate Search. Dzięki bardzo przystępnemu oraz w dużej mie-rze przezroczystemu dla programisty API, system ten wprowadzić można do działają-cych już aplikacji bez konieczności grun-townej zmiany implementacji warstwy dostępu do danych. Hibernate Search udostępnia ponadto szeroką gamę para-metrów, umożliwiając dopasowanie dzia-łania owego narzędzia do własnych po-trzeb. Podstawową sprawą wydaje się jed-nak dobór indeksowanych danych, Analy-zer’a oraz rodzaju wykonywanego zapyta-nia, gdyż właśnie te czynniki wpływają na jakość zwracanych rekordów, które powin-ny zadowalać użytkownika końcowego. Hi-bernate Search to ciekawa alternatywa dla usługi Full-Text Search dostępnej w Mi-crosoft SQL Server 2005/2008 (opisanej w Software Developer’s Journal 7/2009). Przewagę omawianego narzędzia stanowi niezależność od implementacji konkret-nego RDBMS, bezpłatny, powszechny do-stęp oraz możliwość większego rozprosze-nia aplikacji – rozłożenia mocy oblicze-niowej. W celu dalszego zgłębiania swo-jej wiedzy na temat Hibernate Search po-lecam Czytelnikom książkę Hibernate Se-arch in Action.

Rysunek 5. Architektura z wykorzystaniem asynchronicznego klastra (źródło: Emmanuel Bernard – „Hibernate Search in Action”)

��������

����

��������

����

������

������

�������

������

������

�������

������

������

�������

������

������

�������

�����

�����

����

�����

�����

����

�����

�����

����

�����

�����

����������������

������������

���������������������������������

������������������

�����������

���������

������

������

��������

W Sieci

• Strona domowa projektu Hibernate Search: https://www.hibernate.org/410.html;• Dzone Refcardz: http://refcardz.dzone.com/refcardz/getting-started-with-hibernate.

ŁUKASZ ANTONIAKŁukasz Antoniak pracuje w �rmie Oracle Polska na stanowisku programisty Java EE/SE. Na co dzień zajmuje się tworzeniem aplikacji bazoda-nowych z wykorzystaniem produktów rodziny Oracle Fusion Middleware. Od niedawna stosu-je także popularne rozwiązania open-source: Hi-bernate oraz Spring Framework.Kontakt z autorem: [email protected]

Page 44: SDJ_03_2010_PL

03/201044

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 45

Spring Framework powstał domyślnie na platformę Java ponad 5 lat temu. Szybko zdobył dużą popularność ze względu na

bardzo interesujący model działania. Osiowym elementem szkieletu był tak zwany kontener IoC, czyli Inversion of Control (szczegóły w ram-ce Inversion of Control – odwrócenie kontro-li) oraz Dependency Injection (szczegóły w ram-ce Dependency Injection – wstrzykiwanie zależ-ności). Wspomniany kontener w łatwy sposób umożliwiał konfigurowanie aplikacji. W toku swojego rozwoju Spring otrzymywał kolejne możliwości integracji kontenera z innymi popu-larnymi platformami wspomagającymi tworze-nie aplikacji we wszystkich jej warstwach. Licz-ne zalety platformy sprawiły, że Spring szybko zyskał ogromną popularność i w równie krót-kim czasie stał się podstawowym narzędziem tworzenia średnich i dużych aplikacji. Począt-kowo rozwijany tylko na platformie Java, znalazł również zainteresowanie w środowisku .NET. I tak narodził się osobno rozwijany projekt w ra-mach Spring Framework – Spring.NET.

Jako że platforma .NET nie jest tak popu-larna w naszym kraju jak Java, to mam na-dzieję, że ten artykuł znajdzie zaintereso-wanie i zachęci czytelników do korzystania

z niezwykle przydatnego narzędzia, jakim jest Spring.NET. W poniższym artykule po-staram się przybliżyć zasadę działania oraz możliwości podstawowego modułu szkie-letu, jakim jest wspomniany już wcześniej kontener Odwróconej kontroli. Artykuł ten jest kierowany do początkujących progra-mistów .NET lub tych, którzy jeszcze nie mieli okazji zetknąć się ze Springiem. Za-pewniam, że naprawdę warto się tym na-rzędziem zainteresować, ponieważ znacz-nie ułatwia ono tworzenie, testowanie oraz późniejszy rozwój i utrzymanie aplikacji. A więc zacznijmy.

Jak działa SpringSpring, jak już zostało to wspomniane w Ramkach, jest lekkim kontenerem obiek-

tów. Lekki oznacza, że nie integruje się z apli-kacją, a zależności w kodzie są minimalne lub żadne. Ponadto nie potrzebuje do swojego działania specjalnego środowiska uruchomie-niowego, można go zatem wykorzystywać za-równo w aplikacji sieciowej, jak i okienkowej czy konsolowej. Konfiguruje się go za pomo-cą metadanych definiowanych w pliku XML. Za pomocą pliku konfiguracyjnego można w Springu deklaratywnie zdefiniować zależ-ności w całej aplikacji. Od połączenia do ba-zy danych, poprzez konfigurację usług sie-ciowych (ang. web service), na kontrolerach i walidatorach warstwy prezentacji kończąc, a to i tak nie wszystko. Spring ponadto zarzą-dza cyklem życiowym poszczególnych obiek-tów, co można również definiować za pomo-cą pliku XML. Domyślnie wszystkie obiek-ty pobierane z fabryki są singletonami, czyli możemy mieć pewność, że raz zdefiniowany i skonfigurowany obiekt jest dostępny przez cały czas działania aplikacji w postaci jednej instancji. W zależności jednak od potrzeb można obiekt tworzyć na nowo za każdym razem, kiedy zaistnieje potrzeba jego użycia lub, w przypadku aplikacji sieciowej, ograni-czać czas życia obiektów do charakterystycz-

Spring.NET – uniwersalny spinacz

Spring to bardzo wygodne i szeroko konfigurowalne narzędzie pozwalające spinać ze sobą poszczególne obiekty, jak i integrować całe warstwy aplikacji.

Dowiesz się:• Podstaw kon�gurowania fabryki Springa. • W jaki sposób działa odwrócenie kontroli

oraz wstrzykiwanie zależności.

Powinieneś wiedzieć:• Czytelnik powinien znać podstawy języka

C# oraz składnię XML.

Poziom trudności

Wprowadzenie do konfiguracji fabryki obiektów

Inversion of Control – odwrócenie kontroliTermin ten opisuje ogólną zasadę działania wzorca projektowego fabryki, lokalizatora usług lub innych wymienionych w ramce Dependency Injection – wstrzykiwanie zależności. Zwy-czajowo obiekty aplikacji są tworzone w kodzie poprzez użycie operatora new. Odwróce-nie kontroli polega zatem na pobieraniu obiektów poprzez wywołanie odpowiedniej me-tody z obiektu fabrykującego. Wzorzec ten wielokrotnie udowodnił swoją przydatność i jest jednym z najpowszechniej wykorzystywanych szablonów projektowych. Niewątpliwą zale-tą jest brak zależności w kodzie aplikacji od faktycznej implementacji wykorzystywanej usłu-gi. Kod aplikacji jest skupiony na wykonywaniu swojego zadania, czyli delegowaniu wyko-nania pewnych działań do obiektów usług, nie dbając o to, jak te działania są wykonywa-ne. Efektem tego jest większa swoboda mody�kacji aplikacji, jako że kod nie jest uzależnio-ny od konkretnych , a zatem nie ma potrzeby kontrolowania kodu w wielu miejscach w razie zmiany wykorzystywanej implementacji. Kontener IoC Springa to scentralizowane miejsce, skąd są pobierane wszystkie de�nicje obiektów, dodatkowo jest on kon�gurowany za pomo-cą plików XML, co w konsekwencji prowadzi do całkowitego braku potrzeby mody�kowania kodu w razie zmiany implementacji usług.

Page 45: SDJ_03_2010_PL

03/201044

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 45

nych dla protokołu HTTP zasięgów request, session czy application.

Spring, jakkolwiek, nie jest typem rozwią-zania, które wymusza użycia całego zestawu swoich funkcji, można bowiem wykorzystać tylko niewielki zakres jego możliwości dopa-sowany do aktualnych potrzeb. Spring Frame-work jest podzielony na szereg modułów, każ-dy z nich to osobna biblioteka, nie ma zatem również potrzeby importowania niepotrzeb-nych przestrzeni nazw, z których w ogóle by się nie korzystało. Dodatkowo Spring umoż-liwia rozbudowę każdego swojego elemen-tu. Wszystkie oferowane możliwości są opar-te na interfejsach, które w zależności od po-trzeb można zaimplementować, dostarczając własnych rozwiązań.

Kontener IoC – podstawy konfiguracjiTeraz, mając już stworzony nowy projekt i za-importowane biblioteki oraz skonfigurowa-ny kontekst Springa (patrz ramka Konfigura-cja), możemy napisać pierwszy przykład. Plik konfiguracyjny app-config.xml powinien wy-glądać ępująco – Listing 2.

Głównym elementem pliku konfiguracyj-nego jest element objects, który będzie za-wierał w sobie kolejne elementy definiujące poszczególne obiekty. Aby nasz plik XML mógł być walidowany przez środowisko pro-gramistyczne, zostały do elementu objects dodane dodatkowe atrybuty definiujące loka-lizację pliku typu XML Schema, który odpo-wiada za poprawność struktury dokumentu.

Warto już w tej chwili wspomnieć, że Spring umożliwia logiczne rozbicie konfigu-racji XML na kilka plików. Aby definicje z in-nych plików były widoczne przez kontener, należy zaimportować je za pomocą elemen-tu import. Na przykład:

<import resource="services.xml"/>

Można też to uczynić, podając więcej niż je-den element resource do definicji konstruk-tora kontenera (patrz ramka Konfiguracja).

Pierwszą i najważniejszą możliwością kon-tenera jest definiowanie obiektów. Możliwe jest tworzenie obiektu za pomocą jego kon-struktora lub za pomocą metody fabrykują-cej, zarówno statycznej, jak i należącej do in-stancji y.

Inicjowanie obiektu za pomocą domyślnego konstruktora bezargumentowegoZdefiniujmy przykładową klasę Object-

Factory – Listing 3.Przykład na Listingu 4. pokazuje, jak zde-

finiować obiekt o nazwie ObjectFactory, który jest typu ObjectFactory. Jest to pro-sta definicja obiektu odpowiadająca uży-

ciu operatora new w kodzie aplikacji. Poda-ne id musi być unikatowe i przestrzegać ob-ostrzeń nałożonych na element id w for-macie XML. Można jednak podać dowol-ną ilość nazw w elemencie name (rozdzie-lonych przecinkiem, średnikiem lub spa-

cją). Nazwy te będą uważane za aliasy te-go obiektu.

Specyfikując typ inicjowanego obiektu, nale-ży pamiętać o podaniu pełnej ścieżki przestrze-ni nazw, jak i nazwy assembly, w którym skom-pilowany jest dany typ. Spring zawsze użyje naj-

Dependency Injection – wstrzykiwanie zależnościWyobraźmy sobie sytuację, w której otrzymujemy obiekt z fabryki. Nie jest on jeszcze, z re-guły, gotowy do użycia, wymaga bowiem skon�gurowania zależności, czyli przypisania re-ferencji do innych obiektów, z których korzysta. Należałoby zatem pobrać z fabryki instancje wszystkich obiektów zależnych i przypisać referencje ręcznie. W przypadku złożonych zależ-ności kod byłby bardzo długi i nieczytelny, a w szczególności niepotrzebnie skupiałby się na działaniach, z jego punktu widzenia, nieistotnych. Wstrzykiwanie zależności robi to dokład-nie za nas, dając nam w pełni skon�gurowany i gotowy do użycia obiekt w chwili pobrania go z fabryki. Nie ma zatem potrzeby wielokrotnego odwoływania się do niej i ręcznego przypi-sywania wartości lub referencji. W ten sposób kod staje się bardziej czytelnym i prostszym w utrzymaniu. Dodatkowym atutem wynikającym z faktu, że a nie wie, skąd pobierać swoje za-leżności, jest łatwość testowania kodu. Wynika to z faktu, że interfejsy, na których, z reguły, operuje a, mogą być na potrzeby testów zastąpione obiektami naśladującymi. Obiekty te po-zbawione są faktycznej logiki biznesowej, a jedynie zwracają oczekiwane wartości za każdym razem, gdy wywołuje się ich metody.Odwrócenie kontroli w Springu jest realizowane poprzez wstrzykiwanie do właściwości typu set oraz wstrzykiwanie do konstruktorów. Obie techniki zostały szerzej opisane w artykule.

Listing 1. Przykład kon�guracji aplikacji za pomocą pliku App.con�g

<configuration>

<configSections>

<sectionGroup name="spring">

<section name="context" type="Spring.Context.Support.ContextHandler,

Spring.Core"/>

</sectionGroup>

<sectionGroup name="common">

<section name="logging" type="Common.Logging.ConfigurationSectionHandler,

Common.Logging" />

</sectionGroup>

</configSections>

<common>

<logging>

<factoryAdapter type="Common.Logging.Simple.TraceLoggerFactoryAdapter,

Common.Logging">

<arg key="level" value="OFF" />

</factoryAdapter>

</logging>

</common>

<spring>

<context>

<resource uri="assembly://SpringExample1/SpringExample1.config/app-config.xml"

/>

</context>

</spring>

</configuration>

Listing 2. Przykład podstawowej de�nicji pliku kon�guracyjnego app-con�g.xml

<objects xmlns="http://www.springframework.net"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.net

http://www.springframework.net/xsd/spring-objects.xsd">

</objects>

Page 46: SDJ_03_2010_PL

03/201046

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 47

nowszej wersji assembly dostępnego w kontek-ście aplikacji. Jeśli jednak zajdzie potrzeba uży-cia konkretnej wersji, należy ją również wów-czas wyspecyfikować. Nazwę assembly, moż-na pominąć, wówczas Spring domyślnie będzie szukał skompilowanych w assembly, w kontek-ście którego uruchamiana jest aplikacja.

Jeśli typ posiada typ wewnętrzny, wówczas inicjujemy go za pomocą operatora +

<object id="ObjectFactory"

type="SpringExample1.ObjectFactor

y+NestedClass,

SpringExample1"/>

Inicjowanie obiektu za pomocą statycznej metody fabrykującejMożliwe jest również zainicjowanie nowego obiektu za pomocą statycznej metody fabry-kującej. Wówczas jako typ podajemy ę zawie-rającą statyczną metodę oraz dodajemy ele-ment factory-method, którego wartość za-wiera nazwę metody zwracającej obiekt.

<object id="ObjectFromStaticFactoryMethod"

type="SpringExample1.ObjectFactory"

factory-method="GetObjectStatically"/>

Inicjowanie obiektu za pomocą niestatycznej metody fabrykującej należącej do instancji klasyTworzenie obiektu za pomocą metody fabry-kującej należącej do instancji klasy wygląda identycznie, z tą różnicą, że należy dodatko-wo wyspecyfikować wcześniej zdefiniowany obiekt fabryki.

<object id="ObjectFromInstanceFactoryMethod"

factory-method="GetObject"

factory-object="ObjectFactory"/>

Powyższy przykład pokazuje kolejną istotną, omawianą wcześniej, funkcjonalność Springa – wstrzykiwanie zależności. O tym jednak na-piszę później w sekcji Wstrzykiwanie zależności.

Inicjowanie typów generycznychW przeciwieństwie do języka Java, typy gene-ryczne na platformie .NET nie są tylko informa-cją dla kompilatora, ale częścią deklaracji typu. Dlatego Spring.NET umożliwia deklarowanie obiektów generycznych w pliku konfiguracyj-nym. Najpierw zdefiniujemy przykładową kla-sę generyczną Container<T> (patrz Listing 6.)

Następnie pokażemy, jak zadeklarować ją w pliku konfiguracyjnym Springa.

<object id="Container"

type="SpringExample1.Container&lt;string>"/>

Z powodu ograniczeń nałożonych na składnię XML, znak mniejszości musi być zapisany za pomocą tak zwanego bytu HTML – &lt;

Kon�guracjaAby korzystać z możliwości oferowanych przez Springa, należy oczywiście pobrać odpowiednie biblioteki. Można to zrobić, korzystając z poniższego linka http://www.springframework.net/. Na tej stronie również można znaleźć szczegółową dokumentację w języku angielskim. Po pobraniu i rozpakowaniu archiwum od razu rzucą się w oczy pliki *.sln, będące gotowymi projektami Visu-al Studio. Uruchamiając je ,możliwe będzie łatwe przeglądanie kodów źródłowych, co jest zawsze bardzo pomocne, oraz ręczna kompilacja bibliotek. Skompilowane biblioteki będą się znajdowały w katalogu build. Są to pliki *.dll, które należy przekopiować do własnego projektu. Do podstawo-wej pracy ze Springiem potrzeba zaimportować bibliotekę Spring.Core oraz używaną przez Spring bibliotekę Common.Logging. Do projektu należy dodać plik App.con�g oraz, najlepiej w podkatalo-gu con�g, umieścić, na razie pusty, plik app-con�g.xml. W pliku App.con�g wpisujemy ępujący kod, deklaratywnie de�niując tym samym kontener – Listing 1.Powyższa kon�guracja formułuje kontekst Springa, czyli skąd mają być pobierane metadane de�niujące kontener IoC oraz logowanie, z którego Spring korzysta. Logowanie w naszych przykładach nie będzie wykorzystywane, zatem możemy je wyłączyć (stąd poziom logowa-nia ustawiony jest na OFF). Plik metadanych najwygodniej jest załączyć jako zasób wbudo-wany, tak jak widać w przykładzie, jest jednak możliwość wczytywania pliku znajdującego się na dysku, jak również znajdującego się w sieci Internet pod dowolnym adresem URL.W naszym przykładzie należy pamiętać o oznaczeniu pliku app-con�g.xml jako Embedded Re-source, co jest bardzo istotne, ponieważ w przeciwnym razie aplikacja nie będzie mogła zna-leźć pliku, a co za tym idzie w ogóle się nie uruchomi.Można oczywiście zainicjować kontener Springa również bezpośrednio w kodzie aplikacji, ale chcemy mieć jak najmniej zależności w kodzie oraz nie chcemy kompilować programu ponownie za każdym razem, zmieni się ścieżka lub nazwa pliku kon�guracyjnego.Aby móc odwołać się do powyżej zde�niowanego kontenera, wystarczy wywołać poniższy kod:

IApplicationContext springContainer = ContextRegistry.GetContext();

W ten sposób mamy skon�gurowany projekt i możemy przystąpić do napisania pierwszego przykładu.

Listing 3. De�nicja klasy ObjectFactory

public class ObjectFactory

{

public static string GetObjectStatically()

{

return "New string obtained from static factory method";

}

public string GetObject()

{

return "New string obtained from instance factory method";

}

public class NestedClass

{

public string Name { get; set; }

}

}

Listing 4. Przykład stworzenia pojedynczego obiektu

<object id="ObjectFactory"

type="SpringExample1.ObjectFactory, SpringExample1"/>

Listing 5. Przykład stworzenia pojedynczego obiektu, wykorzystując konkretną wersję assembly

<object id="ObjectFactory"

type="SpringExample1.ObjectFactory, SpringExample1, Version=1.0.0.0"/>

Listing 6. De�nicja klasy Container<T>

public class Container<T>

{

public T Content { get; set; }

public NameValueCollection NameValue { get; set; }

}

Page 47: SDJ_03_2010_PL

03/201046

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 47

W tej sekcji poznaliśmy podstawowy mecha-nizm kontenera Springa, jakim jest deklarowa-nie definicji obiektów. Na podstawie tych de-finicji Spring inicjalizuje obiekty w momen-cie powołania do życia kontenera obiektów, a następnie przekazuje do dyspozycji aplikacji, gdy instancja danego obiektu będzie wymaga-na. Aby wydobyć instancję obiektu z kontenera Springa , wystarczy wywołać poniższy kod:

string text1 = springContainer.GetObject("

Text1") as string;

Rzutowanie na typ string jest w tym wy-padku niezbędne, jako że wszystkie obiekty zwracane przez fabrykę są typu object.

Istnieje również alternatywna metoda uzy-skania obiektu, bardziej preferowana przez programistów C#:

string text2 = springContainer["Text2"]

as string;

W powyższym przykładzie zastosowane zo-stało przeciążenie operatora indeksującego.

Inicjalizowanie samych obiektów na nie-wiele się zda, jeśli nie będą one miały w peł-ni skonfigurowanych zależności. Następny rozdział skupi się na kolejnej fundamental-nej funkcjonalności Springa – wstrzykiwa-niu zależności.

Wstrzykiwanie zależnościCzym jest wstrzykiwanie zależności, zostało objaśnione w ramce. Skupmy się zatem od ra-zu, na czym to polega z punktu widzenia kon-figuracji Springa.

Wstrzykiwanie zależności za pomocą właściwości typu setJeśli klasa definiuje bezargumentowy kon-struktor domyślny, wówczas zostanie zaini-cjowany pusty obiekt. Aby zdefiniować za-leżności, używamy do tego właściwości ty-pu set, które również muszą być zdefiniowa-ne w klasie dla każdego pola konfigurowane-go za pomocą Springa. Pola klasy są ustawia-ne już po pełnym zainicjowaniu obiektu, czy-li po wywołaniu jego konstruktora.

Jako przykład weźmy klasę MyClass (Li-sting 7). Klasa MyClass posiada dwa rodzaje zależności. Poniżej przedstawiony jest sposób zdefiniowania zależności w pliku XML.

<object id="MyClass"

type="SpringExample1.MyClass">

<property name="description"

value="Opis"/>

<property name="value" value="25"/>

</object>

Powyżej zdefiniowany został obiekt ty-pu MyClass, który definiuje właściwości

Listing 7. De�nicja klasy MyClass

public class MyClass

{

public MyClass() { }

public MyClass(string description, int value)

{

this.Value = value;

this.Description = description;

}

public string Description { get; set; }

public int Value { get; set; }

}

Listing 8. Wstrzykiwanie zależności do obiektów referencyjnych za pomocą elementu ref.

<object id="Container2" type="SpringExample1.Container<SpringExample1.MyClass>">

<property name="content">

<ref object="MyClass2"/>

</property>

</object>

Listing 9. Zagnieżdżone obiekty anonimowe

<object id="Container3" type="SpringExample1.Container<SpringExample1.MyClass>">

<property name="content">

<object type="SpringExample1.MyClass">

<property name="description" value="..."/>

</object>

</property>

</object>

Listing 10. Klasa będąca kontenerem kolekcji

public class CollectionsDependantClass

{

public IList List { get; set; }

public ISet Set { get; set; }

public NameValueCollection NameValueCollection { get; set; }

public IDictionary Dictionary { get; set; }

}

Listing 11. Deklarowanie elementów listy

<object id="Collections1" type="SpringExample1.CollectionsDependantClass">

<property name="list">

<list>

<value>List text</value>

<ref local="MyClass2"/>

</list>

</property>

</object>

Listing 12. Deklarowanie elementów zbioru

<object id="Collections2" type="SpringExample1.CollectionsDependantClass">

<property name="set">

<set>

<value>Set text</value>

<value>Set text</value>

</set>

</property>

</object>

Page 48: SDJ_03_2010_PL

03/201048

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 49

Description oraz Value i ustawia je na war-tość „Opis” oraz wartość 25.

Zależność deklaruje się, wskazując najpierw nazwę właściwości, która jest definiowana, a na-stępnie podaje się jej wartość. Spring potrafi na podstawie typu właściwości dokonać prawidło-

wej konwersji typu z postaci tekstowej. Potrafi on przekonwertować każdy typ wbudowany. Jeśli obiekt nie definiuje żadnej właściwości lub po prostu żadnej ze zdefiniowanych nie chcemy ustawić, wówczas element object pozostaje pu-sty, jak we wcześniejszych przykładach.

Wstrzykiwanie zależności za pomocą konstruktoraWstrzykiwanie zależności za pomocą kon-struktora odbywa się poprzez wywołanie konstruktora, którego parametry stano-wią zależności. Definiowanie konstruktora oraz metody fabrykującej wygląda w zasa-dzie identycznie, jako że stosuje się w obu przypadkach te same elementy definiujące argumenty.

Argumenty konstruktora definiuje się podobnie jak właściwości typu set, jed-nak zamiast elementu property używamy constructor-arg. Należy pamiętać, że cza-sem typ parametru może być niejednoznacz-ny i Spring nie poradzi sobie z jego rozwikła-niem. Możemy wówczas użyć trzech metod dopasowywania typu.

Dopasowywanie po indeksie argumentuJest to domyślne zachowanie. Spring postara się dokonać konwersji typu według typów za-deklarowanych w konstruktorze w takiej ko-lejności, w jakiej zdefiniowane zostały w pli-ku XML, czyli:

<object id="MyClass1"

type="SpringExample1.MyClass">

<constructor-arg value="Opis1"/>

<constructor-arg value="1"/>

</object>

Jeśli kolejność argumentów zostałaby za-mieniona, wówczas Spring zgłosiłby wyją-tek w momencie inicjalizowania obiektu, twierdząc, że wartość „Opis” nie ma wła-ściwego formatu, by przekonwertować ją na ę Int32. Można jednak jawnie zadeklaro-wać kolejność użycia argumentów. Służy do tego atrybut index, który powie Springowi, do którego argumentu dopasować deklaro-waną wartość.

<object id="MyClass2"

type="SpringExample1.MyClass">

<constructor-arg index="1" value="2"/>

<constructor-arg index="0"

value="Opis2"/>

</object>

Należy pamiętać, że indeksowanie zaczyna się od zera.

Dopasowywanie według jawnie zadeklarowanego typuJak zostało wspomniane w poprzedniej sek-cji, zamiana kolejności spowoduje błąd, moż-na jednak podpowiedzieć Springowi, jakie-go typu jest dany argument tak, by był w sta-nie dokonać prawidłowej konwersji niezależ-nie od kolejności argumentów. Służy do tego atrybut type.

Listing 13. Deklarowanie elementów kolekcji typu nazwa-wartość

<object id="Collections3" type="SpringExample1.CollectionsDependantClass">

<property name="nameValueCollection">

<name-values>

<add key="text" value="NVC text"/>

<add key="object" value="It can only be string"/>

</name-values>

</property>

</object>

Listing 14. Deklarowanie elementów słownika

<object id="Collections4" type="SpringExample1.CollectionsDependantClass">

<property name="dictionary">

<dictionary>

<entry>

<key>

<value>key</value>

</key>

<ref local="MyClass2"/>

</entry>

<entry key="textKey" value="textValue"/>

<entry key-ref="MyClass2" value-ref="MyClass3"/>

</dictionary>

</property>

</object>

Listing 15. a będąca kontenerem kolekcji generycznych

public class GenericCollectionsDependantClass

{

public IList<Container<MyClass>> List { get; set; }

public IDictionary<string, int> Dictionary { get; set; }

}

Listing 16. Deklarowanie kolekcji generycznych

<object id="GenericCollections" type="SpringExample1.GenericCollectionsDependantC

lass">

<property name="list">

<list element-type="int">

<value>1</value>

<value>2</value>

<value>3</value>

</list>

</property>

<property name="dictionary">

<dictionary key-type="string" value-type="int">

<entry key="value1" value="5"/>

<entry key="value2" value="10"/>

<entry key="value3" value="15"/>

</dictionary>

</property>

</object>

Page 49: SDJ_03_2010_PL

03/201048

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 49

<object id="MyClass3"

type="SpringExample1.MyClass">

<constructor-arg type="int" value="3"/>

<constructor-arg type="string"

value="Opis3"/>

</object>

Dopasowywanie po nazwie argumentuTo dopasowanie odbywa się na podstawie za-deklarowanej w klasie nazwy argumentu, którą wskazuje się w pliku XML za pomocą atrybutu name. Działa to podobnie jak indeksowanie argu-mentów, z tą różnicą, że jest bardziej czytelne.

<object id="MyClass4"

type="SpringExample1.MyClass">

<constructor-arg name="value"

value="4"/>

<constructor-arg name="description"

value="Opis4"/>

</object>

Wstrzykiwanie zależności za pomocą właści-wości, jak i konstruktora można ze sobą łą-czyć. Nic nie stoi na przeszkodzie, by pew-ne zależności zdefiniować w konstruktorze, a inne za pomocą właściwości. Kiedy uży-wać którego podejścia? Ogólnie, za pomocą konstruktora powinno się ustawiać zależno-ści wymagane oraz tylko do odczytu, zaś za pomocą właściwości te opcjonalne oraz te, które mogą być swobodnie modyfikowane.

Szczegółowe omówienie konfiguracji zależnościDo tej pory we wszystkich przykładach ope-rowaliśmy na typach prostych, takich jak string czy int. Możliwe jest jednak również deklarowanie typów referencyjnych czy ko-lekcji, i na tym skupią się kolejne sekcje.

Wstrzykiwanie zależności od obiektów referencyjnych za pomocą elementu ref.Element ref służy do definiowania zależ-ności od innych zdefiniowanych wcześniej obiektów referencyjnych. Używa się go w na-stępujący sposób – Listing 8.Może on przyjmować ępujące postaci:

• <ref object="MyClass2"/> – odwołuje się do innego zdefiniowanego wcześniej obiektu w fabryce. Wartość atrybutu ob-ject może wskazywać zarówno na id in-nego obiektu, jak i na dowolną wartość atrybutu name.

• <ref local="MyClass2"/> – odwołuje się do innego zdefiniowanego obiektu w obrę-bie tego samego dokumentu XML. War-tość atrybutu local musi wskazywać na id obiektu zdefiniowanego w edytowa-nym pliku. Umożliwia to sprawdzenie po-prawności użytej nazwy już na etapie wali-dacji dokumentu XML.

• <ref parent="MyClass2"/> – dzia-ła podobnie jak atrybut object, z tą różnicą, iż wskazuje na poszukiwa-nie obiektu w kontenerze fabryki nad-rzędnej. Bardzo rzadko stosowany w praktyce.

Możliwe jest również stworzenie dowiąza-nia do wartości id innego obiektu za pomocą elementu idref. Na przykład:

<idref local="MyClass2"/>

W ten sposób nie wstrzykujemy referencji do obiektu, a jedynie wartość id zdefiniowa-ną w pliku konfiguracyjnym. Użyteczne, je-śli kiedyś któryś ze zdefiniowanych obiektów potrzebowałby bezpośrednio odwołać się do fabryki, by wydobyć konkretny obiekt.

Element ten również jest rzadko stosowa-ny w praktyce.

Istnieje również skrócona notacja wstrzyki-wania referencji do właściwości:

<property name="content" ref="MyClass2"/>

Listing 17. Scalanie kolekcji

<object id="Parent" type="SpringExample1.CollectionsDependantClass"

abstract="true">

<property name="nameValueCollection">

<name-values>

<add key="key1" value="value1"/>

<add key="key2" value="value2"/>

</name-values>

</property>

</object>

<object id="Child" parent="Parent">

<property name="nameValueCollection">

<name-values merge="true">

<add key="key3" value="value3"/> <!-- Dodana zostanie nowa wartość pod

kluczem 'key3' -->

<add key="key2" value="new value"/> <!-- Stara wartość pod kluczem 'key2'

zostanie zastąpiona nową -->

</name-values>

</property>

</object>

Listing 18. Przykład klasy deklarującej zależność od zagnieżdżonej listy list, tablicy asocjacyjnej oraz tablicy wielowymiarowej.

public class IndexedClass

{

public IList List { get; set; }

public IDictionary Dictionary { get; set; }

public int[,] Table { get; set; }

public IList this[int index]

{

get { return this.List[index] as IList; }

set { this.List[index] = value as IList; }

}

public object this[string key]

{

get { return this.Dictionary[key]; }

set { this.Dictionary[key] = value; }

}

public int this[int dim1Index, int dim2Index]

{

get { return this.Table[dim1Index, dim2Index]; }

set { this.Table[dim1Index, dim2Index] = value; }

}

}

Page 50: SDJ_03_2010_PL

03/201050

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 51

W ten jednak sposób domyślnie używamy zakresu object.

Zagnieżdżone obiekty anonimoweNieraz zdarza się, że definicja danego obiek-tu jest używana tylko jednokrotnie na potrze-

by jednego obiektu. Nie ma zatem potrzeby definiować go jako niezależnego bytu z wła-snym id. Wystarczy zadeklarować go jako obiekt zagnieżdżony. Nie będzie go jednak można już wówczas bezpośrednio pobrać z fabryki, ale przecież nie będzie to potrzeb-

ne. Obiekt zagnieżdżony definiujemy zatem ępująco – Listing 9.

Deklarowanie wartości kolekcjiCzęstym przypadkiem jest zależność obiek-tów od kolekcji. Spring udostępnia cztery ty-py elementów: list, set, name-values i dic-tionary, które służą do deklarowania zależ-ności w kolekcjach typów (odpowiednio): IList, ISet, NameValueCollection i IDic-tionary. Przykłady użycia znajdują się poni-żej, zdefiniujmy jednak najpierw ę, której bę-dziemy używać jako kontenera naszych ko-lekcji (Listing 10).

ListaListę deklarujemy za pomocą elementu list. Następnie znajdują się elementy listy. Ponie-waż lista nie jest generyczna, można jej przy-pisać dowolny typ wartości, co też widać w przykładzie. Możliwe jest dalsze zagnież-dżanie kolekcji lub utworzenie obiektu we-wnętrznego – wszystkie elementy definiują-ce zależności są dozwolone.

ZbiórDeklaracja zbioru wygląda prawie identycz-nie. Jedyna różnica wynika z definicji zbioru, który uniemożliwia przechowywanie iden-tycznych elementów. Proszę się zatem nie zdziwić, jeśli po zadeklarowaniu zbioru we-dług powyższego przykładu, zawierającego z pozoru dwa elementy, po wyświetleniu za-wartości okaże się, że zdefiniowany zbiór za-wiera tylko jeden wpis.

Kolekcja nazwa-wartośćJest to specyficzny typ kolekcji, który zawie-ra elementy składające się z dwóch wartości tekstowych, z których pierwsza jest kluczem, a druga wartością.

Kolekcję nazwa-wartość deklaruje się za po-mocą elementu name-values, zaś poszczegól-ne wpisy za pomocą elementu add, wewnątrz którego za pomocą atrybutów key oraz value definiujemy faktyczną zawartość danego wpi-su kolekcji.

SłownikSłownik jest tablicą asocjacyjną, podobnie jak kolekcja nazwa-wartość, z tą różnicą, że klucz oraz wartość mogą być dowolnymi obiekta-mi. Słownik definiuje się za pomocą elemen-tu dictionary. Poszczególne wpisy w słow-niku dodaje się za pomocą elementu entry. Wewnątrz niego umieszcza się element key, w którym zawierać się mogą dowolne ele-menty, takie jak value, ref, idref i inne, po-dobnie jak w przypadku listy czy zbioru. ęp-ny element wewnątrz entry definiuje war-tość i może znowu być dowolnym dozwolo-nym elementem definiującym. Istnieje rów-nież notacja skrócona, jak w przykładzie,

Listing 19. De�niowanie niegenerycznych kolekcji klasy za pomocą indekserów

<object id="IndexedClass" type="SpringExample1.IndexedClass">

<property name="list"><!-- Inicjujemy listę do której elementów będziemy się

odwoływać za pomocą indeksu -->

<list>

<list>

<value>0</value>

</list>

</list>

</property>

<property name="dictionary"><!-- Inicjujemy instancję słowika -->

<dictionary/>

</property>

<property name="table" expression="new int[1,1]"/> <!-- Inicjumemy instancję

tablicy -->

<property name="[0][0]"><!-- Ustawiamy pierwszy element listy będący pierwszym

elementem listy 'list' za pomocą indeksu -->

<value>-5</value>

</property>

<property name="['keyOne']" value="10"/><!-- Ustawiamy element słownika pod

kluczem 'keyOne' w tym wypadku dodajemy nową wartość do

pustej tablicy asocjacyjnej -->

<property name="[0,0]"><!-- Ustawiamy pierwszy i jedyny element tablicy

dwuwymiarowej za pomocą indeksu -->

<value type="int">-24</value>

</property>

</object>

Listing 20. Generyczna klasa z indekserami

public class GenericIndexedClass<T>

{

public IList<IList<T>> List { get; set; }

public IDictionary<string, T> Dictionary { get; set; }

public T[,] Table { get; set; }

public IList<T> this[int index]

{

get { return this.List[index]; }

set { this.List[index] = value; }

}

public T this[string key]

{

get { return this.Dictionary[key]; }

set { this.Dictionary[key] = value; }

}

public T this[int dim1Index, int dim2Index]

{

get { return this.Table[dim1Index, dim2Index]; }

set { this.Table[dim1Index, dim2Index] = value; }

}

}

Page 51: SDJ_03_2010_PL

03/201050

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 51

umożliwiająca w jednaj linijce zdefiniować wpis za pomocą atrybutów elementu entry. Atrybutami tymi są:

• key – klucz będący tekstem;• key-ref – klucz będący referencją do

wcześniej zdefiniowanego obiektu;• value – wartość będąca tekstem;• value-ref – wartość będąca referencją.

Oczywiście wszystkie cztery kombinacje użycia są dozwolone.

Deklarowanie wartości kolekcji generycznychDeklarowanie kolekcji generycznych wyglą-da podobnie, z tą różnicą, że nie można mie-szać typów elementów w kolekcji, jak miało to miejsce w przykładach z poprzedniej sek-cji. W tym wypadku odpadają nam również kolekcje typu nazwa-wartość oraz zbiór, po-nieważ nie są generyczne.

Zdefiniujmy najpierw ę zawierającą kolek-cje generyczne (Listing 15).

Deklarowanie kolekcji generycznych wy-gląda ępująco – Listing 16.

Jak widać w przykładzie, elementy list oraz dictionary mają specjalne atrybuty, za pomocą których deklarujemy typ elementów kolekcji. Należy jednak pamiętać o zgodności typów. Jeśli spróbujemy utworzyć niegene-ryczną deklarację kolekcji generycznej (i vi-ce versa), wówczas podczas tworzenia fabry-ki Spring zgłosi wyjątek, mówiąc, że nie mo-że utworzyć kolekcji, ponieważ zaistniała nie-zgodność typów. Wyjątek ten może wyglądać ępująco:

Core.TypeMismatchException: Cannot convert

property value of

type [System.Collec

tions.ArrayList] to

required type [Sys

tem.Collections.Ge

neric.IList`1] for

property 'list'.

Taka postać zgłoszonego wyjątku wynika z faktu, że z powodu braku generycznego ty-pu w deklaracji listy Spring próbuje do gene-rycznego interfejsu przypisać niegeneryczną implementację, co jest błędem.

Scalanie kolekcjiSpring umożliwia (od wersji 1.3) scalanie kolekcji zdefiniowanej w obiekcie rodziciel-skim z kolekcją w obiekcie dziedziczącym. Na temat dziedziczenia obiektów w fabry-ce Springa użytkownik będzie mógł prze-czytać później w sekcji Dziedziczenie defini-cji obiektów.

Zasada scalania kolekcji polega na tym, że obiekt dziedziczący z innego zdefiniowanego

w fabryce obiektu, który posiada zależność od kolekcji, może zdefiniować tę kolekcję na nowo. Efektem będzie dodanie elementów nowych

oraz nadpisanie już istniejących. Funkcjonal-ność ta szczególnie się przydaje w sytuacji, gdy mamy jeden wspólny obiekt rodzicielski z li-

Listing 21. De�niowanie generycznych kolekcji klasy za pomocą indekserów

<object id="GenericIndexedClass" type="SpringExample1.GenericIndexedClass<double>

">

<property name="list">

<list element-type="System.Collections.Generic.IList<double>">

<list element-type="double">

<value>0</value>

</list>

</list>

</property>

<property name="dictionary">

<dictionary key-type="string" value-type="double"/>

</property>

<property name="table" expression="new double[1,1]"/>

<property name="[0][0]">

<value type="double">-18.6</value>

</property>

<property name="['keyTwo']">

<value type="double">30.4</value>

</property>

<property name="[0,0]">

<value type="double">6.23e+2</value>

</property>

</object>

Listing 22. Przykład klasy de�niującej zdarzenia

public class Processor<T>

{

public event ProcessorEventHandler<T> ProcessingComplete;

public event ProcessorEventHandler<T> ProcessingStarted;

public void ProcessData(T data, Func<T, T> action)

{

processingStarted(data);

new Thread(() => { processingComplete(action(data)); }).Start();

}

private void processingComplete(T processedData)

{

if (ProcessingComplete != null)

{

ProcessingComplete(this, new ProcessorEventArgs<T>(processedData));

}

}

private void processingStarted(T dataToBeProcessed)

{

if(ProcessingStarted != null)

{

ProcessingStarted(this, new ProcessorEventArgs<T>(dataToBeProcessed));

}

}

}

Page 52: SDJ_03_2010_PL

03/201052

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 53

stą, która powinna być zdefiniowana podobnie dla każdego dziecka. Użytkownik, chcąc dodać nowe lub zmienić niektóre z elementów w jed-nym z dziedziczonych obiektów bez potrzeby definiowania kolekcji od nowa, może skorzystać z mechanizmu scalania kolekcji, który właśnie to umożliwia. Proszę prześledzić poniższy przy-kład – Listing 17.

W powyższym przykładzie zadeklarowany zo-stał obiekt rodzicielski parent, stanowiący sza-blon dla obiektu potomnego child. Dokładny mechanizm dziedziczenia zostanie wyjaśniony później, w tej chwili istotne jest, że obiekt dzie-dziczący definiuje atrybut merge="true" dla ko-lekcji, którą scala. W przypadku braku tego atry-butu kolekcja zostałaby nadpisana całkowicie. Dzięki niemu zaś kolekcja wynikowa ma trzy ele-menty (nie dwa) jako efekt scalenia. Dodatkowo wartość elementu key2 została nadpisana nową.

Ustawianie wartości list za pomocą indekserówBardzo ciekawą własnością języka C# jest prze-ciążanie operatora indeksującego. Można za jego pomocą odwoływać się do elementów klasy, jak do kolekcji (najczęściej do wewnętrznej kolekcji klasy). Spring umożliwia zatem konfigurowanie wartości pól klasy za pomocą indeksów.

Zdefiniujmy przykładową klasę IndexedClass zawierającą trzy najczęściej używane typy kolek-cji: zagnieżdżoną listę list, tablicę asocjacyjną oraz tablicę wielowymiarową. Banalny przypadek ta-blicy jednowymiarowej został pominięty.

Teraz zdefiniujemy zawartość kolekcji tej klasy za pomocą jej indekserów – Listing 19.

Proszę zwrócić uwagę na nowy atrybut, ja-ki pojawił się w elemencie property inicjują-cym tablicę dwuwymiarową – expression. Za pomocą tego atrybutu możemy wykorzy-

stać specjalny moduł Springa, jakim jest ję-zyk wyrażeń. Atrybut expression występu-je w każdym elemencie definiującym wartość i jest alternatywą dla atrybutu value, w prze-ciwieństwie do którego umożliwia obliczenie wartości na podstawie podanego wyrażenia. W tym wypadku zainicjowania nowej jedno-elementowej tablicy dwuwymiarowej.

Język wyrażeń (SpEL) dostarczany przez Springa jest potężnym i wygodnym narzę-dziem, którego dokładny opis wykracza poza ramy tego artykułu. Zainteresowanych odsy-łam do dokumentacji Springa.

Dodatkową rzeczą, na którą chciałbym zwró-cić uwagę czytelnika, jest deklaracja typu ele-mentu w przypadku dostępu za pomocą in-deksera wielowymiarowego. Z niewiadomych przyczyn brak tej deklaracji powoduje zgłosze-nie wyjątku o nieprawidłowym typie indeksu. Jest to o tyle dziwne, że występuje tylko w przy-padku indeksera wielowymiarowego.

To jednak nie wszystko – skoro możliwe jest definiowanie typów generycznych oraz kolekcji generycznych, dlaczego nie pokusić się o defini-cję łączącą oba elementy w jedną całość.

Rozważmy następującą klasę (Listing 20).A następnie zdefiniujmy jej zawartość (Li-

sting 21).Tym razem deklaracja typu jest obecna na

każdym elemencie definiującym i nie powin-na już dziwić.

W kilku powyższych sekcjach omówione zostały szczegółowe sposoby definiowania specyficznych typów zależności, takich jak na przykład listy. W ępnej sekcji pokażemy, jak można deklaratywnie skonfigurować metody obsługi zdarzeń.

Deklaratywne rejestrowanie metod obsługi zdarzeńZdarzenia w języku C# są częścią jego specyfi-kacji. Za pomocą słowa kluczowego event de-finiuje się zdarzenie, do którego podpina się jedną lub więcej funkcji, które zostaną wyko-nane w momencie aktywowania zdarzenia. Spring umożliwia deklaratywne podpięcie funkcji obsługujących zdarzenia do obiektów je zgłaszających w reakcji na ich aktywację.

Rozważmy klasę definiującą dwa zdarzenia (Listing 22).

Klasa ta jest prostym odpowiednikiem klasy BackgroundWorker z pakietu .NET. Jedynym jej zadaniem jest uruchomienie funkcji przekaza-nej jako parametr w osobnym wątku, informu-jąc poprzez zdarzenia o rozpoczęciu oraz zakoń-czeniu przetwarzania danych.

Na potrzeby naszego przykładu potrzeb-ny będzie również typ delegacyjny, definiu-jący rodzaj funkcji obsługujących zdarzenia (Listing 23).

Klasa ProcessorEventArgs jest kontene-rem argumentów przekazywanych do funk-cji obsługi zdarzenia (Listing 24).

Listing 23. Typ delegujący de�niujący rodzaj funkcji obsługujących zdarzenia

public delegate void ProcessorEventHandler<T>(object source, ProcessorEventArgs<T>

args);

Listing 24. Klasa będąca kontenerem argumentów zdarzeń

public class ProcessorEventArgs<T>

{

public T ProcessedData { get; private set; }

public ProcessorEventArgs() { }

public ProcessorEventArgs(T processedData)

{

this.ProcessedData = processedData;

}

}

Listing 25. Klasa będąca kontenerem funkcji obsługi zdarzeń

public class ProcessorListener<T>

{

public void ProcessingStartedHandleEvent(object source, ProcessorEventArgs<T> args)

{

//operacje wykonywane przed rozpoczęciem przetwarzania

}

public void ProcessingCompleteHandleEvent(object source, ProcessorEventArgs<T> args)

{

//operacje wykonywane po zakończeniu przetwarzania

}

}

Listing 26. De�niowanie obsługi zdarzeń za pomocą Springa

<object id="Processor" type="SpringExample1.Processor<string>"/>

<object id="ProcessorListener" type="SpringExample1.ProcessorListener<string>">

<listener event="ProcessingStarted" method="ProcessingStartedHandleEvent">

<ref local="Processor"/>

</listener>

<listener event="ProcessingComplete" method="ProcessingFinishedHandleEvent">

<ref local="Processor"/>

</listener>

</object>

Page 53: SDJ_03_2010_PL

03/201052

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 53

Ostatnim niezbędnym elementem jest a będąca kontenerem funkcji obsługi zdarzeń (Listing 25).

Mając zdefiniowane wszystkie niezbędne ele-menty, możemy przystąpić do spięcia wszystkie-go w całość za pomocą Springa – Listing 26.

W powyższym listingu zdefiniowaliśmy obiekt klasy Processor, który następnie zo-staje przekazany jako źródło zdarzeń do me-tod obsługujących zdarzenia. Jak widać w przy-kładzie, zadeklarowany został obiekt klasy ProcessorListener, którego dwie metody zo-stały podpięte do zdarzeń zgłaszanych przez nasz procesor. Teraz wystarczy już jedynie pobrać tak skonfigurowany obiekt procesora z fabryki i wy-wołać jego metodę ProcessData, by zobaczyć, jak nasza konfiguracja sprawuje się w praktyce.

Deklaratywne podpinanie funkcji obsługi zdarzeń dostarczane przez Springa umożliwia także wykorzystywanie wyrażeń regularnych do automatycznego podłączenia większej ilości funkcji do danego zdarzenia lub kilku naraz.

Dość częstą praktyką jest nazywanie funk-cji obsługi zdarzeń w taki sposób, aby się z nim kojarzyła. Można zatem wykorzystać wyrażenia regularne, aby łatwo podpiąć funk-cje za pomocą jednej deklaracji. Dla naszego przykładu będzie to:

<object id="ProcessorListener"

type="SpringExample1.

ProcessorListener&lt;string>">

<listener method="${event}HandleEvent">

<ref local="Processor"/>

</listener>

</object>

W powyższym przykładzie widzimy, że nie zdefiniowaliśmy konkretnej metody dla kon-kretnego zdarzenia, ale raczej podpięliśmy funkcje do wszystkich zgłaszanych zdarzeń. To, jaka funkcja zostanie użyta dla danego zda-rzenia, zależeć będzie od jego nazwy.

Możliwe jest również podpięcie większej ilości funkcji do zdarzenia za pomocą jedne-go wyrażenia regularnego.

<object id="ProcessorListener"

type="SpringExample1.

ProcessorListener&lt;string>">

<listener method=".+HandleEvent">

<ref local="Processor"/>

</listener>

</object>

W ten sposób dla każdego zdarzenia zosta-ną wykonane funkcje, których nazwa koń-czy się na HandleEvent.

Opóźniona inicjalizacja obiektówDomyślnie wszystkie obiekty w fabryce Sprin-ga są preinicjalizowane. Przydaje się to bardzo

często w sytuacji, w której czas potrzebny na inicjalizację obiektu jest długi lub gdy chce-my już na etapie uruchamiania aplikacji być poinformowani o błędach w procesie two-rzenia obiektów. Czasem jednak obiekty są wykorzystywane bardzo rzadko i nie ma po-trzeby tworzenia ich podczas startu aplikacji. Można zatem nakazać fabryce ich inicjaliza-cję dopiero w momencie ich pierwszego uży-cia. Służy do tego atrybut lazy-init umiesz-czany w elemencie object. Jeśli zostanie on ustawiony na true, instancja obiektu nie zo-stanie utworzona w momencie inicjalizacji kontenera Springa, ale dopiero w momencie,

gdy po raz pierwszy fabryka otrzyma żądanie zwrócenia danego obiektu.

Autowiązanie zależnościW tej sekcji zajmiemy się bardzo interesującą funkcjonalnością dostarczaną przez Springa, jaką jest możliwość automatycznego wstrzyk-nięcia zależności do obiektów poprzez anali-zę struktury zdefiniowanego kontenera. Au-towiązanie jest definiowane na poziomie de-finicji obiektu za pomocą atrybutu autowire. Jest zatem możliwe, aby dla pewnych obiek-tów tę funkcjonalność włączyć, podczas gdy dla innych dowiązywać zależności ręcznie.

Listing 27. Przykład klasy implementującej interfejs IObjectFactoryAware

public class FactoryAwareContainer<T> : IObjectFactoryAware where T : class

{

private IObjectFactory factory;

public T Content

{

get

{

return this.factory["Content"] as T;//zależność od API Springa

}

}

public IObjectFactory ObjectFactory

{

set { this.factory = value; }

}

}

Listing 28. Przykład klasy de�niującej sygnaturę metody, która zostanie przez Springa zamieniona metodą wyszukującą

public abstract class MethodInjectedContainer<T>

{

public T Content

{

get

{

return GetContent();

}

}

protected abstract T GetContent();

}

Listing 29. Wstrzykiwanie metody wyszukującej

<object id="MyClass" type="SpringExample1.MyClass" singleton="false">

<constructor-arg index="1" value="42"/>

<constructor-arg index="0" value="Opis"/>

</object>

<object id="MethodInjectedContainer"

type="SpringExample1.MethodInjectedContainer <SpringExample1.MyClass>">

<lookup-method name="GetContent" object="MyClass"/>

</object>

Page 54: SDJ_03_2010_PL

03/201054

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 55

Autowiązanie umożliwia zredukowanie lub zupełne wyeliminowanie potrzeby definio-wania argumentów konstruktora lub właści-wości, prowadząc w konsekwencji do zmniej-szenia ilości pisanego kodu.

Autowiązanie ma pięć trybów, które cha-rakteryzują się różnymi sposobami dopaso-wywania wstrzykiwanych zależności. Są to:

• Autowiązanie wyłączone (no lub default) – jest to tryb domyślny. Konfiguracja za-leżności musi być wykonywana ręcznie, co ma tę zaletę, że wszystkie zależności są dobrze udokumentowane. Ten tryb jest zalecany przy dużych projektach, ponie-waż zbyt duże skomplikowanie zależno-ści w przypadku wiązania automatyczne-go może doprowadzić do bardzo nieczy-telnej, trudnej w analizie i utrzymaniu konfiguracji.

• Autowiązanie po nazwie (byName) – Spring analizuje strukturę kontenera ,wy-szukując obiekty nazwane dokładnie tak, jak właściwość obiektu (za pomocą atry-butu id lub name), który ma włączony ten tryb automatycznego wiązania. Jeśli ta-ki znajdzie, wówczas zostanie on wstrzyk-nięty jako zależność. Ten tryb jest rzadko wykorzystywany, ponieważ wymusza do-pasowywanie nazw deklarowanych obiek-tów do nazw właściwości, co może powo-dować niejasności i konflikty, ponieważ różne klasy mogą mieć właściwości o tych samych nazwach.

• Autowiązanie po typie (byType) – Spring analizuje strukturę kontenera i wyszuku-je obiekty o takim samym typie co dowią-zywana właściwość. Jeśli znajdzie zero lub więcej niż jeden obiekt danego typu, wów-czas zostanie zgłoszony wyjątek.

• Autowiązanie do konstruktora (construc-tor) – podobne w działaniu do wiązania po typie, z tą różnicą, że wstrzyknięcie za-leżności nie ępuje poprzez właściwość, ale poprzez konstruktor. Podobnie jak w przy-padku wiązania po typie, jeśli w kontene-rze nie jest zadeklarowany dokładnie jeden obiekt pasujący do typu argumentu defi-niowanego przez konstruktor, wówczas zo-stanie zgłoszony wyjątek.

• Autowiązanie wybierane automatycznie (autodetect) – Spring samodzielnie wy-biera autowiązanie po typie lub do kon-struktora w zależności od definicji klasy. Jeśli klasa posiada konstruktor domyśl-ny, wówczas zostanie wykorzystane wią-zanie po typie.

Warto podkreślić, że jakiekolwiek ręczne zdefiniowanie właściwości lub argumentów konstruktora nadpisuje działanie autowią-zania, czyli innymi słowy ustawienia auto-wiązania zostają wówczas przez Springa zi-gnorowane.

Wstrzykiwanie metod wyszukującychJeśli obiekt korzysta z innych obiektów i ich cy-kle życiowe są zgodne (na przykład obiekt typu singleton korzysta również z obiektów single-tonowych), wówczas wszystkie zależności mo-gą być wstrzyknięte w chwili tworzenia obiek-tu nadrzędnego. Problem zaczyna się w mo-mencie, gdy potrzebujemy wymieszać cykle ży-ciowe obiektów. Jeśli obiekt typu singleton ko-rzysta z obiektu prototypowego, wówczas za-leżność zostanie wstrzyknięta tylko raz pod-czas inicjowania kontenera. Może to prowadzić do nieprzewidzianych konsekwencji (o cyklach życiowych obiektów będziemy jeszcze pisać). Bez specjalnej konfiguracji nie ma możliwości, aby obiekt singletonowy uzyskał nową instancję obiektu za każdym razem, gdy chce go użyć.

Dość częstą sytuacją jest również potrze-ba zdefiniowania w singletonowym obiekcie serializowanym zależności od obiektu niese-rializowanego. W takiej sytuacji niezbędne jest ustawienie pola nieserializowanego jako [NonSerialized], ale wówczas po deserializa-cji obiektu referencja ta będzie pusta. Pisanie zaś własnego mechanizmu serializacji wydaje się być zadaniem karkołomnym.

Spring umożliwia zatem rozwiązanie takie-go problemu na przynajmniej dwa sposoby. Jeden z nich polega na tym, że obiekt imple-mentuje interfejs IObjectFactoryAware. Im-plementacja tego interfejsu wymusza doda-nie do klasy właściwości ObjectFactory, która jest wykorzystywana przez Springa do wstrzyk-nięcia zależności, jaką jest fabryka obiektów.

W związku z tym, klasa może w swoim ko-dzie zażądać określonego obiektu bezpośred-nio z fabryki za każdym razem, gdy będzie te-go potrzebowała. Implementowanie interfejsu IObjectFactoryAware ma jednak jedną bardzo niepożądaną konsekwencję. Uzależnia się nasz kod aplikacji od API Springa oraz wymusza się konkretną nazwę dla definiowanego w fabryce obiektu (w tym wypadku „Content”).

Drugie rozwiązanie jest zdecydowanie bar-dziej eleganckie i polega na tytułowym dla tej sekcji wstrzykiwaniu metod wyszukujących.

Nasza klasa zależna od kłopotliwej właści-wości musi zadeklarować metodę mającą na-stępującą sygnaturę:

<public|protected> <abstract|virtual>

<return-type>

MethodName(no-

arguments);

Na przykład tak jak w przykładziew listin-gu 28.

Naturalnie klasa sama w sobie musi być za-deklarowana jako abstrakcyjna w momencie, gdy chcemy zadeklarować metodę jako abs-trakcyjną. Natomiast jeśli nie chcemy, aby na-sza klasa była abstrakcyjna, musimy zadekla-rować metodę jako wirtualną.

Teraz wystarczy powiedzieć Springowi, co ma robić – Listing 29.

W powyższej konfiguracji powiedzieliśmy Springowi, aby zastąpił zdefiniowaną przez nas metodę GetContent swoją implementa-cją, która jako rezultat zwracać będzie kon-kretny obiekt (tutaj „MyClass”) za każdym razem, gdy zostanie wywołana. Nasz będący singletonem kontener będzie zatem otrzymy-wał nową instancję niesingletonowego obiek-tu „MyClass” za każdym razem, gdy zostanie odczytana jego właściwość Content.

Spring umożliwia jeszcze zastępowanie konkretnych istniejących implementacji me-tod innymi implementacjami napisanymi przez użytkownika. Jest to jednak funkcjo-nalność rzadko wykorzystywana i nie będę jej tutaj opisywał. Zainteresowanych odsyłam do dokumentacji Springa.

Zasięg widoczności obiektówKażda definicja obiektu pojawiająca się w pli-ku konfiguracyjnym Springa jest wzorcem dla konkretnych obiektów, które zostaną utworzone przez fabrykę. Instancji danego wzorca może być wiele lub może być tylko jedna. Spring umożliwia definiowanie zasię-gu widoczności obiektów spośród pięciu do-starczanych przez sam kontener.

• Singleton – tylko jedna instancja da-nej definicji obiektu będzie istniała w in-stancji fabryki. Każde odwołanie się do id takiej definicji zwróci zawsze dokład-

Listing 30. Dziedziczenie de�nicji obiektów

<object id="parent" type="SpringExample1.MyClass" abstract="true">

<property name="value" value="5"/>

<property name="description" value="Opis"/>

</object>

<object id="child" type="SpringExample1.YourClass" parent="parent">

<property name="value" value="10"/><!-- Nadpisanie wartości z definicji

rodzicielskiej -->

</object>

Page 55: SDJ_03_2010_PL

03/201054

WarsztatyWprowadzenie do Spring.NET

www.sdjournal.org 55

nie tę samą instancję obiektu. Typ single-ton jest domyślnym zakresem widocz-ności obiektów, można go jednak jaw-nie zadeklarować, używając atrybutu singleton="true" na elemencie object.

• Prototyp – dokładne przeciwieństwo singletonu. Każde odwołanie się do id definicji obiektu spowoduje utworzenie nowej instancji obiektu. Deklaruje się go za pomocą atrybutu singleton="false" na elemencie object.

• Request – dana instancja obiektu jest wi-doczna w zakresie HTTP Request. Każdy request ma swoją własną instancję obiektu, która jest singletonem podczas jego trwa-nia. Zakończenie danego żądania HTTP kończy również cykl życiowy obiektu o zasięgu widoczności typu request. Mo-że być używany tylko w kontenerze webo-wym. Deklaruje się go za pomocą atrybutu scope="request" na elemencie object.

• Session – działa identycznie jak zakres request, z tą różnicą, że obiekty są wi-doczne w zakresie danej sesji HTTP. Podobnie jak request może być uży-wany tylko w kontenerze webowym. Deklaruje się go za pomocą atrybutu scope="session" na elemencie object.

• Application – analogiczny jak dwa po-wyższe, z tą różnicą, że widoczność obej-muje cykl życiowy aplikacji webowej. Jest to zatem w zasadzie singleton, jeśli nasza konfiguracja nie jest współdzielo-na przez kilka instancji aplikacji webo-wej. Deklaruje się go za pomocą atrybu-tu scope="application" na elemencie object.

Wymienione powyżej zakresy widoczności ty-pu request, session oraz application mogą być używane jedynie w odpowiedniej imple-mentacji interfejsu IapplicationContext, ja-ką jest WebApplicationContext, która jest spe-cyficzna dla kontenera webowego. Jeśli któ-ryś z tych zasięgów zostałby użyty w stoso-wanej w naszych przykładach implementacji XmlApplicationContext, wówczas podczas inicjalizacji kontekstu zgłoszony zostałby wy-jątek mówiący, że został użyty nieznany zakres widoczności.

Informowanie obiektu o jego cyklu życiowymW poprzednich sekcjach opisane zostały dość szczegółowo metody konfiguracji obiektów oraz ich zależności. Czasem zachodzi dodat-kowo potrzeba przeprowadzenia dodatkowych operacji po zainicjowaniu obiektu (na przykład sprawdzenia, czy wszystkie zależności zosta-ły poprawnie ustawione) lub na chwilę przed zniszczeniem kontenera (na przykład zamknię-cie uchwytów do zasobów plikowych lub połą-czenia do bazy danych). Spring udostępnia me-

chanizmy, dzięki którym obiekty mogą zostać poinformowane przez kontener o zaistnieniu wspomnianych sytuacji. Mechanizmy te wystę-pują również w dwóch formach – implementa-cji odpowiednich interfejsów lub deklaratywne zdefiniowanie, która metoda powinna zostać wywołana w momencie zaistnienia opisanych wcześniej zdarzeń.Wspomniane interfejsy to:

• IInitializingObject – dostarcza on metodę AfterPropertiesSet, która zo-staje wywołana zaraz po zainicjowaniu instancji obiektu.

• IDisposable – dostarcza on metodę Dispose, która zostaje wywołana w momen-cie, gdy cykl życiowy obiektu się skończy.

Nie zaleca się jednak stosowania powyższego podejścia ze względu na to, że niepotrzebnie wiąże to kod klasy z API Springa (w przypad-ku użycia interfejsu IInitializingObject). Dlatego też istnieje drugie podejście – dekla-ratywne. Można na elemencie definiującym obiekt umieścić atrybut wskazujący, któ-ra metoda z danej klasy zostanie wywołana. Atrybuty owe to:

• init-method="<method-name>" – wska-zuje, która metoda zostanie wywołana zaraz po zainicjowaniu obiektu. Meto-da ta nie może przyjmować żadnego ar-gumentu ani zwracać żadnej wartości.

• destroy-method="<method-name>" – wskazuje, która metoda zostanie wywo-łana w momencie niszczenia obiektu. Podobnie jak w przypadku metody post-inicjalizacyjnej także i ta nie może przyj-mować żadnego argumentu ani zwracać żadnych wartości.

Dziedziczenie definicji obiektówJuż wcześniej w tym artykule zostało wspo-mniane, przy okazji omawiania kwestii scala-nia kolekcji, że definicje obiektów mogą być dziedziczone. Teraz bliżej przyjrzymy się te-mu zagadnieniu.

Definicja obiektu, jak to zostało przedsta-wione wcześniej, może obejmować wiele ele-mentów deklaratywnie formułujących postać obiektu, który później zostanie zainicjalizo-wany. Definicje dziedziczące, podobnie jak w każdym języku obiektowym, mogą przej-mować deklaracje rodzicielskie, jak również nadpisywać je swoimi (Listing 30).

W powyższym przykładzie widzimy defi-nicję obiektu rodzicielskiego typu MyClass oraz dziedziczącego po nim obiektu typu YourClass. Klasy obiektów mogą być iden-tyczne lub zupełnie różne, niezwiązane ze so-bą nawet dziedziczeniem . Jedyne, co musi je łączyć, to kompatybilność definicji właściwo-ści lub konstruktorów. Dlatego też a YourC-

lass musi deklarować właściwości Value oraz Description, podobnie jak klasa MyClass. Dziecko dziedziczy wszystkie elementy od ro-dzica, takie jak argumenty konstruktora, wła-ściwości, metody inicjujące i inne, oraz mo-że definiować swoje własne. Każda ponowna deklaracja tego samego elementu definiujące-go nadpisuje ustawienia rodzicielskie, tak jak w przykładzie. Atrybut abstract znajdują-cy się na definicji obiektu parent jest wskaza-niem dla fabryki obiektów, że definicja ta jest jedynie szablonem dla innych i nie powinna zostać zainicjalizowana instancja obiektu na jej podstawie. Każda próba użycia definicji rodzi-cielskiej jako wartości elementu ref lub próba wyłuskania definicji z fabryki spowoduje zgło-szenie wyjątku. Ma to dość duże znaczenia, ponieważ szablon rodzicielski nie musi dekla-rować wszystkich elementów, które mogą być niezbędne do prawidłowego funkcjonowania obiektu, na przykład typu (wówczas atrybut abstract jest wymagany). Możliwa jest rów-nież sytuacja odwrotna, czyli brak deklaracji typu obiektu dziedziczącego, w takiej sytuacji będzie on tego samego typu, co rodzic, o ile typ został zdefiniowany dla rodzica.

ZakończenieW ten sposób dotarliśmy do końca tego artyku-łu. Zawarte w nim zostały wszystkie najważniej-sze i najczęściej używane funkcjonalności defi-niowania obiektów i zależności oferowanych przez Springowy kontener IoC. Nie są to jednak wszystkie możliwości przez kontener udostęp-niane, te jednak, które zostały pominięte, są rza-dziej używane lub są zbyt zaawansowane dla po-czątkującego użytkownika. Informacje zawarte w niniejszym artykule są niemniej wystarcza-jące, by stworzyć w pełni funkcjonalną aplika-cję, jakkolwiek jedynie okienkową lub konso-lową. W kolejnych artykułach przybliżę czy-telnikowi wsparcie Springa dla poszczególnych warstw aplikacji webowej, a wspiera on każdą z nich, od umożliwienia deklaratywnego defi-niowania połączenia do bazy danych i obsługi transakcji oraz warstwy DAO, poprzez warstwę logiki biznesowej, której funkcjonalności są wy-stawiane poprzez usługi sieciowe, a na wsparciu warstwy prezentacji kończąc. Mam nadzieję, że ten artykuł zainteresował czytelnika możliwo-ściami udostępnianymi przez Springa oraz dał choć lekki wgląd w to, jak potężnym i niezwy-kle pomocnym jest narzędziem. Zapraszam do przeczytania kolejnych artykułów, bo prawdzi-wa zabawa dopiero się zacznie.

PIOTR WYCZÓŁKOWSKIProgramista zajmujący się na co dzień systemami typu enterprise opartymi na szkielecie Springa. Zawodowo związany z Javą, zafascynowany jed-nak możliwościami języka C# 3.0 i technologii LI-NQ, zajmuje się hobbystycznie platformą .NET.

Page 56: SDJ_03_2010_PL

03/201056

Aplikacje biznesoweSOA

www.sdjournal.org 57

SOA (ang. Service Oriented Architectu-re) to pewna koncepcja, rodzaj podej-ścia w tworzeniu oprogramowania,

w której staramy się tworzyć niezależne usłu-gi, które następnie są udostępniane w celu wykonania określonego zadania lub łączone w łańcuchy kolejnych wywołań, tworząc tzw. łańcuchy procesów biznesowych.

Tak naprawdę trudno w kilku zdaniach powiedzieć, czym jest SOA. W tym artykule skupimy się na tworzeniu usług sieciowych, wykorzystując JAX-WS, które wykorzystamy jako wygodne narzędzie wspomagające pro-ces integracji wielu systemów.

Definicja procesu, analiza problemuPracę zaczniemy od zdefiniowania procesu, na podstawie którego przeprowadzimy ana-lizę architektoniczną. Naszym celem jest przygotowanie pewnego procesu bizneso-wego który udostępnimy zewnętrznym od-biorcom. Zgodnie z modelem SOA chce-my oprogramować pewien proces, dla które-go udostępnimy interfejs dla zewnętrznych odbiorców. Proces ma być hermetyczny, na-si klienci nie potrzebują mieć wiedzy na te-

mat tego, w oparciu o jakie technologie, na jaką platformę itp zostało przygotowane na-sze rozwiązanie.

Od strony biznesowej chcemy przygotowac rozwiązanie przyjmujące pewne dane, które zostaną przekazane w miejsce, gdzie w spo-sób właściwy zostaną przetworzone, dając oczekiwany rezultat (Rysunek 1).

Nasz proces będzie podzielony na dwie części (komponenty), które mogą być do-starczone przez niezależnych dostawców. Pierwsza część procesu związana jest z na-pisaniem aplikacji JEE, a dokładnie serwi-su odpowiedzialnego za przekazanie odpo-wiednio sparsowanej wiadomości do kolej-ki MQ (ang. Message Queue) ,wykorzystu-jąc JMS (ang. Java Message Service). Druga

część procesu obejmować będzie przygoto-wanie wyzwalacza (ang. Trigger) po stronie MQ odpowiedzialnego za wywołanie apli-kacji odpowiedzialnej za przetworzenie ko-munikatu umieszczonego w kolejce (Rysu-nek 2).

Mając zarys procesu, przejdźmy do anali-zy technologicznej. Do przygotowania ser-wisu wykorzystamy JAX-WS, JMS oraz ser-wer Websphe AS 7. Mechanizm kolejek zo-stanie zrealizowany przez Websphere MQ 7, który wykorzysta mechanizm wyzwala-czy w celu wywołania aplikacji Java odpo-wiedzialnej za przetworzenie komunikatów (Rysunek 3).

W naszej analizie przyjęliśmy dwa istot-ne kryteria. Pierwsze to fakt, iż część zwią-zana z przetwarzaniem komunikatów po stronie MQ powinna być odseparowana i niedostępna na zewnątrz , czego konse-kwencją było drugie kryterium związane ze stworzeniem niezależnej warstwy po-średniej, dostępnej dla zewnętrznych apli-kacji klienckich. Warstwa pośrednia po-winna być uniwersalnym pośrednikiem od-powiadającym za wywołanie pewnego zde-finiowanego procesu biznesowego. Opiera-

SOA

Tworzenie rozwiązań integracyjnych to nie trend, ale wymóg stawiany przed projektantami systemów informatycznych. Coraz bardziej złożone procesy biznesowe wymagają od nas projektowania rozwiązań dotykających coraz to większej ilości systemów, które w przeszłości często nie były projektowane w sposób zapewniający łatwą możliwość inetgracji.

Dowiesz się:• Jak integrować aplikacje stworzone w JEE z

instniejącymi aplikacjami;• Jak skon�gurować Websphere AS do pracy

z Websphere MQ;• Czym jest tzw. triggering w MQ i jak go wy-

korzystać w procesie integracji.

Powinieneś wiedzieć:• Podstawowa znajomość standardu JEE;• Znajomość podstawowych pojęć związa-

nych z Websphere MQ (SDJ 10/2008);• Znajomość zagadnień kon�guracji We-

bsphere AS do pracy z Websphere MQ (SDJ 9/2009).

Poziom trudności

Tworzenie serwisów wspomagających proces integracji

Rysunek 1. Schemat procesu biznesowego

Page 57: SDJ_03_2010_PL

03/201056

Aplikacje biznesoweSOA

www.sdjournal.org 57

jąc się o model SOA, wykorzystamy do te-go celu Webservice, który będzie interfej-sem dostępowym dla niezależnych klien-tów. Z punktu widzenia klienta, dostaje-my interfejs do usługi, która odpowiada za odebranie danych i odpowiednie ich prze-procesowanie. Cała usługa jest hermetycz-na, mamy interfejs odpowiedzialny za do-stęp do procesu, który odbiera dane, parsu-je, a następnie przesyła do kolejki, gdzie na-stępuje przetworzenie tych danych i wyge-nerowanie odpowiedniego rezultatu.

Tworzenie usługi sieciowej z wykorzystaniem JAX-WSW pierwszej fazie implementacji naszego procesu biznesowego zajmiemy się utwo-rzeniem Webservice’u odpowiedzialne-go za odebranie komunikatu i przekaza-niu go do asynchronicznego przetwarzania. Jak już wcześniej wspomniałem, wykorzy-stamy standard JEE do utworzenia serwisu oraz uzyskania dostępu do obiektów zwią-zanych z JMS. Pierwszym krokiem będzie utworzenie naszego serwisu, który poprzez JMS prześle komunikat do kolejki. Dzię-ki wsparciu dla Websphere MQ od strony Websphere AS zostaną utworzone obiekty QueueConnectionFactory oraz Queue , któ-re dzięki odpowiedniej konfiguracji będą naszym interfejsem dostępowym do We-bsphere MQ. W pierwszym kroku utwo-rzymy instancje obiektu QueueConnection-Factory, wykorzystując Websphere MQ ja-ko dostawcę usługi zarządzania komunika-tami (Rysunek 4).

Po utworzeniu QueueConnectionFactory, ustawienia naszej fabryki powinny nawiązy-wać do tego, co zostało zdefiniowane po stro-nie MQ. W naszym przypadku dla fabryki połączeń konfiguracja została przedstawiona na Rysunku 5.

W sposób analogiczny tworzymy obiekt Queue, który musi zostać powiązany z odpo-wiadającą mu kolejką po stronie MQ (dokład-ny opis konfiguracji Websphere AS do pracy z Websphere MQ za pomocą JMS został omó-wiony w SDJ 9/2009).

Mając zdefiniowane obiekty JMS, może-my stworzyć nasz serwis do przekazywania komunikatów do kolejki MQ. W tym celu wykorzystamy standard JEE, a dokładnie adnotację @Webservice, która pozwala nam w bardzo prosty sposób stworzyć kod nasze-go Webservice’u (Listing 1) oraz obiektu wejściowego (Listing 2).

Po prawidłowej instalacji aplikacji na ser-werze powinniśmy mieć możliwość wy-generowania WSDL-a (Listing 3). W tym celu należy wpisać w przeglądarce http://localhost:9080/sdjservice/MyServiceService-?wsdl. Oczywiście url może wyglądać nieco inaczej, w moim przypadku serwis został za-

Listing 1. Webservice w JAX-WS

@WebService

public class MyService {

private static final Logger LOGGER = Logger.getLogger(MyService.class);

@Resource(mappedName="MQFactoryJNDI")

private QueueConnectionFactory queueConnectionFactory;

@Resource(mappedName="SDJQueue")

private Queue queue;

public Boolean sendData(InputData input){

LOGGER.debug("Invoking MyService.sendData");

LOGGER.debug(input);

try{

QueueConnection conn = queueConnectionFactory.createQueueConnection();

Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);

MessageProducer producer = session.createProducer(queue);

TextMessage msg = session.createTextMessage();

msg.setText(input.mqMessage());

LOGGER.debug("Sending message to mq");

LOGGER.debug(input.mqMessage());

producer.send(msg);

conn.close();

return new Boolean(true);

}catch(JMSException ex) {

LOGGER.error(ex);

return new Boolean(false);

}

}

}

Rysunek 2. Schemat procesu biznesowego z podziałem na komponenty

Rysunek 3. Schemat procesu biznesowego z uwzględnieniem technologii

Page 58: SDJ_03_2010_PL

03/201058

Aplikacje biznesoweSOA

www.sdjournal.org 59

instalowany na lokalnej maszynie z ustawio-nym kontekstem sdjservice.

Na tym etapie dysponujemy poprawnie za-instalowanym serwisem, który wykorzystu-jąc odpowiednio zdefiniowane obiekty JMS prześle komunikat do kolejki zdefiniowanej po stronie MQ.

Test usługi sieciowejW tej chwili możemy przeprowadzić pełny test naszego komponentu odpowiedzialne-

go za odebranie danych od klienta i prze-kazanie ich do kolejki. Dzięki podzieleniu naszego procesu biznesowego na dwa nie-zależne komponenty możemy przeprowa-dzić test i ostatecznie dostarczyć w peł-ni funkcjonalny komponent niezależnie od tego, czy dostawca odpowiedzialny za utworzenie części związanej z przetwa-rzaniem komunikatu po stronie MQ do-starczył już swój komponent, czy nie. Ta-kie podejście wyodrębniania niezależnych

części procesu biznesowego pozwala dość łatwo zdywersyfikować dostawców kom-ponentów do naszego systemu, a co za tym idzie zmniejszyć ryzyko związane z uzależ-nieniem projektu od jednego konkretnego dostawcy.

W celu przeprowadzenia testu nasze-go komponentu wykorzystamy komunikat SOAP, którym wywołamy nasz serwis (Li-sting 4).

Po wywołaniu naszego serwisu do MQ zo-stał przekazany komunikat, który możemy podejrzeć za pomocą narzędzia MQ Explo-ler (Rysunek 6) oraz zwrócony komunikat SOAP (Listing 5).

Tworzenie i konfiguracja aplikacji – konsumenta komunikatów MQDruga faza naszego procesu jest związana z przetwarzaniem komunikatu umieszczo-nego w MQ. Nasz proces biznesowy z zało-żenia ma być częścią większego systemu, który w oparciu o pewne kryteria wywoła nasz proces lub nie. Załóżmy więc, że mo-że istnieć sytuacja, w której nie będzie co przetwarzać przez kilka lub kilkanaście go-dzin. Jak w takiej sytuacji zapewnić wydaj-ne oraz optymalne przetwarzanie komuni-katów? Zastosowanie klasycznej aplikacji wielowątkowej na pewno spełni nasze ocze-

Rysunek 5. Kon�guracja QueueConnectionFactory

Listing 2. De�nicja klasy InputData

@XmlAccessorType(XmlAccessType.FIELD)

@XmlRootElement(namespace=

"http://model.sdj",name="InputData")

public class InputData {

private String code;

private Integer num;

@Override

public String toString() {

StringBuilder sb =

new StringBuilder();

sb.append("InputData \n");

sb.append("code="+code + ";");

sb.append("num="+num + ";");

return sb.toString();

}

public String mqMessage(){

StringBuilder sb =

new StringBuilder();

sb.append("code="+code + ";");

sb.append("num="+num + ";");

return sb.toString();

}

}

Rysunek 4. Kon�guracja dostawcy zarządzania komunikatami

Page 59: SDJ_03_2010_PL

03/201058

Aplikacje biznesoweSOA

www.sdjournal.org 59

kiwania w zekresie funkcjonalnym, ale czy będzie rozwiązaniem optymalnym i wydaj-nym na płaszczyźnie naszego problemu? Czy stworzenie wątku głównego, który sta-le będzie musiał w krótkich odstępach cza-su odpytywać MQ o to, czy istnieją jakieś komunikaty, jest rozwiązaniem oczekiwa-nym ? Bardzo możliwe, że w pewnych wa-runkach tak, natomiast w naszych na pew-no nie. Zakładamy, że nasz proces jest czę-ścią większego systemu, który może w pew-nych warunkach poprosić nas o asynchro-niczne przetworzenie pewnych informa-cji we względnie krótkim czasie, ale zara-zem może nas o to nie prosić przez dłuż-szy okres. W takiej sytuacji oczekiwanym rozwiązaniem byłby mechanizm, który po-zwalałby konsumować komunikaty tylko wtedy, kiedy będą w kolejce, unikając sytu-acji, w której komunikat czeka w kolejce na przetworzenie.

MQ dostarcza nam taki mechanizm, tzw. triggering, który pozwala zdefiniować po stro-nie MQ proces odpowiedzialny za wywołanie aplikacji – konsumenta w chwili nadejścia nowego komunikatu do kolejki.

Od strony programisty naszego kompo-nentu kod nie ulega dużym modyfikacjom. Tworzymy aplikację odpowiedzialną za na-wiązanie połączenia z managerem kolej-ki, pobraniem komunikatu i wykonaniem dedykowanej logiki biznesowej w oparciu o informacje zawarte w komunikacie (Li-sting 6).

Tak przygotowany kod wraz z odpowied-nim plikiem MANIFEST.MF (Listing 7) na-leży zapakować w plik jar i wraz z zależnymi bibliotekami umiejscowić gdzieś na dysku. Jest to o tyle istotne, ponieważ MQ pozwa-la wywoływać za pomocą triggera pliki wy-konywalne, zatem chcąc wywołać naszą apli-kację JAVA, będziemy wykonywać polecenie „java -jar D:\lib\mqclient.jar”.

W ostatnim etapie dokonamy niezbęd-nej konfiguracji po stronie MQ. W pierw-szym kroku należy stworzyć kolejkę (INI-TQ) wykorzystywaną przez monitor wyzwa-lacza (ang. Trigger Monitor). Od strony czy-sto technicznej proces wyzwalania aplikacji przez MQ przebiega w czterech podstawo-wych krokach:

• wstawienie wiadomości do kolejki QUEUE _ SDJ ;

• wygenerowanie wiadomości – wyzwa-lacza (ang. Trigger control message) i wsta-wienie jej do kolejki INITQ (proces ten przebiega automatycznie);

• odebranie zgłoszenia przez monitor z ko-lejki INITQ;

• wyzwolenie (uruchomienie) naszej apli-kacji JAVA poprzez wykonanie polece-nia „java -jar D:\lib\mqclient.jar”.

Listing 3. WSDL serwisu MyService

<?xml version="1.0" encoding="UTF-8"?>

<definitions name="MyServiceService" targetNamespace="http://service.sdj/"

xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/

XMLSchema"

xmlns:tns="http://service.sdj/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">

<types>

<xsd:schema>

<xsd:import namespace="http://service.sdj/" schemaLocation="MyServiceService

_schema1.xsd"/>

</xsd:schema>

<xsd:schema>

<xsd:import namespace="http://model.sdj" schemaLocation="MyServiceService_

schema2.xsd"/>

</xsd:schema>

</types>

<message name="sendDataResponse">

<part name="parameters" element="tns:sendDataResponse">

</part>

</message>

<message name="sendData">

<part name="parameters" element="tns:sendData">

</part>

</message>

<portType name="MyService">

<operation name="sendData">

<input message="tns:sendData">

</input>

<output message="tns:sendDataResponse">

</output>

</operation>

</portType>

<binding name="MyServicePortBinding" type="tns:MyService">

<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/

http"/>

<operation name="sendData">

<soap:operation soapAction=""/>

<input>

<soap:body use="literal"/>

</input>

<output>

<soap:body use="literal"/>

</output>

</operation>

</binding>

<service name="MyServiceService">

<port name="MyServicePort" binding="tns:MyServicePortBinding">

<soap:address location="http://ewadl00gb1c74j.ams.com:9080/sdjservice/

MyServiceService"/>

</port>

</service>

</definitions>

W Sieci

• http://publib.boulder.ibm.com/infocenter/wmqv7/v7r0/index.jsp?topic=/com.ibm.mq.amqtac.doc/wq11350_.htm;

• http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp;• http://java.sun.com/javaee/5/docs/tutorial/doc/.

Page 60: SDJ_03_2010_PL

03/201060

Aplikacje biznesoweSOA

www.sdjournal.org 61

Drugim krokiem będzie odpowiednia kon-figuracja kolejki głównej (QUEUE_SDJ) do wykorzystywania mechanizmu wyzwala-nia (Rysunek 7). Do najważniejszych usta-wień można zaliczyć:

• kontrola wyzwalacza – ustawia możli-wość wykorzystywania mechanizmu wyzwalaczy dla kolejki, musi być włą-czona;

• wyzwalacz uruchamiany zapełnieniem – ilość komunikatów, która spowoduje uruchomienie naszej aplikacji;

• kolejka inicjująca – kolejka skojarzona z monitorem, ustawiamy na INITQ;

• nazwa procesu – nazwa procesu, z któ-rym zostanie skojarzona nasza aplikacja – konsument.

Trzecim i zarazem ostatnim krokiem będzie zdefiniowanie procesu powiązanego z na-szą kolejką QUEUE _ SDJ. W tym celu należy rozwinąć QM_SDJ->Zaawansowane i utwo-rzyć nowy proces o nazwie SDJ _ PROCESS i wybrać Dalej. W kolejnym oknie w polu ID aplikacji należy wpisać java -jar D:\lib\mqclient.jar (Rysunek 8).

Listing 4. Komunikat SOAP do wywołania serwisu MyService

<soapenv:Envelope xmlns:soapenv=

"http://schemas.xmlsoap.org/soap/

envelope/" xmlns:ser="http:/

/service.sdj/">

<soapenv:Header/>

<soapenv:Body>

<ser:sendData>

<arg0>

<code>A1B2C3</code>

<num>875</num>

</arg0>

</ser:sendData>

</soapenv:Body>

</soapenv:Envelope>

Listing 5. Komunikat SOAP z odpowiedzią z MyService

<soapenv:Envelope xmlns:soapenv=

"http://schemas.xmlsoap.org/soap/

envelope/">

<soapenv:Body>

<dlwmin:sendDataResponse xmlns:

dlwmin="http://service.sdj/">

<return xmlns:ns2=

"http://model.sdj">

true</return>

</dlwmin:sendDataResponse>

</soapenv:Body>

</soapenv:Envelope>

Rysunek 6. Przeglądarka komunikatów

Rysunek 7. Kon�guracja QUEUE_SDJ

Rysunek 8. Kon�guracja procesu

Page 61: SDJ_03_2010_PL

03/201060

Aplikacje biznesoweSOA

www.sdjournal.org 61

Po przeprowadzeniu konfiguracji może-my przejść do etapu testowania naszego kom-ponentu.

Test konsumentaW celu przetestowania części związa-nej z przetwarzaniem komunikatów na-leży uruchomić monitor na kolejce QU-EUE_SDJ, który będzie oczekiwał zgłosze-nia nadejścia komunikatu do kolejki i osta-tecznie uruchamiał aplikacje konsumenta. W tym celu należy z katalogu bin wykonać polecenie:

runmqtrm –m QM_SDJ –q INITQ

W momencie nadejścia komunikatu do ko-lejki, nasz monitor wywoła aplikację klienc-ką, która przetworzy nasz komunikat (Ry-sunek 9).

PodsumowanieW przedstawionym artukule omówiłem pewien scenariusz integracyjny dla zdefi-niowanego procesu biznesowego. Oczywi-ście to pewna koncepcja, ponieważ w za-leżności od potrzeb i możliwości techno-logicznych nasz scenariusz integracyjny może wyglądać zupełnie inaczej. W przy-padku intagracji większych systemów, w których nasza aplikacja kliencka miałaby pełnić rolę pośrednika pomiędzy kolejnymi kolejkami, zamiast tworzyć aplikacje prze-twarzające komunikaty, możemy wykorzy-stać technologię IBM Message Broker. Wy-korzystanie Message Broker-a pozwala wy-godnie definiować przepływy MQ, zwal-niając nas z konieczności implementacji aplikacji odpowiedzialnej za przetworzenie i przesłanie komunikatu.

Listing 6. Klient do przetwarzania komunikatów

public class MQClient {

private static String qMgrName = "QM_SDJ";

private static String qName = "QUEUE_SDJ";

private static MQQueueManager qMgr;

private static MQQueue queue;

public static void main(String[] args) throws EOFException, IOException {

System.out.println("Rozpoczecie przetwarzania");

try {

qMgr = new MQQueueManager(qMgrName);

System.out.println("Polaczenie z Queue Managerem : " + qMgrName);

int openOptions = MQC.MQOO_INPUT_SHARED |

MQC.MQOO_FAIL_IF_QUIESCING | MQC.MQOO_SET;

queue = qMgr.accessQueue(qName, openOptions);

MQMessage msg = new MQMessage();

MQGetMessageOptions gmo = new MQGetMessageOptions();

gmo.options = MQC.MQGMO_WAIT;

gmo.waitInterval = MQC.MQWI_UNLIMITED;

queue.get(msg, gmo);

System.out.println("Komunikat odebrany");

String msgString1 = msg.readStringOfByteLength(msg.getMessageLength());

System.out.println("Tresc: " + msgString1);

queue.setTriggerControl(MQC.MQTC_ON);

queue.close();

System.out.println("Kolejka " + qName + " zamknieta");

qMgr.disconnect();

}catch (MQException ex) {

System.out.println("Completion code " +

ex.completionCode + " Reason code " + ex.reasonCode);

}

}

}

Rysunek 9. Monitor wyzwalacza

Listing 7. Plik MANIFEST.MF

Manifest-Version: 1.0

Package: sdj.client

Main-Class: sdj.client.MQClient

Class-Path: com.ibm.mq.jar

com.ibm.mq.headers.jar

com.ibm.mq.pcf.jar

com.ibm.mq.jmqi.jar jta.jar

connector.jar

com.ibm.mq.com

monservices.jar

PAWEŁ PIETRASZConsultant, CGI Information Systems and Mana-gement Consultants (Polska) Sp. z o. o.,Warszawa, ul. Sienna 39, tel. 022 526 57 00Kontakt: [email protected]

Page 62: SDJ_03_2010_PL

03/201062

Felieton

Od samego początku pojawienia się technologii Java zyskuje ona co-raz więcej zwolenników, głównie ze

względu na możliwości, jakie daje użytkowni-kom. Certyfikacje Sun Microsystems z zakre-su Javy stały się jednymi z najpopularniejszych na rynku programistów. Statystyki pokazują, że certyfikację Suna zrealizowało ponad 600 tyś. osób na całym świecie. Niewątpliwa większość to oczywiście egzaminy z zakresu technologii Ja-va, a Europa jest liderem w tej tematyce.

Dlaczego tak się dzieje? Co jest powodem za-interesowania programami certyfikacyjnymi w tak dużym stopniu? W jaki sposób certyfika-cja może wpłynąć na karierę zawodową? Mam

nadzieję, że poniższy artykuł i przedstawione w nim wyniki badań przeprowadzone w jed-nym z europejskich krajów pomogą odpowie-dzieć na zadane pytania.

Program certyfikacyjny Suna składa się z ośmiu egzaminów dotyczących trzech plat-form Javy. Ich tematyka ściśle wiąże się z wyko-nywanymi przez działy IT zadaniami, ich rolami i koncentruje się na trzech grupach: Programi-stach, Deweloperach i Architektach.

Ścieżka certyfikacyjna JavaNajczęściej podejmowanym certyfikatem jest Sun Certified Java Programmer (SCJP) jako podstawa do dalszego rozwoju. Zdecydowanie mniej osób decyduje się na ścieżkę Dewelopera wymagającą większego doświadczenia. Obser-wując rynek IT, stosunek procentowy jest w cią-gu ostatnich trzech lat bardzo elastyczny i zmie-nia się na korzyść zaawansowanych certyfikacji. Najbardziej jednak „ekskluzywną” ścieżką wy-daje się być Sun Certified Enterprise Architect

(SCEA), do której podchodzi każdego roku je-dynie kilkanaście osób w Polsce i pokrywa on ca-łe spektrum Java Enterprise Edition.

Faktem jest, że zainteresowanie ścieżkami cer-tyfikacyjnymi rośnie z roku na rok w bardzo zna-czącym tempie. Polska jest jednym z tych kra-jów europejskich, w których certyfikacja sta-ła się podstawowym elementem oferty Działu Szkoleń, a tym samym jednym z ważniejszych. Na tym etapie byłoby ciężko odpowiedzieć na pytanie dlaczego tak się dzieje, co spowodowało w ostatnich latach takie zainteresowanie egzami-nami. To, co bez wątpienia narzuca się, to fakt, że profesjonaliści są bardziej świadomi swoich moż-liwości i wartości na dzisiejszym rynku IT nie tyl-ko w obrębie Polski, ale i na rynku europejskim. Aktualnie certyfikacja stała się twardym dowo-dem kompetencji, ambicji pracownika oraz mo-tywacją do dalszego rozwoju. Jest niewątpliwie wartością dodaną profesjonalisty dla jego obec-nego lub przyszłego pracodawcy. Dla kadry za-rządzającej certyfikacje Sun są postrzegane jako zbiór kompetencji, zdobytych umiejętnościach i rozwijania wiedzy technicznej. W swojej wie-loletniej pracy nie raz spotkałam się z opinią, że każdy zdobyty przez pracowników certyfikat jest poświadczeniem jego wiedzy i praktycznych umiejętności. Wzrost zainteresowania egzami-nami jest na tę opinię jedynie żywym dowodem. Świadomość tego, że członkowie zespołu mogą wykazać się wiedzą praktyczną potwierdzoną

dodatkowo certyfikatem, jest zdecy-dowanie atutem w środowisku infor-matycznym.

Jednocześnie z rozwojem popu-larności Javy coraz więcej firm za-częło się na niej koncentrować, co spowodowało duże zapotrzebowa-nie na specjalistów w tej dziedzinie. Ten moment rozwoju samej techno-logii stał się kluczem do sukcesu cer-tyfikacji, a jednocześnie zmotywował pracodawców do inwestycji w swo-ich pracowników. Taka tendencja by-

Certyfikacja i co dalej?Obecna sytuacja na rynku pracy oraz otwarcie rynków europejskich dla polskich pracowników powoduje, że coraz częściej zastanawiamy się nad sposobem poniesienia własnych kwalifikacji. Szukamy metod, które są postrzegane jako wartościowe i uznawane nie tylko przez polskich pracodawców i managerów. Rozwiązaniem coraz częściej wybieranym przez Programistów zajmujących się Java są certyfikacje SUNa ze względu na ich uniwersalność.

Dowiesz się:• Jakie znaczenie na rynku pracy maja certy�katy

SUNa• Czy warto inwestować w egzaminy certy�kacyjne• Jak certy�kacja SUNa postrzegana jest przez ludzi,

którzy już certy�katy posiadają

Powinieneś wiedzieć:• Znajomość podstawowych pojęć związa-

nych z programowaniem w Java’ie• Czym są egzaminy certy�kacyjne SUNa

Poziom trudności

�����������������������������������������

������������������������������������

�����������������������������������

�����������������������������

������

����������������������������

����������������

���������������������������������

�����������������

����������������������������������

��������������������

���������������������������������

����������������

������

������������

���������

��������� ��������� ���������

Rysunek 1. Ścieżka certy�kacyjna Java

Page 63: SDJ_03_2010_PL

63

Page 64: SDJ_03_2010_PL

03/201064

Felieton

ła widoczna przez kilka lat, jednak ostatnie czte-ry lata pokazują, że pracownicy coraz częściej sa-mi inwestują w swój rozwój i podejmują się cer-tyfikacji na własny rachunek. Spowodowane jest to niewątpliwie ciągłą fluktuacją pracowników na rynku informatycznym i chęcią podnoszenia własnych kwalifikacji.

Faktyczna wartośćObecnie certyfikacja z technologii Java jest wy-soko ceniona nie tylko przez programistów, de-weloperów czy architektów, ale również przez pracodawców oraz samego Suna – twórcy tech-nologii. Dla Suna ważne jest, żeby technologia rozwijała się, ważne jest promowanie jakości ko-dowania w Javie. Z drugiej strony pracodawcom zależy na zatrudnianiu certyfikowanych specja-listów choćby z czysto ekonomicznego punktu widzenia, co jednocześnie wpływa na wzrost wartości profesjonalistów na rynku IT.

Programowanie w języku Java jest bardzo powszechne na uczelniach wyższych, co czy-ni technologię bardziej pożądaną i rozwojową. Nie zawsze jednak wiedza akademicka jest wy-starczająca do podjęcia się certyfikacji szczegól-nie tych bardziej zaawansowanych, chociażby tych wymagających znajomości architektury Ja-vy EE. Zdawanie egzaminów z Javy z zadowala-jącym wynikiem wymaga wiedzy praktyczniej, a taką mogą się pochwalić osoby pracujące na ży-wym organizmie, jakim jest Java. Szkolenia, któ-re Sun przygotował również pod kątem ścieżek certyfikacyjnych, są często dla uczestników uzu-pełnieniem wiedzy nabytej na studiach czy rów-nież w pracy zawodowej.

W ciągu ostatnich kilku lat cyklicznie wpro-wadzamy program darmowych powtórek egza-minów (tzw. Second Shot) i bez wahania mogę potwierdzić, że cieszy się on ogromnym zainte-resowaniem na całym świecie. Po pierwszej edy-cji byliśmy zaskoczeni, jak marginalny procent uczestników musiał skorzystać z egzaminu po-prawkowego w stosunku do liczby uczestni-ków, i narzuca się jeden wniosek, że osoby pod-chodzące do certyfikacji są świetnie przygotowa-ne. Dzieje się tak dlatego, że w dzisiejszych cza-sach nie mamy czasu na poprawki i dodatkowe przygotowywanie się do egzaminów. Ten przy-kład w dużej mierze świadczy również o warto-ści certyfikacji – są cenione i traktowane profe-sjonalnie na ryku użytkowników Javy.

Jak się przygotować?Sposobów na dobre przygotowanie się do certy-fikacji jest wiele i każdy bazuje w tej dziedzinie na własnym doświadczeniu, może nawet z cza-sów szkoły? Najczęściej wybieranym sposobem nauki jest douczanie się we własnym zakresie na podstawie sieci czy książek dostępnych na rynku. Ale czy to wystarczy? W niektórych przypadkach pewnie tak, zwłaszcza jeżeli wiedza teoretycz-na podparta jest praktyką zawodową, ale dlacze-go nie spróbować sprawdzonych rozwiązań? Sun

od lat wspomaga ścieżki certyfikacyjne pakietami ePractice, które są zbiorem próbnych pytań egza-minacyjnych i elementem wspomagającym zdo-bytą wiedzę. Pozwalają na zapoznanie się z samą formą egzaminu czy stopniem trudności pytań.

Dodatkowo od kilku lat na terenie Euro-py, w tym również w Polsce, prowadzimy Bo-otcampy z technologii Java. Mają one na celu przygotować uczestników do zaawansowanych certyfikacji, tj. Sun Certified Java Architect. Se-sja taka trwa sześć dni i prowadzona jest przez najlepszych w tej dziedzinie, sprawdzonych in-struktorów Suna. Uczestnicy takich warszta-tów upewniają nas tylko, że zajęcia są potrzebne i przede wszystkim wychodzą z nich z faktycz-ną wiedzą, a nie tylko książkowym zarysem. Studenci dzielą się posiadaną wiedzą, dyskutu-jąc o różnych przypadkach zaczerpniętych z ich praktyki zawodowej.

W roku 2009 przeprowadziliśmy sesję certy-fikacyjną z zakresu SCJP, która cieszyła się spo-rym zainteresowaniem, a jej założeniem było przybliżyć uczestników do samego egzaminu. Zajęcia prowadzone w formie dyskusji i oma-wiania problematycznych kwestii, jednocze-śnie kontakt z wieloletnim instruktorem Sun – a efekt – kilku kolejnych certyfikowanych programistów! Pomysł na warsztat wypłynął z inicjatyw naszych klientów, ich potrzeb i zain-teresowania technologią. Okazuje się, że coraz chętniej specjaliści sięgają po takie formy nauki i nie chodzi tu wyłącznie o szkolenia, ale bardziej o krótkie, jednodniowe sesje.

Jak postrzegają certyfikację sami użytkownicy?W roku 2009 Java Magazine i Sun Microsys-tems w Belgii opublikowały wyniki ankiety przeprowadzonej wśród specjalistów z zakresu Javy. Grupą docelową byli deweloperzy z prak-tyką zawodową oraz zdaną certyfikacją. Ce-lem przeprowadzonego badania było uzyska-nie informacji ,jak programy certyfikacyjne Su-na wpływają na rozwój osób podejmujących się tego wyzwania i czy faktycznie mają one wpływ na karierę zawodową. W ankiecie wzięło udział około 300 osób, a najciekawsze fragmenty zosta-ły przedstawione poniżej.

Każdy może zdać egzaminZ opinii ankietowanych wynika, że około 240 osób potwierdziło duże znaczenie certyfikacji w ich karierze zawodowej. Zdecydowanie więk-sza część uczestników (80%) przyznała, że ma certyfikację lub planuje podejście do egzaminu w najbliższej przyszłości. W tym przypadku zna-czącą popularnością cieszy się Sun Certified Java Programmer jako podstawowy egzamin w ścież-ce Javy. Kolejnym krokiem podejmowanym przez specjalistów jest Web Component Develo-per – w przypadku rynku polskiego zdecydowa-nie częściej podejmowanym jest Java Developer i to ten egzamin zajmuje drugie miejsce. Z każ-

dym rokiem popularność bardziej zaawansowa-nych certyfikacji wzrasta, jednak wciąż najbar-dziej wymagającym jest Sun Certified Java Ar-chitect i to potwierdzają również badania prze-prowadzone na rynku belgijskim.

W większości ankietowani przyznają, że war-to było podejść do egzaminu nie tylko dla same-go „papierka”. Z drugiej jednak strony jednym z wniosków narzucających się po analizie an-kiety jest fakt, że w opinii uczestników Java Pro-grammer to zupełna podstawa Javy - sama Javy Standard Edition i nie wymaga specjalistycznej wiedzy podpartej praktyką. Czy tak faktycznie jest? SCJP to w ścieżce certyfikacyjnej pierwszy krok do kolejnych zaawansowanych egzaminów wymagających dużo doświadczenia i znajomo-ści tematu ponad samą Javę SE, w tym Mobile oraz Enterprise Edition. Podejście do tego egza-minu nie wymaga wieloletniej praktyki w tema-cie technologii Java, ale niewątpliwie posiadanie tej certyfikacji jest atutem na rynku pracy.

Certyfikat często przybliża tematy, które są nam znane, a jednak w codziennej pracy o nich zapominamy czy zwyczajnie je pomijamy.

Wartość certyfikacji na rynku pracyTak naprawdę odpowiedzią na powyższe stwier-dzenie jest przedstawiony wykres – a wyniki są bardzo zbliżone do tego, co obserwujemy na rynku polskim. Około 87% ankietowanych po-twierdza wartość certyfikatów na rynku pracy. Tylko 10% nie zgadza się z tym stwierdzeniem. Na tym etapie pojawia się pytanie, na ile certy-fikaty odzwierciedlają rzeczywistą wiedzę, a na ile są jedynie zbiorem teoretycznych zagadnień. Okazuje się jednak, że takie wątpliwości doty-czą jedynie nielicznych uczestników, a znaczą-ca większość mówi, że certyfikaty są potwier-dzeniem wiedzy i umiejętności. Faktem jest, że posiadanie certyfikatu z Javy nie czyni dobrym programistą, jednak jest wartością dodaną dla każdego, kto podejdzie do egzaminu przygoto-wany i zda go z dobrym wynikiem.

Z szeregu ankiet przeprowadzonych na po-trzeby Suna oraz z bezpośrednich opinii spe-cjalistów i ich managerów wynika jednoznacz-nie, że certyfikacje są na rynku bardzo warto-ściowym elementem. Są uzupełnieniem wiedzy i umiejętności oraz wartością dodaną dla każde-go, kto ma choćby jeden z certyfikatów.

Aktualnie certyfikacja postrzegana jest jako duża szansa na rozwój i wyzwanie nie tylko dla programistów, deweloperów czy architektów, ale również pracodawców, którzy na rynku po-szukują specjalistów najwyższej klasy. Ma to zna-czenia dla firm, ponieważ świadczy o pewnym usystematyzowaniu i ugruntowaniu wiedzy przez członków zespołów Javowych.

Niezbędne informacje o certyfikacjach znaj-dują się na stronie www.sun.com.pl/training

OLIWIA ŁĄCZYŃSKABusiness Development Manager Sun Learning Services

Page 65: SDJ_03_2010_PL

65

Page 66: SDJ_03_2010_PL

03/201066

Praca w zespoleAudyt techniczny

www.sdjournal.org 67

Jak jednak klient może sprawdzić, czy pra-ce projektowe i ich poszczególne produkty odpowiadają jego oczekiwaniom i wyma-

ganiom? Nierzadko instytucja zlecająca realiza-cję usług IT nie posiada odpowiednich zasobów, by móc zweryfikować jakość np. procesu wy-twórczego czy zgodności architektury z ustale-niami kontraktowymi. Może nie mieć persone-lu o odpowiednich kompetencjach lub możli-wości technicznych sprawdzenia pewnych kwe-stii – może też pragnąć (z różnych względów, w tym tzw. politycznych), by kontrola taka wy-konywana była przez zespół całkowicie nieza-leżny i obiektywny. Rozwiązaniem tego proble-mu jest audyt techniczny. Umożliwi on nieza-leżną ocenę procesów, produktów i innych ob-szarów projektu realizowanego przez dostaw-cę IT – weryfikację zgodności z wymagania-mi, identyfikację błędów i usterek oraz dostar-czy rekomendacji czy zaleceń, które – wdrożo-ne przez dostawcę – ulepszą jakość prac.

Audyt – ale co to jest?Zgodnie z definicją audyt jest oceną i weryfika-cją pewnych hipotez. W kontekście projektu IT hipotezą taką może być:

• zarządzanie projektem zgodnie z określo-nym standardem;

• stosowanie ustalonych procedur zarząd-czych (np. procedur zarządzania zmia-nami);

• stosowanie wytycznych określonych standardów IT (np. standardów dotyczą-cych dokumentacji, testowania);

• wykorzystywanie odpowiednich narzę-dzi w pracach projektowych;

• kod źródłowy zgodny ze standardami (np. zgodny z wytycznymi kodowania w Java).

W jakim celu wykonuje się audyt? Powody mogą być następujące:

• by upewnić się, że rzeczywiste prace re-alizowane w ramach projektu odpo-wiadają zadeklarowanym w kontrakcie, a ich jakość spełnia wymagania klienta;

• by sprawdzić, czy jakość poszczególnych produktów odpowiada zapewnieniom dostawcy i oczekiwaniom klienta;

• by ujawnić problemy występujące w róż-nych obszarach projektu i móc je wyeli-minować.

Audyt nie jest oceną ogólną – wykonywany jest na podstawie określonych danych wejścio-wych, wymagań, standardów etc. Przygotowa-nie audytu polega na opracowaniu testów, list kontrolnych czy metod obserwacji, które obej-mują wszystkie wymagane aspekty kontroli – np. jeśli audyt dotyczy dokumentacji, audytor musi sprawdzić:

• czy dokumentacja utrzymywana przez do-stawcę odpowiada zapisom umowy;

• czy dokumentacja przechowywana jest w sposób odpowiedni do udokumentowa-nych wymagań;

• czy narzędzia wykorzystywane do two-rzenia i zarządzania dokumentacją to te, które były deklarowane w umowie/uzgodnieniach;

• czy format dokumentów jest zgodny z za-łożeniami;

• czy jakość rozumiana jako kompletność, jednoznaczność, spójność, przejrzystość jest zachowana;

• czy obieg dokumentacji jest spójny z wy-maganiami etc.

Pytania te muszą zostać zadane – niekoniecz-nie dosłownie: większość tzw. dowodów zbie-ra się podczas wywiadów i obserwacji – a od-powiedzi zanotowane celem dalszej analizy. Nieraz obserwacja dostarcza więcej informa-cji, niż słowa – ponieważ umożliwia obserwa-

Audyt techniczny

Instytucja zlecająca realizację prac IT z definicji pragnie, by jakość tych prac była jak najlepsza, a wymagania dotyczące produkcji systemu oraz samego produktu spełnione na odpowiednim poziomie. Zapewnienia i deklaracje dostawcy to jedno – a uzyskanie konkretnych, obiektywnych dowodów spełnienia wymagań to co innego.

Dowiesz się:• Na czym polega audyt techniczny;• Jakich informacji dostarcza klientowi.

Powinieneś wiedzieć:• Podstawowa wiedza z zakresu zarządzania

projektem.• Podstawowa wiedza z zakresu zarządzania

jakością i realizacji audytów.

Poziom trudności

Czyli jak sprawdzić jakość prac dostawcy IT?

AudytAudyt – ocena danej osoby, organizacji, systemu, procesu, projektu lub produktu. Audyt jest przeprowadzany w celu upewnienia się co do prawdziwości i rzetelności informacji, a także oceny systemu kontroli wewnętrznej. Celem audytu jest wyrażenie opinii na temat osoby/organizacji/systemu itd. w ramach oceny w oparciu o przeprowadzone testy. Audyt polega na ocenie przez kompetentny i niezależny zespół audytujący, czy dany przedmiot audytu spełnia wymagania. Zespół audytujący składa się z jednego lub więcej audytorów.Celem audytu może być wery�kacja, czy cel wyznaczony przez organizację audytowaną został osiągnięty lub czy jej działania są zgodne z zaakceptowanymi standardami, statusem czy prak-tykami. Audyt ocenia także procedury kontrolne celem stwierdzenia, czy przedmiot audytu tak-że w przyszłości będzie odpowiadał uzgodnionym do stosowania wymaganiom. Oprócz oceny wskazuje także zalecenia zmian w procedurach, w tym sprawdzających oraz w politykach.

Page 67: SDJ_03_2010_PL

03/201066

Praca w zespoleAudyt techniczny

www.sdjournal.org 67

cję rzeczywistych zachowań, wyników i prak-tyk, a nie teorii czy pobożnych życzeń.

Postawione powyżej przykładowe pytania kontrolne nie wydają się szczególnie skompli-kowane. Czy zatem rzeczywiście konieczne jest zlecanie audytu instytucji zewnętrznej? Czy or-ganizacja klienta nie jest w stanie samodzielnie wykonać oceny prac dostawcy?

Odpowiedź brzmi – może i nie może. De-cyzja zależy w dużym stopniu od celu posta-wionego przed audytem, zależności pomię-dzy klientem a dostawcą, zasad obowiązują-cych w instytucji klienta. W przypadku gdy klient ma uzasadnione wątpliwości co do ja-kości prac dostawcy i przestrzegania przez niego ustaleń umowy, wskazane jest zlece-nie audytu zewnętrznego. Dlaczego? Ponie-waż wyniki takiego audytu będą obiektyw-ne i klient uniknie posądzeń o próbę mani-pulacji.

Wspomnieliśmy o audycie zewnętrznym – jest i wewnętrzny, czyli audyt przeprowa-dzany przez ludzi z organizacji klienta. Au-dyt taki ma sens, jeżeli zespół audytujący po-siada kwalifikacje odpowiednie do postawio-nego celu i jest w stanie rzetelnie ocenić dany przedmiot. Często audyt wewnętrzny jest je-dynie pierwszą fazą całościowej procedury au-dytu, mającą na celu zidentyfikowanie podsta-wowych zagrożeń, braków i usterek występu-jących w określonych procesach, infrastruk-turze, produktach etc. Zazwyczaj zakończe-nie procedury audytu wewnętrznego wiąże się z podjęciem decyzji o zamówieniu całościowe-go audytu informatycznego u zewnętrznej wy-specjalizowanej firmy.

Audyt zewnętrzny przeprowadzany jest znacznie sprawniej niż wewnętrzny, jako że or-ganizacje zajmujące się tym zawodowo mają doświadczenie i metodologię pozwalającą na znaczne skrócenie procedury audytu. Bardzo istotną cechą audytu zewnętrznego jest brak ryzyka stronniczości – gdyż dokonywany jest przez niezależną instytucję – dzięki czemu wy-niki oceny lepiej i pełniej oddają stan rzeczywi-sty. Rekomendacje audytorów zewnętrznych są też z reguły trafniejsze (co wynika głównie z do-świadczenia) i umożliwiają realne usprawnie-nie wadliwych obszarów.

Rodzaje audytów to nie tylko wewnętrzny i zewnętrzny – audytów w IT jest wiele (ram-ka), w zależności od postawionego celu i tego, co zlecający ocenę chce osiągnąć.

Wiemy już, czym jest audyt, przejdźmy teraz do tego, jak go przygotować i wykonać.

Procedura audytu

Przygotowanie i zakres audytuAudyt, podobnie jak każde przedsięwzięcie, musi zostać odpowiednio zaplanowany i przy-gotowany. Etapy realizacji audytu można okre-ślić poprzez następujące punkty:

• Określenie celu i zakresu audytu.

Czynność ta należy do organizacji zlecającej audyt i polega na określeniu, co ma być przed-miotem oceny oraz na podstawie jakich kryte-riów będzie ona wykonywana. Wskazane jest też sprecyzowanie produktów prac (np. ra-portów).

• Wybór jednostki audytującej.

Zlecający audyt dokonuje wyboru jednostki au-dytującej. Audytorzy powinni pochodzić z fir-my specjalizującej się w danym typie audytu – lub posiadający odpowiednie kompetencje, by taką ocenę wykonać. Zdarza się, że audyt nie jest realizowany przez firmę konsultingową, zajmu-jącą się tylko i wyłącznie wykonywaniem audy-tów. W przypadku np. opisywanego tu audytu technicznego projektu IT, klient może zlecić au-dyt innemu dostawcy IT – przy założeniu, że:

Audytów jest wiele...Oto wybrane rodzaje audytów związane z branżą IT.

• Audyt bezpieczeństwa danych (Technicznej infrastruktury) – ma za zadanie zde�niowanie istniejących zagrożeń i słabych punktów systemu zarządzania bezpieczeństwem infor-macji �rmy oraz oszacowanie ryzyka operacyjnego, na jakie narażona jest organizacja klienta. W wyniku audytu powinny zostać też przedstawione rekomendacje w zakresie bezpieczeństwa informacji oraz propozycje możliwych rozwiązań, które mogą się przy-czynić do podniesienia bezpieczeństwa informacji �rmy oraz do zagwarantowania płyn-ności procesów biznesowych. Podstawą audytu jest zwykle norma PN-I-07799-2:2004.

• Audyt informatyczny – proces zbierania i oceniania dowodów w celu określenia, czy sys-tem informatyczny i związane z nim zasoby właściwie chronią majątek, utrzymują integral-ność danych i dostarczają odpowiednich i rzetelnych informacji, osiągają efektywnie cele or-ganizacji, oszczędnie wykorzystują zasoby i stosują mechanizmy kontroli wewnętrznej, tak aby dostarczyć rozsądnego zapewnienia, że osiągane są cele operacyjne i kontrolne, oraz że chroni się przed niepożądanymi zdarzeniami lub są one na czas wykrywane, a ich skutki na czas korygowane. Normami często stosowanymi w audytach informatycznych są: • ISO 9001; • ITIL; • COBIT.

Normą wyspecjalizowaną w zakresie bezpieczeństwa informatycznego, według której czę-sto prowadzi się audyty, jest ISO/IEC 27001. Towarzyszącą normą służącą do budowania sys-temów zarządzania bezpieczeństwem informacji jest ISO/IEC 27002.

• Audyt techniczny – wery�kacja realizacji projektu zgodnie z umową (warunkami wskaza-nymi przez zlecających projekt lub innych udziałowców); może być wykonywany w każ-dym czasie podczas trwania projektu.

• Audyt technologiczny – ocena rzeczywistych możliwości wykorzystania technologii w �r-mie. Audyt technologiczny to rodzaj kontroli planów wdrożeniowych, kontroli niezależ-nej, udokumentowanej, opartej o określone kryteria. Audyt ten wiąże się z pytaniami: • jakie są inne technologie w danej dziedzinie?;• jaki jest poziom rozwoju ocenianych technologii?;• jaki stopień innowacyjności posiada technologia?.

Audyt technologiczny może być: opiniujący, oceniający lub kontrolny.

• Audyt oprogramowania – polega na analizie stanu oprogramowania zainstalowanego w �rmie, uporządkowaniu i uzupełnieniu brakujących licencji oraz wdrożeniu procedur usprawniających zarządzanie oprogramowaniem.

Na czym się opierać...

• COBIT (Control Objectives for Information and related Technology) – standard opracowa-ny przez ISACA oraz IT Governance Institute, zbiór dobrych praktyk z zakresu IT Governan-ce, które mogą być wykorzystywane w szczególności przez audytorów systemów infor-matycznych. COBIT 4.1 opisuje 34 wysokopoziomowe procesy, które obejmują 210 celów kontrolnych pogrupowanych w czterech domenach:• planowanie i organizacja (Planning and Organization);• nabywanie i wdrażanie (Acquisition and Implementation);• dostarczanie i wsparcie (Delivery and Support);• monitorowanie i ocena (Monitoring and Evaluation).

• ITIL (Information Technology Infrastructure Library) –- kodeks postępowania dla działów infor-matyki. Zbiór zaleceń, jak efektywnie i skutecznie oferować usługi informatyczne. Podstawą koncepcji ITIL jest zde�niowanie procesów, które powinny funkcjonować w ramach organi-zacji świadczącej usługi IT. ITIL pozwala na modelowanie procesów zarówno w organizacjach komercyjnych (np. �rmy komputerowe, programistyczne), jak i niekomercyjnych (agencje rzą-dowe itp.), niezależnie od wielkości �rmy, typu organizacji czy też posiadanych narzędzi. Każ-dy proces powinien posiadać zde�niowane role i odpowiedzialności.

Page 68: SDJ_03_2010_PL

03/201068

Praca w zespoleAudyt techniczny

www.sdjournal.org 69

• dostawca ów nie jest powiązany z do-stawcą poddawanym audytowi;

• pracownicy firmy, która będzie reali-zować audyt, posiadają kwalifikacje i niezbędne umiejętności w zakresie prowadzenia audytu;

• sam dostawca dysponuje zasobami i mechanizmami umożliwiającymi poprawne wykonanie oceny;

• klientowi nie zależy na uzyskaniu certyfikatu z wykonanego audytu i

nie ma przymusu angażowania wy-specjalizowanej firmy audytującej.

• Dobór zespołu audytującego

Na audytorach spoczywa odpowiedzialność za proces obiektywny, niezależny i wiary-godny. Zgromadzenie dowodów obiektyw-nych, pomoc w wykryciu przyczyn źródło-wych, informacja zwrotna, sugestie doty-czące doskonalenia procesów, zasugerowa-nie działań korygujących to podstawowe

cele audytu. Zespół audytujący musi skła-dać się z ludzi będących w stanie ocenić da-ny przedmiot – dlatego też zwykle audyt re-alizowany jest przez kilku audytorów, przy czym każdy zajmuje się inną specjalnością.

• Analiza informacji wejściowych

Polega na zebraniu przez zespół audytujący danych stanowiących podstawę do wykony-wania oceny (kryteriów audytu) i opracowa-niu ich w takiej postaci, by mogły służyć ja-ko mechanizmy przeprowadzania audytu. Czyli – jeżeli audyt dotyczy oceny procesu testowania wewnętrznego (funkcjonalnego) dostawcy, danymi wejściowymi będą np.:

• plan projektu – z uwzględnieniem czasu przeznaczonego na testy, by skonfrontować go następnie z rze-czywistym czasem trwania testów oraz zapisami planu testów;

• plan testów (wraz z wybranym po-dejściem do testów np. black box, podejście oparte na wymaganiach; harmonogramem testów, składem zespołu testującego, kryteriami wejścia/wyjścia testów);

• projekt testów (czyli zbiór przypad-ków i scenariuszy testowych – ana-lizowany celem identyfikacji pokry-cia funkcjonalności testami);

• wykaz narzędzi stosowanych do za-rządzania testami i wspierania rapor-towania (by określić, czy narzędzia owe są rzeczywiście wykorzystywane w procesie testowym i w jakim stop-niu);

• dokumentacja procedur – np. obsłu-gi zgłoszeń błędów, żądań zmian etc.

Informacje zawarte w danych wejściowych należy przetworzyć celem ustalenia punk-tów kontroli (tzn. istotnych elementów pro-cesu, które będą poddawane audytowi), któ-re w następnej kolejności umożliwią opraco-wanie formularzy, list kontrolnych przezna-czonych do dalszego wykorzystania już pod-czas przeprowadzania audytu.

• Opracowanie harmonogramu audytu

Audyt nie może odbywać się ad hoc, bez ła-du i składu, audytorzy nie mogą biegać bez-ładnie po całej placówce, odpytując raz jedną osobę, raz drugą. Plan przeprowadzania po-szczególnych działań audytowych powinien być jasno określony i zakomunikowany au-dytowanym przed rozpoczęciem oceny.

Harmonogram uwzględnia wykonanie wszystkich zaplanowanych działań audytowych, przez wszystkie osoby wykonujące ocenę i dla wszystkich obszarów/przedmiotów oceny. Każ-dy dzień audytu powinien rozpoczynać się spo-

Standardy audytu systemów informatycznych (IS Auditing Standards)

• 010 Prawa i powinności audytuObowiązki, uprawnienia i odpowiedzialność obszaru działania realizującego funkcję audytu systemów informatycznych mają być odpowiednio udokumentowane w dokumencie regu-lującym prawa i powinności audytu lub w umowie na jego przeprowadzenie.

• 020 NiezależnośćWe wszystkich sprawach związanych z audytowaniem audytor systemów informatycznych musi być niezależny od poddawanych audytowi, zarówno co do stanowiska, jak i postępowania.

• 020.020 Powiązania organizacyjneFunkcja audytu systemów informatycznych musi być wystarczająco niezależna od audyto-wanego obszaru, aby umożliwić obiektywne prowadzenie audytu.

• 030 Standardy i etyka zawodowaAudytor systemów informatycznych jest zobowiązany do stosowania się do Kodeksu Etyki Zawodowej Stowarzyszenia do spraw audytu i kontroli systemów informatycznych (ISACA – Information Systems Audit and Control Association).

• 030.020 Należyta staranność zawodowa Wobec wszelkich aspektów pracy Audytora Systemów Informatycznych obowiązuje należy-ta zawodowa staranność i przestrzeganie odpowiednich standardów audytu.

• 040 KompetencjeAudytor systemów informatycznych ma być kompetentny w zagadnieniach technicznych, posiadając umiejętności i wiedzę niezbędną do wykonywania pracy audytorskiej.

• 040.020 Ustawiczne szkolenie zawodoweAudytor systemów informatycznych ma utrzymywać kompetencje dotyczące zagadnień technicznych poprzez właściwe i ustawiczne szkolenie zawodowe.

• 050 PlanowanieAudytor Systemów Informatycznych powinien planować prace związane z audytem sys-temów informatycznych pod kątem realizacji celów audytu oraz zgodnie z odpowiednimi standardami przeprowadzania audytów.

• 060 Wykonywanie prac audytowych

Personel zajmujący się audytem systemów informatycznych ma być odpowiednio nadzorowany, aby zapewnić, że cele audytu są spełnione oraz że są spełnione stosowne, zawodowe standardy audytu.

• 060.020 DowodyZadaniem Audytora Systemów Informatycznych podczas przeprowadzania audytu jest zgro-madzenie wystarczających, wiarygodnych, odpowiednich (relewantnych) i użytecznych do-wodów, tak by efektywnie zrealizować zadania audytu. Wyniki audytu oraz wnioski powinny być poparte odpowiednią analizą i interpretacją tychże dowodów.

• 070 RaportowanieZadaniem Audytora Systemów Informatycznych jest dostarczenie określonym odbiorcom odpowiednio sformowanego raportu dotyczącego wykonanych prac audytowych. Raport z audytu ma przedstawiać obszar, cele, okres oraz rodzaj i zakres wykonanych prac audyto-wych. Raport ma wskazywać organizację, planowanych odbiorców raportu oraz wszelkie za-strzeżenia co do jego obiegu. Raport ma przedstawiać wyniki, wnioski i rekomendacje oraz wszelkie zastrzeżenia lub uwarunkowania audytora wobec audytu.

• 080 Dalszy tok działańAudytor systemów informatycznych ma domagać się i oceniać stosowne informacje o wcześniej-szych wynikach, wnioskach i rekomendacjach, aby określić, czy zostały podjęte na czas właściwe działania.

Page 69: SDJ_03_2010_PL

03/201068

Praca w zespoleAudyt techniczny

www.sdjournal.org 69

tkaniem otwierającym, które angażuje uczestni-ków audytu (zarówno ze strony audytującego, audytowanego, jak i klienta) i polega głównie na przedstawieniu planu działań na dany dzień.

Podobnie – każdy dzień audytu kończy się spotkaniem zamykającym, na którym przed-stawiane są wyniki dotychczasowej oceny, wynikłe problemy, uwagi, spostrzeżenia.

• Przygotowanie list kontrolnych

Na podstawie danych wejściowych – do-kumentacji, informacji ustnych – audytor opracowuje listy kontrolne i formularze. Po-winny one zawierać co najmniej:

• pytanie kontrolne;• referencję do dokumentu związanego/

punktu normy, który jest weryfikowa-ny za pomocą danego pytania;

• pole do odnotowania uwag i spo-strzeżeń.

Przykładowe pytania kontrolne (dotyczy au-dytu procesu testowania):

• jakie etapy testowania wykonuje się w ramach projektu;

• na czym oparte są scenariusze i przy-padki testowe;

• w jaki sposób zapewnione jest wymaga-ne pokrycie funkcjonalności testami;

• w jaki sposób wykonywane są testy i raportowane ich wyniki.

Jak widać, pytania kontrolne są otwarte – czyli umożliwiają audytowanemu udziele-nie swobodnej odpowiedzi, bez ograniczania możliwości wypowiedzi i narzucania czy su-gerowania odpowiedzi właściwej. Pytanie ty-pu: czy wykonujecie testy zgodnie z planem te-stów nie jest pytaniem właściwym, jako że

Przykład – określenie celu i zakresu audytu dotyczącego procesu wytwórczego dostawcy

Cel:Ocena zgodności procesu wytwórczego z ustaleniami umowy i deklarowanym w DIP podej-ściem do zarządzania projektem.

Zakres prac:

• identy�kacja etapów procesu wytwórczego;• identy�kacja mechanizmów kontroli jakości w poszczególnych etapach procesu wy-

twórczego;• wery�kacja kompetencji osób realizujących prace rozwojowe;• wery�kacja podejścia do planowania prac przez dostawcę;• analiza procesu dokumentacji realizacji prac przez dostawcę. Sposób dokumentowania

prac i zarządzanie dokumentacją;• określenie wymagań jakościowych i sposoby wery�kowania ich realizacji;• analiza zagrożeń i potencjalnych ataków oraz uwzględnienie funkcjonalności zapewnia-

jących ochronę przed zidenty�kowanymi zagrożeniami. Zapewnienie bezpieczeństwa w ramach implementacji funkcjonalności;

• analiza procesu testowania aplikacji; • identy�kacja zabezpieczenia kodu przed utratą jego poufności;• identy�kacja wymagań związanych z dostarczeniem produktu odbiorcy i utrzymaniem

produktu;• wery�kacja planu reagowania na zgłoszone problemy z funkcjonowaniem oprogramo-

wania.

Oczekiwane wyniki prac:

• stwierdzone nieprawidłowości wraz ze wskazaniem ich potencjalnego wpływu na jakość i bezpieczeństwo oprogramowania;

• rekomendowane działania korekcyjne wraz z uzasadnieniem i wskazaniem priorytetu.

Rola audytora wiodącegoZadaniem audytora wiodącego jest opracowanie planu audytu. Plan ten powinien uwzględniać informacje dotyczące celu audytu, dokumentów odniesienia, zakresu au-dytu, ram czasowych (przy czym audyt nie powinien wybiegać poza godziny pracy au-dytowanych).Audytor wiodący dokonuje podziału pracy pomiędzy członków zespołu. Wszyscy au-dytorzy przed przystąpieniem do audytu są zobowiązani zapoznać się z dokumentacją dotyczącą obszarów poddawanych ocenie oraz opracować niezbędne formularze i listy kontrolne.

Rysunek 1. Plan audytu

Page 70: SDJ_03_2010_PL

03/201070

Praca w zespoleAudyt techniczny

www.sdjournal.org 71

Tabela 1. Harmonogram audytu – dzień 1

Temat/obszar

Zakres audytu Kryteriumodniesienia

Przed-stawiciel ze strony klienta

Audytorzy Godzinaod do

Spotkanie otwiera-jące

Przedstawienie planu audytu, opis wykonywanych działań audy-towych.

Plan audytu Odpowie-dzialny za realizację audytu

Zespół audy-towy

09:00 – 09:30

Analiza ar-chitektu-ry rozwią-zania

Analiza adekwatności zastosowanej architektury do celu stawiane-go systemowi.

Dokumentacja architektury

Audytor ds. architektury

09:45 – 10:15

Analiza podziału przetwarzania informacji pomiędzy poszczególnymi aplikacjami. Wery�kacja mechanizmów wymiany informacji pomię-dzy poszczególnymi aplikacjami i zabezpieczenia danych w trakcie transferu Wery�kacja mechanizmów wymiany informacji pomiędzy danym systemem a innymi systemami informatycznymi.

Dokumentacja architekturySpecy�kacja in-tegracji

Audytor ds. architektury

10:15 – 11:00

Wery�kacja optymalności zapisu danych w systemie. Audytor ds. architektury

11:00 – 12:00

Wery�kacja efektywności zarządzania systemem w kontekście je-go architektury.

Audytor ds. architektury

12:15 – 13:00

Przerwa 13:00 – 14:00

Wery�kacja zapewnienia spójności przetwarzania danych w ra-mach zastosowanej architektury.

Audytor ds. architektury

14:00 – 14:30

Identy�kacja zagrożeń związanych z poszczególnymi komponen-tami rozwiązania i z powiązaniami pomiędzy tymi komponentami.

Audytor ds. architektury

14:30 – 15:00

Wery�kacja mechanizmów logowania zdarzeń. Audytor ds. architektury

15:00 – 15:25

Zarządzanie zabezpieczeniami (w tym zarządzanie mechanizmami kryptogra�cznymi).

Audytor ds. architektury

15:25 – 16:00

Wery�kacja komponentów sprzętowych wchodzących w skład sys-temu i ich kon�guracja.

Audytor ds. architektury

16:00 – 16:30

Proces wy-twórczy dostawcy

Identy�kacja etapów procesu wytwórczego Plan projektuDIP

Audytor ds. zarządzania procesem

09:45 – 10:15

Identy�kacja mechanizmów kontroli jakości w poszczególnych etapach procesu wytwórczego

Plan projektuPlan zarządza-nia jakością

Audytor ds. zarządzania procesem

10:15 – 11:00

Wery�kacja podejścia do planowania prac przez dostawcę Plan projektuDIP

Audytor ds. zarządzania procesem

11:00 – 11:45

Analiza procesu dokumentacji realizacji prac przez dostawcę Plan projektuDIP

Audytor ds. zarządzania procesem

11:45– 12:30

Wery�kacja kompetencji osób realizujących prace rozwojowe Plan projektuPlan zarządza-nia zasobami

Audytor ds. zarządzania procesem

12:30 – 13:00

Przerwa 13:00 – 14:00 Project Manager Test Manager Analityk ze stro-ny dostawcy

Określenie wymagań jakościowych i sposoby wery�kowania ich re-alizacji

Plan zarządza-nia jakością

Audytor ds. zarządzania procesem

14:00 – 15:00

Analiza procesu testowania aplikacji Plan projektuPlan testów

Audytor ds. zarządzania procesem

15:00 - 16:00

Wery�kacja planu reagowania na zgłoszone problemy z funkcjo-nowaniem oprogramowania

Plan zarządza-nia jakością

Audytor ds. zarządzania procesem

16:00 – 16:30

Spotka-nie zamy-kające ko-niec audy-tu dnia 1

Potwierdzenie wykonanego planu audytu, opis wykonywanych działań audytowych.Ustalenie spostrzeżeń z przeprowadzonego audytu. Opracowanie rekomendacji.

Audytor ds. architekturyAudytor ds. zarządzania procesem

16:30 – 17:00

Page 71: SDJ_03_2010_PL

03/201070

Praca w zespoleAudyt techniczny

www.sdjournal.org 71

sugeruje odpowiedź. Audytor powinien sa-modzielnie ocenić zgodność stanu rzeczy na podstawie udzielonych, pośrednich odpowie-dzi i własnych obserwacji. Nie wystarcza więc tylko pytać – ustne deklaracje należy zwery-fikować np. w przypadku pytania: jakie na-rzędzia służą do zarządzania testami, należy sprawdzić, czy rzeczywiście takie narzędzie jest wykorzystywane. Jak? Po prostu prosząc audytowanego o pokazanie owego narzędzia na stacji roboczej i wglądzie w jego zawartość (repozytorium testów).

• Przeprowadzenie audytu i zbieranie danych

Przeprowadzenie audytu musi opierać się na konkretnych podstawach, by uzyskane od-powiedzi pozwalały jednoznacznie i rzetel-nie ocenić dany problem. Audytor przepro-wadza wywiady i dokonuje obserwacji, po-sługując się opracowanymi przez siebie for-mularzami i listami kontrolnymi. Doku-menty te zawierają pytania kontrolne doty-czące każdego elementu istotnego dla doko-nania oceny – audytor przeprowadza wy-wiad, opierając się na owych pytaniach, w ra-zie konieczności już w trakcie dyskusji z au-dytowanym, dodając nowe pytania i uwa-gi oraz odnotowując uzyskane odpowiedzi. Wypełnione listy kontrolne służą następnie do opracowania wyników – czyli stwierdze-nia, które z elementów przedmiotu audytu są zgodne z założeniami, a gdzie wystąpiły niezgodności.

• Analiza i opracowanie wyników

Zebrane w trakcie audytu informacje nale-ży poddać analizie celem określenia, czy stan rzeczy odpowiada wymaganiom i założeniom

przyjętym dla oceny. Przykładowo, jeśli kry-terium oceny dla testów funkcjonalnych jest używanie narzędzia JIRA do zarządzania błę-dami, a w wyniku wywiadu okazało się, iż sto-sowane jest inne narzędzie, należy wykazać to jako niezgodność i umieścić w raporcie koń-cowym z audytu. Zalecenia dotyczące usta-leń z audytu opisane są szczegółowo w punk-cie 6.5.5 normy ISO/DIS 19011 Opracowanie ustaleń z audytu. Ustalenia z audytu dotyczą-cego zgodności i niezgodności oraz wspierają-ce je dowody powinny być zapisywane; każde spostrzeżenie, informacje zdobyte w oparciu o obserwację czy uzyskane w rozmowie z au-dytowanym powinny być zanotowane. Powód jest prosty – pamięć ludzka jest zawodna, i na-wet jeżeli audytor doskonale pamięta przebieg wywiadu dziś, może nie być w stanie przypo-mnieć go sobie jutro. A informacje z wywia-dów czy obserwacji są podstawą do opracowa-nia raportu końcowego z wyników.

W przypadku wykrytych niezgodności au-dytor powinien określić ich priorytet, czy-li stopień, w jakim wpływają na jakość da-nych prac. Priorytety mogą być określane na-stępująco:

• niski – niezgodność mająca znikomy wpływ na jakość procesu/produktu;

• normalny – niezgodność wpływająca ne-gatywnie na jakość procesu/produktu, lecz nie krytyczna;

• krytyczny – krytyczne uchybienie w zało-żeniach polityki jakości, rażący błąd etc.

Niezgodności o priorytecie niskim nieko-niecznie muszą być usuwane, lub przynajm-niej nie w pierwszej kolejności. Niezgodno-ści krytyczne powinny być wyeliminowane jak najszybciej – w ściśle określonym termi-

nie. Usunięcie niezgodności jest komuniko-wane klientowi i może być potwierdzone ko-lejnym audytem (np. jeżeli wykryto znaczną liczbę niezgodności krytycznych).

Analiza wyników audytu to nie tylko wy-kazanie zgodności i niezgodności – to rów-nież określenie zaleceń i rekomendacji służą-cych udoskonaleniu wadliwych procesów czy produktów. Rekomendacje nie są przymu-sem, a ich wdrożenie obligatoryjne dla orga-nizacji zlecającej audyt – mogą jednak w zna-czącym stopniu usprawnić działanie instytu-cji czy wybranego obszaru.

• Spotkanie zamykające i raport dla klienta

Spotkanie zamykające to ostatni punkt au-dytu. Może odbyć się dopiero po wykonaniu wszystkich zaplanowanych działań audyto-wych, przeanalizowaniu wyników i opraco-waniu raportu końcowego.

Niezgodności wykryte podczas audytu są komunikowane osobom odpowiedzialnym za audytowany obszar oraz zapisywane przez audytora wiodącego (na podstawie informacji uzyskanych od pozostałych audytorów) w od-powiednim dokumencie np. Protokole nie-zgodności.

Spotkanie zamykające prowadzone jest przez audytora wiodącego. Podczas tego spotkania omawiane są ustalenia, wnioski z audytu oraz mocne i słabe strony przedmiotu oceny.

Problemem, który może wystąpić w trakcie spotkania zamykającego, jest tzw. czynnik ludz-ki i reakcje na krytykę. Podczas wywiadów i ob-serwacji audytorzy są neutralni, skupiają się na zadawaniu pytań i wysłuchiwaniu odpowie-dzi bez oceniania czy wyrażania własnej opinii bądź krytycznych uwag – spotkanie zamykają-ce zmienia ten stan rzeczy: polega na jasnym,

Tabela 2. Lista kontrolna dla procesu testowania

L.p. Pytanie Referencja Komentarz

1 Kto zarządza procesem testowania? Plan testów 4. Odpowiedzialności i uprawnienia

Odp. Test manager - Uwaga – zgodne

2 Kto jest odpowiedzialny za przygotowa-nie projektu testów?

Plan testów 4. Odpowiedzialności i uprawnienia

Odp. Odpowiedzialność - Test manager Wykonanie – zespół QAUwaga – zgodne

3 Na jakiej podstawie opracowuje się sce-nariusze i przypadki testowe?

Plan testów 6. Podstawy testów Odp. Specy�kacja projektowa.Uwaga – zgodne lecz brak odniesienia do danych testowych (rekomendacja)

4 Gdzie ewidencjonowane są scenariusze i przypadki testowe?

Plan testów 8. Narzędzia Odp. TestLinkUwaga – zgodne

5 Kto odpowiada za aktualizację i utrzy-mywanie projektu testów?

Plan testów 4. Odpowiedzialności i uprawnienia

Odp. Tester odpowiedzialny za utworzenie Uwaga – zgodne

6 Jak wykonywane są testy? Plan testów 5. Podejście do testówDIP

Odp. Technika black box, na postawie scenariuszyUwaga – zgodne

7 W jaki sposób raportowane są błędy w aplikacji?

Plan testów 9. Raportowanie Odp. Po zakończeniu cyklu testówUwaga – niezgodne z Plan testów 9. Raportowanie. W planie jest: „niezwłocznie po znalezieniu błędu”

8 Gdzie raportowane są błędy? Plan testów 8. NarzędziaPlan testów 9. Raportowanie

Odp. JIRAUwaga – zgodne.Brak integracji z TestLink (rekomendacja).

Page 72: SDJ_03_2010_PL

03/201072

Praca w zespole

dosłownym i popartym dowodami wykazaniu braków, niezgodności i usterek. Nie wszyscy po-trafią przyjąć krytykę bez emocji i prób udo-wadniania swoich racji. Zdarza się, iż audytowa-ny reaguje na wytknięte błędy agresją – dlatego szczególnie ważne jest zebranie i przedstawie-nie niepodważalnych dowodów, faktów wystą-pienia niezgodności. Audytor nie stawia wnio-sków na podstawie swoich przeczuć czy insynu-acji – lecz na postawie faktów, których audyto-wany nie podważy.

Audytor nie może obawiać się przekaza-nia swojej informacji zwrotnej – audyt to

nie kontrola, a proces z założenia mający na celu doskonalenie organizacji. Tu nie szu-ka się odpowiedzialnych, winnych i nie oce-nia – celem jest wskazanie miejsc, które wy-magają ulepszenia i dostarczenie obiektyw-nej informacji zwrotnej o skali i rodzaju nie-zgodności.

Spotkanie zamykające to również ustalenie koniecznych działań korygujących i określe-nie terminów ich wykonania. Niezgodności zidentyfikowane jako krytyczne lub ważne dla jakości procesu powinny zostać usunięte i pod-dane kontroli zespołu audytowego.

• Weryfikacja skuteczności działań kory-gujących

Spotkanie zamykające audyt skutkowało usta-leniem pewnych dalszych działań, które pole-gają na usunięciu zaistniałych niezgodności. Za wykonanie tych działań w ściśle określo-nym terminie odpowiedzialni są wyznacze-ni pracownicy (w tym przypadku dostawcy IT). Wyniki prac korygujących powinny być zakomunikowane zespołowi audytującemu i klientowi – a ich poprawność i kompletność zweryfikowana.

Audyt może być uważany za zakończony po usunięciu i potwierdzeniu poprawności wszystkich niezgodności, które zostały prze-kazane do korekty.

PodsumowanieAudyt to nie kontrola, a audytorzy nie stawia-ją sobie za cel numer jeden pognębienie od-pytywanego i udowodnienie mu, że źle wy-konuje swoją pracę. Dobrze rozumiany au-dyt, z wyraźnie postawionym celem i profe-sjonalnym przygotowaniem, pomoże udosko-nalić organizację i wyeliminować błędy, któ-rych być może ktoś z wewnątrz danej orga-nizacji nigdy by nie odkrył. Zaangażowanie zewnętrznej firmy audytowej daje pewność, że uzyskamy obiektywne i pozbawione pew-nych uprzedzeń spojrzenie na funkcjonowa-nie naszej organizacji. Wyniki, które da au-dyt, będą pozbawione przekłamań i rzetelnie oddadzą rzeczywisty stan rzeczy oraz praw-dziwe problemy i zagrożenia – a znając je, ła-twiej nam będzie je usunąć.

Audyt zewnętrzny może być też począt-kiem wdrażania własnych mechanizmów doskonalenia w organizacji, która ów audyt zleciła – bazując na już zdobytych doświad-czeniach i obserwacji działań zawodowych audytorów, firma może opracować i stoso-wać podobne, w mniejszym zakresie lub mniej sformalizowane, działania celem oce-ny innych niż poddane audytowi procesy czy produkty. I stopniowo udoskonalać całość or-ganizacji.

Pojęcia

• Audyt – usystematyzowany, niezależny i udokumentowany proces uzyskania dowodu z audytu i obiektywnej oceny w celu określenia, w jakim stopniu spełniono uzgodnione kryteria audytu;

• Audytor – osoba posiadająca kompetencje do przeprowadzenia audytu;• Audytor wyznaczony do kierowania audytem określany jest audytorem wiodącym;• Audytowany – organizacja, komórka, która jest audytowana;• Kryteria audytu – zestaw polityk, procedur lub wymagań określonych jako odniesienie;• Niezgodność – niespełnienie ustalonych wymagań;• Wnioski z audytu – wynik audytu, przedstawiony przez zespół audytujący w Raporcie z audytu.

W Sieci

• www.audyty.pl – strona na temat audytów. Opisuje m.in. audyty informatyczne;• www.audyt.net – strona zawiera praktyczne informacje dla audytorów wewnętrznych,

a także narzędzia i wzorce dokumentów audytowych;• www.isaca.org/cobit – o�cjalna strona ISACA, informacje na temat COBIT;• ww.itil-officialsite.com – o�cjalna strona ITIL;• http://www.ffiec.gov/ffiecinfobase/booklets/audit/audit_00a_roles_rRespons.html – role

i odpowiedzialności w audycie IT.

KAROLINA ZMITROWICZPracuje na stanowisku Analityka biznesowe-go w �rmie Pro�-Data. Karolina specjalizuje się obecnie w modelowaniu wymagań biznesowych dla ubezpieczeń. Wcześniej pracowała jako Ma-nager Quality Assurance w projektach informa-tycznych w sektorze �nansowo – bankowym.Pro�-Data to producent oprogramowania spe-cjalizujący się w budowie dedykowanych rozwią-zań informatycznych dla ubezpieczeń, bankowo-ści i administracji publicznej. Firma posiada cer-ty�kat jakości ISO 9001:2000 w zakresie produk-cji oprogramowania.Kontakt z autorem: [email protected]

Trochę psychologii...Perspektywa audytu może budzić w pracownikach dostawcy obawy i poczucie zagrożenia. Audyt nie jest kontrolą, nie polega na doszukiwaniu się błędów i niedociągnięć ze strony au-dytowanego – jednak uczestnicy audytu mogą odczuwać presję i stres, wynikające z poczu-cia, iż są oceniani. Często audyt odbierany jest jako próba podważenia kompetencji osób au-dytowanych lub brak zaufania do rzetelności audytowanego.To, czy ten problem zostanie rozwiązany, a audytowany nabierze zaufania do oceniających i poczuje się w miarę komfortowo, w dużej mierze zależy od samego zespołu audytorów. Od ich umiejętności i doświadczenia będzie zależało, czy uda im się nawiązać odpowiednie rela-cje i zbudować dobry model współpracy z audytowanymi. Profesjonalny audytor nie stosu-je technik, które polegają na przysłowiowym zapędzaniu przesłuchiwanego w kozi róg i łapaniu za słówka. Zadaniem audytora jest ocena stanu rzeczywistego, nie udowadnianie audytowa-nemu, że się myli. Wytyczne odnoszące się do kompetencji personelu przeprowadzającego audyt są przedmiotem punktu 7 normy ISO/DIS 19011 – Kompeten cje audytorów.Punkt ów mówi, że do prowadzenia audytów jakości należy zatrudniać personel umiejący je realizować nie tylko z merytorycznego punktu widzenia – tzn. posiadający odpowiednie kompetencje, znajomość branży, technik oceny dotyczących badania, sporządzania rapor-tów, kierowania, planowania i organizowania. Równie istotne są cechy osobiste charaktery-zujące audytora – umiejętność skutecznej komunikacji interpersonalnej, dyplomacji, pracy w zespole, asertywność i przede wszystkim etyczne postępowanie. Od predyspozycji i umie-jętności interpersonalnych audytorów w znaczącym stopniu zależy, czy proces audytu bę-dzie procesem interaktywnym i umożliwi osiągnięcie postawionych przed nim celów. Dla-czego? Ponieważ wystraszony i zestresowany audytowany nie jest skory do współpracy. Aby porozumiewać się skutecznie, należy używać jasnych sformułowań, aktywnie i uważnie słu-chać, przejąć odpowiedzialność za prowadzenie rozmowy, jeśli jest taka potrzeba, porządko-wać wypowiedzi chaotyczne, podtrzymywać sprzyjający klimat komunikacji.Bardzo ważną kwestią jest komunikacja – jasne poinformowanie audytowanych o celach i za-kresie audytu może skutecznie wyeliminować negatywne reakcje – opór, kwestionowanie audytu, złośliwość i agresja.

Page 73: SDJ_03_2010_PL

��������������������� ��������������������

Page 74: SDJ_03_2010_PL

03/201074

Efektywność pracyKlient, który wie czego chce

www.sdjournal.org 75

Czy nie nurtuje Cię, dlaczego czę-sto uważa się, że klient nie wie, czego chce od {programistów |

analityków | liderów projektów | teste-rów}? Z drugiej strony, dlaczego jest oczy-wiste, że programiści zawsze wiedzą, cze-go chcą od klienta? Z pewnością jesteśmy bardzo przywiązani do takich przekonań, zwłaszcza gdy wyrobiliśmy je sobie pod-czas szeregu spotkań z osobami nietech-nicznymi (oni naprawdę nie wiedzą, cze-go chcą!). Chyba jednak zgodzisz się z na-mi, że to jednostronne spojrzenie nie jest do końca sprawiedliwe. Przypatrzenie obu stronom komunikacji poszerzy naszą per-spektywę.

Potrzebuję...Gdy siedzisz naprzeciw klienta i nie wiesz, co próbuje Ci przekazać, to powinieneś so-bie uświadomić, że ma on jakąś potrze-bę (w zależności od człowieka może być to problem lub cel do osiągnięcia) i uwa-ża, że technologie informatyczne na pew-no mu w tym pomogą. Gdyby klient czegoś nie potrzebował, to nie spotykałby się z To-bą. Z tego względu prosimy Cię, abyś w trak-

cie czytania tego artykułu przyjął następują-ce założenia:

• Klient zawsze wie, czego chce – wie, że chce rozwiązać jakiś problem, osiągnąć jakiś cel, coś usprawnić;

• Klient nie zawsze wie, czego potrze-buje – ponieważ jest skupiony przede wszystkim na swoich procesach bizne-sowych;

• Klient często nie zdaje sobie sprawy z konsekwencji swoich oczekiwań – gdyż jest ekspertem w obrębie swojej działalności biznesowej, a nie w obsza-rze technologii informatycznych.

Jeśli zgodzisz się na chwilę myśleć w ten sposób, wspólnie poszukamy narzędzi, które ułatwią pracę z klientem.

Po drugiej stronie lustraCzy zastanawiałeś się kiedyś, jakby to było, gdybyś budząc się pewnego dnia, ze zdzi-wieniem zauważył, że przepadła gdzieś ta-jemniczo cała twoja, z trudem zbierana, wiedza informatyczna, wiedza o programo-waniu, o systemach, że cała Twoja umiejęt-ność pracy z komputerem sprowadza się do wciskania przycisku Power i czasem Reset? Jakby to było, gdybyś przeżył coś takiego?

Trwając w tej perspektywie, wytłumacz nam, że chciałbyś, abyśmy stworzyli opro-gramowanie, które będzie Twoim osobi-stym organizerem. Opisz swoje oczekiwa-

nia dotyczące tej aplikacji. Czy to łatwe za-danie, czy trudne? O jakich rzeczach bę-dziesz mówił z tego IT-agnostycznego punk-tu widzenia?Prawdopodobnie opowiesz o:

• swoich wyobrażeniach;• o tym, co widziałeś w aplikacjach po-

dobnego typu;• o tym, co ktoś Ci powiedział na temat

aplikacji tego typu;• o swoich problemach z papierowymi

organizerami;• o swoich obawach związanych z przej-

ściem z organizera papierowego na elektroniczny.

Chociaż większość klientów jest pewnie o wiele bardziej wyedukowana w kontak-cie z komputerem, to jednak to krótkie do-świadczenie pozwoliło Ci spojrzeć na świat oczami stereotypowego klienta. Jakbyś się poczuł, gdybyśmy powiedzieli Ci, że nie wiesz, czego chcesz? Pewnie uważałbyś, że nie mamy racji, przecież Ty wiesz, cze-go chcesz: chcesz mieć elektroniczny orga-nizer i starasz się to wytłumaczyć najlepiej jak potrafisz.

Po pierwszej stronie lustraWróćmy teraz do znajomej perspektywy programisty. Gdybyś otrzymał zadanie do zaimplementowania: Stworzyć elektroniczny organizer, to chciałbyś dowiedzieć się czegoś więcej: jak ma wyglądać? Jakie funkcjonal-ności ma udostępniać? Jak konkretnie ma działać? To jest sedno sprawy. Aby stwo-rzyć oprogramowanie, potrzebujesz bar-dzo konkretnych i szczegółowych informa-cji, których klient Ci nie dostarcza. Dlacze-go? Bo nie wie, że powinien. Opisuje swo-je oczekiwania tak dobrze jak tylko potrafi.

Klient, który wie czego chceJeśli zdarza Ci się spotykać z osobami nietechnicznymi i musisz pozyskiwać od nich konkretne informacje, aby móc sprawnie implementować swoje zadania, to ten artykuł jest dla Ciebie. Skupiliśmy się w nim na technice zadawania pytań, która ogólne informacje pomaga przekuć na mierzalne konkrety.

Dowiesz się:• Jak uzyskiwać potrzebne Ci konkrety z ogól-

nych informacji;• Jak omijać impas w rozmowie.

Powinieneś wiedzieć:• Tylko to, co już wiesz.

Poziom trudności

Czyli sztuka zadawania pytań

Page 75: SDJ_03_2010_PL

03/201074

Efektywność pracyKlient, który wie czego chce

www.sdjournal.org 75

Dziwi go, że chciałbyś wiedzieć, czy zapiski w organizerze powinny być trwale przecho-wywane i jak długo, ile osób ma mieć dostęp do organizera, czy organizer ma być używa-ny na komputerze PC, notebooku, netbo-oku, czy telefonie itd. Odpowiedzi na nie-które pytania są dla klienta oczywiste. Czy zapiski mają być przechowywane? Jasne, że tak! Jak długo? Na zawsze! Inne pytania bu-dzą u klienta zdziwienie, gdyż nie wiedział, że są istotne podczas tworzenia oprogramo-wania. Do momentu, gdy nie wyewoluuje w ludziach umiejętność telepatii, zadawa-nie pytań jest najlepszym sposobem na po-znanie myśli innych osób.

Potęga pytańCzęstym zarzutem powtarzanym przez użytkowników w kierunku programistów jest to, że programiści na wszystkie prośby o dodanie lub zmianę funkcjonalności ma-ją tę samą odpowiedź: Nie da się! Użytkow-nicy skarżą się, że o cokolwiek by nie prosili, pierwsze, co słyszą, to odmowa. Łatwo mo-żesz odczuć frustrację użytkowników: zwy-czajnie wyobraź sobie, że przy każdej proś-bie o podwyżkę słyszysz: Nie da się!

Zamiast takiej twardej odpowiedzi moż-na zadać kilka pytań:

• Jak ważna jest ta funkcjonalność?• O ile można przesunąć termin i zwięk-

szyć budżet projektu, aby ją zaimple-mentować?

• Jak do tej pory radziłeś sobie z tym pro-blemem?

Cała moc odpowiedniego pytania pole-ga na tym, że nie mobilizuje użytkowni-ka do konfrontacji, lecz do zastanowienia się nad swoimi oczekiwaniami. Nie chodzi o to, by ktoś kogoś przechytrzył, lecz o to, by wspólnie poszukiwać najlepszych roz-wiązań. Zadawanie pytań pozwala skupić się na problemie zamiast na słownych prze-pychankach.

W stronę konkretówGdybyśmy w codziennych rozmowach chcieli operować na poziomie absolutnych i mierzalnych konkretów, to doba musiała-by trwać o wiele dłużej niż 24 godziny. Do-datkowo w życiu codziennym niedopowie-dzenia mają swój urok. Tracą go jednak, gdy mowa o systemach informatycznych, któ-re trzeba wykonać w ograniczonym czasie i przy ograniczonym budżecie.

W trakcie rozmowy powinniśmy być świadomi, że brak konkretów wynika ze sposobu komunikowania się rozmówcy, a nie z jego złej woli. Powinniśmy być rów-nież świadomi nagminnie występujących zakłóceń:

• usunięć, np.: Po wciśnięciu przycisku Generuj system od razu generuje ra-port – co to znaczy „od razu”? Jak dłu-go ma trwać generowanie raportu? Po-między jakimi zdarzeniami mierzony jest ten czas? Jak konkretnie ma wyglądać ra-port? Jakie informacje ma zawierać? Kto może wcisnąć przycisk Generuj? W jaki sposób system dostarcza raport?

• uogólnień, np.: Trzeba odbyć dziesięć spotkań – kto tak powiedział? Z czego wy-nika ta konieczność? Co by się stało, gdy-byśmy odbyli osiem lub dziewięć spotkań?

• zniekształceń, np.: Myślałem, że tak to ma działać – co sprawiło, że tak pomyśla-łeś? Czy jakieś moje zachowanie? Co kon-kretnie w moim zachowaniu sprawiło, że tak pomyślałeś? W jaki sposób to konkret-

ne zachowanie sprawiło, że wyciągnąłeś wnioski na temat działania systemu?

Zadając odpowiednie pytania, możemy stopniowo wydobywać cenne informa-cje z tego, co mówi klient lub użytkow-nik. Skąd zatem wiadomo, kiedy przestać pytać? Wtedy, gdy informacje są komplet-ne i jednoznaczne. Gdy na ich podstawie można przystąpić do implementacji. Oczy-wiście podczas programowania mogą po-jawić się dodatkowe pytania. Może oka-zać się, że wymagania nie zostały zebrane w wystarczająco konkretnej formie. W ta-kich przypadkach znów sięgamy po narzę-dzie, jakim jest Sztuka Zadawania Pytań.

Warto wspomnieć również o jednej waż-nej rzeczy: zanim zaczniesz zadawać pyta-

UsunięciaPowstają, gdy rozmówca pomija część informacji niezbędnych do prawidłowego zrozumie-nia przekazu.

• Interfejs ma być intuicyjny – po czym poznasz, że interfejs jest intuicyjny? Jakie są objawy in-tuicyjnego interfejsu?

• Organizer nie działa tak, jak trzeba – co konkretnie nie działa? Jakie konkretne czynności wykonujesz? Jakiego efektu się spodziewałeś? Jaki efekt otrzymałeś?

• Sortowanie ma być szybkie – jak długo maksymalnie może trwać sortowanie? Pomiędzy którymi momentami należy przeprowadzić pomiar?

UogólnieniaPowstają, gdy na podstawie niewielkiej liczby przesłanek rozmówca przedstawia daleko idą-ce ogólne wnioski.

• Tego się nie da zaimplementować – na jakiej podstawie tak twierdzisz? Co konkretnie spra-wia, że się nie da?

• Zawsze trzeba pisać testy jednostkowe – kto tak powiedział? Co się stanie, jeśli w przypad-ku X lub Y nie napiszę testu jednostkowego? Czy istnieją okoliczności, w których można nie pi-sać testu jednostkowego?

• Klienci nie wiedzą, czego chcą – którzy klienci? Po czym poznajesz, że nie wiedzą? Po czym poznasz, że już wiedzą?

ZniekształceniaPowstają wówczas, gdy rozmówca przekazuje informacje, których znaczenie jest niepo-prawne.

• Widzę, że się nie rozumiemy – co dokładnie widzisz? W jaki sposób to, co widzisz, oznacza, że się nie rozumiemy?

• Zbieranie wymagań jest zbędne – które konkretnie elementy procesu zbierania wymagań uważasz za zbędne? Na jakiej podstawie? Co należałoby w nich zmienić, aby były użyteczne?

• Przez Ciebie się spóźniliśmy – co takiego zrobiłem? W jaki sposób to, co zrobiłem, wpłynie na to, czy się spóźnimy, czy nie? Czy wystąpiły jeszcze jakieś inne czynniki opóźniające pracę? Który z nich był dominujący?

Bądź czujnyPoniżej podajemy przykłady sformułowań, których pojawienie się w rozmowie na temat wy-magań powinno wzbudzać Twoją czujność. Sformułowania te sygnalizują, że właśnie umyka-ją Ci istotne informacje na temat oczekiwań klienta.

• dużo, mało, lepiej, szybciej, intuicyjny, ładny, prosty, poprawić, zależy od, kilka, idealnie, dobrze, zwiększać, zmniejszać, optymalizować, gdy to konieczne, kiedy trzeba, pomię-dzy, taki jak, tak samo jak, wspierać, efektywny, drogi, tani.

Page 76: SDJ_03_2010_PL

03/201076

Efektywność pracy

nia, poproś rozmówcę o zgodę. Tego typu szczegółowe pytania sprawiają, że rozmów-ca musi dokładnie zastanowić się nad od-powiedzią, musi zastanowić się nad moty-wacją swoich oczekiwań. Czasem użytkow-nik zdaje sobie sprawę, że nie do końca prze-myślał swoje oczekiwania lub że są one nie-realistyczne czy niepotrzebne. Nie jest to zbyt miłe uczucie. Zadbaj zatem o kom-fort rozmowy i poproś o zgodę na zadawa-nie pytań.

Ominąć impasZ pewnością zauważyłeś, że przedstawiony sposób na wydobywanie konkretów z infor-macji przekazywanych przez rozmówcę po-zwala tylko na poruszanie się w jedną stro-nę: od ogółu do szczegółu. Co jednak, gdy klient rzuca na stół poważny argument na dużym poziomie konkretności, np.: Chcę, żeby mój organizer został zaimplemento-wany w technologii JMS! Po krótkim szoku zaczniesz zastanawiać się, jak z tej sytuacji wybrnąć. Mógłbyś zacząć wyliczać powo-dy, dlaczego w tym konkretnym rozwiąza-niu JMS nie jest najlepszym rozwiązaniem. Z drugiej strony pamiętasz, że bitwa na ar-gumenty w sytuacji gdy strony są na zupeł-nie innych poziomach merytorycznych, do-prowadzi donikąd. Co zrobić w takiej sytu-acji? Oczywiście zadać pytanie!

Pytania uszczegóławiające, które oma-wialiśmy do tej pory, noszą nazwę pytań

w dół (ang. chunk down). Istnieją również pytania uogólniające, które prowadzą w od-wrotnym kierunku. Nazywane są one py-taniami w górę (ang. chunk up). Pytania w dół kierują uwagę w stronę konkretów, np.: Co dokładnie? Po czym poznasz? Jak zmierzysz? Pytania w górę pozwalają od-kryć przyczyny i potrzeby: Dlaczego? Co jest w tym ważnego? Finezja w posługiwa-niu się pytaniami w górę i w dół (albo tech-niką chunk up / chunk down) polega na tym, że gdy pojawia się kwestia sporna na temat szczegółów, to szukamy przyczyny, a następnie innego szczegółowego sposobu wynikającego z tej samej przyczyny. Przy-kładowy dialog z klientem mógłby przebie-gać następująco:

• Klient: Chcę, żeby mój organizer został zaimplementowany w technologii JMS!

• Programista [pytanie w górę]: Dlaczego takie ważne jest, aby organizer był wyko-nany w technologii JMS?

• Klient: Ponieważ słyszałem, że programy w tym napisane są bardzo bezpieczne

• Programista [szuka rzeczy ważnej dla klienta]: Rozumiem, że bezpieczeństwo oprogramowania jest dla Pana bardzo ważne?

• Klient: Tak.• Programista [pytanie w dół]: Po czym

konkretnie poznaje Pan, że oprogramowa-nie jest bezpieczne?

• Klient: Po tym, że trzeba się zalogować, a w razie awarii komputera nie są traco-ne żadne dane.

• Programista [proponuje inny sposób na zapewnienie bezpieczeństwa]: W tech-nologii, którą rekomendujemy, stosujemy specjalny bezpieczny moduł logowania. Jednocześnie możemy wbudować w orga-nizer mechanizm kopii bezpieczeństwa. Polega on na tym, że przy każdym uru-chomieniu wszystkie ważne dane są ko-piowane na specjalny serwer bezpieczeń-stwa.

• Klient: Ok, podoba mi się to rozwiązanie.

Zauważ, że programista za pomocą py-tań w górę i w dół trafnie zidentyfikował rzecz ważną dla klienta. Jest nią bezpie-czeństwo oprogramowania. Zauważ rów-nież, że proponując inne rozwiązanie niż technologia JMS, buduje swoją wypo-wiedź odkrytej ważnej rzeczy. Kilkukrot-nie podkreśla, że nowe rozwiązanie bę-dzie bezpieczne. W ten sposób udaje mu się ominąć impas w rozmowie. To jest wła-śnie istota rzeczy: programista omija im-pas zamiast go przełamywać. Nie upiera się przy swoim rozwiązaniu, lub co gorsze nie krytykuje rozwiązania klienta. Świa-domie stara się odkryć potrzeby i rzeczy ważne. Poszukuje przyczyn, które spra-wiły, że klient obstaje przy jakimś szcze-gółowym rozwiązaniu. Gdy już zidentyfi-kuje i upewni się co do potrzeb, wtedy ma o wiele szersze pole do działania i może zaproponować najlepsze jego zdaniem roz-wiązanie. W przedstawionym przypadku klient wiedział, czego chciał. Chciał bez-pieczeństwa. Nie wiedział tylko, czego konkretnie potrzebował.

Sytuacje podobne do omówionych w ar-tykule dzieją się każdego dnia. W setkach firm, na tysiącu spotkań miliony sfrustro-wanych uczestników wciąż na nowo zadają sobie te same pytania: Co on mówi? Dlacze-go mnie nie słucha? O co mu chodzi? Dlaczego on nic nie rozumie? Gdy rozmawiasz z kimś, to od Ciebie zależy przynajmniej 50% efek-tywnej komunikacji. Jak zamierzasz wyko-rzystać swoje 50%?

MICHAŁ BARTYZEL, MARIUSZ SIERACZKIEWICZTrenerzy i konsultanci w �rmie BNS IT. Badają i rozwijają metody psychologii programowania, pomagające programistom lepiej wykonywać ich pracę. Na co dzień Autorzy zajmują się zwiększa-niem efektywności programistów poprzez szkole-nia, warsztaty oraz coaching i trening. Kontakt z autorami: [email protected], [email protected] Rysunek 1. Pytania w górę i w dół

Page 77: SDJ_03_2010_PL
Page 78: SDJ_03_2010_PL

KLUB PROOferta skierowana dla fi rm

Jeżeli Twoja fi rma jest prenumeratorem Software Developer’s Journal za comiesięczną dopłatą 50 PLN +VAT możesz dodatkowo otrzymać reklamę.

Wystarczy tylko, aby profi l Twojej fi rmy pokrywał się z naszym magazynem.

Wyślij do nas: logo fi rmy, dane kontaktowe i informację o fi rmie

Reklama przez 12 kolejnych numerów tylko za 600 PLN +VAT.

Jeżeli nie posiadasz jeszcze prenumeraty możesz ją zamówić w atrakcyjnej cenie.

Dla nowych prenumeratorów specjalna oferta – 690 PLN.

Future ProcessingFuture Processing to dynamiczna fi rma technolo-giczna działająca na globalnym rynku oprogramo-wania. Jesteśmy zespołem wysokiej klasy specja-listów posiadających wiedzę i doświadczenie nie-zbędne do realizacji ambitnych projektów informa-tycznych. Jeśli programowanie to Twoja pasja do-łącz do nas! (możliwość pracy zdalnej).

http://www.future-processing.pl

Skontaktuj się z nami:tel. +48 22 877 20 80fax: +48 22 877 20 [email protected]

Kei.plKei.pl działa na rynku usług hostingowych od 2000 roku. Do naszych zadowolonych Klientów z du-mą możemy zaliczyć wiele przedsiębiorstw sekto-ra MSP, instytucji oraz osób prywatnych. W ofer-cie Kei.pl znajdują się pakiety hostingowe, a także usługi dla wymagających Użytkowników – platfor-my e-Biznes oraz serwery fi zyczne.

http://www.kei.pl

Opera SoftwareOpera Software’s vision is to deliver the best In-ternet experience on any device. We are offering browser for PC/desktops and embedded pro-ducts that operates across devices, platforms and operating systems. Our browser can deliver a faster, more stable and fl exible Internet expe-rience than its competitors.

http://www.opera.com

Architektury systemów ITTwórca frameworków JUVE i serwera aplikacji AVAX oferuje usługi, doradztwo, rozwiązania do tworzenia nowoczesnych, dużych systemów i roz-wiązań informatycznych/internetowych, integrujące architektury ery post-J2EE/.NET, wykorzystujące MDD/MDA dla dziedzin – bankowość, telekomuni-kacja, handel, e-commerce, ERP/Workfl ow/CRM, rozwiązania internetowe, portalowe.www.mpsystem.com [email protected]

WSISiZ w WarszawieINFORMATYKA ZARZĄDZANIEstudia stopnia I i II (stacjonarne i niestacjonar-ne) specjalności: inżynierskie, magisterskie i licencjackie. Szczegółowe plany studiów, opi-sy poszczególnych specjalności – zapraszamy na stronę uczelni.

http://www.wit.edu.pl

Playsoft Playsoft jako lider portowania aplikacji na plat-formy mobilne wciąż powiększa bazę swo-ich klientów: EA Mobile, Sega, THQ, Kona-mi. W ramach rozszerzania swojej działalno-ści, poszukujemy doświadczonego programi-sty, który byłby odpowiedzialny za tworzenie aplikacji na platformy Iphone, Windows Mobi-le, Android.http://www.playsoft.fr

Page 79: SDJ_03_2010_PL

KLUB PRO

TTS Company Sp. z o.o.Sprzedaż i dystrybucja oprogramowania komputero-wego. Import programów na zamówienie. Ponad 200 producentów w standardowej ofercie. Chcesz kupić oprogramowanie i nie możesz znaleźć polskiego do-stawcy? Skontaktuj się z nami – sprowadzimy nawet pojedyncze licencje.

http://www.OprogramowanieKomputerowe.pl

IT SOLUTIONSWdrożenia i szkolenia z zakresu:• SQL Server• SharePoint Services• MS Project / Server• Tworzenie aplikacji w technologii .NET

http://[email protected]

IT SOLUTIONS

Softline rozwiązania mobilneWiodący producent systemów mobilnych, do-stawca aplikacji użytkowych dla biznesu (Sym-bian OS, Windows Mobile, J2ME ) zaprasza do współpracy. Zostań naszym partnerem. Dołącz do zespołu.

http://www.softline.com.pl

INFOTEX SP.J Śmietanowski i Wsp.Dystrybutor XP Unlimited – Serwer Terminali dla Windows XP i VISTA. Program umożliwia łącze-nie się z dowolnego klienta Windows, Linux z wy-korzystaniem protokołu RDP. Cena wersji Classic dla 5 użytkowników - 165€, dla nieograniczonej liczby - 235€. Ponadto oferujemy opiekę serwiso-wą i aplikacje internetowe na zamówienie.

http://www.infotex.com.pl

Proximetry Poland Sp. z o.o.Proximetry Poland Sp. z o.o. jest polskim od-działem amerykańskiej firmy Proximetry Inc. – dostawcy systemów zarządzania sieciami bez-przewodowymi opartymi na technologiach WiFi i WiMAX. Naszą misją jest dostarczenie klien-tom rozwiązań poprawiających jakość usług (QoS) dostarczanych drogą radiową. Dołącz do najlepszych i zostań członkiem naszej ekipy!

http://www.proximetry.com

Systemy bankowe, ISOF HEUTHES istnieje na rynku od 1989 r. Obok systemów informatycznych dla banków, ofe-ruje nowoczesne oprogramowanie do obsługi fi rm. System ISOF jest udostępniany klientom w trybie SaaS lub licencji. Pracuje na platfor-mie Linux i zawiera m.in. takie moduły jak CRM, DMS, Magazyn, Sprzedaż, Logistyka oraz Rachunkowość. http://www.isof.pl

Page 80: SDJ_03_2010_PL

v

Roczna prenumerata

tylko256Software Developer’s Journal (poprzednio Software 2.0) jest miesięcznikiem głównie dla programistów, którzy li-czą, że dostarczymy im gotowe rozwiązania, oszczędza-jąc im czasu i pracy. Jesteśmy czytani przez tych, któ-rzy chcą być na bieżąco informowani o najnowszych osią-gnięciach w dziedzinie IT i nie chcą, żeby jakiekolwiek istotne wydarzenia umknęły ich uwadze. Aby zadowolić naszych czytelników, prezentujemy zarówno najnowsze rozwiązania, jaki starsze, sprawdzone technologie.

,-,-Obsługa prenumeraty Obsługa prenumeraty

UWAGA!

Zmiana danych kontaktowych

Roczna prenumerata

Software Developer’s Journal (poprzednio Software 2.0) jest miesięcznikiem głównie dla programistów, którzy li-czą, że dostarczymy im gotowe rozwiązania, oszczędza-

,-,-UWAGA!

Roczna prenumerata

Software Developer’s Journal (poprzednio Software 2.0) jest miesięcznikiem głównie dla programistów, którzy li-

Software Wydawnictwo1. Telefon(022) 427 37 592. Fax(022) 244 24 593. [email protected]. AdresSoftware Wydawnictwoul. Bokserska 102-682 Warszawa

EuroPress1. Telefon+48 22 877 20 802. Fax22 877 20 703. [email protected]. AdresEuroPress Polska Sp. z o.o.ul. Jana Kazimierza 46/5401-248 Warszawa

Page 81: SDJ_03_2010_PL

v

Prenumerujesz – zyskujeszl oszczędność pieniędzy

szybka dostawa prezentybezpieczna płatność

on–line

TytułIlość

nume-rów

Ilość zama-

wianych prenu-merat

Od numeru pisma

lub mie-siąca

Cena

Software Developer’s Journal (1 płyta DVD) – dawniej Software 2.0

12 256PLN

Zadzwoń+48 22 877 20 80

lubzamów

mailowo!

pieniędzy l szybka dostawa l prezentyl bezpieczna płatność on–line

+48 22 877 20 80

Page 82: SDJ_03_2010_PL

Felieton��������

���������

����������������������������������������������������������������������������������������������������������������������������������������������������������������

������������������������������������������������������������������������������������

��������������������������������������������������������������������������������������������������������������������������������������� ������ ������������� ������� ����� ����� ��������������� ������������������������������������������������������������������������������������������������������������������������������������ ������������ ��������� ������� �������� ������ ���� ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� ������� ������������ ������� ������� ���� �� ���

�������������������������������������������� ��������� ���������������������������������������������������������������������������������������������������������������������������������������������� ��������������������������������������������������������������� ��������� ��������� ��������� �������� �������� ������ ��������������� ������������ ������ �������� ���������������� ������������������� ������ �������� �������� ��������� ��������� ������� ���������������������������������������������������������������������������������������������������������������������������� ���������������������� �������������� �� ������������� ������������� ����������� ����������������� �������������������������������������������������������������������������������������������������������

�������������������������������������������������������������������������� �� ������������������������������ ���� �������������� ������������������� ������������� ���������� ���� ����������������������������������������������������������������������������������������

������������������������������������������������������������������������������������������������������������������������������������� ����������� ������� ������������� �� ����������� ������ ������������������� �� ��������������������������������������������������� ���� ��������� ���� �������� �������������� �� �������� ������� ���� ������������ ��������� ���������� ���������� ������ ������������ ��������� ��������������������� ��������� ����������� ������� ��������������� ����������������������������������������������������������������� ������� �������������� ������������ ��������

������������������������������������������������������������������������������������������������������������������������������������� ����������� ��� ���������� �������� ���� ������������ ������������������� ��������������������������������������������� ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

����������������������������������������������������������������������������������������������������������������������������������������������������������������������

�����������

�������������������� ��������������������

03/2010

Page 83: SDJ_03_2010_PL

Jan,Dyrektor

Finansowy

Robert,Dyrektor Techniczny

Kasia,Data Center Manager

Bardzo realna wydajność! Bardzo realna wydajność!

Rzeczywista wydajność wirtualnej infrastruktury. Już teraz możesz mieć spójną, wirtualną infrastrukturę IT, która obejmuje zarówno duże centra danych, jak i stacje robocze użytkowników. Pytanie jest tylko takie, czy słowo „mieć” jest tutaj najlepsze, w końcuto wszystko jest wirtualne… Wykorzystując Windows Server 2008 R2 z wbudowaną technologią Hyper-V, możesz łatwo pozbyćsię wysokich kosztów zakupu dodatkowego oprogramowania. Dodaj do tego nieograniczone możliwości wirtualizacji SQL Server 2008 Enterprise, a pozbędziesz się szaf z serwerami, które i tak nigdy w pełni nie wykorzystywały swoich mocy obliczeniowych. Centralnym zarządzaniem całą infrastrukturą w przedsiębiorstwie, aż do poziomu pojedynczych aplikacji, zajmie się System Center.Co to wszystko oznacza? Posiadasz elastyczną, dynamicznie zarządzaną, wirtualną infrastrukturę IT, za pomocą której zmaksymalizujesz korzyści ze swoich inwestycji, obniżysz koszty i zapewnisz stabilność swojego biznesu.

Aby dowiedzieć się, jak zwiększyć wydajność infrastruktury IT za pomocą wirtualizacji serwerów, odwiedź stronę www.itwspierabiznes.pl

3 SDJ MB 203x297.indd 13 SDJ MB 203x297.indd 1 1/14/10 4:59 PM1/14/10 4:59 PM

Page 84: SDJ_03_2010_PL