You are on page 1of 84

Jarek,

Dyrektor Michaâ,
Compliance Administrator
Systemu

Dostępna, stabilna i skalowalna infrastruktura IT.


Po prostu większe możliwości i kropka.

Czy przechodziă na najnowsze wersje serwerów wâaŒnie teraz? Zdecydowanie TAK! JeŰeli szukasz rozwiāzaĽ z wbudowanā moŰliwoŒciā
wirtualizacji, o niskim poborze energii elektrycznej, pozwalajācych na przenoszenie maszyn wirtualnych bez przerwy w Œwiadczeniu
dostarczanych usâug – to odpowiedziā jest Windows Server 2008 R2. Dla niego to moŰliwe. A moŰe chcesz mieă proste narzĕdzia
do centralnego zarzādzania infrastrukturā IT, zapewniajāce jednoczeŒnie bezpieczeĽstwo dla wszystkich, którzy korzystajā ze zdalnego
dostĕpu do zasobów Twojego przedsiĕbiorstwa? Kto by nie chciaâ? Wystarczy skorzystaă z najnowszej wersji System Center oraz Forefront,
a dostaniesz to. Ufff… Caâkiem duŰo jak na tak niewielkie zmiany – prawda?

Aby dowiedzieă siĕ wiĕcej o tym, jak przejŒcie na najnowsze wersje serwerów moŰe przyczyniă siĕ do wzrostu wydajnoŒci w Twoim
przedsiĕbiorstwie, odwiedŮ stronĕ www.itwspierabiznes.pl

02-03-reklama.indd 2 2010-03-05, 18:52:59

SDJ MB 203x297.indd 1 11/25/09 10:57 AM


04/2010 (184)

SPIS TREŚCI
11 Opis DVD JĘZYKI PROGRAMOWANIA
34 The Go programming language
Bartosz Przybylski
BIBLIOTEKA MIESIĄCA Język programowania Go jest językiem młodym, gdyż jego pre-
mierę światową datuje się na 10 listopada 2009 roku. Właśnie
6 Google Protocol Buffers – Wydajna i elastyczna wtedy na blogu Google Code została zmieszczona informacja na
alternatywa dla XML temat upublicznienia tego języka na licencji BSDL.
Rafał Kocisz
XML to potężne i przenośne rozwiązanie, aczkolwiek czasami wy-
daje się być nieco... przerośnięte. Jeśli szukasz prostszej, bardziej
wydajnej, a zarazem elastycznej alternatywy, to koniecznie prze-
czytaj poniższy artykuł. Biblioteka Google Protocol Buffers jest
SZTUCZNA INTELIGENCJA
prawdopodobnie właśnie tym czego szukasz! 40 Sztuczna inteligencja do gier logicznych –
Jak nauczyć komputer gry w szachy
Mateusz Bożykowski
Chciałeś kiedyś napisać sztuczną inteligencję, która umiałaby

KLUB TECHNICZNY grać w szachy, warcaby lub inną, podobną grę? To wcale nie mu-
si być trudne. Dzięki temu artykułowi nauczysz się jak ją stwo-
12 Debugowanie aplikacji – Część 1: Podstawy rzyć, a dodatkowo otrzymasz kod gotowy do wykorzystania
debugowania w Twojej własnej aplikacji oraz przykładowy program grający
Jeanette Stallons w warcaby.
Debugowanie to proces wyszukiwania i usuwania błędów lub
problemów występujących w aplikacji. Zadanie to jest często
trudne i frustrujące, ale można je znacznie uprościć, korzystając
z debugera – programu narzędziowego, który umożliwia moni-
torowanie i kontrolowanie wykonywania aplikacji krok po kroku
WARSZTATY
oraz monitorowanie i modyfikowanie wartości zmiennych. 50 Postprocessing w OpenGL – Dostawca usługi
zarządzania komunikatami dla Websphere AS 7
Bartłomiej Filipek
Ogrom mocy obliczeniowej, którą mamy dostępną we współcze-
PROGRAMOWANIE C++ snych kartach graficznych (GPU), sprawia, że metody renderingu,
które kiedyś były bardzo czasochłonne i zajmowały kilka godzin,
16 Wizytator – Upraszczanie zależności teraz z powodzeniem mogą być stosowane w interaktywnych
przy modyfikacji interfejsu klas aplikacjach graficznych, jak gry komputerowe. Jedną z takich me-
Robert Nowak tod jest postprocessing obrazów.
Operacje dla obiektów w hierarchii klas często implementuje-
my, wykorzystując funkcje wirtualne. Gdy liczba takich metod 56 Tworzenie galerii zdjęć przy pomocy ASP.NET
rośnie, klasy mają trudną do określenia odpowiedzialność, kod MVC, cz. I – Programowanie części galerii zdjęć
staje się mało przejrzysty. Przedstawiona technika rozwiązuje przeznaczonej dla użytkownika z wykorzystaniem
ten problem. możliwości platformy .NET Framework oraz
wzorca projektowego MVC.
Marcin Jamro
W pierwszej części artykułu została przedstawiona „krok po kro-
PROGRAMOWANIE JAVA ku” budowa fragmentu aplikacji przeznaczonego dla użytkowni-
ka, tzn. umożliwiającego wyświetlanie listy albumów, zdjęć nale-
20 Java EE 6 – Nowa era aplikacji serverowych żących do konkretnego albumu, a także pojedynczego zdjęcia
Piotr Kochański oraz dodawanie komentarzy. Ponadto, krótko opisany został fra-
Artykuł przedstawia możliwości najnowszej, szóstej, wersji tech- mework ASP.NET MVC, a także sposoby tworzenia aplikacji inter-
nologii Java Enterprise Edition (Java EE). Wersja ta wprowadziła netowych korzystających z niego.
wiele istotnych modyfikacji, dzięki którym Java EE zostało znacz-
nie unowocześnione. Poprawiono funkcjonalność technologii,
kładąc jednocześnie duży nacisk na łatwość użycia, elastyczność.

4 04/2010
APLIKACJE BIZNESOWE
Miesięcznik Software Developer’s Journal (12 numerów w roku)
64 Modelowanie procesów biznesowych – jest wydawany przez Software Press Sp. z o.o. SK
Praktyczne wykorzystanie BPMN
Marcin Sałaciński
Redaktor naczelny:
Analiza i opis procesów biznesowych jest chlebem powszednim Łukasz Łopuszański lukasz.lopuszanski@software.com.pl
analityka IT. Rezultaty prac, diagramy i opisy można przedsta-
wiać w dowolny zrozumiały sposób, jednak najlepszym podej- Projekt okładki: Agnieszka Marchocka
ściem jest wykorzystanie w tym celu standardów, jak BPMN (Bu-
siness Process Modeling Notation), notacja zrozumiała dla więk- Skład i łamanie:
Tomasz Kostro www.studiopoligraficzne.com
szości odbiorców prac analitycznych. W artykule przedstawiam
najważniejsze pojęcia, elementy i praktyczny przykład procesu
biznesowego. Kierownik produkcji:
Andrzej Kuca andrzej.kuca@software.com.pl

Dział produkcji i kolportażu:


Alina Stebakow alina.stebakow@software.com.pl

PROGRAMOWANIE GIER
74 Tworzenie gry Flash w pigułce
Robert Podgórski, Bartek Indycki, Michał Wróblewski Nakład: 6 000 egz.
Programowanie gier to jedne z ciekawszych rodzajów projek-
tów programistycznych. Dzięki platformie Flash’owej stworze- Adres korespondencyjny:
Software Press Sp. z o.o. SK,
nie ciekawej (i przynoszącej niezłe zyski) gry nie jest większym ul. Bokserska 1, 02-682 Warszawa, Polska
problemem. tel. +48 22 427 36 91, fax +48 22 224 24 59
www.sdjournal.org cooperation@software.com.pl

EFEKTYWNOŚC PRACY Dział reklamy: adv@software.com.pl

78 Ile to zajmie? – Rzecz o szacowaniu zadań Obsługa prenumeraty: EuroPress Polska


software@europress.pl
programistycznych
Michał Bartyzel, Mariusz Sieraczkiewicz Dołączoną do magazynu płytę CD przetestowano programem
W artykule skoncentrowaliśmy się na jednym z częściej powta- AntiVirenKit firmy G DATA Software Sp. z o.o.
rzanych pytań w zespołach programistycznych: ile to zajmie? Redakcja dokłada wszelkich starań, by publikowane w piśmie
Pytanie to nieustannie spędza z oczy sen zarówno liderom, jak i na towarzyszących mu nośnikach informacje i programy były
i programistom. Skoro jest ono tak ważkie, to przyjrzyjmy mu się poprawne, jednakże nie bierze odpowiedzialności za efekty
wykorzystania ich; nie gwarantuje także poprawnego działania
dokładniej! 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:


cooperation@software.com.pl

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ą.

www.sdjournal.org 5
Biblioteka miesiąca

Google Protocol Buffers


Wydajna i elastyczna alternatywa dla XML

XML to potężne i przenośne rozwiązanie, aczkolwiek czasami wydaje


się być nieco... przerośnięte. Jeśli szukasz prostszej, bardziej wydajnej,
a zarazem elastycznej alternatywy, to koniecznie przeczytaj poniższy
artykuł. Biblioteka Google Protocol Buffers jest prawdopodobnie
właśnie tym czego szukasz!
nych o prostej strukturze podejście to
Dowiesz się: Powinieneś wiedzieć: sprawdza się całkiem nieźle. Wymaga
• Do czego służy biblioteka Google Protocol • Podstawowa znajomość języka C++; ono stworzenia i utrzymywania dedy-
Buffers; • Podstawowa znajomość języka XML. kowanego parsera (można w tym celu
• Jak rozpocząć z nią pracę; użyć np. wyrażeń regularnych). Nieste-
• Jak zastosować ją w praktyce. ty, w przypadku przetwarzania dużych
ilości danych o skomplikowanej struk-
turze, nakład pracy na stworzenie par-
sera staje się uciążliwy. Dodatkowo ro-
nia listą zadań (ang. task manager). Funda- dzi się problem związany z optymaliza-
mentalną strukturą danych w tej aplikacji cją wydajności procesu przetwarzania
Poziom jest zadanie (ang. task), składające się z ta- danych, przez co utrzymywanie parse-
trudności kich pól jak: tytuł, kategoria, opcjonalny ra staje się jeszcze bardziej złożone.
opis oraz opcjonalna data, która wskazuje • Zapis danych w formacie XML. W teo-
termin określający do kiedy zadanie musi rii: panaceum na wszystkie opisane
być wykonane. Jednym z fundamentalnych wyżej problemy (pełna przenośność,

N
iniejszy artykuł przedstawia bi- zadań Twojej aplikacji będzie serializacja i duża elastyczność, ogólnodostępne
bliotekę Google Protocol Buf- deserializacja listy zadań. Możesz to zreali- parsery itd.). Niestety, w praktyce czę-
fers, która oferuje niezależny od zować na kilka sposobów: sto okazuje się, że XML obarczony jest
platformy i języka programowania, rozsze- dużym, a co gorsza – niepotrzebnym
rzalny mechanizm służący do serializacji • Bezpośredni zapis zawartości pól narzutem. Parsery XML to zazwyczaj
danych o regularnej strukturze. Jak piszą struktur do pliku (w postaci binar- olbrzymie kombajny, które ważą nie-
autorzy biblioteki, rozwiązanie to można nej). Ten sposób, na pierwszy rzut oka mało, zaś ich wydajność pozostawia
postrzegać jako prostszą, lżejszą i bardziej wydaje się być najprostszy, okazuje się sporo do życzenia; dokumenty zapi-
efektywną alternatywę dla formatu XML. jednak fatalnym rozwiązaniem pod sywane w formacie XML zawierają
W kolejnych punktach artykułu opi- względem przenośności oraz rozsze- bardzo dużo metadanych co skutkuje
szę koncepcję Google Protocol Buffers, a rzalności. Pojawiają się zależne od plat- nadmiernym zużyciem pamięci dysko-
następnie pokażę jak można skonfiguro- formy problemy związane z mapowa- wej. Kłopotliwe jest również mapowa-
wać i zastosować w praktyce tę bibliote- niem danych w pamięci (ang. memory nie struktury drzewa DOM na struk-
kę. Google Protocol Buffers dostępna jest layout) czy z kolejnością zapisu bajtów turę obiektów w pamięci programu.
w trzech językach: C++, Java i Python, acz- (ang. endianness), których rozwiązanie • Wykorzystanie Google Protocol Buf-
kolwiek większość przykładów zaprezento- nie jest wcale trywialne. Występują fers. W tym przypadku uzyskasz te
wanych w niniejszym artykule opierać się również kłopoty wynikające z ewolu- same zalety co w przypadku stosowa-
będzie na C++. cji struktur danych, na których pracu- nia formatu XML, unikając jednocze-
je program: wszystkie zmiany trzeba śnie powiązanych z nim wad. W jaki
Co mi da w takim przypadku wykonywać ma- sposób twórcy tej biblioteki rozwiąza-
Google Protocol Buffers? nualnie, na poziomie kodu źródłowe- li tę łamigłówkę, przekonasz się czyta-
Na początek spróbujmy odnaleźć odpo- go; bardzo kłopotliwe staje się również jąc kolejny punkt.
wiedź na fundamentalne pytanie: do cze- utrzymywanie zgodności z poprzedni-
go może mi się przydać biblioteka Google mi wersjami programu. Google Protocol Buffers:
Protocol Buffers? • Próba zakodowania danych progra- z czym to się je?
Rozważmy prosty przykład. Wyobraź so- mu w postaci tekstowej. W przypad- Rozwiązanie opracowane w ramach Go-
bie, że piszesz prostą aplikację do zarządza- ku przetwarzania niewielkich ilości da- ogle Protocol Buffers wygląda następu-

6 04/2010
Alternatywa dla XML

jąco: na początek należy przygotować w nomenklaturze biblioteki Google Proto- być zarówno typem podstawowym jak i ty-
specjalny plik (.proto), zawierający de- col Buffers to struktura, bądź rekord. W ję- pem złożonym, zdefiniowanym przez użyt-
finicję struktury danych, którą chcemy zyku C++ każda wiadomość będzie repre- kownika. Wreszcie mamy nazwę pola (w
serializować/deserializować. W kolejnym zentowana jako klasa. Wiadomości posiada- naszym przypadku: name). Każda defini-
kroku należy wykorzystać generator kodu ją dane składowe. W naszym przykładzie na cja pola ma przypisany tzw. tag. Tag w kon-
(dostępny w ramach biblioteki), który na początek definiujemy wiadomość o nazwie tekście Google Protocol Buffers to unikal-
podstawie opisu zawartego w pliku .proto Task (zadanie). Zgodnie z wcześniejszymi na liczna, która służy do identyfikacji pól w
stworzy implementację klasy zawierającej założeniami, każde zadanie posiada nazwę plikach zawierających wiadomości zapisane
operacje serializacji i deserializacji, a tak- (ang. name), kategorię (ang. category), opcjo- w postaci binarnej. Rola tagów opisana bę-
że metody dostępu do składowych (ang. nalny opis (ang. description) oraz opcjonalną dzie w dalszej części artykułu.
getters/setters). Jedyne co musi zrobić pro- datę (ang. date). Na Listingu 1 można zaob- Jak widać na przykładowym pliku .proto,
gramista, to stworzyć w kodzie instancję ta- serwować jak powyższe założenia przekłada- w definicjach wiadomości można umiesz-
kiej klasy i wywołać odpowiednie metody. ją się na kształt definicji wiadomości Task. czać również enumeracje (u nas enumera-
Proste, prawda? Rozważmy przykładową definicję pola: cja została wykorzystana w celu wyliczenia
kolejnych miesięcy w roku). Wiadomości
Jak to wygląda w praktyce? required string name = 1; mogą być również zagnieżdżone (tak wła-
Rozważmy jak w praktyce można wyko- śnie jest w przypadku definicji wiadomości
rzystać Google Protocol Buffers. Jak wspo- Na początku mamy słowo kluczowe okre- Date). Jak widać w zaprezentowanym przy-
mniałem na wstępie, biblioteka ta jest do- ślające, czy dane pole jest wymagane czy kładzie plik .proto może zawierać dowolną
stępna w trzech językach: C++, Java i Py- nie (w tym drugim przypadku należałoby liczbę definicji wiadomości.
thon. Na potrzeby niniejszego artykułu za- użyć słowa kluczowego optional), tudzież
stosujemy implementację w języku C++. czy może występować wielokrotnie (słowo Generowanie kodu
Na początek bibliotekę należy pobrać z kluczowe: repeated). Dalej znajdujemy de- W efekcie budowania biblioteki Google
Internetu. Jest ona ogólnie dostępna w ser- klarację typu pola, przy czym typ ten może Protocol Buffers uzyskujemy szereg kom-
wisie Google Code (patrz: Ramka W Sieci).
W trakcie pisania niniejszego artykułu Go- Listing 1. Zawartość pliku task_list.proto
ogle Protocol Buffers dostępna była w wer-
sji 2.3.0. package taskmanager;
Biblioteka oferowana jest zarówno w po-
staci źródłowej oraz binarnej. Ta druga wer- message Task {
sja będzie dla nas nieprzydatna, więc po- required string name = 1;
bieramy pierwszą. Po rozpakowaniu archi- required string category = 2;
wum z biblioteką (umówmy się, że katalog optional string description = 3;
do którego rozpakowana została bibliote-
ka nazywać będziemy %PROTOBUF_HO- enum Month {
ME%), należy ją zbudować. Ja w tym ce- JAN = 0;
lu skorzystałem z pakietu Microsoft Visu- FEB = 1;
al Studio 2008. W podkatalogu %PROTO- MAR = 2;
BUF_HOME%/vsprojects znajduje się plik APR = 3;
solucji oraz plik projektów kompatybilne z MAY = 4;
Visual Studio, a także pliki readme.txt, któ- JUN = 5;
ry opisuje dokładnie proces budowania bi- JUL = 6;
blioteki. Oprócz tego w archiwum bibliote- AUG = 7;
ki znajdują się pliki konfiguracyjne umożli- SEP = 8;
wiające jej zbudowanie pod inne, popular- OCT = 9;
ne systemy. NOV = 10;
Gdy biblioteka zostanie pomyślnie zbu- DEC = 11;
dowana można wziąć się do pracy. Spróbu- }
jemy przy jej pomocy stworzyć silnik do za-
pisu i odczytu danych w naszym hipote- message Date {
tycznym programie TaskManager. Zaczy- required uint32 year = 1;
namy od definicji struktury danych. De- required Month month = 2;
finicję tę umieścimy w pliku task_list.pro- optional uint32 day = 3;
to. Zawartość tego pliku przedstawiona jest }
na Listingu 1.
Przyjrzymy się po kolei zawartym tam ele- optional Date date = 4;
mentom. Na samym początku w oczy rzu- }
ca się definicja pakietu. W przypadku gene-
rowania kodu w języku C++ pakiet ten bę- message TaskList {
dzie odwzorowany na przestrzeń nazw, dzię- repeated Task task = 1;
ki czemu łatwo da się uniknąć kolizji pomię- }
dzy nazwami typów. Dalej mamy definicję
tzw. wiadomości (ang. message). Wiadomość

www.sdjournal.org 7
Biblioteka miesiąca

ponentów. Jednym z nich jest kompila- lub na: Jak widać, generator kodu dołączony w
tor (ang. Protocol Buffers Compiler): pro- ramach Google Protocol Buffers odrobił
toc.exe. Korzystając z tego narzędzia mo- protoc.exe --java_out=. task_list.proto za nas przysłowiową czarną robotę i stwo-
żemy wygenerować kod na podstawie na- rzył definicje klas odpowiadające struktu-
szego przykładowego pliku .proto. Posłuży- uzyskalibyśmy kod do obsługi naszych rom danych występującym w zadanym mu
my się w tym celu następującą komendą wiadomości wygenerowany odpowiednio pliku .proto.
(zakładam, że plik protoc.exe jest dostęp- w Pythonie bądź w Javie. W naszym przykładzie tworzymy na po-
ny w miejscu w którym znajduje się plik Teraz czas zastosować wygenerowany czątek listę zadań (TaskList), którą następ-
task_list.proto): kod w rzeczywistym programie. nie wypełniamy zawartością. Warto zwró-
cić uwagę, że operacja dodania nowego za-
protoc.exe --cpp_out=. task_list.proto Wiadomość w akcji! dania (Task) do listy odbywa się w dość spe-
Aby skorzystać z wygenerowanego kodu cyficzny sposób:
W efekcie otrzymamy dwa pliki: task_ potrzeba niewiele. Plik .cc trzeba oczywi-
list.pb.h oraz task_list.pb.cc, które zawie- ście skompilować i zlinkować z biblioteka- taskmanager::Task* newTask1 =
rają implementację klas reprezentujących mi Google Protocol Buffers (uzyskanymi w taskList.add_
wiadomości zdefiniowane w task_list.pro- procesie budowania). Kompilator musi też task();
to. Gdybyśmy zamienili powyższą komen- widzieć pliki nagłówkowe Google Protocol
dę na: Buffers. Kiedy to wszystko uda się pomyśl- Obiekt reprezentujący zadanie jest two-
nie skonfigurować, to wreszcie można na- rzony i dodawany w metodzie add _
protoc.exe --python_out=. task_ pisać i skompilować program przedstawio- task(), po czym dostajemy wskaźnik usta-
list.proto ny na Listingu 2. wiony na ten obiekt. Dzięki temu nie mu-
simy się kłopotać własnoręcznym tworze-
Listing 2. Wiadomość w akcji: zapisywanie niem obiektu, tudzież związanym z tym
niebezpieczeństwem wycieku pamięci.
#include <fstream> Otrzymany obiekt, w którym pola zaini-
#include <iostream> cjowane są wartościami domyślnymi, mo-
żemy dowolnie modyfikować, co też czyni-
#include "task_list.pb.h" my. Warto zwrócić uwagę na dedykowane
funkcje do modyfikacji pól (set_name(),
int main() set_description()). W przypadku złożo-
{ nej składowej (date), dostęp do niej uzysku-
taskmanager::TaskList taskList; jemy poprzez metodę mutable_date(), któ-
ra zwraca niestałą referencję do obiektu.
taskmanager::Task* newTask1 = taskList.add_task(); W przedstawionym przykładzie dodaje-
newTask1->set_name("Buy birthday gift for Monisia"); my dwa zadania, po czym zapisujemy listę
newTask1->set_category("family"); na dysk za pomocą wywołania:
newTask1->mutable_date()->set_year(2010U);
newTask1->mutable_date()->set_month(taskmanager::Task_Month_MAR); taskList.SerializeToOstream(&ofs);
newTask1->mutable_date()->set_day(10U);
Obiekt ofs reprezentuje strumień zapi-
taskmanager::Task* newTask2 = taskList.add_task(); su do pliku. Warto w tym miejscu zauwa-
newTask2->set_name("Send monthly project report to Bob"); żyć, że biblioteka Google Protocol Buf-
newTask2->set_category("job"); fers jest bardzo elastyczna w kontekście
newTask2->mutable_date()->set_year(2010U); mechanizmów serializacji: wiadomość
newTask2->mutable_date()->set_month(taskmanager::Task_Month_FEB); może być zapisana na różne sposoby, np.
newTask2->mutable_date()->set_day(28U); do tablicy czy do napisu; w całości lub
fragmentami.
std::ofstream ofs("tasklist.db"); Po uruchomieniu programu w jego kata-
logu roboczym otrzymamy wynikowy, bi-
if(taskList.SerializeToOstream(&ofs)) narny plik tasklist.db. Oczytanie tego pliku
{ jest równie łatwe jak jego zapis. Czynność
std::cout << "task list serialization succeed!" << std::endl; ta zrealizowana jest na Listingu 3.
return 0; Widać w tym przypadku dobitnie, jak
} prosto można deserializować, a następ-
else nie odwoływać się do składników naszej
{ listy zadań. Gdybyśmy chcieli zrealizo-
std::cout << "task list serialization failed!" << std::endl; wać to zadanie przy pomocy języka XML,
return 1; to po pierwsze: musielibyśmy sami stwo-
} rzyć klasy reprezentujące struktury da-
nych, zaś po drugie: napisać kod odpowie-
} dzialny za fabrykowanie obiektów tych
klas na podstawie zawartości pliku XML.
Pomijam już fakt, że sama składnica da-

8 04/2010
Alternatywa dla XML

nych w postaci pliku XML zajmowała by


o wiele więcej miejsca zaś operacje jej zapi- Listing 3. Wiadomość w akcji: odczyt
su i odczytu byłyby o rząd wielkości wol-
niejsze w stosunku do binarnego formatu #include <fstream>
wspieranego przez bibliotekę Google Pro- #include <iostream>
tocol Buffers.
#include "task_list.pb.h"
Sztuczki i haczyki...
Przykłady pokazane na Listingach 2 i 3 są int main()
(celowo) bardzo proste. Google Protocol {
Buffers oferuje programiście cały szereg taskmanager::TaskList taskList;
praktycznych udogodnień, które uspraw-
niają pracę z tą biblioteką. W tym punkcie std::ifstream ifs("tasklist.db");
przedstawię kilka z nich. Więcej informacji
na ten temat należy szukać w dokumenta- if(taskList.ParseFromIstream(&ifs))
cji biblioteki (patrz: Ramka W Sieci) {
Bardzo ważne jest to, aby pliki nagłów- std::cout << "task list deserialization succeed!" << std::endl;
kowe oraz skompilowane biblioteki Go- }
ogle Protocol Buffers miały tę samą wer- else
sję. Twórcy udostępnili w tym celu specjal- {
ne makro: std::cout << "task list deserialization failed!" << std::endl;
return 1;
GOOGLE_PROTOBUF_VERIFY_VERSION; }

którego umieszczenie na początku funkcji int ntasks = taskList.task_size();


main() sprawdzi ten warunek.
Oprócz metod dostępu, każda klasa re- for(int i=0; i<ntasks; ++i)
prezentująca wiadomość posiada zestaw {
przydatnych, standardowych metod: std::cout << "Task #" << i << std::endl
<< "name: " << taskList.task(i).name() << std::endl
• bool IsInitialized() const;: spraw- << "category: " << taskList.task(i).category() << std::endl
dza czy wszystkie wymagane (requ- << "description: " << taskList.task(i).description() << std::endl
ired) pola w wiadomości zostały usta- << std::endl;
wione. }
• string DebugString() const;: zwra-
ca napis prezentujący zawartość wia- return 0;
domości w czytelnej postaci. Tej me- }
tody moglibyśmy użyć w programie
pokazanym na Listingu 3 w celu wy-
pisania zawartości poszczególnych za- Listing 4. Przykładowy napis zwrócony z metody DebugString()
dań; wystarczyłoby napisać: std::
cout << taskList.DebugString() <<
std::endl;. Wynikowy napis (dla na- task {
szej przykładowej listy zadań) przed- name: "Buy birthday gift for Monisia"
stawiony jest na Listingu 4. category: "family"
• void CopyFrom(const Task& from);: date {
nadpisuje zawartość pól danej wiado- year: 2010
mości wartościami pobranymi z from . month: MAR
• void Clear();: przywraca wiadomość day: 10
do stanu początkowego (pola są puste, }
bądź zainicjowane wartościami do- }
myślnymi).
task {
Należy również pamiętać, iż klasy repre- name: "Send monthly project report to Bob"
zentujące wiadomości są, mimo wszystko, category: "job"
tylko prostymi kontenerami, służącymi do date {
przechowywania pól. W sytuacji kiedy po- year: 2010
jawia się potrzeba rozszerzenia możliwo- month: FEB
ści tych klas (np. gdybyśmy chcieli dodać day: 28
do naszej klasy reprezentującej zadanie }
metody implementujące jakąś bardziej za- }
awansowaną logikę) to zalecanym podej-
ściem jest użycie agregacji, tj. opakowa-

www.sdjournal.org 9
Biblioteka miesiąca

nie wiadomości inną klasą. Absolutnie nie nym przykładzie. Na początek zachowaj- Podsumowanie:
powinno się stosować w tym celu dziedzi- my sobie plik wykonywalny odczytujący jeszcze więcej możliwości!
czenia, gdyż 1) jest to niezgodne z dobrym zawartość listy zadań. Następnie wygene- Na tym etapie można by powiedzieć, c'est
stylem obiektowego rujmy nowy zestaw klas reprezentujących ça! (fr. to jest to!). Mamy wydajną i elastycz-
projektowania klas; 2) psuje zachowanie tę listę, z dodanym nowym, opcjonalnym ną alternatywę dla formatu XML! Okazu-
wewnętrznych mechanizmów biblioteki polem opisującym priorytet zadania: je się, że biblioteka Google Protocol Buf-
Google Protocol Buffers. fers niesie ze sobą cały szereg innych moż-
optional uint32 priority = 5; liwości. Jak sugerują twórcy tej bibliote-
Zmiany, zmiany... ki, za jej pomocą można zaimplemento-
... to jedyny pewnik w przedsięwzięciach Następnie korzystając ze zmodyfikowanej wać w języku C++ coś na kształt mechani-
informatycznych – tak mawiają kierownicy wersji programu z Listingu 2 zapiszmy no- zmu refleksji (ang. reflection) z języka Ja-
projektów z branży IT. I mają rację. Prędzej wy plik tasklist.db, zawierający informację va. Mechanizm ten pozwala w trakcie wy-
czy później zmiany dotkną również Twoich o priorytetach. Modyfikacje polegać będą konywania programu iterować po składo-
programów oraz danych, na których one na dodaniu dwóch linijek ustawiających wych obiektu i modyfikować ich zawar-
operują. Jak w takiej sytuacji radzie sobie wartości pola priority: tość. Obecność takiego mechanizmu daje
biblioteka Google Protocol Buffers? Oka- programistom języka C++ bardzo szerokie
zuje się, że całkiem nieźle... newTask1->set_priority(0); pole do popisu. Korzystając z Google Pro-
Kluczem do zarządzania zmianami w tocol Buffers należy pamiętać również ja-
przypadku omawianej biblioteki są tagi, oraz: ka organizacja jest odpowiedzialna za jej
o których wspominałem na początku ar- stworzenie i co się z tym wiąże. Kompo-
tykułu. Przypomnę, że tagi te identyfiku- newTask2->set_priority(5); nenty biblioteczne wyprodukowane przez
ją jednoznacznie pola w wiadomościach i Google mają opinię najbardziej intensyw-
na przestrzeni czasu absolutnie nie wol- Teraz wielka chwila: uruchamiamy za- nie przetestowanych na świecie. Warto
no ich modyfikować. Generalnie, Google chowany wcześniej plik wykonywalny, podkreślić również, iż biblioteka Google
Protocol Buffers narzuca kilka prostych za- podsuwając mu nowe dane. To działa! Protocol Buffers jest bardzo intensywnie
sad, które pozwolą zarówno Twoim progra- Nasz stary program poprawnie wczytał wykorzystywana w infrastrukturze Go-
mom jak i danym przetrwać trudną próbę listę zadań. Nowe pole zostało po prostu ogle: stanowi ona podstawowy komponent
zmian. Zasady te są następujące – na prze- pominięte. Co więcej; nasza lista będzie do przetwarzania danych w tej infrastruk-
strzeni czasu: równie bezproblemowo obsłużona przez turze. To ewidentnie świadczy o jakości te-
program napisany w Javie czy Pythonie. go rozwiązania...
• nie wolno zmieniać numerów tagów To jest dopiero przenośność! Gdybyśmy Oczywiście istnieją nadal dziedziny,
w żadnym z istniejących pól, korzystali z prostej serializacji obiektów w których XML razem z całą gamą towa-
• nie wolno dodawać lub usuwać żad- (tak jak opisałem to na początku artyku- rzyszących mu technologii satelitarnych
nych pól wymaganych (required), łu) to opisany wyżej scenariusz zakończył (XML Schema, XSLT, XQuery, XPAth,
• wolno usuwać pola oznaczone jako by się najprawdopodobniej spektakularną itd.) pozostaje niezwyciężony. Jednakże,
opcjonalne (optional) lub powtarzal- katastrofą... zanim sięgniesz po to narzędzie – sprawdź
ne (repeated), Kończąc niniejszy punkt warto wspo- zaprezentowaną tu alternatywę!
• wolno dodawać nowe opcjonalne lub mnieć, iż Google Protocol Buffers oferu- Resumując, serdecznie zachęcam Cię
powtarzalne pola, pod warunkiem, że je szereg mechanizmów wspomagających drogi Czytelniku do zapoznania się i – co
użyjemy nowego numeru taga. proces zarządzania tagami pól. Więcej ważniejsze – do praktycznego zastosowa-
szczegółów na ten temat można znaleźć nia Google Protocol Buffers w Twoich roz-
Voila! Przy tych założeniach niestraszne w dokumentacji biblioteki (patrz: Ram- wiązaniach. Najprawdopodobniej nie poża-
nam zmiany. Rozważmy to na konkret- ka W Sieci). łujesz tego!

Licencja
Biblioteka Google Protocol Buffers jest udostępniana na licencji BSD. Licencja ta przewidu-
je możliwość wykorzystywania biblioteki bez uiszczania żadnych opłat, tak w otwartych jak
i w komercyjnych projektach.
RAFAŁ KOCISZ
Pracuje na stanowisku Dyrektora Techniczne-
go w �rmie Gamelion, wchodzącej w skład Gru-
py BLStream. Rafał specjalizuje się w technolo-
W Sieci giach związanych z produkcją oprogramowa-
• http://code.google.com/intl/pl/apis/protocolbuffers/ – strona domowa biblioteki Google nia na platformy mobilne, ze szczególnym na-
Protocol Buffers. ciskiem na tworzenie gier. Grupa BLStream po-
• http://code.google.com/intl/pl/apis/protocolbuffers/docs/overview.html – sekcja dokumen- wstała, by efektywniej wykorzystywać potencjał
tów na stronie domowej Google Protocol Buffers; znajdziesz tu szereg cennych informa- dwóch szybko rozwijających się producentów
cji o tym rozwiązaniu.
oprogramowania – BLStream i Gamelion. Firmy
• http://code.google.com/intl/pl/apis/protocolbuffers/docs/tutorials.html – sekcja samoucz-
ków związanych z biblioteką Google Protocol Buffers. wchodzące w skład grupy specjalizują się w wy-
• http://code.google.com/intl/pl/apis/protocolbuffers/docs/proto.html – tu znajdziesz szcze- twarzaniu oprogramowania dla klientów korpo-
gółowy opis języka opisu struktur danych, wykorzystywanego w plikach .proto. racyjnych, w rozwiązaniach mobilnych oraz pro-
dukcji i testowaniu gier.
Kontakt z autorem: rafal.kocisz@game-lion.com

10 04/2010
Opis DVD

Programowanie w języku Java niejące rozwiązanie stworzone w poprzednim odcinku i zamie-


nimy rozwiązanie oparte o serwlet na akcję Struts2 z wykorzysta-
Od Witaj świecie do aplikacji korporacyjnych. niem pomocniczych mechanizmów, jakich jak automatyczne uzu-
Wprowadzenie do szkieletu aplikacji Struts2 pełnianie pól klasy na podstawie parametrów żądania i udostęp-
niania pól klasy w widoku.
Siódmy odcinek wideo kursu to kolejny krok w stronę zaawan- Następnie zrealizujemy widok JSP z użyciem znaczników do-
sowanych rozwiązań związanych z tworzeniem aplikacji inter- starczonych w Struts2, które ułatwiają tworzenie dynamicznych
netowych na platformie Java. Dzięki poprzednim odcinkom stron internetowych.
poznaliśmy podstawy korzystania z serwletów oraz tworzenia Ostatecznie połączymy wszystkie elementy z pomocą pliku
stron JSP. Jest to podstawa, na której zbudowane są zaawanso- struts.xml i uruchomimy zmodyfikowaną aplikację.
wane technologie jakie jak Struts2.
W pierwszej części zostaną przedstawione podstawowe zało-
żenia stojące za Struts2, jak ten szkielet aplikacji realizuje wzo- SoftQA – magazyn
rzec MVC i jakie są jego główne elementy składowe. o testowaniu oprogramowania
Następnie przyjrzymy się dystrybucji Struts2, jej składowym
elementom oraz wybierzemy minimalny podzbiór z tejże dys- Szanowni czytelnicy oddajemy do Waszych rąk pierwsze wydanie
trybucji, który pozwoli rozpocząć pracę z aplikacjami interneto- magazynu SoftQA, poświęconego szeroko rozumianemu testowa-
wymi z użyciem tego szkieletu. niu oprogramowania. Pismo będzie wydawane w wersji elektro-
W następnej, zasadniczej części, przyjrzymy się głównym nicznej i będzie dostępne na płytach dołączanych do SDJ, a także
składowym Struts2. W pierwszym kroku zmodyfikujemy ist- na naszych stronach internetowych.

Jeśli nie możesz


odczytać zawartości
płyty DVD, a nie jest ona Redakcja
uszkodzona mechanicznie, nie udziela pomocy
sprawdź ją na co najmniej dwóch technicznej w instalowaniu
napędach DVD. i użytkowaniuprogramów
W razie problemów z płytą, prosimy pisać zamieszczonych na płytach DVD-ROM
pod adres: cd@software.com.pl dostarczonych razem z pismem.

11
Klub techniczny Adobe

Debugowanie aplikacji
Część 1: Podstawy debugowania
Debugowanie to proces wyszukiwania i usuwania błędów lub proble-
mów występujących w aplikacji. Zadanie to jest często trudne i frustru-
jące, ale można je znacznie uprościć, korzystając z debugera – progra-
mu narzędziowego, który umożliwia monitorowanie i kontrolowanie
wykonywania aplikacji krok po kroku oraz monitorowanie i modyfiko-
wanie wartości zmiennych.
• przerywanie pracy w pętli i skoki do
Dowiesz się: Powinieneś wiedzieć: wierszy kodu z wykorzystaniem nowe-
• Jak przeglądać i rozumieć komunikaty błę- • Podstawy z technologii Flex. go polecenia Run to Line.
dów kompilacji;
• Jak używać polecenia trace(); Wymagania
• Jak korzystać i zarządzać tzw. breakpointami; Niezbędnym uzupełnieniem niniejszego
• Jak korzystać i zarządzać tzw. watchpointami. podręcznika są:

• Flash Builder 4 beta


• używanie polecenia trace() do wyświe- • Pliki demonstracyjne
tlania informacji w trakcie debugowania • Flex4Debugging_ start.zip
Poziom w widoku konsoli. (www.adobe.comhttp: //
trudności download.macromedia.com /pub /
Część 2 omawia: developer/Flex4Debugging_start.zip)
(ZIP 25 KB)
• korzystanie z punktów wstrzymania • Flex4Debug ging_ solution.zip

D
ebugowanie to proces wyszukiwa- programu (breakpoints) po dojściu do (www.adobe.comhttp: //
nia i usuwania błędów lub pro- określonego wiersza kodu; download.macromedia.com /pub /
blemów występujących w aplika- • korzystanie z nowych, warunkowych developer / Flex4Debug ging _ solu-
cji. Zadanie to jest często trudne i frustrują- punktów wstrzymania; tion.zip) (ZIP 25 KB)
ce, ale można je znacznie uprościć, korzysta- • zarządzanie punktami wstrzymania
jąc z debugera — programu narzędziowego, z poziomu ich widoku; Wymagana wiedza
który umożliwia monitorowanie i kontrolo- • kontrolowanie wykonywania kodu Pomocna jest podstawowa znajomość two-
wanie wykonywania aplikacji krok po kroku przez zawieszanie, wznawianie i kończe- rzenia aplikacji Flex 4. Więcej informacji
oraz monitorowanie i modyfikowanie warto- nie działania aplikacji; na temat tworzenia i dostosowywania apli-
ści zmiennych. • krokowe wykonywanie kodu za pomo- kacji Flex 4 można znaleźć w Dokumenta-
Pakiet Flash Builder 4 beta ma wbudowa- cą instrukcji Step Into, Step Over i Step Re- cji Flex 4, zestawie filmów wideo i ćwiczeń
ny debuger oraz perspektywę debugowania. turn. Flex 4 in a week (www.adobe.com/devnet/flex/
Ten trzyczęściowy podręcznik ułatwia naukę videotraining/flex4beta/index.html) oraz w ser-
obsługi tych modułów. Część 3 omawia: wisie Flex Developer Center (www.adobe.com/
Część 1 omawia: devnet/flex).
• używanie widoku zmiennych do bada-
• import projektu końcowego i zapozna- nia ich wartości; Import projektów początkowego
nie się z prawidłowym działaniem apli- • zmiany wartości zmiennych w czasie se- i końcowego aplikacji Flex
kacji; sji debugowania w celu testowania po- W tym rozdziale omówiono import dwóch
• import projektu początkowego i prze- prawek; projektów umożliwiających rysowanie róż-
gląd kodu pierwotnej aplikacji Flex, • korzystanie z widoku wyrażeń w ce- nych kształtów aplikacji Flex do oprogramo-
gdzie rysowanie zawiera błędy; lu obserwowania wartości określonych wania Flash Builder. Aplikacja z projektu po-
• przegląd komunikatów o błędach zmiennych i wyrażeń; czątkowego nie działa prawidłowo, zaś apli-
w trakcie wykonywania w wersji opro- • korzystanie z nowych punktów monitoro- kacja z projektu końcowego działa. Aplika-
gramowania Flash Player z debugerem wania (watchpoints) w celu wstrzymywa- cja końcowa służy do celów referencyjnych.
i w widoku konsoli środowiska Flash nia wykonywania programu w przypadku W niniejszej serii podręczników omówiono
Builder; zmiany wartości określonej zmiennej; debugowanie aplikacji początkowej.

12 04/2010
Debugowanie aplikacji Flex 4

• Jeżeli nie zostało to jeszcze zrobione, po- kształt oraz kolor i rysować (patrz Rysu- pes — które wykorzystując graficzny interfejs
bierz i zainstaluj Flash Builder 4 beta. nek 3). programowania, rysuje kształt w miejscu,
• Jeżeli nie zostało to jeszcze zrobione, po- • Kliknij prawym przyciskiem myszy Fle- w którym znajduje się kursor. Gdy użyt-
bierz pliki początkowe i pliki rozwiąza- x4DebuggingTutorial_EndProject w oknie kownik zwalnia przycisk myszy, obserwa-
nia, a następnie rozpakuj je. Package Explorer i wybierz Close Project tor onMouseMove() zostaje usunięty. W pra-
• Uruchom Flash Builder. (Zamknij projekt). Zamknięcie projek- wym górnym rogu aplikacji dostępne są ele-
• Wybierz File > Import Flex Project (FXP) tu zapobiegnie przypadkowej pracy na menty sterujące, które umożliwiają wybór
(Plik > Import projektu Flex (FXP)). plikach końcowych, zamiast na począt- innego kształtu, zmianę koloru oraz kasowa-
• W oknie dialogowym Import Flex Project kowych. nie rysunku.
(Import projektu Flex) kliknij przycisk • Powtarzając kroki 4–9, zaimportuj plik Uwaga: Aplikacja ta została zrefaktoryzo-
Browse (Przeglądaj) obok pola File (Plik). Flex4DebuggingTutorial_StartProject.fxp. wana zgodnie ze wskazówkami dotyczący-
• W oknie dialogowym Open (Otwórz) mi umieszczenia całego kodu w jednym pli-
przejdź do miejsca, w którym został za- Uruchom aplikację. Powinien zostać wy- ku zawartymi w podręczniku Refactoring Flex
pisany plik Flex4DebuggingTutorial_ świetlony błąd uruchomienia programu 4 Applications (www.adobe.com/devnet/flex/
EndProject.fxp, zaznacz go i kliknij Open (patrz Rysunek 4). articles/flashbuilder4_refactoring.html).
(Otwórz). Uwaga: Komunikat o błędzie pokazany
• W oknie dialogowym Import Flex Project na Rysunku 4 zostanie wyświetlony tylko Debugowanie aplikacji
(Import projektu Flex) kliknij przycisk w przypadku używania wersji oprogramo- Do debugowania aplikacji za pomocą pakietu
Finish (Zakończ) (patrz Rysunek 1). wania Flash Player z debugerem. Na ogół wer- Flash Builder potrzebna jest wersja Flash Play-
• W oknie Package Explorer klikaj strzał- sję tę mają tylko programiści tworzący aplika- era z debugerem oraz wersja pliku SWF prze-
ki, aby rozwijać foldery Flex4Debugging- cje Flash i Flex. Użytkownicy zwykłej wersji znaczona do debugowania.
Tutorial_EndProject, src i (default pac- Flash Playera nie zobaczą tego komunikatu. W trakcie instalowania programu Flash
kage), aż zobaczysz plik Drawing.mxml Aplikacje po prostu nie będą działać. Builder wersja Flash Playera z debugerem
(patrz Rysunek 2). jest instalowana dla różnych przeglądarek
• Dwukrotnie kliknij plik Drawing.mxml, • Kliknij przycisk Dismiss All (Ignoruj w systemie. Ręczne zainstalowanie wer-
aby go otworzyć. wszystkie). sji Flash Playera z debugerem jest koniecz-
• Aby skompilować i przetestować aplika- • Powróć do Drawing.mxml (w projekcie ne w dwóch przypadkach: jeśli chcesz za-
cję, kliknij zielony przycisk Run (Uru- początkowym!) i przeanalizuj kod. instalować nowszą wersję Flash Playera
chom) na głównym pasku narzędzi lub bez debugera po zainstalowaniu programu
wybierz Run > Run Drawing (Uruchom Aplikacja nasłuchuje zdarzeń mouseDown, Flash Builder lub chcesz zainstalować wer-
> Uruchom rysunek). Otworzy się mouseMove oraz mouseUp i reaguje na nie. sję nowszą niż dostarczona z programem
okno przeglądarki, w którym powinna Gdy użytkownik naciska przycisk myszy, Flash Builder. Komunikat o błędzie przy
być widoczna aplikacja. dodany zostaje obserwator, który śledzi jej próbie debugowania aplikacji za pomocą
• Kliknij i poruszaj myszą, aby rozpocząć poruszenia. Słuchacz ten, onMouseMove(), pakietu Flash Player oznacza, że koniecz-
rysowanie. tworzy wystąpienie klasy Shape (kształt) — ne jest zainstalowanie wersji Flash Play-
• Zmieniaj parametry w prawym górnym Circle (Koło), Square (Kwadrat) lub Star era z debugerem. Można to także spraw-
rogu okna aplikacji, aby dostosowywać (Gwiazda) z pakietu com.adobe.samples.sha- dzić, klikając prawym przyciskiem myszy
plik SWF w oknie przeglądarki i wyświe-
tlając menu kontekstowe (powinna w nim
być widoczna pozycja Debuger) lub używa-
jąc metody flash.system.Capabilities.
isDebugger() w kodzie. Aby zainstalować
najnowszą wersję Flash Playera z debuge-
rem, wejdź na stronę Flash Player Downlo-
ads (www.adobe.com/support/flashplayer/do-
wnloads.html) i uruchom deinstalator, a na-
stępnie instalator odpowiedni dla danej
przeglądarki.

Rysunek 1. Import projektu Flex Rysunek 2. Lokalizacja głównego pliku aplikacji

www.sdjournal.org 13
Klub techniczny Adobe

W trakcie kompilowania aplikacji za po- • Kliknij przycisk Debug (Debuguj) na upuszczanie w różnych miejscach. Aby
mocą Flash Buildera – przez jej uruchamia- głównym pasku narzędzi (patrz Rysu- powrócić do układu domyślnego, wy-
nie, debugowanie lub bezpośrednie budowa- nek 5) lub wybierz Debug > Drawing bierz Window > Perspective > Reset Per-
nie – Flash Builder tworzy wersję pliku SWF (Debuguj > Rysunek). spective (Okno > Perspektywa > Resetuj
aplikacji przeznaczoną do debugowania i za- Otworzy się okno przeglądarki, a następ- perspektywę).
pisuje ją w folderze bin-debug projektu. Ten nie powinien nastąpić powrót do Flash • Kliknij zakładkę Flash w prawym gór-
plik SWF jest większy od wersji nieprzezna- Buildera, gdzie może się pojawić okno nym rogu, aby włączyć perspektywę
czonej do debugowania, ponieważ zawiera dialogowe z pytaniem, czy użytkownik programowania (patrz Rysunek 8).
dodatkowy kod i metadane wykorzystywane chce włączyć perspektywę debugowania • Przełącz z powrotem na perspektywę
przez debuger. Te właśnie pliki SWF są wy- (patrz Rysunek 6). Jeżeli nie nastąpi na- debugowania, klikając zakładkę Flash
korzystywane podczas debugowania aplika- tychmiastowy powrót do Flash Builde- Debug w prawym górnym rogu (patrz
cji. Należy pamiętać, aby po zakończeniu de- ra, należy wybrać jego zadanie na pasku Rysunek 8).
bugowania, a przed użyciem aplikacji, utwo- zadań lub w doku. • Jeżeli nie są widoczne obydwie zakładki,
rzyć mniejszą, nieprzeznaczoną do debugo- • Jeżeli pojawi się okno dialogowe Con- kliknij brzeg lewej zakładki i przeciągnij
wania wersję SWF, wybierając Project > Export firm Perspective Switch (Potwierdź włą- go w lewo (patrz Rysunek 9).
Release Build. czenie perspektywy), należy zazna- • Przejdź do widoku konsoli. Powinien
Aby debugować aplikację, należy użyć po- czyć pole Remember My Decision (Pa- być widoczny komunikat o błędzie, któ-
lecenia Debug (Debuguj) zamiast polecenia miętaj moją decyzję), aby to okno dia- ry pojawił się w przeglądarce podczas
Run (Uruchom). W tym celu należy kliknąć logowe nie było wyświetlane przy każ- uruchamiania aplikacji (patrz Rysunek
przycisk Debug (Debuguj) na głównym pa- dym debugowaniu aplikacji, a następ- 10). Z tego komunikatu wynika, że na-
sku narzędzi lub wybrać Run > Debug (Uru- nie kliknąć Yes (Tak). Flash Builder prze- stąpiło odwołanie do jakiegoś obiektu,
chom > Debuguj). Aplikacja pojawia się w łącza się na perspektywę debugowa- który jeszcze nie został utworzony.
oknie przeglądarki tak samo, jak w przypad- nia, która ma inne okna niż perspekty- • Należy przejrzeć kod w widoku edy-
ku standardowego uruchomienia. Ewentu- wa programowania (patrz Rysunek 7). tora. Wiersz 18 jest podświetlony, co
alne błędy lub ostrzeżenia Flash Playera są Uwaga: Panele mogą być ułożone in- oznacza, że to tam występuje błąd.
wyświetlane w widoku konsoli programu aczej, niż na tym rysunku. Ich układ Strzałka na pasku markerów przy
Flash Builder. można zmieniać przez przeciąganie i tym wierszu wskazuje, że tam zosta-
ło zatrzymane wykonywanie kodu. Po-
wstał problem przy próbie nadania
zmiennej shapeColor wartości wła-
ściwości selectedColor z modułu
ColorPicker.
• Znajdź deklarację modułu ColorPicker
w wierszu 66. colorSelector to nazwa
obiektu modułu ColorPicker.

Problem w wierszu 18 polega na tym, że


jest on wykonywany przed utworzeniem
egzemplarza modułu ColorPicker, zatem
colorSelector jeszcze nie istnieje i nie
można się odwołać do jego właściwości.

• Zakończ sesję debugowania, klikając


czerwony przycisk Terminate (Zakończ)
lub wybierając Run > Terminate (Wyko-
Rysunek 3. Przykładowe rysunki wykonane w aplikacji naj > Zakończ).

Rysunek 5. Debugowanie aplikacji

Rysunek 6. Włączenie perspektywy


Rysunek 4. Komunikat o błędzie debugowania

14 04/2010
Debugowanie aplikacji Flex 4

wyświetlany w widoku konsoli w trakcie de-


bugowania aplikacji.

• Włącz śledzenie łańcucha „in init” we-


wnątrz funkcji init(), wstawiając po-
niższy kod:

private function init():void{


trace("in init");
addEventListener(MouseEvent.MOUSE_
DOWN,onMouseDown);
...

• Debuguj aplikację i obserwuj widok kon-


soli. Wstawiony łańcuch jest niewidocz-
ny. Kod wewnątrz funkcji init() nie jest
wykonywany.
• Patrz na zakładkę Application (Aplika-
cja). Funkcja init() nie jest wywoływa-
Rysunek 7. Perspektywa debugowania na.
• Niech init() będzie funkcją obsługu-
jącą zdarzenie creationComplete apli-
• Zmień kod w wierszu 18, nadając nia, czy kod wewnątrz funkcji jest wyko- kacji:
shapeColor wartość początkową black nywany, oraz badania wartości zmiennych
(czarny): i wyrażeń jest użycie metody trace() — co <s:Application xmlns:fx=
private var shapeColor:uint=0x000000; zostanie tu zademonstrowane. We wcze- "http://ns.adobe.com/mxml/2009"
snym okresie tworzenia aplikacji na platfor- xmlns:s="library://ns.adobe.com/flex/spark"
Wykonaj ponownie debugowanie aplikacji. mę Flash, zanim pojawiło się zintegrowane xmlns:mx="library://ns.adobe.com/flex/mx"
Błąd w przeglądarce lub w widoku konso- środowisko programowania (Integrated De- creationComplete="init()"
li Flash Builder nie powinien już wystę- velopment Environment — IDE) z debuge- >
pować. rem, był to jedyny sposób debugowania
aplikacji. Obecnie, ponieważ istnieje debu- • Zatrzymaj sesję debugowania, a następ-
• Spróbuj coś narysować. ger, można także sprawdzać, czy funkcja ta nie znowu debuguj aplikację i obserwuj
• Nie można niczego narysować. Został jest wywoływana, oraz przeglądać wartości widok konsoli. Zobaczysz śledzony łań-
rozwiązany jeden problem, ale trzeba zmiennych w tym punkcie, umieszczając cuch; funkcja init() jest teraz prawi-
jeszcze znaleźć i rozwiązać inne pro- wewnątrz funkcji punkt wstrzymania i de- dłowo wywoływana.
blemy. bugując aplikację. Zastosowanie punktów • Powróć do przeglądarki i spróbuj coś na-
• Zakończ sesję debugowania. wstrzymania będzie omawiane w następ- rysować. Nadal nie można rysować; trze-
nej części tej serii. ba jeszcze znaleźć inne błędy.
Obserwowanie zmiennych Aby skorzystać z metody trace(), należy • Powróć do Flash Buildera i zatrzymaj se-
i wyrażeń wstawić do niej łańcuch, zmienną lub wyra- sję debugowania.
Następnym krokiem powinno być spraw- żenie, które ma być obserwowane. Wynik jest
dzenie, czy reszta kodu jest wykonywana
prawidłowo. Jednym ze sposobów ustale-

Rysunek 8. Przełączanie pomiędzy Rysunek 10. Lokalizacja błędu w widoku konsoli


perspektywami programowania i debugowania

JEANETTE STALLONS
Jest niezależną instruktorką i konsultantką ds. technologii Flex. Brała udział w szkoleniach pra-
cowników wielu firm, między innymi Adobe, Oracle, Boeing, Wachovia, Morgan Stanley i Char-
les Schwab. Zanim zaczęła działać samodzielnie, pracowała w firmach Allaire, Macromedia
i Adobe w dziale szkoleń i zajmowała się tworzeniem architektury, pisaniem, uczeniem oraz
tworzeniem aplikacji Flash i Flex, a także innych produktów. Jej najnowszy projekt to Ado-
Rysunek 9. Włączanie widoczności zakładek be Flex Learning Paths. Jest jego pomysłodawczynią, programistką oraz głównym ekspertem
obydwu perspektyw w zakresie treści.

www.sdjournal.org 15
Programowanie C++

Wizytator
Upraszczanie zależności przy modyfikacji interfejsu klas

Operacje dla obiektów w hierarchii klas często implementujemy,


wykorzystując funkcje wirtualne. Gdy liczba takich metod rośnie,
klasy mają trudną do określenia odpowiedzialność, kod staje się mało
przejrzysty. Przedstawiona technika rozwiązuje ten problem.

kim podejściu kod dotyczący tej samej funkcji


Dowiesz się: Powinieneś wiedzieć: będzie rozproszony, każda klasa będzie miała
• Co to jest wzorzec wizytatora; • Jak pisać proste programy w C++; fragment funkcjonalności, zmniejsza to czy-
• Jak używać biblioteki boost::variant. • Co to jest dziedziczenie i funkcje wirtualne. telność kodu i utrudnia jego modyfikacje.
Przykładowo, obliczanie statystyk (liczby żoł-
nierzy, liczby czołgów itd.) wymaga, oprócz
klasy przechowującej liczniki, dostarczenia
mi w książce Gamma, Helm, Johnson, Vlis- metody w każdej klasie konkretnej, która
sides ,,Wzorce projektowe'', pozwala spra- aktualizuje te liczniki (Listing 1). Dodatko-
Poziom wić, że modyfikacja funkcjonalności będzie wo klasy reprezentujące jednostki będą mia-
trudności prosta, natomiast złożona będzie modyfika- ły wiele różnych metod, trudno będzie okre-
cja struktury. Przykład ilustrujący omawianą ślić ich odpowiedzialność.
technikę wykorzystuje hierarchię klas Unit, Kod źródłowy będzie bardziej przejrzysty,
przedstawioną na Rysunku 1, reprezentują- jeżeli daną funkcję zrealizujemy w spójnym

D
odawanie nowej klasy do hierar- cą różne oddziały wykorzystywane w grze fragmencie kodu. Dla rozpatrywanego przy-
chii polega na utworzeniu tej klasy strategicznej: jednostki piechoty, czołgi, wy- kładu możemy aktualizację liczników prze-
oraz nadpisaniu metod. Modyfiku- rzutnie rakiet itd. Podczas tworzenia kolej- nieść do klasy, która je zawiera (patrz Listing
jemy jedynie fragment kodu źródłowego za- nych wersji gry hierarchii tej prawie nie bę- 2). Klasy reprezentujące jednostki będą prost-
wierający implementację nowej klasy, najczę- dziemy zmieniać, typy jednostek będą usta- sze, nie muszą zawierać metod związanych
ściej tworzymy nowy plik. Dodawanie meto- lone we wczesnych etapach implementacji. z obliczaniem statystyk. Kod realizujący da-
dy, która będzie nadpisywana, jest bardziej Spodziewamy się, że będą zmieniane funkcje ną funkcję jest umieszczony w jednym miej-
skomplikowane: modyfikujemy klasę bazową operujące na jednostkach lub grupach jedno- scu, np. kod obliczania statystyk zawiera kla-
oraz wszystkie klasy pochodne, dostarczając stek. Funkcje te będą obliczały wartość bojo- sa Statistics (Listing 2). Niestety przedsta-
odpowiednią funkcjonalność. W tym przy- wą jednostek, ich szybkość przemieszczania, wiona technika wymaga jawnego badania ty-
padku modyfikacja obejmuje wiele różnych będą automatycznie rozmieszczać jednostki pu obiektu za pomocą mechanizmów reflek-
fragmentów kodu. Taka asymetria pomię- na danym obszarze, obrazować te jednostki, sji, co jest dosyć czasochłonne. Łańcuch in-
dzy modyfikacją hierarchii klas a modyfika- generować statystyki itp. Zbiór tych funkcji strukcji warunkowych, który porównuje typ
cją interfejsu w tej hierarchii jest niewygod- będziemy rozszerzali podczas tworzenia ko- obiektu ze wszystkimi typami w hierarchii,
na, zwłaszcza gdy częściej będziemy modyfi- lejnych wersji aplikacji. nie jest eleganckim rozwiązaniem.
kować funkcjonalność niż strukturę. Artykuł Gdyby funkcje operujące na jednostkach Wzorzec wizytatora pozwala zachować za-
przedstawia wzorzec projektowy wizytatora implementować jako metody klas, tak jak po- lety wynikające z umieszczenia kodu realizu-
(inna nazwa to odwiedzający), który pozwa- kazano na Listingu 1, to wystąpi niedogod- jącego daną funkcję w jednym miejscu (poza
la uprościć zależności przy modyfikacji funk- ność, o której była mowa na początku, doda- klasami w hierarchii), usuwając konieczność
cji operujących na hierarchii klas. W dalszej wanie lub usuwanie metody wymaga mody- badania typu obiektu za pomocą łańcucha in-
części artykułu przedstawiona zostanie za- fikacji wszystkich klas w hierarchii. Przy ta- strukcji dynamic_cast.
sada działania tego wzorca, przykład użycia
oraz wykorzystanie wizytatora w bibliotece
boost::variant.
Szybki start
Aby uruchomić przedstawione przykłady, należy mieć dostęp do kompilatora C++ oraz edy-
tora tekstu. Niektóre przykłady korzystają z udogodnień dostarczanych przez bibliotekę bo-
Niedogodności ost::variant, warunkiem ich uruchomienia jest instalacja bibliotek boost (w wersji 1.36 lub
nadpisywania metod nowszej) Na wydrukach pominięto dołączanie odpowiednich nagłówków oraz udostępnia-
Wzorzec projektowy wizytatora (inna na- nie przestrzeni nazw, pełne źródła dołączono jako materiały pomocnicze.
zwa to odwiedzający), opisany między inny-

16 04/2010
Wizytator

Wzorzec wizytatora plementacji operacji na przechowywanym wych, wielkość (zajętość pamięci) jest zależ-
Wzorzec wizytatora wykorzystuje pomocni- obiekcie. Szablon variant tworzy typ złożo- na od wielkości największego obiektu skła-
czą hierarchię klas, zwaną hierarchią wizy- ny, który jest równoważny unii z C (definio- dowego. Typ ten posiada semantykę warto-
tującą lub odwiedzającą, która jest związana wanej przez union) lub rekordowi z warian- ści, to znaczy konstruktor kopiujący, opera-
z hierarchią klas, dla której chcemy odwie- tami z Pascala. Obiekt typu variant mo- tor przypisania, można go stosować jako ar-
dzać (wizytować) obiekty. Klasa bazowa tej że przechowywać jeden z obiektów składo- gument funkcji, zwracać jako wartość, prze-
nowej hierarchii, abstrakcyjny wizytator, do-
starcza metod, które będą wołane dla poszcze- Listing 1. Przykład funkcji, która wymaga mody�kacji wszystkich elementów w hierarchii
gólnych obiektów klas hierarchii odwiedza-
nej. Dla omawianego przykładu abstrakcyjny class Statistics { //Klasa reprezentuje statystyki
wizytator został przedstawiony na Listingu 3. public:
Dla każdej klasy odwiedzanej (wizytowanej) void addSoldiers(int n){ soldiers_ += n; }; //aktualizuje licznik
dostarczamy metodę accept, która woła odpo- void addTanks(int n){ tanks_ += p; }
wiednią metodę wizytatora. Metoda accept void addRockets(int n) { rockets_ += n; }
jest wykorzystywana przez wizytator do uzy- private:
skania informacji o typie obiektu. Modyfikacja int soldiers_; //licznik żołnierzy
hierarchii odwiedzanej została przedstawiona int tanks_;
na Listingu 4. Dla wizytatora, który jest argu- int rockets_;
mentem, wołana jest jedna z przeciążonych };
metod visit, w zależności od typu obiektu, class Unit { //klasa bazowa
który nadpisuje metodę accept. public: //zawiera interfejs do metody aktualizującej statystyki
Operacje na hierarchii odwiedzanej, na virtual void update(Statistics& s) = 0;
przykład zbieranie statystyk, implementu- };
jemy, wykorzystując klasę pochodną po abs- void Infantry::update(Statistics& s)//aktualizuje statystyki
trakcyjnym wizytatorze (Listing 5). Technika s.addSoldiers( countSoldiers() );
ta pozwala na uzyskanie typu obiektu odwie- }
dzanego bez rzutowania dynamicznego, wy- void Tank::update(Statistics& s) {
korzystujemy dwukrotnie funkcje wirtualne. s.addTanks(1);
Innymi słowy, metody visit dostają jako ar- }
gument obiekt odpowiedniego typu, wybór
tego typu odbywa się poprzez mechanizm Listing 2. Odwrócenie zależności pozwala zgrupować kod aktualizujący w jednym miejscu,
jednak wymaga jawnego badania typu obiektów
późnego wiązania, użyty przy wyborze odpo-
wiedniej metody accept oraz przy wołaniu void Statistics::update(const Unit& unit) {
metody visit. if(const Infantry* p = dynamic_cast<const Infantry*>(&unit) ) { //jawne badanie
Przykład użycia wizytatora zawiera Listing typu
6. Metoda accept jest wołana dla obiektu u, soldiers_ += p->countSoldiers();
który jest typu Tank (ale wskaźnik jest ty- } else if (dynamic_cast<const Tank*>(&unit) {
pu Unit*). Metoda ta będzie wołała metodę ++tanks_;
visit(Tank&) dla przekazanego argumentu, } // łańcuch warunków dla wszystkich typów w hierarchii
czyli dla obiektu s. }
Wzorzec wizytatora stosuje się po to, aby
ułatwić dodawanie nowych operacji dla hie- Listing 3. Abstrakcyjny wizytator dla jednostek z omawianej gry strategicznej
rarchii klas, oraz po to, aby metody wykonują- class Visitor { //abstrakcyjny wizytator
ce daną funkcję umieścić w tym samym miej- public:
scu. Dodanie nowego wizytatora nie wymaga virtual void visit(Infantry&) = 0;
wiele zmian, należy wyprowadzić nową klasę virtual void visit(Tank&) = 0;
z klasy Visitor, a następnie nadpisać w niej virtual void visit(Rocket&) = 0;
odpowiednie metody. Inne klasy nie są zmie- };
niane, w szczególności nie są zmieniane klasy
w hierarchii wizytującej. Listing 4. Metoda w hierarchii odwiedzanej wykorzystywana przez wizytatory
class Unit { //Klasa bazowa
boost::variant public:
Wzorzec wizytatora jest wykorzystywany virtual void accept(Visitor& v) = 0;
w szablonie klasy boost::variant do im- };
class Infantry : public Unit {
public:
virtual void accept(Visitor& v) { v.visit(*this); } //woła v.visit(Infantry&)
};
class Tank : public Unit {
public:
virtual void accept(Visitor& v) { v.visit(*this); } //woła v.visit(Tank&)
};
Rysunek 1.

www.sdjournal.org 17
Programowanie C++

chowywać w kontenerach standardowych. nie static_visitor(parametrem tego sza- Wariant, czyli unia z kontrolą typów i wy-
Typami składowymi mogą być klasy dostar- blonu jest typ zwracany z metod wizytu- różnikiem bieżącego typu, korzysta z pro-
czające konstruktorów, co jest zabronione jących, możemy zwracać wartość w meto- gramowania generycznego do bezpiecznego
przy używaniu unii w C++. Przykłady wy- dzie wizytującej). Metody wizytujące imple- przechowywania obiektów w tym samym
korzystania wariantów pokazano na wy- mentuje się jako przeciążone operatory wo- miejscu pamięci. Narzuty pamięciowe są ma-
druku 7. łania funkcyjnego, zamiast metod visit, co łe, związane jedynie z wyróżnikiem aktual-
Dostęp do przechowywanych warto- jest częstą praktyką przy implementacji tego nego typu (obecnie 4 bajty na platformach
ści wariantu jest możliwy poprzez wi- wzorca w C++. Wizytację uruchamia funk- wspieranych przez boost). Wewnętrzny bu-
zytator. Musimy dostarczyć konkretne- cja apply_visitor, do której przekazujemy for ma wielkość maksymalnego obiektu, któ-
go wizytatora dziedziczącego po szablo- obiekt wizytatora i wariant, patrz Listing 8. ry może być przechowywany w wariancie
(uwzględniając wyrównanie).
Listing 5. Klasa obliczająca statystyki jako wizytator
Podsumowanie
class Statistics : public Visitor { //wizytator konkretny Wizytator pozwala implementować funk-
public: cje operujące na hierarchii klas jako oddziel-
virtual void visit(Infantry& u) {//wołane dla jednostek piechoty ne typy, co upraszcza zależności w aplikacji
soldiers_ += u.countSoldiers(); i pozwala na tworzenie przejrzystego kodu.
} Oprócz wielu zalet wizytator ma kilka wad.
virtual void visit(Tank& t) {//wołane dla czołgów Operacje na hierarchii odwiedzanej, imple-
++tanks_; mentowane w wizytatorze, wykorzystują in-
} terfejs klas odwiedzanych, więc muszą istnieć
/* ... */ odpowiednie metody, które daną operację po-
}; zwolą wykonać. Może to prowadzić do złama-
nia enkapsulacji, na przykład jeżeli do realiza-
Listing 6. Przykład użycia wizytatora
cji funkcji implementowanych w wizytatorze
Unit* u = new Tank(); //dostarcza jednostkę wymagana jest znajomość wewnętrznego sta-
Statistics s; //tworzy obiekt statystyk nu obiektów odwiedzanych.
s.accept(u); //dwukrotnie wykorzystuje funkcje wirtualne Wizytator wprowadza cykliczne zależności
pomiędzy hierarchią odwiedzaną i odwiedza-
Listing 7. Wariant, przykłady użycia jącą, które sprawiają, że implementacja tego
//obiekt, który może przechowywać jeden z trzech typów wzorca wymaga użycia deklaracji klas. Klasa
variant<int, double, std::string> var;//obiekt przechowujący int bazowa hierarchii odwiedzanej (klasa Unit)
var = "Hej";//teraz przechowuje napis jest zależna od deklaracji klasy bazowej hie-
var = 2.7; //teraz przechowuje wartość typu double rarchii klas wizytujących (klasa Visitor), po-
//przykładowa deklaracja funkcji, która ma argument typu variant nieważ zawiera deklarację metody accept.
void function(const variant<int, double, std::string>& v); Abstrakcyjny wizytator jest zależny od de-
function(var); //przekazuje jako argument do funkcji klaracji wszystkich klas konkretnych w hie-
rarchii odwiedzanej, a więc od klas Infantry,
Listing 8. Dostęp do wartości przechowywanych w wariancie Tank, Rocket. Klasy konkretne w hierarchii
typedef variant<int, double, string> Var; odwiedzanej zależne są od klasy bazowej,
class MyVisitor //dostęp za pomocą wizytatora dziedziczą po niej. Wprowadzając dodatko-
: public boost::static_visitor<void> { //metody visit zwracają void we klasy i wykorzystując dziedziczenie wie-
public: lobazowe, możemy pozbyć się zależności cy-
void operator()(int& i) { /* metoda dla obiektu typu int */ } klicznych w tym wzorcu. Taką implementa-
void operator()(double& d) { /* ... */ } cję opisano w książce „Średnio zaawansowane
void operator()(string& s) { /* ... */ } programowanie w C++” (patrz ramka).
}; Wizytator dostarcza mechanizmu równo-
apply_visitor(MyVisitor, var); //wizytacja obiektu var ważnego dodawaniu metod do klas. Dostar-
cza on mechanizmu wyboru odpowiedniej
metody w zależności od dwóch typów, jed-
nym jest typ obiektu odwiedzanego, dru-
Więcej w książce gim typ wizytatora. Rozszerzeniem tej tech-
Zagadnienia dotyczące współcześnie stosowanych technik w języku C++, wzorce projekto-
we, programowanie generyczne, prawidłowe zarządzanie zasobami przy stosowaniu wy- niki są wielometody, które będą omówione
jątków, programowanie wielowątkowe, ilustrowane przykładami stosowanymi w bibliotece w jednym z kolejnych artykułów.
standardowej i bibliotekach boost, zostały opisane w książce ,,Średnio zaawansowane pro-
gramowanie w C++'', która ukaże się niebawem.
ROBERT NOWAK
Adiunkt w Zakładzie Sztucznej Inteligencji Insty-
tutu Systemów Elektronicznych Politechniki War-
W Sieci szawskiej, zainteresowany tworzeniem aplikacji
• http://www.boost.org – dokumentacja bibliotek boost; bioinformatycznych oraz zarządzania ryzykiem.
• http://www.open-std.org – dokumenty opisujące nowy standard C++. Programuje w C++ od ponad 10 lat.
Kontakt z autorem: rno@o2.pl

18 04/2010
Programowanie Java

Java EE 6
Nowa era aplikacji serwerowych

Artykuł przedstawia możliwości najnowszej, szóstej, wersji technologii


Java Enterprise Edition (Java EE). Wersja ta wprowadziła wiele istotnych
modyfikacji, dzięki którym Java EE zostało znacznie unowocześnione.
Poprawiono funkcjonalność technologii, kładąc jednocześnie duży
nacisk na łatwość użycia, elastyczność.
projektowe przeszły istotną ewolucję, która
Dowiesz się: Powinieneś wiedzieć: musiała znaleźć odbicie po stronie Java EE.
• Jak zmieniła się Java EE 6 w stosunku do • Czytelnik powinien znać podstawy języka Twórcy szóstej wersji Java EE, opracowu-
wcześniejszej wersji; Java oraz technologii Java EE. jąc nową specyfikację, wzięli sobie do ser-
• Jak zmieniła się architektura aplikacji Java EE; ca potrzeby programistów, uwzględnili do-
• Jak utworzyć nowoczesną aplikację WWW świadczenia najlepszych i najbardziej spraw-
przy użyciu JEE 6; dzonych istniejących rozwiązań, takich jak
• Jak wdrażać i testować aplikacje JEE 6; Spring Framework, JBoss Seam czy Google
Guice. Zobaczmy teraz, jaki jest efekt ich wy-
siłków.
WS, korzystająca z dobrodziejstw konfigura-
cji przy pomocy metadanych, zastąpiła trud- Przykładowa aplikacja
Poziom ne w użyciu interfejsy SAAJ i JAX-RPC. Za- Aby dobrze poznać możliwości Java EE 6,
trudności dbano także tym razem o to, aby usługi sie- utworzymy, wdrożymy i przetestujemy apli-
ciowe JAX-WS z powodzeniem komunikowa- kację webową Todo, służącą do zarządzania
ły się z usługami pisanymi w innych technolo- listą rzeczy do zrobienia. Pozwala ona doda-
giach, przede wszystkim .NET. wać użytkowników oraz przypisywać im za-

P
rzyglądając się funkcjonalności JEE Java EE 5 była krokiem ewolucyjnym, nie dania. Można także wyświetlić listę zadań i
6, będziemy śledzić proces tworzenia rewolucyjnym. Poprawiono to, co tego wy- użytkowników. Główny ekran aplikacji mo-
przykładowej aplikacji, która zade- magało, a te elementy technologii, które spe- żemy obejrzeć na Rysunku 1. Aplikacja jest
monstruje nowe interfejsy programistyczne cjalnie nikomu się nie naraziły, pozostawiono dość prosta, ale pozwoli nam dobrze zrozu-
i rozwiązania architektoniczne. bez zmian. Charakterystyczny był zwłaszcza mieć omawiane technologie, pokazując ich
Wprowadzenie Java EE w wersji 5 zaowo- brak istotnych zmian w warstwie webowej zastosowanie w konkretnej sytuacji.
cowało usunięciem najważniejszych pro- technologii Java EE. Kolejność omawiania technologii będzie
blemów, z którymi borykali się programi- Wprowadzenie Java EE 6 jest posunięciem odpowiadała ich miejscu w cyklu budowy
ści aplikacji serwerowych. Największy na- znacznie radykalniejszym. Oprócz zmian ty- aplikacji. Rozpoczniemy od modelu dome-
cisk położono wtedy na dopracowanie naj- powo ewolucyjnych, ulepszających istniejące nowego aplikacji, który znajduje swoje odbi-
bardziej nielubianej technologii JEE – kom- technologie – na przykład wprowadzenie Ja- cie w warstwie trwałego przechowywania da-
ponentów EJB. va Persistence API 2.0 – przedefiniowano zu- nych, następnie przejdziemy do implemen-
Komponenty EJB są teraz zwyczajnymi pełnie sposób patrzenia na tworzenie i wdra- tacji logiki biznesowej przy pomocy kompo-
klasami Java (ang. Plain Old Java Objects żanie aplikacji, projektowanie architektury nentów EJB, a na końcu zajmiemy się inter-
– POJO), a cała konfiguracja modułu EJB czy wręcz na strukturę samego serwera apli- fejsem użytkownika.
może być zrealizowana przy pomocy meta- kacyjnego.
danych (ang. annotations), zamiast pliku kon- Zmiany te nie wzięły się znikąd. Przede Java Persistence API 2.0
figuracyjnego. Komponenty encyjne EJB, od- wszystkim w świecie IT jest bardzo wyraź- JPA 2.0 jest konsekwentnym krokiem w stro-
powiedzialne we wcześniejszych wersjach ny trend wykorzystywania coraz powszech- nę utworzenia standardowego i funkcjonal-
JEE za komunikację z bazą danych, nie były niej złożonych aplikacji internetowych (ang. nego mostu relacyjno-obiektowego dla apli-
satysfakcjonującym rozwiązaniem i zastąpiła Rich Internet Applications), co powoduje, że kacji Java. Dodano do technologii parę drob-
je nieporównywalnie lepsza technologia: Java webowy interfejs użytkownika odgrywa co- nych udogodnień, między innymi:
Persistence API. raz większą rolę. W rezultacie potrzebna jest
Znaczącą innowacją było wprowadzenie technologia, która maksymalnie ułatwia two- • usuwanie osieroconych rekordów w bazie
wygodnego interfejsu programistycznego na rzenie takich aplikacji. Dodatkowo sposób danych, w przypadku gdy został usunięty
potrzeby usług sieciowych. Technologia JAX- budowania architektury aplikacji, wzorce rekord pełniący rolę rodzica w relacji;

20 04/2010
Java EE 6

• ułatwiono modelowanie kolekcji obiek- Klasa Attachment (Listing 2) jest bardzo dację w jednym miejscu. Odbywa się to po-
tów – nie ma już potrzeby tworzenia do- prosta i zawiera tylko opis szczegółów doty- przez dodanie odpowiednich metadanych
datkowej, czasem zupełnie zbytecznej, czących wyglądu pól w bazie danych, w szcze- przy wybranych polach klas.
encji JPA wraz z odpowiednią relacją; gólności pole content jest zadeklarowane ja- Wybór miejsca centralnej konfiguracji wa-
• rozbudowano język zapytań Java Persi- ko pole typu BLOB o wielkości 512 kB. lidacji jest dość naturalny: klasy reprezentu-
stence Query Language (JPQL) o nowe Metadane @Entity, @Id, @GeneratedValue jące dane – w naszym przypadku są to encje
użyteczne elementy. są standardowymi elementami konfiguracji JPA. W którejkolwiek warstwie aplikacji bę-
JPA oznaczającymi, odpowiednio, że: ma- dziemy używać tych klas, to walidacja będzie
Pojawiły się także z dawna wyczekiwane po- my do czynienia z encją JPA, pole taskId re- zawsze przeprowadzana w ten sam sposób.
ważniejsze zmiany, przede wszystkim inter- prezentuje klucz podstawowy w tabeli od- Do dyspozycji mamy szereg domyślnych
fejs zapytań Criteria, znany dobrze użyt- powiadającej encji Task, a jego wartość bę- walidatorów, między innymi @NotNull,
kownikom Hibernate. dzie generowana domyślnym mechanizmem @Size, @Min, których znaczenie jest dość
Zapytania Criteria są konstruowane dla określonej bazy danych. Z kolei @Column oczywiste, oprócz tego możemy spawdzać,
z obiektów Java, są dzięki temu silnie typo- i @Temporal uściślają wygląd schematu ba- czy pola typu Date lub Calendar reprezentu-
wane, w odróżnieniu od zapytań JPQL, które zy danych. ją datę w przyszłości (@Future), bądź w prze-
są łańcuchami znaków. Znakomicie ułatwia Oprócz metadanych konfigurujących en- szłości (@Past). Niektóre metadane są zasto-
to tworzenie złożonych, dynamicznie budo- cję Task z punktu widzenia mechanizmu sowane do encji Task (Listing 1).
wanych zapytań czy refaktoryzację aplikacji i, trwałości, znajduje się tam też kilka metada- Bardzo elastycznym walidatorem jest @Pa
oczywiście, poprawia wydajność aplikacji. nych związanych z inną, zupełnie nową tech- ttern(regexp="<wyrażenie regularne>"),
Drugą znaczącą zmianą jest możliwość pe- nologią. który testuje poprawność wartości pola
symistycznego blokowania rekordów. JPA 1.0 względem podanego wyrażenia regularnego.
pozwalał wyłącznie na optymistyczne bloko- Walidatory Można również tworzyć własne metadane
wanie rekordów, wykorzystując mechanizm Walidatory (ang. Bean Validation) dają nam walidujące, definiujące narzucane przez nas
wersjonowania rekordów. JPA 2.0 pozwala ustandaryzowany sposób na sprawdzanie po- warunki poprawności danych. Łatwo można
także na stosowanie blokad pesymistycznych, prawności danych. Jest to w sumie dość pro- napisać, na przykład, metadane @Pesel lub
czyli faktycznego zablokowania dostępu do ste rozwiązanie technologiczne, ale niezwy- @Nip, sprawdzające poprawną wartość nume-
rekordu w bazie danych. kle pożyteczne. ru PESEL lub NIP.
Budowę aplikacji Todo rozpoczniemy od W praktyce największe problemy z wali-
modelu domenowego, opisującego najważniej- dacją pojawiają się wtedy, gdy musi być ona EJB 3.1
sze jej elementy. Zadanie do zrobienia repre- przeprowadzana w kilku warstwach apli- Mamy gotowy model domenowy aplika-
zentuje encja Task, użytkownika encja User. kacji. Wyobraźmy sobie, że pewne dane są cji Todo, czas go ożywić, dodając warstwę
Dodatkowo możemy zadaniom przypisywać wprowadzane w warstwie interfejsu użyt- wykonującą na nim różnego rodzaju opera-
kategorie (encja Category) oraz dodawać do kownika, następnie przetwarzane przez cje – logikę biznesową. Java EE zakłada, że
nich załączniki, opisywane klasą Attachment. warstwę logiki biznesowej
Listing 1 pokazujący encję Task. Ponieważ za- i wreszcie trwale zapisywa-
łączniki interesują nas wyłącznie jako część za- ne przez warstwę komuni-
dania, to nie ma powodu tworzyć dla nich en- kacji z bazą danych. W za-
cji i relacji między nią a Task. Wykorzystujemy sadzie powinniśmy wali-
więc uproszczony sposób definiowania pola ty- dować dane we wszystkich
pu kolekcja. W JPA 2.0 nie musimy tworzyć, trzech miejscach, co oczy-
sztucznej w tym kontekście, relacji – wystar- wiście może spowodować
czy przy polu attachments dodać metadane duplikowanie kodu lub, co
@ElementCollection @CollectionTable(name gorsza, pojawienie się róż-
= "task_attachments"), a mechanizm JPA bę- nych reguł walidacji w każ-
dzie sam dbał o zapisywanie do bazy danych od- dej z warstw.
powiednich rekordów. Po stronie kodu Java po Walidatory Java EE po-
prostu używamy obiektu typu Set. zwalają skonfigurować wali-

Rysunek 2. Diagram wdrożenia aplikacji Todo na serwerze


Java EE wersja 5. Szarym kolorem oznaczone są opcjonalne pliki
Rysunek 1. Główny ekran aplikacji Todo kon�guracyjne

www.sdjournal.org 21
Programowanie Java

do tego celu będą wykorzystywane kompo- ne lub dowolne inne dane, które powinny być 6. Tym razem cała aplikacja jest zamknięta w
nenty EJB. Kiedyś taki wybór był traktowa- współdzielone. jednym archiwum WAR.
ny jako zło konieczne, często też zupełnie Singletony można konfigurować pod Rysunek 3 sygnalizuje możliwość wdroże-
ignorowano tę technologię, przede wszyst- względem zachowania w środowisku wielo- nia aplikacji w kontenerze EJB działającym w
kim ze względu na niewygodną konfigura- wątkowym. Domyślnie są one bezpieczne dla trybie wbudowanym. Nowy, standardowy in-
cję, trudności przy testowaniu i tworzeniu wątków, ale gdy udostępniają one dane tylko terfejs programistyczny pozwala na urucho-
elastycznego modelu obiektowego. Dodat- do odczytu, to można to zachowanie zmie- mienie kontenera EJB i wdrożenie na nim
kowo komponenty EJB musiały być wdra- nić, z korzyścią dla wydajności aplikacji. komponentów z poziomu kodu aplikacji Ja-
żane jako osobne archiwa, co stanowiło nie- W komponentach EJB można tworzyć me- va SE, ułatwia to bardzo testowanie kompo-
potrzebną komplikację w przypadku aplika- tody asynchroniczne, których wywołanie nie nentów EJB.
cji webowych. powoduje wstrzymania wątku klienta. Meto- Przy okazji nowej wersji JEE naprawio-
Część problemów związanych z tą techno- dy takie mogą albo nie zwracać wartości, albo no jeszcze jedno, bolesne zwłaszcza dla po-
logią rozwiązało wprowadzenie wersji EJB zwracać obiekt java.util.concurrent.Futu czątkujących użytkowników Java EE, nie-
3.0; najnowsza odsłona specyfikacji stawia re<V>, dzięki któremu można stwierdzić, czy dociągnięcie specyfikacji – brak jakiejkol-
kropkę nad i, dając nam do ręki narzędzie o jest już dostępny wynik działania metody i go wiek standaryzacji nazw JNDI dla kompo-
bardzo dużych możliwościach (deklaratyw- pobrać. Dotychczas jedynym mechanizmem nentów EJB. Nadal oczywiście można im
ne zarządzanie transakcjami, klastrowalność, asynchronicznym w EJB były komponenty nadawać dowolne nazwy, ale gdy tego nie
itp.), a jednocześnie proste w użyciu i funk- zorientowane na komunikaty (ang. Message zrobimy, serwer przypisuje im przenośną,
cjonalne. Driven Beans). ustandaryzowaną nazwę (ang. portable JNDI
Pierwszym ułatwieniem jest brak koniecz- Znaczącym ułatwieniem dla programi- name), zgodnie z następującym schema-
ności użycia interfejsów dla komponentów stów tworzących aplikacje WWW jest moż- tem java:global[/<nazwa-aplikacji>]/
EJB o dostępie lokalnym (ang. No-interface liwość wdrażania komponentów EJB w ar- <nazwa-modułu>/<nazwa-komponentu>
view), wszystkie publiczne metody kompo- chiwach WAR aplikacji WWW (ang. war [!<kwalifikowana-nazaw-interfejsu>].
nentu są automatycznie dostępne dla lokal- deployment). Kolejną przyjemną niespodzianką w spe-
nych klientów. Diagram wdrożenia aplikacji Todo, napisa- cyfikacji jest „odtłuszczone EJB” (ang. EJB
Pojawił się także nowy typ komponentów nej przy użyciu Java EE 5, demonstruje Rysu- Lite). Skąd potrzeba takiej niskokalorcz-
sesyjnych – Singleton. Wszyscy klienci apli- nek 2: część webowa aplikacji znajduje się w nej wersji EJB? Otóż wiele typów aplika-
kacji używają tylko jednej instancji takiego archiwum WAR, komponenty EJB w archi- cji, zwłaszcza webowych, nie potrzebuje
komponentu, dzięki czemu łatwo tworzyć wum JAR, a całość zamknięta jest w archi- całej funkcjonalności EJB, na przykład in-
komponenty, które pełnią rolę schowków na wum EAR, które dopiero jest wdrażane na tegracji z CORBA czy zdalnych interfej-
dane, przechowują ustawienia konfiguracyj- serwer. Dla kontrastu popatrzmy na Rysu- sów – stąd pomysł utworzenia uproszczo-
nek 3, na którym nej specyfikacji, dzięki której użycie kom-
Listing 1. Encja JPA reprezentująca zadanie do zrobienia widzimy model ponentów będzie proste i łatwe w naucze-
wdrożeniowy To- niu się. Również twórcy serwerów aplika-
@Entity do zbudowanej cyjnych czy webowych będą mieli łatwiejsze
@NamedQuery(name="findAllTasks", query="select t from Task t") przy użyciu JEE zadanie przy implementacji nowego stan-
@XmlRootElement
public class Task implements Serializable {
@Id @GeneratedValue
private Long taskId;
@Column(length = 32)
@NotNull @Size(min=5, max=32)
private String name;
@Temporal(TemporalType.TIMESTAMP)
private Calendar startDate;
@Temporal(TemporalType.TIMESTAMP)
@Future
private Calendar dueDate;
private String description;
@Min(0) @Max(10)
private int priority;
private Status status;
@ElementCollection
@CollectionTable(name = "task_attachments")
private Set<Attachment> attachments = new HashSet<Attachment>();
@ManyToOne
private User user;
@ManyToOne
private Category category;

//metody get/set ...


} Rysunek 3. Diagram wdrożenia aplikacji Todo na serwerze Java EE
wersja 6. Szarym kolorem oznaczone są opcjonalne pliki kon�guracyjne

22 04/2010
Java EE 6

dardu, mając możliwość ograniczenia się do jąc metamodel dla naszej encji, czyli specjal- chomienia aplikacji na klastrze serwerów.
wersji EJB Lite. ną klasę, która służy do konstrukcji zapyta- Singletony EJB 3.1 usuwają za jednym zama-
Zobaczmy teraz EJB 3.1 w akcji. Apli- nia. Jednak dla naszej, jak by nie patrzeć, dość chem wszystkie tego typu utrudnienia. Zaj-
kacja Todo zawiera dwa komponenty EJB, prostej aplikacji, użycie metamodelu należa- mijmy się klasą CategoryCache (Listing 4),
UserBean, zajmujący się obsługą użytkowni- łoby uznać za przesadę (chyba że w przyszło- która jest zaimplementowana właśnie jako ta-
ków, oraz TaskBean, który jest odpowiedzial- ści zamierzamy dodać jeszcze kilkadziesiąt ki komponent.
ny za operacje na zadaniach. Drugi z tych nowych zapytań...). Podstawowa konfiguracja sprowadza się do
komponentów, umieszczony na Listingu 3, Metoda TaskBean.changeStatus demon- umieszczenia przy nazwie klasy metadanej
jest ciekawszy, skoncentrujmy się zatem na struje możliwość pesymistycznego bloko- @Singleton. Możemy także określić dokład-
nim. Na pierwszy rzut oka nie wygląda zbyt wania rekordów. Przy zmianie statusu za- nie, jak ma się zachowywać singleton w śro-
przyjaźnie do analizy, nie należy jednak przej- dania chcemy uniknąć jednoczesnej mo- dowisku wielowątkowym, ustawienie warto-
mować się dużą liczbą umieszczonych przy dyfikacji rekordu przez równolegle prze- ści metadanej @Lock na LockType.READ ozna-
nim metadanych – stopniowo przyjrzymy się biegające transakcje, w tym celu blokuje- cza, że zezwalamy na jednoczesny dostęp
im wszystkim. my dostęp do zadania na potrzeby aktual- wielu wątków do współdzielonych zasobów.
Ponieważ nasza aplikacja ma mieć z założe- nej transakcji, dodając do wywołania me- Druga dostępna wartość, LockType.WRITE,
nia wyłącznie interfejs WWW, to TaskBean tody find klasy EntityManager argument daje z kolei taki dostęp wyłącznie jednemu
jest zadeklarowany jako bezstanowy kompo- LockModeType.PESSIMISTIC_WRITE. wątkowi w jednym czasie.
nent EJB (metadana @Stateless), nie posia- Każde zadanie w naszej aplikacji może być Użyta w przykładzie metadana @Startup
dający interfejsu (metadana @LocalBean). przypisane do kategorii. W naszym modelu powoduje inicjalizację komponentu w chwi-
Wszystkie metody tego komponentu wy- zestaw kategorii nie może być modyfikowa- li wdrożenia aplikacji, dzięki temu nawet w
konują jakieś operacje bazodanowe, dla- ny przez użytkowników. Lista kategorii za- przypadku długotrwałej inicjalizacji można ją
tego potrzebny nam jest uchwyt do klasy tem nie zmienia się w czasie działania apli- przeprowadzić w sposób niezauważalny dla
EntityManager, która wykonuje działania kacji i z tego powodu nie warto jej wyciągać użytkownika.
na encjach JPA. Instancję tej klasy dostarcza za każdym razem, gdy jest potrzebna, z bazy Funkcjonalność singletonów jest znacz-
nam serwer aplikacji, używając do tego me- danych, tylko trzymać ją w pamięci. Najwy- nie bardziej rozbudowana niż pokazuje to
chanizmu wstrzykiwania zależności, co wy- godniejszym sposobem implementacji takie- nasz przykład. Można, na przykład, samo-
muszamy, umieszczając przy polu em me- go schowka z listą kategorii jest wykorzysta- dzielnie zarządzać zachowaniem wielowąt-
tadaną @PersistenceContext(unitName = nie komponentu Singleton. kowym, synchronizując wybrane metody.
"todopu") – todopu jest nazwą konfiguracji Dotychczas implementacja singletonów Jeżeli używamy kilku singletonów inicja-
JPA do pracy z konkretną bazą danych, znaj- była trudna. Trzeba było samodzielnie ra- lizowanych przy starcie aplikacji, to moż-
dującą się w pliku konfiguracjnym persisten- dzić sobie z problemem jednoczesnego do- na określić kolejność ich tworzenia. W
ce.xml, dołączonym do aplikacji. stępu wielu wątków do takiego obiektu czy przypadku synchronizowanych singleto-
Metoda TaskBean.findTasks, znajdująca poprawnym zachowaniem w przypadku uru- nów można ustawić maksymalny czas trzy-
się na samym dole Listingu 3, wykorzystuje
wspomniany wcześniej interfejs Criteria w
typowym dla niego zastosowaniu. Metoda
pozwalająca wyszukać zadania w bazie przyj-
muje wiele argumentów, z których użytkow-
nik może podać tylko te, które go interesują.
Konstruowanie zapytania JPQL uwzględnia-
jącego wszystkie możliwe zestawy podanych
parametrów jest co najmniej uciążliwe; inter-
fejs Criteria bardzo ułatwia to zadanie.
Tak na marginesie, to przykład demonstru-
je tylko proste użycie Criteria, w szczególno-
ści nazwy pól encji Task, które uwzględnia-
my w wyszukiwaniu, są podawane jako łań-
cuchy znaków. Można tego uniknąć, budu-

Listing 2. Klasa reprezentująca załącznik


do zadania

public class Attachment implements


Serializable{
@Column(length=32)
private String name;
@Lob
@Column(length=1024*512)
private Byte[] content;

//metody get/set ...


}
Rysunek 4. Wysokopoziomowy model aplikacji Todo zrealizowanej w technologii JEE 5

www.sdjournal.org 23
Programowanie Java

mania blokady, co w przypadku użycia złożonej funkcjonalności, testy integracyjne ło to jednak tylko ograniczonej liczby a prio-
@Lock(LockType.WRITE) chroni nas przed wymagają większego lub mniejszego udzia- ri określonych przez specyfikację zasobów
zakleszczaniem wątków aplikacji. łu usług serwera aplikacji. Nic nie stoi na (komponenty EJB, źródła baz danych, czyli
Mamy napisany fragment logiki biznesowej przeszkodzie, aby używać istniejących roz- obiekty DataSource, EntityManager techno-
aplikacji, warto by ją teraz przetestować. Z po- wiązań (wspomniany Spring Pitchfork), ale logii JPA itp.). Niestety na tym kończyły się
zoru jest to bardzo proste zadanie, gdyż kom- teraz mamy znacznie prostszą opcję – in- możliwości wykorzystania tego użytecznego
ponenty EJB, podobnie jak encje JPA, są zwy- terfejs programistyczny serwera w trybie wzorca projektowego.
kłymi klasami Java, których można używać wbudowanym (ang. embeddable EJB conta- JEE 6 jest pod tym względem prawdzi-
niezależnie od serwera aplikacji. Trzeba jed- iner). Pozwala on na uruchomienie serwe- wym przełomem. Technologia wstrzykiwa-
nak pamiętać, że w przypadku komponentów ra i wdrożenie na nim aplikacji z poziomu nia zależności – CDI, czyli Contexts and
EJB bardzo istotną rolę pełnią usługi, których kodu Java. Dependency Injection for the Java EE Plat-
normalnie dostarcza serwer aplikacji. Najbar- Przykład na Listingu 5 pokazuje fragment form – jest teraz częścią JEE. Pozwala ona
dziej potrzebna jest inicjalizacja używanych klasy TaskBeanTest, która używa JUnit-a do na wstrzykiwanie praktycznie dowolnych
zasobów i klas poprzez wstrzykiwanie zależ- sprawdzenia poprawności działania metody klas i dodatkowo umożliwia określenie kon-
ności – na przykład EntityManagera. Zazwy- TaskBean.addTask. Metoda oznaczona me- tekstu, w jakim będą funkcjonować. Kon-
czaj trudno się obejść bez mechanizmu auto- tadaną @BeforeClass inicjalizuje serwer w tekst odpowiada w pewnym sensie czaso-
matycznego zarządzania transakcjami. Z tych trybie wbudowanym, a metoda addTaskTest wi życia komponentu, co najłatwiej zrozu-
powodów aplikacje z komponentami EJB za- sprawdza, czy udało się dodać nowe zadanie. mieć w przypadku aplikacji WWW: obiekt
zwyczaj testowało się na serwerze, co nie jest Warto zwrócić uwagę na wykorzystanie może istnieć tylko przez czas obsługi żąda-
szczególnie wygodne, albo symulowało się je- domyślnej nazwy dla komponentu, java: nia klienta, ale także przez cały czas trwania
go zachowanie: samodzielnie lub wykorzy- global/todo/TaskBean, zgodnej z opisaną wcze- jego sesji lub wręcz istnieć przez cały czas
stać do tego celu gotową bibliotekę, na przy- śniej konwencją. działania aplikacji.
kład Spring Pitchfork. Wróćmy na chwilę do klasy TaskBean
JEE 6 przychodzi nam z pomocą także Wstrzykiwanie zależności (Listing 3), interesuje nas metoda
i w aspekcie testowania aplikacji. Proste te- Java EE 5 umożliwiała odwoływanie się do setTaskNotifier umieszczona na sa-
sty jednostkowe tak czy inaczej będziemy zasobów zarządzanych przez serwer aplikacji mym początku klasy, inicjalizująca pole
chcieli wykonywać niezależnie od serwera przy użyciu mechanizmu wstrzykiwania za- taskNotifier. Proszę zwrócić uwagę na me-
aplikacji, ale przetestowanie jakiejś bardziej leżności (ang. Dependency Injection), dotyczy- tadaną @Inject, która informuje serwer apli-
kacji, że powinien nam dostarczyć w tym
miejscu obiekt typu TaskNotifier. Infor-
mację o tym, która konkretnie klasa ma być
wstrzykiwana, jest dostarczana przy pomo-
cy odpowiedniej metadanej (kwalifikato-
ra). W przykładzie użyta jest standardowa
metadana @Default, służąca konwencjonal-
nie do oznaczania domyślnej implementacji,
ale można bez problemu samodzielnie two-
rzyć własne metadane. Postąpiliśmy zresz-
tą tak w naszym przypadku, pisząc metada-
ną @SMS (Listing 6) na potrzebę implementa-
cji, w której powiadomienie o dodaniu nowe-
go zadania powinno być wysyłane SMS-em, a
nie emailem.
Interfejs TaskNotifier znajduje się
na Listingu 7, podobnie jak dwie imple-
mentacje tego typu: EmailTaskNotifier i
SMSTaskNotifier. Klasa EmailTaskNotifier
jest oznaczona metadaną @Default, zaś
SMSTaskNotifier metadaną @SMS.
Możliwość nieograniczonego wykorzysta-
nia wstrzykiwania zależności jest chyba naj-
bardziej znaczącą zmianą, jaką przyniosło
JEE 6. Pozwala to na znacznie lepszą modu-
laryzację aplikacji i znakomicie ułatwia jej
testowanie. Jest to naprawdę nowa jakość w
świecie Java EE.
Dotychczas, jeżeli chcieliśmy używać
wstrzykiwania zależności, to musieliśmy uży-
wać jakiejś alternatywy, zazwyczaj Spring Fra-
mework. Rozwiązanie to, mimo swojej nie-
wątpliwej użyteczności, cierpiało i cierpi na
pewne niedociągnięcia i braki w funkcjonal-
Rysunek 5. Wysokopoziomowy model aplikacji Todo zrealizowanej w technologii JEE 6 ności w porównaniu z pełnym standardem

24 04/2010
Java EE 6

Listing 3. Klasa TaskBean skon�gurowana do pracy jako komponent lokalny EJB, udostępniająca swoje metody jako usługę REST oraz źródło danych
dla kontrolek JavaServer Faces

@Stateless return new ArrayList(0);


@LocalBean }else{
@Path("/") if(u.getTasks() == null){
@Named("taskBean") return new ArrayList(0);
}else{
public class TaskBean { return new ArrayList<Task>(u.getTasks());
@PersistenceContext(unitName = "todopu") }
private EntityManager em; }
}
private TaskNotifier taskNotifier;
public List<Task> getAllTasks(){
/** return em.createNamedQuery("findAllTasks").getResultLis
* Wstrzykujemy domyślną implementację klasy TaskNotifier t();
* Można użyć także @SMS zamiast @Default }
*/
@Inject public void changeStatus(Task t, Status newStatus){
public void setTaskNotifier(@Default TaskNotifier Task task = em.find(Task.class, t.getTaskId(),
taskNotifier) { LockModeType.PESSIMISTIC_WRITE);
this.taskNotifier = taskNotifier; task.setStatus(newStatus);
} }

public Task addTask(User u, String name, Calendar dueDate, public List<Task> findTasks(String name, String
String description) { description, Calendar startDate,
Task t = new Task(name, dueDate, description); Calendar dueDate, int priotity, Status status){
if(u != null){ CriteriaBuilder cb = em.getCriteriaBuilder();
User usr = em.find(User.class, u.getUserId()); CriteriaQuery<Task> query = cb.createQuery(Task.class);
if(usr != null){ Root<Task> task = query.from(Task.class);
t.addUser(u); query.select(task);
taskNotifier.sendNotification(u); query.distinct(true);
}
} List<Predicate> criteria = new ArrayList<Predicate>();
em.persist(t); if(name != null){
return t; ParameterExpression<String> p = cb.parameter(String.cl
} ass, "name");
criteria.add(cb.equal(task.get("name"), p));
public Task addTask(Task t, User u){ }
User user = em.find(User.class, u.getUserId()); if(description != null){
if(t.getCategory() != null){ ParameterExpression<String> p = cb.parameter(String.cl
Query findCategory = em.createNamedQuery("findCategoryB ass, "description");
yName"); criteria.add(cb.equal(task.get("description"), p));
findCategory.setParameter("name", t.getCategory().get }
Name()); TypedQuery<Task> q = em.createQuery(query);
Category c = (Category) findCategory.getSingleResult(); if(name != null)
t.assignCategory(c); q.setParameter("name", name);
} if(description != null)
t.addUser(user); q.setParameter("description", description);
em.persist(t);
return t; //pozostałe argumenty wywołania metody pominięte
} return q.getResultList();
}
@GET }
@Path("/task/{userId}")
@Produces({"application/json"})
public List<Task> listTasks(@PathParam("userId") Long
userId){
User u = em.find(User.class, userId);
if(u == null){

www.sdjournal.org 25
Programowanie Java

Java EE. Dodatkowo Spring jest produktem ciem na potrzeby aplikacji uruchamianych na zamienia istniejący zasób, a jeżeli chcemy da-
jednej firmy, co przywiązuje do niej jego użyt- serwerze aplikacyjnym. Dzięki temu moż- ny zasób usunąć, to wysyłamy żądanie typu
kowników na dobre i na złe. Teraz mamy w na sobie wyobrazić sytuację, że kod normal- DELETE.
ręku technologię łączącą możliwości zarów- nie używany na serwerze aplikacyjnym bę- „Czyste” usługi REST działają właśnie tak,
no JEE, jak i Spring Framework. dzie można uruchomić bez żadnych zmian że na każdym zasobie można wykonać jed-
Warto wspomnieć przy tej okazji o tym, (oprócz konfiguracji) przy pomocy Spring ną z czterech wymienionych wyżej operacji,
jak wygląda w ogóle implementacja wzorca Framework czy JBoss Seam. w praktyce oczywiście jest to zbyt ogranicza-
wstrzykiwania w Javie – temat do pewne- jące i mimo niezadowolenia purystów często
go momentu dość kontrowersyjny i czasem Usługi sieciowe REST nazwę operacji określa się w samym URL-u,
powodujący niepotrzebne zamieszanie i na- Usługi sieciowe REST (ang. Representational na przykład http://nazwa.domeny/todo/task?ta-
pięcia. Sytuacja jest tak naprawdę prosta. Ja- State Transfer) urodziły się jako dosyć abstrak- skId=XXX&akcja=kasowanie.
va dorobiła się wreszcie standardowego me- cyjna koncepcja wymiany danych pomiędzy Co pociąga ludzi w tego typu usługach?
chanizmu konfiguracji wstrzykiwania zależ- aplikacjami już w 2000 roku. Z czasem oka- Przede wszystkim prostota. Napisanie usłu-
ności. Odpowiedzialna jest za to specyfika- zało się, że pomysł jest całkiem dobry i da się gi sieciowej jest proste w praktycznie dowol-
cja JSR 330: Dependency Injection for Ja- go przekuć na praktyczne zastosowania. Na nym języku programowania, wykorzystanie
va. Promotorami tego rozwiązania są dwaj czym polegają te usługi sieciowe? jej, czyli implementacja klienta, jest równie
czołowi dostawcy konkretnych (i popular- Pierwsze założenie polega na tym, że każ- łatwa. Można zresztą nawet tego nie robić,
nych) implementacji wstrzykiwania zależ- dy zasób jest reprezentowany przez URL. tylko posłużyć się wtyczką Poster do przeglą-
ności: SpringSource (producent Spring Fra- Na przykład zadanie w naszej aplikacji mo- darki Mozilla Firefox. Pozwala ona wywołać
mework) oraz Google (producent Google że być reprezentowane przez URL http: dowolną usługę typu REST. Kontrast łatwo-
Guice). //nazwa.domeny/todo/rest/task/XXX, gdzie ści użycia w stosunku do tradycyjnych usług
Aby zapewnić elastyczność i możliwość XXX jest identyfikatorem zadania. Jeżeli sieciowych, bazujących na standardzie SOAP
tworzenia różnych implementacji, specyfika- chcemy na tym zasobie wykonać jakąś ope- i WSDL, jest aż nadto wyraźny.
cja JSR 330 celowo jest bardzo prosta i ogól- rację, to używamy do tego celu jednej ze zde- Kolejna zaleta to brak narzuconego for-
na, z założenia ma być elementem Java SE. finiowanych w ramach protokołu HTTP me- matu komunikatu usługi REST. Może być
Wprowadzona w ramach Java EE 6 technolo- tod. Metoda GET służy do pobierania da- to XML, tak jak w usługach SOAP/WSDL,
gia CDI bazuje na JSR 330 i jest jej rozwinię- nych, metoda POST dodaje nowy zasób, PUT ale równie dobrze można użyć zwykłego tek-
stu, tekstu CSV czy bardzo popularnego for-
Listing 4. Klasa implementująca singleton dający dostęp do listy zde�niowanych kategorii matu JSON.
zadań Jak w Javie tworzymy takie usługi? Najpry-
mitywniejsza metoda polega na napisaniu
@Singleton odpowiedniego serwletu, przetwarzającego
@Startup żądanie określonego typu dla odpowiednie-
@Lock(LockType.READ) go zasobu HTTP. Alternatywnie, można wy-
@Named("categoryCache") korzystać interfejs JAX-WS, który, mimo że
public class CategoryCache { przeznaczony do usług SOAP/WSDL, pozwa-
@PersistenceContext la tworzyć też usługi REST – niestety w prak-
private EntityManager em; tyce nie jest to szczególnie łatwe.
private List<Category> categories = new ArrayList<Category>(); JEE 6 daje programistom zupełnie nowy
interfejs do pracy z usługami typu REST. Jest
@PostConstruct on zarówno bardzo funkcjonalny, jak i prosty
void initCategories(){ w użyciu.
categories = em.createNamedQuery("findAllCategories").getResultList(); Najlepiej zobaczyć od razu odpowiedni
} przykład. Znowu wrócimy do komponentu
EJB TaskBean, który już oglądaliśmy na Li-
public List<Category> getCategories(){ stingu 3. Jeżeli klasa ma pełnić rolę usługi sie-
if(categories.isEmpty()){ ciowej REST, to musimy wskazać, jakie adre-
Category[] initialCategories = new Category[]{ sy mają być przekierowywane na jej metody.
new Category("BRAK"), Konfigurujemy te adresy przy pomocy meta-
new Category("PRACA"), danej javax.ws.rs.Path, umieszczonej przy
new Category("DOM"), nazwie klasy.
new Category("PRYWATNE"), Każda z metod klasy może teraz obsłu-
}; giwać określone żądania i ścieżki adresu
for (Category c : initialCategories) { URL. TaskBean zawiera jedną taką metodę,
em.persist(c); List<Task> listTasks(@PathParam("userI
em.flush(); d") Long userId), zwracającą listę zadań dla
categories.add(c); użytkownika o podanym w parametrze me-
} tody identyfikatorze. Metoda ta jest urucha-
} miana w przypadku żądań typu GET (me-
return Collections.unmodifiableList(categories); tadana @GET), dla URL-a, który kończy się
} ścieżką /task/identyfikator-użytkownika, za to
} zachowanie odpowiada metadana @Path("/
task/{userId}"). Parametr userId URL-a

26 04/2010
Java EE 6

umieszczony w nawiasach klamrowych jest rvlets 3.0 pozwalają na rozbicie pliku kon- filtry serwletów do aplikacji dynamicznie, z
automatycznie podstawiany w miejsce para- figuracyjnego web.xml na kilka części (ang. poziomu kodu.
metru wywołania metody, oznaczonego me- web fragments), dzięki czemu różnego ro- Serwlety były jedną z najbardziej trafio-
tadaną @PathParam("userId"). Oczywiście dzaju szkielety aplikacyjne mogą „same sie- nych specyfikacji Java EE, proste, bardzo wy-
nic nie stoi na przeszkodzie, żeby tych pa- bie” konfigurować. Jest to wyraźny ukłon w dajne, stały się bazą praktycznie wszystkich
rametrów było więcej. Metoda zwraca od- stronę producentów wszelkich dodatków do webowych szkieletów aplikacyjnych. Mimo
powiedź w formacie typu JSON (metadana standardu JEE. to stagnacja nie bardzo im służyła, na szczę-
@Produces({"application/json"})). Serwlety w wersji 3.0 mają wbudowany ście większość potrzeb została poprawnie za-
W jaki sposób przetestować działanie tej mechanizm przetwarzania asynchroniczne- adresowana w Java EE 6.
metody. Potrzebna nam będzie do tego celu go, dzięki czemu wątek obsługujący klienta
wyłącznie przeglądarka stron WWW oraz in- nie musi być zatrzymywany na czas odwoły- JavaServer Faces 2.0
formacja o konfiguracji REST w naszej aplika- wania się do zasobów o wolnym czasie dostę- Kolejną technologią, która została solidnie
cji WWW. Aby zdobyć tę ostatnią, zajrzyjmy pu. Łatwiej także tworzyć aplikacje wykorzy- przemodelowana, jest JSF. Ważną zmianą
do pliku web.xml, umieszczonego na Listin- stujące AJAX. wprowadzoną w JSF 2.0 jest użycie Face-
gu 8. Ważne jest dla nas mapowanie serwletu Nowością jest możliwość konfigurowania lets jako standardowego sposobu tworzenia
com.sun.jersey.spi.container.servlet. serwletów przy pomocy metadanych, de- stron JSF, dzięki czemu od razu pozbywamy
ServletContainer, który odpowiada za im- skryptor wdrożenia web.xml nie jest już wy- się wielu problemów związanych z użyciem
plementację usług sieciowych na używanym magany. Można też dodawać serwlety oraz JSP jako bazy dla stron JSF. W odróżnieniu
przez nas serwerze GlassFish 3. Ponieważ ma-
powanie to jest skonfigurowane jako ścieżka Listing 5. Fragment klasy testującej komponent TaskBean z wykorzystaniem JUnit-a i serwera
/rest/*, to ilekroć będziemy chcieli dostać się uruchamianego w trybie wbudowanym
do usługi sieciowej REST, będziemy poprze-
dzać skonfigurowaną dla niej ścieżkę (meta- public class TaskBeanTest {
dana @Path) przedrostkiem /rest/. private static EJBContainer c;
Zbierając razem wszystkie te informacje,
możemy wejść po wdrożeniu aplikacji na ser- @BeforeClass
wer na adres URL http://127.0.0.1:8080/todo/ public static void setUpClass() throws Exception {
rest/task/identyfikator-zadania – otrzymamy Map<String, Object> p = new HashMap<String, Object>();
listę zadań dla użytkownika o wskazanym p.put(EJBContainer.APP_NAME, "todo");
identyfikatorze. Zakładamy tutaj, że serwer c = EJBContainer.createEJBContainer(p);
GlassFish jest uruchomiony na adresie lokal- }
nym, na porcie 8080. Lista będzie miała po-
stać tablicy formatu JSON; przykładowy wy- @AfterClass
nik działania usługi sieciowej prezentuje Li- public static void tearDownClass() throws Exception {
sting 9. (dla poprawienia czytelności część c.close();
wyświetlanych pól została usunięta). }
Bardzo użytecznym mechanizmem, w ja-
ki jest wyposażona implementacja usług sie- @Test
ciowych serwera GlassFish 3 (projekt Jer- public void addTaskTest() {
sey), jest automatyczna konwersja typów Ja- try {
va na format JSON lub XML – korzystaliśmy Context ic = c.getContext();
z tego w naszym przykładzie. Aby mogła być TaskBean taskBean = (TaskBean) ic.lookup("java:global/todo/TaskBean");
ona przeprowadzona do encji Task (Listing Task t = taskBean.addTask(null, "z1234567", new GregorianCalendar(2010,
1), musieliśmy dodać metadaną technologi Calendar.FEBRUARY, 22), "z opis");
JAXB @XmlRootElement; resztą zajął się sam Logger.getLogger(TaskBeanTest.class.getName()).log(Level.INFO, "ID nowego
serwer aplikacji. zadanie: " + t.getTaskId());
Dodanie wygodnego interfejsu dla usług Assert.assertNotNull(t.getTaskId());
REST jest wielką wygodą dla programi- } catch (NamingException ex) {
stów Java EE, gdyż do szerokiej gamy zasto- Logger.getLogger(TaskBeanTest.class.getName()).log(Level.SEVERE, null, ex);
sowań są one idealnie przystosowane. Wy- fail("Nie znaleziony komponent o nazwie java:global/todo/TaskBean");
starczy spojrzeć, jak wiele usług jest w ten }
sposób udostępniane, poczynając od serwi- }
sów społecznościowych, takich jak Twitter // pominięte pozostałe metody testujące
czy Facebook, a kończąc na usłudze S3 fir- }
my Amazon.
Listing 6. Metadana używana jako kwali�kator konkretnego sposobu informowania
Aplikacje WWW użytkowników o dodanym zadaniu, użyta na Listingu 6
JavaEE 6, w przeciwieństwie do swojej po-
przedniczki, wprowadziła sporo zmian w @Qualifier
interfejsach służących do tworzenia aplika- @Retention(RUNTIME)
cji WWW. @Target({TYPE, METHOD, FIELD, PARAMETER})
Solidny lifting przeszły praktycznie nie public @interface SMS {}
modyfikowane od wielu lat serwlety. Se-

www.sdjournal.org 27
Programowanie Java

od JSP strony Facelets w naturalny sposób znacznik <ui:define> zawierający zawar- ry ma wypełnić użytkownik, jest połączo-
wpisują się w cykl życia JSF. Facelets pozwa- tość wypełniającą odpowiednie miejsca sza- ny z odpowiednim komponentem zarzą-
lają również na bardzo proste tworzenie sza- blonu, zdefiniowane wcześniej przy pomocy dzanym JSF (ang. Managed Bean), deklaru-
blonów stron JSF i prostych komponentów. <ui:insert>. Omawiana strona zawiera ele- ją to wyrażenia języka wyrażeń (ang. Expres-
Zobaczymy, jak wygląda w praktyce wyko- menty top i content, element foot i menu są sion Language – EL), na przykład #{ta-
rzystanie Facelets w naszej aplikacji. Każda pominięte, co oznacza, że przyjmują domyśl- skController.task.name} dla nazwy za-
strona aplikacji jest zbudowana na bazie tego ną wartość, określoną w szablonie. dania. Cykl życia JSF powoduje, że w mo-
samego szablonu, który można zobaczyć na JSF 2.0 standaryzuje również sposób za- mencie kliknięcia w przycisk dodawania
Listingu 10. Na Rysunku 1 widzieliśmy głów- rządzania zasobami aplikacji takimi jak sty- zadania inicjalizowany jest obiekt klasy
ny ekran aplikacji, każda inna formatka skła- le CSS, plik JavaScript, grafika itp. Zgodnie z TaskController (Listing 12), której przypi-
da się z tych samych elementów: standardo- konwencją zasoby są przechowywane w pod- sany jest identyfikator taskController. Na-
wego menu i stopki oraz generowanych przez katalogach katalogu resources. Podkatalogi te stępnie przeprowadzana jest konwersja i wa-
każdą ze stron treści oraz nagłówka. Szablon są nazywane bibliotekami (ang. library). W lidacja pobranych danych, wypełniane są ni-
Facelets musi być poprawną stroną XHTML. zawiązku z tym, chcąc odwołać się do pli- mi pola klasy, a na końcu jest wywoływana
Najważniejszymi elementami szablonu są ku CSS na stronie szablonu template.xhtml, metoda „przyczepiona” do przycisku, czyli
elementy <ui:insert>, które definiują ele- wystarczy napisać <h:outputStylesheet TaskController.addTask.
menty szablonu, wypełniane przez korzysta- library="css" name="default.css"/>. W poprzedniej wersji JSF informa-
jące z niego strony. Oznacza to, że plik default.css znajduje się w cja o tym, która klasa ma identyfikator
Zobaczmy teraz, jak szablon jest wyko- katalogu /resources/css aplikacji. taskController ,była umieszczana w pli-
rzystywany przez stronę dodawania nowych Przyjrzyjmy się teraz dokładniej stronie ku konfiguracyjnym faces-config.xml. JSF 2.0
zadań addTask.xhtml (Listing 11). Każdy addTask.xhtml. Każdy element strony, któ- pozwala zrobić to samo przy pomocy meta-
danych. Klasa TaskController zawiera de-
klarację @ManagedBean(name="taskContro
Dodatkowe informacje ller") @RequestScoped, konfigurującą na-

• Oficjalnym zbiorem informacji dotyczących Java EE 6 jest strona http://java.sun.com/ zwę identyfikatora komponentu zarządzane-
javaee/technologies, w szczególności znajdziemy tam odnośniki do specyfikacji techno- go JSF oraz zasięg tego komponentu – będzie
logii wchodzących w skład JEE. on istniał tak długo, jak trwa żądanie użyt-
• Dobry opis nowych możliwości Java EE znajdziemy na stronie http://java.sun.com/ kownika.
developer/technicalArticles/JavaEE/JavaEE6Overview.html.
Kolejną nowością jest sposób definiowa-
• Dobrym wstępem do usług sieciowych REST jest artykuł Wikipedii http://
en.wikipedia.org/wiki/Representational_State_Transfer. nia nawigacji między stronami. W poprzed-
• Wtyczka Poster do przeglądarki Firefox, służąca do testowania usług sieciowych REST, niej wersji JSF jedynym miejscem, gdzie moż-
jest dostępna pod adresem http://code.google.com/p/poster-extension. na było definiować reguły nawigacji mię-
• Informacje o formacie JSON, użytym w przykładzie demonstrującym usługi sieciowe, dzy stronami, był wspomniany plik konfi-
znajdziemy na stronie http://www.json.org. guracyjny faces-config.xml. Metody wywoły-
Uwaga: odnośniki do stron firmy SUN mogą ulec zmianie w wyniku przejęcia jej przez firmę
wane z poziomu stron JSF (akcje), takie jak
Oracle. TaskController.addTask w naszym przy-
kładzie, zwracały łańcuch znaków. W pliku
konfiguracyjnym określaliśmy, gdzie ma być
Listing 7. Klasy odpowiedzialne za implementację powiadamiania o dodanych zadaniach przekierowany użytkownik, jeżeli w wyni-
ku wykonania metody-akcji zostanie zwró-
//TaskNotifier.java cona określona wartość. Teraz mamy do dys-
public interface TaskNotifier { pozycji prostszą metodę: metoda akcji może
void sendNotification(User u); zwrócić po prostu nazwę strony JSF, w na-
} szym przykładzie metoda addTask przekiero-
wuje w ten sposób użytkownika na stronę li-
//EmailTaskNotifier.java stTasks.xhtml.
@Default Następna, bardzo istotna zmiana, jaką
public class EmailTaskNotifier implements TaskNotifier{ przynosi JSF 2.0, to możliwość wykorzy-
public void sendNotification(User u) { stania komponentów EJB bezpośrednio z
Logger.getLogger(EmailTaskNotifier.class.getName()).log(Level.INFO, poziomu języka wyrażeń JSF. Dotychczas,
"Wysyłamy maila do " + u.getUsername()); jeżeli chcieliśmy wykorzystać komponent
} EJB w ramach technologii JSF, to musieli-
} śmy każdą jego metodę delegować w klasie
komponentu zarządzanego JSF. Powodowa-
// SMSTaskNotifier.java ło to konieczność pisania mnóstwa zbytecz-
@SMS nego kodu.
public class SMSTaskNotifier implements TaskNotifier{ Teraz, jeżeli potrzebujemy na przykład li-
public void sendNotification(User u) { stę zadań, to możemy ją bezpośrednio po-
//send sms brać z komponentu, który ją generuje, po-
} przez wyrażenie #{taskBean.allTasks}.
} Aby taka operacja była możliwa, kompo-
nent EJB musi mieć nadaną nazwę, zajmuje
się tym metadana @Named, która dla kompo-

28 04/2010
Java EE 6

nentu TaskBean (Listing 3) ma wartość "ta- śniejszej wersji, J2EE) zmieniała się w dość ła” komponenty encyjne przed niewydaj-
skBean". Komponent musi mieć także me- zasadniczy sposób. Pojawienie się JEE 5 bar- nym dostępem oraz obiektów transferu da-
todę getAllTasks, aby zgodnie z konwencją dzo uprościło architekturę części aplikacji nych (ang. Data Transfer Objects – DTO), po-
JavaBeans była ona widziana przez język wy- odpowiedzialnej za komunikację z bazą da- zwalających przesyłać dane pomiędzy war-
rażeń jako allTasks. Przykład użycia listy nych. Java Persistence API pozwoliło zrezy- stwami aplikacji. Model aplikacji Todo zre-
zadań znajdziemy na stronie listTasks.xhtml gnować z użycia komponentów encyjnych alizowanej przy pomocy JEE 5 pokazuje Ry-
(Listing 13). EJB, które ze względów wydajnościowych, sunek 4.
Możliwość użycia komponentów EJB na zbyt małą funkcjonalność i elastyczność Najnowsza wersja JEE idzie jeszcze o krok
stronach JSF upraszcza architekturę aplika- wymagały tworzenia dodatkowych warstw dalej. W przypadku lokalnego dostępu do
cji, komponenty zarządzane JSF mogą zaj- w aplikacji: fasady sesyjnej, która „ukrywa- komponentów EJB nie są potrzebne inter-
mować się teraz wyłącznie tym, do czego
zostały stworzone, czyli logiką biznesową Listing 8. Deskryptor wdrożenia web.xml aplikacji Todo; na Listingu pominięto przestrzenie nazw
odpowiedzialną za wyświetlanie interfejsu
użytkownika. Nie muszą już zawierać me- <?xml version="1.0" encoding="UTF-8"?>
tod, których jedynym przeznaczeniem by- <web-app version="3.0" ...>
ło wyłącznie wywoływanie metod kompo- <context-param>
nentów EJB. <param-name>javax.faces.PROJECT_STAGE</param-name>
JSF 2.0 ma wbudowane wsparcie dla tech- <param-value>Development</param-value>
nologii AJAX. Wystarczy zadeklarować na </context-param>
formatce chęć asynchronicznego odwoła- <servlet>
nia do komponentu zarządzanego i właśnie <servlet-name>Jersey Web Application</servlet-name>
tak będzie ono zrealizowane. Nowy znacz- <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-
nik, <f:ajax>, pozwala wykonywać żąda- class>
nia ajaksowe na różne sposoby. Można wska- <init-param>
zać metodę, która ma być wywołana asyn- <param-name>com.sun.jersey.config.property.packages</param-name>
chronicznie oraz fragment strony odświe- <param-value>pl.xoft.todo.ejb</param-value>
żany w wyniku tego wywołania. Podobną </init-param>
funkcjonalność znają użytkownicy bibliote- <load-on-startup>1</load-on-startup>
ki RichFaces. </servlet>
Przykład innego zastosowania <f:ajax> <servlet>
mamy na stronie addTask.xhtml, przy <servlet-name>Faces Servlet</servlet-name>
okazji walidacji pola name (nazwa zada- <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
nia). Znacznik <f:ajax event="keyup" <load-on-startup>1</load-on-startup>
render="namemsg"/> oznacza, że w przy- </servlet>
padku zdarzenia polegającego na puszcze-
niu klawisza wartość pola name będzie wy- <servlet-mapping>
słana na serwer, gdzie będzie poddana stan- <servlet-name>Jersey Web Application</servlet-name>
dardowej konwersji i walidacji. Wartość pa- <url-pattern>/rest/*</url-pattern>
rametru render oznacza, że w wyniki żąda- </servlet-mapping>
nia zostanie odświeżony znacznik o identy-
fikatorze namemsg – przechowuje on infor- <servlet-mapping>
mację o błędach (o ile jakieś są). Jeżeli czytel- <servlet-name>Faces Servlet</servlet-name>
nik zastanawia się, skąd się bierze warunek <url-pattern>/faces/*</url-pattern>
walidacji tego pola, to musi wrócić na sam </servlet-mapping>
początek artykułu, do Listingu 1, i spraw-
dzić, jaki walidator został zdefiniowany dla <welcome-file-list>
pola Task.name. <welcome-file>faces/index.xhtml</welcome-file>
Wsparcie dla AJAX-a w JSF 2.0 znacz- </welcome-file-list>
nie wykracza poza to, co demonstruje przy- </web-app>
kład. Można bardzo precyzyjnie określić, jak
będzie wyglądało żądanie asynchroniczne,
Listing 9. Wynik działania usługi sieciowej REST zaimplementowanej przy pomocy klasy
włącznie z możliwością użycia własnego ko- TaskBean (Listing 3); część mniej ciekawych pól została usunięta
du JavaScript – JSF 2.0 tylko nam pomaga, w
niczym nie przeszkadzając. {"task":[
JSF nie miało dotychczas dobrej prasy, {"dueDate":"2010-03-24T01:00:00+01:00","name":"nazwa1","status":"NOT_
wprowadzenie JSF 2.0 powinno to zmienić. STARTED","user":
Ułatwienie tworzenia aplikacji AJAX, do- {"email":"mis1@uszatek.pl","fname":"Uszatek","username":"mis1"}},
bra współpraca z EJB czy pozbycie się bala- {"dueDate":"2010-03-24T01:00:00+01:00","name":"nazwa1","status":"NOT_
stu JSP powinno być zachętą dla wielu pro- STARTED","user":
gramistów. {"email":"mis1@uszatek.pl","fname":"Uszatek","username":"mis1"}},
]
Architektura aplikacji Java EE }
Architektura aplikacji Java EE (czy wcze-

www.sdjournal.org 29
Programowanie Java

fejsy, punktami końcowymi usług REST mo- ne JSF z kodu delegującego metody z kompo- projektowych w rodzaju fabryki (ang. Factory,
gą być komponenty EJB, dodatkowo strony nentów EJB. Factory method) czy lokalizatora usług (ang.
JSF mogą bezpośrednio używać komponen- Wbudowanie wstrzykiwania zależności Service Locator), co oczywiście znowu zmniej-
tów EJB, co oczyszcza komponenty zarządza- (CDI) w JEE uwalnia nas od użycia wzorców sza liczbę potrzebnych klas.
Uproszczoną, zgodną z JEE 6, architekturę
aplikacji Todo pokazuje Rysunek 5.
Uruchomienie przykładów dołączonych do artykułu
Kod źródłowy wszystkich przykładów jest dostępny razem z czasopismem oraz pod adresem
http://www.xoft.pl/wp-content/uploads/2010/02/todo.zip. Nowa struktura JEE: elastycz-
Przykłady są udostępnione w postaci projektu środowiska NetBeans. Aby je otworzyć, należy ność i rozszerzalność
zainstalować NetBeans w wersji 6.8 lub nowszej (wersję Java lub All). Trzeba także pamiętać, Przez długi czas Java EE była traktowana
żeby w czasie instalacji NetBeans został zainstalowany serwer GlassFish. przez jej twórców jako sztywny zestaw tech-
Następnie należy rozpakować archiwum todo.zip i po uruchomieniu NetBeans wybrać File >
nologii, z których każda miała swoje miejsce
Open Project, wskazać katalog z projektem, powstały po rozpakowaniu i kliknąć Open Project.
Jeżeli chcemy uruchomić testy aplikacji, musimy wcześniej uruchomić bazę danych. W oknie i powinna być użyta w określonym celu. Na
Services NetBeans należy kliknąć prawym klawiszem na połączeniu do bazy danych o adresie szczęście, ze względu na elastyczność Java
URL jdbc:derby://localhost/1527/sample i wybrać z menu kontekstowego Connect.... Gdy baza EE, nie było problemów z tym, żeby posłu-
danych będzie już uruchomiona, wracamy do okna Projects i odnajdujemy w naszym projekcie giwać się tylko wybranymi elementami JEE.
todo folder Test Packages, w którym znajdują się dwie klasy testujące działanie naszej aplikacji. Programiści nagminnie używali Hiberna-
Testy uruchamiamy, klikając prawym klawiszem na nazwie klasy i wybierając Run File.
te zamiast komponentów encyjnych EJB al-
Wdrożenie aplikacji na serwerze GlassFish jest równie proste, wystarczy kliknąć prawym klawi-
szem na nazwie projektu i z menu kontekstowego wybrać Run. NetBeans sam uruchomi ser- bo ograniczali się do stosowania wyłącznie
wer i wdroży na niego aplikację, domyślnie będzie ona dostępna przez przeglądarkę stron technologii webowych JEE plus jednego z
WWW pod adresem http://127.0.0.1:8080/todo. wielu dostępnych szkieletów aplikacyjnych
(począwszy od Struts, a kończąc na JBoss Se-
am i Spring Framework).
Listing 10. Szablon Facelets dla naszej aplikacji – plik template.xhtml Standard Java EE zupełnie ignorował fakt,
że ludzie, tworząc aplikacje serwerowe, wy-
<?xml version='1.0' encoding='UTF-8' ?> bierają to, co im się podoba lub używają szkie-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ letów aplikacyjnych „przykrywających” stan-
TR/xhtml1/DTD/xhtml1-transitional.dtd"> dardowe interfejsy Java EE. Java EE 6 zmienia
<html xmlns="http://www.w3.org/1999/xhtml" to podejście radykalnie.
xmlns:ui="http://java.sun.com/jsf/facelets" Przede wszystkim wprowadzono możli-
xmlns:h="http://java.sun.com/jsf/html"> wość definiowania profili serwerów aplika-
cyjnych. Dotychczas serwer mógł uzyskać
<h:head> certyfikację wyłącznie wtedy, gdy implemen-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> tował wszystkie technologie Java EE, w rezul-
<h:outputStylesheet library="css" name="cssLayout.css"/> tacie takie popularne serwery WWW jak Jet-
<h:outputStylesheet library="css" name="default.css"/> ty czy Tomcat nie istniały z punktu widze-
<title>Aplikacja Todo</title> nia Java EE.
</h:head> Profile serwerów definiują zestawy tech-
nologii, które muszą wejść w skład profilu,
<h:body> by być uznane za zgodne z nim. Obecnie je-
dynym, bo najbardziej potrzebnym, jest pro-
<div id="top"> fil dla aplikacji WWW (ang. Web Profile). Z
<ui:insert name="top">Top</ui:insert> czasem można sobie wyobrazić powstawanie
</div> profili powiązanych z jakimiś bardziej specy-
ficznymi typami aplikacji, na przykład tele-
<div> komunikacyjnych, bankowych itp.
<div id="left"> Kolejna zmiana to ukłon w stronę szkie-
<ui:insert name="left"> letów aplikacyjnych. Dotychczas ich użycie
<ui:include src="menu.xhtml"/> pociągało za sobą konieczność samodzielne-
</ui:insert> go skonfigurowania aplikacji do pracy z ni-
</div> mi. Zazwyczaj polegało to na wprowadze-
<div id="content" class="left_content"> niu zmian do pliku konfiguracyjnego aplika-
<ui:insert name="content">Content</ui:insert> cji webowej web.xml. Obecnie każdy szkielet
</div> aplikacyjny może zawierać jako swoją część
</div> potrzebny mu fragment tego pliku (web
<div id="bottom"> fragments).
<ui:insert name="bottom">
<ui:include src="foot.xhtml"/> Wsparcie JavaEE 6
</ui:insert> Obecnie (styczeń 2010) jedynym gotowym
</div> do pracy w środowisku produkcyjnym ser-
</h:body> werem Java EE 6 jest GlassFish 3.0 – jest
</html> on jednocześnie implementacją referencyj-
ną. JBoss w wersji 6 będzie wkrótce wspie-

30 04/2010
Java EE 6

Listing 11. Strona z formularzem pozwalającym dodawać nowe zadania (addTask.xhtml)


<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
template="./template.xhtml">

<ui:define name="top">
Dodawanie zadań
</ui:define>
<ui:define name="content">
<h:messages errorStyle="color: red"/>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel for="name">Nazwa zadania: </h:outputLabel>
<h:panelGroup>
<h:inputText id="name" value="#{taskController.task.name}">
<f:ajax event="keyup" render="namemsg"/>
</h:inputText>
<h:message id="namemsg" for="name" style="color: red; padding-left: 5px"/>
</h:panelGroup>
<h:outputLabel for="user">Użytkownik: </h:outputLabel>
<h:selectOneMenu id="user" value="#{taskController.userId}">
<f:selectItems value="#{taskController.users}"/>
</h:selectOneMenu>

<h:outputLabel for="description">Opis zadania: </h:outputLabel>


<h:inputTextarea rows="6" id="description" value="#{taskController.task.description}"/>

<h:outputLabel for="startDate">Data rozpoczęcia: </h:outputLabel>


<h:inputText id="startDate" value="#{taskController.task.startDate.time}">
<f:convertDateTime pattern="dd-MM-yyyy"/>
</h:inputText>

<h:outputLabel for="dueDate">Data wykonania: </h:outputLabel>


<h:inputText id="dueDate" value="#{taskController.task.dueDate.time}">
<f:convertDateTime pattern="dd-MM-yyyy"/>
</h:inputText>

<h:outputLabel for="priority">Priorytet: </h:outputLabel>


<h:selectOneMenu id="priority" value="#{taskController.task.priority}">
<f:selectItems value="#{taskController.priorities}"/>
</h:selectOneMenu >

<h:outputLabel for="category">Kategoria: </h:outputLabel>


<h:selectOneMenu id="category" value="#{taskController.task.category}">
<f:converter converterId="categoryConverter"/>
<f:selectItems value="#{taskController.categories}"/>
</h:selectOneMenu>

<h:outputLabel for="status"></h:outputLabel>
<h:selectOneRadio id="status" value="#{taskController.task.status}" layout="lineDirection">
<f:selectItems value="#{taskController.statuses}" />
</h:selectOneRadio>
<h:commandButton value="Dodaj zadanie"
action="#{taskController.addTask}"/>
</h:panelGrid>
</h:form>
</ui:define>
</ui:composition>

www.sdjournal.org 31
Programowanie Java

rał tę specyfikację, przynajmniej profil we- rów, zapewne dołączą do tego grona kolej- wreszcie łatwą w użyciu. Narzuca się oczy-
bowy. W przeciągu roku-półtora prawdopo- ne produkty. wiście pytanie, co z resztą powstałego eko-
dobnie wszystkie 14 certyfikowanych ser- systemu Java. Od dawna istnieje Spring Fra-
werów Java EE 5 będzie wspierało najnow- Podsumowanie mework, bardzo ciekawym i dojrzałym roz-
szą wersję Java EE. Dodatkowo, ze wzglę- Java EE 6 jest niewątpliwie technologią ela- wiązaniem jest JBoss Seam, jest wiele bar-
du na możliwość tworzenia profili serwe- styczną, o bardzo dużych możliwościach i dziej niszowych rozwiązań – Stripes, Wicket
czy chociażby kolejna wersja bardzo kiedyś
Listing 12. Klasa przetwarzająca dane przesłane ze strony addTask.xhtml (Listing 11) popularnego szkieletu aplikacyjnego Struts.
Czy rozwiązania te powinny odejść i być za-
@ManagedBean(name="taskController") stąpione „czystym” Java EE.
@RequestScoped Wygląda na to, że sami twórcy technolo-
public class TaskController { gii, firma SUN, zdecydowanie odrzucili filo-
private @EJB TaskBean taskBean; zofię „jednego poprawnego” rozwiązania na
private @EJB UserBean userBean; rzecz podejścia znacznie bardziej realistycz-
private @EJB CategoryCache categoryCache; nego i dającego szansę na szybki rozwój tech-
nologii, przy zachowaniu pewnego wspólne-
private Task task = new Task(); go, bardzo stabilnego rdzenia.
private Long userId; Ewolucja Java EE już od dłuższego czasu
była do pewnego stopnia inspirowana przez
private static List<SelectItem> priorities; technologie spoza JEE. Technologia JPA ko-
private static List<SelectItem> statuses; rzystała z doświadczeń Toplinka, Kodo czy
private List<SelectItem> categories; Hibernate, CDI czerpało z doświadczeń
private List<SelectItem> users; Spring Framework, Google Guice i JBoss Se-
am. Jeszcze wcześniej JavaServer Faces po
public String addTask(){ części powstało jako następca Struts.
User u = userBean.findUserById(userId); Bardziej otwarta struktura Java EE, uła-
taskBean.addTask(task, u); twienie konfiguracji zewnętrznych do Java
return "listTasks.xhtml"; EE technologii, pełne wsparcie dla wstrzy-
} kiwania zależności powodują, że nie ma
//pominięta inicjalizacja list i metody get/set przeszkód, aby używać Java EE jako bazy
} do bardziej wyspecjalizowanych rozwiązań.
Można więc wyobrażać sobie przyszłość Spring
Framework w ten sposób, że z jednej strony bę-
Listing 13. Strona z listą zadań (listTasks.xhtml), wykorzystująca metodę komponenty EJB dzie on implementacją części specyfikacji Ja-
<?xml version='1.0' encoding='UTF-8' ?> va EE, na przykład profilu WWW (lub jakie-
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http:// goś innego), a oprócz tego będzie zawierał swo-
www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> je własne rozszerzenia i dodatki. W ten sposób
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets" zyskają wszyscy. Z jednej strony będzie wspól-
xmlns:h="http://java.sun.com/jsf/html" ny zestaw bazowych technologii, dzięki czemu
xmlns:f="http://java.sun.com/jsf/core" znaczna część każdej aplikacji będzie bardzo
template="./template.xhtml"> przenośna, z drugiej strony zaś będzie też moż-
na korzystać z tych elementów, które są dla nas
<ui:define name="top"> wygodne, nie ma ich w standardzie, lecz ma je
Lista zadań jakiś szkielet aplikacyjny.
</ui:define> Zgodnie z taką filozofią był rozwijany
JBoss Seam, od początku zakładał on moż-
<ui:define name="content"> liwie największe wykorzystanie standardu
<h:form> plus uzupełnianie go tam, gdzie to jest po-
<h:dataTable border="1" cellpadding="4" cellspacing="4" value="#{taskBean.al trzebne lub wygodne.
lTasks}" var="t"> Mimo że firma SUN jako samodzielny byt
<h:column> odchodzi do przeszłości, to technologia, jaką
<f:facet name="header"> nam w spadku zostawia, jest naprawdę im-
<h:outputText style="font-weight: bold" value="Nazwa zadania"/> ponująca i dobrze dopasowana do tego, cze-
</f:facet> go potrzebuje dzisiejszy świat IT.
#{t.name}
</h:column>
<!-- pozostałe kolumny pominięte -->
</h:dataTable>
</h:form> PIOTR KOCHAŃSKI
</ui:define> Autor jest konsultantem �rmy Erudis, odpowie-
dzialnym za technologię Java.
</ui:composition> Kontakt z autorem: p.kochanski@erudis.pl,
Strona WWW autora: http://www.xoft.pl.

32 04/2010
Języki programowania

The Go programming
language
Język programowania Go jest językiem młodym, gdyż jego premierę
światową datuje się na 10 listopada 2009 roku. Właśnie wtedy na blogu
Google Code została zmieszczona informacja na temat upublicznienia
tego języka na licencji BSDL.

64-bitowych rodziny x86, który jest najdłu-


Dowiesz się: Powinieneś wiedzieć: żej rozwijany, oraz arm, dla 32-bitowych pro-
• Czym jest język programowania go, jak rów- • W celu zrozumienia niektórych elementów cesorów ARM, jednak ten port nie jest jeszcze
nież poznasz podstawę semantyki i podsta- artykułu przydatna jest znajomość dowol- kompletny. Tak więc przykładowymi zmienny-
wowe biblioteki owego języka; nego języka programowania bazującego na mi globalnymi są:
• Jak tworzyć procedury GO (ang. goroutines) składni języka C.
i je obsługiwać. GOARCH=amd64
GOBIN=/Users/aqu/bin
GOOS=darwin
kiego, bezpiecznego i łatwego języka, ze wspar- GOROOT=/Users/aqu/go
ciem dla wielowątkowści od strony języka. Mi-
Poziom mo kilku niedogodności, które mogą z począt- Przy czym warto zaznaczyć, że zmienna GOBIN
trudności ku zdziwić programistów takich języków jak powinna znajdować się w zmiennej PATH, żeby
C/C++, to myślę, że cel został w dużym stop- łatwiej było uruchamiać kompilator i linker.
niu osiągnięty. Najlepiej te zmienne dodać do pliku ~/.bash_
profile, ponieważ zmienne te są potrzebne do
Krótka historia języka Instalacja kompilatora instalacji kompilatora, oraz kompilacji i linko-
Sama idea stworzenia nowego języka miała Wspomnieć tutaj trzeba, że na dzień dzisiejszy wania jakichkolwiek programów. Sama insta-
miejsce w siedzibie głównej Google, Mountain niestety użytkownicy systemu Windows nie lacja odbywa się bezstresowo i w bardzo pro-
View, California (zwana też Googleplex). Datę mogą korzystać z języka go, gdyż wydane zo- sty sposób. Wystarczy wykonać serie następu-
tego pomysłu szacuje się na koniec roku 2007, stały tylko wersje na Linuksa i Mac OS. Na ra- jących komend:
gdy pomysłodawcy Robert Griesemer, Rob Pike zie nic nie wiadomo na temat portu na system
i Ken Thompson stwierdzili, że kompilacja do- Microsoft’u. Autor zakłada, że czytelnik posia- $ cd $GOROOT/src
wolnych aplikacji zajmuje zbyt dużo czasu. I od da komputer z systemem Linux lub Mac OS $ ./all.bash
tego momentu, aż do połowy roku 2008, język z procesorem Intel. Aby móc zacząć używać ję- jeśli wszystko zakończy się powodzeniem,
istniał tylko na “tablicy”, a inicjatorzy projektu zyka od firmy Google, należy pobrać jego źródła zobaczymy komunikat
poświęcali wolny czas na pracę nad nowym ję- przy pomocy mercurial’a komendą: rodzaju:
zykiem (polityka zatrudnienia Google oferuje --- cd ../test
10% czasu dla deweloperów na własne projek- hg clone -r release N known bugs; 0 unexpected bugs
ty). I tak oto powstał kompilator oraz środowi- https://go.googlecode.com/hg/ $GOROOT gdzie N to liczba
sko uruchomieniowe (ang. run-time). Go zaczę-
ło żyć jako język wewnętrzny firmy stosowany gdzie $GOROOT to całkowita ścieżka, w której Jeżeli natomiast instalacja kompilatora zakoń-
do pisania prostych oraz wielowątkowych apli- mają znaleźć się źródła. Po ściągnięciu źródeł czy się niepowodzeniem, to może to wskazy-
kacji. Od tego czasu język był używany tylko należy ustalić zmienne środowiskowe GOARCH, wać na jeden z dwóch problemów. Albo pró-
wewnątrz firmy, aż do listopada 2009, kiedy uj- GOBIN, GOOS, GOROOT, z czego GOROOT już omó- bujemy zainstalować program jako root, nie-
rzał światło dzienne. wiliśmy. GOOS określa nasz system, docelowe stety zabrania tego instalator, albo nie posia-
wartości to linux i darwin, gdzie darwin ozna- damy odpowiednich bibliotek. Rozwiązaniem
Założenia projektowe cza system Mac OS. Zmienna GOBIN przecho- tego problemu jest instalacja aplikacji: kompi-
Głównym założeniem, jak już wspomniano, wuje ścieżkę, gdzie kompilator ma być zain- lator gcc, standardowa biblioteka C, parser ge-
była chęć przyśpieszenia etapu kompilacji, co, stalowany. Natomiast zmienna GOARCH mówi nerator Bison i edytor tekstu ed. W przypadku
jak się przekonamy później, udało się bardzo o tym, jaki rodzaj procesora posiadamy, akcep- OS X biblioteki te zainstalowane być powin-
dobrze. Jednak nie był to jedyny cel projek- towalne wartości to: 386 dla procesorów 32-bi- ny razem z pakietem XCode, w Linuksie nato-
tantów, ponadto celem było stworzenie szyb- towych rodziny x86, amd64 dla procesorów miast wystarczy wykonać polecenie rodzaju:

34 04/2010
The Go programming language

$ sudo apt-get install bison gcc libc6- w katalogu $GOROOT/src/pkg/. Widzimy de- śli mamy kilka zmiennych, które chcemy zaini-
dev ed finicję funkcji main, która ze względu na swo- cjować, nie musimy tego robić jak w C:
ja specyfikę nie zwraca nic, brak zwracanych da-
Dodatkowo można jeszcze zainstalować na- nych można traktować trochę jak void w języku int jeden = 1, dwa = 2, trzy = 3;
kładkę na pakiet gcc, który stworzy dla nas C. Ważnym jest, że main() nie przyjmuje argu-
aplikację gccgo, która sama już zajmie się kon- mentów, obsługa parametrów wprowadzanych W go możemy zainicjować zmienne na końcu
solidacją i podstawową optymalizacją kodu bi- z konsoli odbywa się w inny sposób. linii, co czasem może być przydatne (związa-
narnego. Następnie widać coś, co na pierwszy rzut oka nych jednak z zapisem z C zawiedzie, że zapis
Gdy już zainstalujemy kompilator go, jeste- może wyglądać dziwnie, lecz nie jest niczym in- znany z tego języka nie jest wspierany), powyż-
śmy gotowi do pisania własnych programów nym, niż najzwyklejszą deklaracją zmiennych. szy przykład w go można zapisać:
(oczywiście jeśli mamy poprawnie ustawione Programiści języków algolowych nie przywyk-
zmienne globalne). Jednak wcześniej sprawdź- nęli do tego typu deklaracji, łatwo jednak “roz- var jeden, dwa, trzy int = 1, 2, 3;
my, na ile spełniona jest obietnica o szybkiej gryźć”, jak deklarować zmienne w tym języku,
kompilacji aplikacji. semantyka jest następująca: Rozwińmy teraz wspomniany już temat
zmiennej “samo określającej”. Tego typu
Prędkość kompilacji “var ” + nazwyZmiennych + typZmiennych “ = zmienne można interpretować jakby miały dy-
Najłatwiej to sprawdzić, kompilując źródła pa- “ wartości. namiczny typ. Jednak ustalany tylko raz w ob-
kietów, które dostajemy wraz z kompilatorem. rębie czasu jej życia. Przydaje się to, jeżeli nie
Znajdują się one w katalogu $GOROOT/src/ Programistom Pascala zapewne znajome jest do końca jesteśmy pewni, jakiej zmiennej ocze-
pkg, całkowita ilość kodu w tym katalogu w ję- słowo kluczowe var, które określa miejsce, kujemy, jaka będzie dla nas najodpowiedniej-
zyku go wynosi 128756 linii kodu(wraz z ko- w którym zaczyna się deklaracja zmiennych. sza, lub po prostu jesteśmy za leniwi, żeby do-
mentarzami), co łatwo można sprawdzić przy Później wypisujemy nazwy zmiennych uży- pisywać typ i słowo var (przecież to jest całe od
pomocy narzędzia wc. W tym drzewie mamy tych w programie, na końcu mamy typ zmien- 5 do 10 znaków! ;).
wszystkie operacje we/wy, funkcje matematycz- nych wcześniej wypisanych. W stosunku do ję- Połączmy teraz kategorię multideklaracji
ne, obsługę gniazd sieciowych, funkcje krypto- zyka C jest mniej wbudowanych typów, wy- z deklarowaniem interpretowanym (samo okre-
graficzne i wiele innych. Teraz można spraw- bierać możemy pomiędzy: int (typ całkowity), ślającym), można przy połączeniu tych dwóch
dzić, jak szybko wykona się kompilacja całego float (typ zmiennoprzecinkowy), bool (typ bo- rodzajów deklaracji zdefiniować wiele zmien-
tego poddrzewa, należy wykonać następujące olowski, wartości prawda/fałsz), string (łań- nych, które powinny przyjmować różne typy
polecenia: cuch znaków). Szczegóły jednak za chwilę. Na- danych, bądź obsłużyć funkcje zwracające kilka
stępnie mamy przypisanie do zmiennej “samo rodzajów danych (o nich później). Przykładem
cd $GOROOT/src/pkg określającej”, specyficzny dla języka operator : na to może być następujący kod: Da on wynik:
make clean # czyscimy wszystkie pliki = mówi coś w rodzaju. Rozpoznaj typ danych
wyjsciowe po prawej stronie i zainicjuj zmienne po lewej int float string bool
time make z tą wartością. W kolejnej linii tworzymy tabli-
ce łańcuchów znakowych przez zwykłe rzuto- czyli taki, jaki oczekiwaliśmy. Widać więc,
Na komputerze z zainstalowanym systemem wanie już istniejących wartości. Następnie wi- że multideklaracja wraz z deklarowaniem in-
Mac OS X 10.6 i procesorem C2D 2.4 GHz dzimy pętle for, która w tym wypadku imi- terpretowalnym daje duże możliwości, nato-
w technologii peryn średni czas wyniósł: tuje znaną z innych języków pętlę while. Ran- miast jak się przekonamy, łącząc to z funkcja-
8.978sec (z czego ~30% to ładowanie danych ge natomiast jest słowem kluczowym języka, mi zwracającymi kilka danych, daje naprawdę
do pamięci) przy różnym dodatkowym obcią- które możemy przyjąć za iterator elementów potężne możliwości programistyczne.
żeniu procesora. Widać, że czas kompilacji ta- tablicy hw. Tzn przypisuje zmiennej val war- Dodatkowym typem są stałe, jak można się
kiego zestawu kodu jest dość imponujący. Jed- tości 0,1,..., i, gdzie i to ilość elementów tablicy domyślić, definiuje się je za pomocą słowa klu-
nak mimo tego, czas ten uważany jest za jeden (range działa analogicznie, jeżeli mamy tablicę czowego const. Stałe możemy definiować na
z gorszych, ponieważ większość aplikacji użyt- mapowaną za pomocą dowolnego elementu). trzy sposoby, pierwszy z nich nasuwa się z in-
kowników kompiluje się w czasie poniżej 1sek. W każdej iteracji pętli wypisujemy pojedyncze nych języków programowania i ma postać: cont
słowo z tablicy hw przy pomocy funkcji Printf nazwa typ = wartość.
Szybki wstęp do go pakietu fmt. Ostatnim krokiem jest wypisanie
Przejdźmy teraz do omówienia składni go, po- znaku nowej linii przez zdefiniowaną funkcję Listing 1. hello_world.go
równaniem dla nas będzie język C. Println, (Println nie przyjmuje ciągów for-
Zacznijmy może od hello world, ale w nieco matujących) znaną z Pascal’a lub Java. Tyle jeśli package main
zmodyfikowanej wersji. chodzi o szybki wstęp do języka. Przejdźmy do
W powyższym programie można zaobser- szczegółowego opisu. import “fmt”
wować już kilka specyficznych elementów, któ-
re pojawiają się w go, jednak dla programistów Zmienne, pętle, warunki, funkcje func main() {
Pythona czy Java są oczywiste. Pierwszym z ele- Wyżej przedstawiony został prosty program ty- var h string = “Hello “;
mentów jest określenie nazwy pakietu. Może pu hello world, i jego krótki opis, zajmijmy się w := “world!”;
on być dowolny, jeżeli pisany przez nas kod ma teraz bardziej szczegółowym opisem poszcze- var hw []string = []string{h, w};
być rodzajem biblioteki, jednak o tym dalej. Na- gólnych elementów powyższego programu. for val := range hw {
stępne, co widzimy, to deklaracja importowa- Zacznijmy od zmiennych, powiedziane już fmt.Printf(“%s”, hw[val]);
nia modułów zewnętrznych. W tym przypad- zostało, jak deklarować zmienne, jednak war- }
ku używamy pakietu do formatowania stan- to dodać, że można też skorzystać z, nazwijmy fmt.Println();
dardowego wyjścia “fmt” (ang. ForMaT). Listę to, multideklarowania (którą widać w przedsta- }
wszystkich pakietów można już było obejrzeć wionej semantyce deklaracji). Chodzi o to, że je-

www.sdjournal.org 35
Języki programowania

Co więcej, wcale nie musimy podawać typu var a []int = []int{1,2,3,4,5}; przedstawiony już wariant zachowuje się jak
stałej, jaką deklarujemy, kompilator sam przypi- s := a[1:3]; powszechnie znany while, ostatni wariant jest
sze jej typ (nie musimy używać nawet operatora : nieskończoną pętlą,
= ), więc dopuszczalna jest deklaracja typu const z powyższego kodu wynika, że stworzymy ta-
nazwa = wartość. Ponadto projektanci języka blice liczb całkowitych o długości 2 (od indek- for { /* kod */ }
wyszli naprzeciw programistom i jeżeli deklaru- su 1 do 3 wyłącznie), które przybiorą wartości
jemy wiele stałych w programie, możemy wyko- odpowiadające elementom tablicy a. ważne jest, że po słowie kluczu for nie mo-
nać rodzaj “rzutowania” na const. Umieszczając Długość tablicy możemy sprawdzić specjal- gą występować nawiasy, jest to sprzeczne z se-
wszystkie pożądane stałe w nawiasie. Przykłado- ną funkcją len(). mantyką języka. To tyle jeśli chodzi o pętle,
wy zapis będzie wyglądał następująco: Jeśli chodzi o zmienne, to pozostają nam do przejdźmy zatem do warunków.
omówienia mapy. Tak jak w innych językach Budowa instrukcji warunkowych w go nie
const ( a = 12; b = 13). programowania, indeksy mapy mogą być do- odbiega od tego typu instrukcji w innych języ-
wolnym elementem (liczbą całkowitą, zmien- kach programowania (może poza zakazem uży-
Należy się również słowo wyjaśnienia noprzecinkową, łańcuchem znakowym czy in- wania nawiasów), więc myślę, ze prosty przy-
o wskaźnikach, ponieważ te także wystę- nym obiektem). Tablice mapowaną tworzymy kład wyjaśni wszystko.
pują w go, i jak można się domyślić, są ozna- przy użyciu słowa kluczowego map. Tworze- Do wyjaśnienia jedynie zostaje użycie pa-
czane tak samo jak w C/C++ przez znak nie i używanie mapy odbywa się w następują- kietu time i rand, są one odpowiedzialne ko-
gwiazdki. Daje nam to możliwość przekazy- cy sposób: lejno za pobieranie/formatowanie czasu na
wania zmiennych do funkcji przez wskaza- lokalnej maszynie oraz za generowanie liczb
nie. Przyda się też to do innej rzeczy przed- m := map[string]int{“jeden”:1, “dwa”:2}; pseudolosowych. Linia rand.Seed(time.S
stawionej później. Ciekawostką jest to, że econds()) ustawia ziarno w generatorze na
mimo tego, iż go posiada wskaźniki, to jed- Stworzy to dla nas mapę liczb całkowitych in- ilość sekund od daty 1 stycznia 1970 godz.
nak nie ma arytmetyki wskaźnikowej, co deksowaną za pomocą łańcuchów znakowych, 00:00:00 UTC, kolejna linia ustawia i na
jest, uważam, dużym plusem, jeśli chodzi do elementów takiej tablicy odwołujemy się wartość pseudolosową pomiędzy [0; n), resz-
o bezpieczeństwo aplikacji. Brak arytmety- jak do zwyczajnej tablicy, tyle że przy użyciu ta kodu powinna być oczywista.
ki wskaźnikowej oznacza, że nie możemy zdefiniowanych przez nas kluczy. Poprawny- Drugim rodzajem instrukcji warunkowej
mieszać w pamięci poza otrzymanym przy- mi więc są odwołania: dostępnej w go jest switch. Tak jak w przy-
działem adresowym. Już nawet operacja od- padku for składnia jest niemalże identycz-
niesienia się do elementu z poza tablicy wy- m[“jeden”]; m[“dwa”]; na jak w innych językach programowania.
rzuca wyjątek i najczęściej kończy działanie Pierwszą różnicą jest to, że jeżeli nie poda-
programu. Dodatkową informacją jest, że Jednak należy pamiętać, że mapa jest struktu- my “co porównywać”, to wstawiana jest war-
gdy definiujemy kilka zmiennych w linii, to rą asocjacyjną, więc odwołanie się do elemen- tość true, więc znów posłużymy się wyłącznie
wskaźnik odnosi się do wszystkich, a nie je- tu, który nie istnieje, utworzy go. Do przeglą- przykładem z wykorzystaniem wyżej wymie-
dynie do wybranej zmiennej, tak jak to ma dania map służy opisany już operator range. nionej różnicy. Drugą jest brak słowa kluczo-
miejsce w C++. Więc zapis: Przejdźmy teraz do pętli. Tak naprawdę to wego “break”, instrukcja switch jest automa-
nie ma zbyt wiele do omawiania, gdyż wszyst- tycznie kończona, gdy skończą się instrukcje
var a, b, c *int; kie dostępne pętle już widzieliśmy w programie danego przypadku.
hello_world.go, chodzi tu o pętle for, istnieją Pozostaje do omówienia jeszcze mecha-
Utworzy trzy zmienne a, b i c, które będą tylko jej warianty wprowadzające większą róż- nizm funkcji, jednak zostanie on rozwinię-
wskaźnikami na zmienne typu całkowitego. norodność. Pierwszą z możliwości jest klasycz- ty dodatkowo w dziale ze strukturami. Sa-
Jest jeszcze specjalna konstrukcja zwana slice ny for postaci ma semantyka definicji funkcji wygląda na-
(tłum. kawałek). Znana może ona być progra- stępująco:
mistom Pythona. Slice, jak nazwa mówi, służy for init; warunek; po { /* kod */ }
do wycinania kawałka tablicy lub łańcucha zna- Listing 3. if_statements.go
kowego. Slice tworzy nową tablicę/łańcuch zna- i działa identycznie jak for znany z innych języ-
kowy tego samego typu, jakiego jest tablica “ro- ków programowania, następną postacią jest package main
dzica”. Przykładem na tworzenie wykroju tabli-
cy może być następujący kod: for warunek { /* kod */ } import “fmt”
import “rand”

Listing 2. vars_dec.go import “time”

package main func main() {


rand.Seed(time.Seconds());
import “fmt” i := rand.Intn(51);
if i > 25 {
func main() { fmt.Println(“i > 25”);
a, b, c, d := 1, 0.5, “nazwa”, true; } else if i < 25 {
// Uwaga użyjemy w funkcji Printf specyficznego dla go znacznika fmt.Println(“i < 25”);
// formatu %T, który mówi funkcji, ze powinna ona wydrukować typ } else {
// zmiennej zamiast jej wartość. fmt.Println(“i == 25”);
fmt.Printf(“%T %T %T %T\n”, a, b, c, d); }
} }

36 04/2010
The Go programming language

“func ” + nazwaFunkcji + “(“ + zmienne +”)” Kolejnym elementem, na który chcę zwrócić powiedniego pliku z kodem i jeżeli żadna z funk-
+ zwracanyTyp + uwagę, jest to, że jeśli parametry tego samego ty- cji w nim zawartych nie używa funkcji z zaim-
cialoFunkcji pu, można umieścić na samym końcu listy argu- portowanych paczek, to proces kompilacji koń-
mentów. Czyli poprawny jest zapis: czy się porażką, z komunikatem, że chcemy do-
gdzie zmienne wpisujemy podobnie jak przy łączyć bibliotekę, której nie używamy.
ich deklaracji, z tym że sam język nie obsługu- func foo(a, b string, c, d int) Przejdźmy teraz do dość ważnej części języ-
je wartości domyślnych. W podanej semanty- ka, jakim są struktury. Struktury w go są czymś
ce brakuje jeszcze jednego elementu, ale jak już Ostatnią rzeczą, jeśli chodzi o funkcję, jest jej więcej niż te znane z C, ale czymś mniej niż kla-
wspomniano, zostanie to uzupełnione w czasie zasięg, czyli czy funkcja ma być widoczna po- sy z C++. Go samo w sobie nie posiada klasy,
omawiania struktur. za pakietem. Coś na kształt static w C i public/ jednak struktury w tem języku rekompensują
Dowolna funkcja może zwracać dowol- private w C++, choć w tym ostatnim odnosi się brak klas. Żeby zdefiniować dowolną strukturę,
ną ilość danych, więc w ten sposób dość pro- to do klas. W go zasady są proste, jeśli funkcja/
sto można zaimplementować złożoną obsługę zmienna/struktura/interfejs/typ zaczyna się Listing 4. switch_statement.go
błędów. Przykładem funkcji, która zwraca kil- z dużej litery, to znaczy, że funkcja jest pu-
ka wartości, może być np. parsowanie łańcucha bliczna (widoczna poza pakietem). Jeśli nato- package main
znakowego na część rzeczywistą i urojoną liczb miast nazwa zaczyna się z małej litery, ozna-
zespolonych. Nagłówek takiej funkcji mógłby cza to, że funkcja lub zmienna będzie widocz- import “fmt”
wyglądać następująco: na jedynie w obrębie pliku. Teraz powinno
mieć sens, dlaczego używane przez nas funk- func main() {
func parseComplex(p string) (float, float) cje (Printf, Seed, Intn) były pisane z wiel- i := 3;
kiej litery, parseComplex natomiast będzie wi- for {
zapis ten oznacza, że funkcja parseComplex doczna tylko w obrębie pliku, w którym zosta- switch {
przyjmuje jeden parametr typu string i zwra- ła zdefiniowana. case i==3: fmt.Println(“trzy...”);
ca dwie wartości zmiennoprzecinkowe, użycie case i==2: fmt.Println(“dwa...”);
takiej funkcji może być następujące: Struktury, pakiety, interfejsy case i==1: fmt.Println(“jeden...”);
Zacznijmy od czegoś, co używane było we default: fmt.Println(“BUM!!!”);
re, im := parseComplex(“2+4i”); wszystkich programach, ale dokładne wyja- return;
śnienie zostało wstrzymane aż do teraz, mowa }
Wtedy zostaną zainicjowane dwie zmienne typu o paczkach. Jak można się domyślić, definiuje- i--;
zmiennoprzecinkowego re i im. Co jednak, kie- my je słowem kluczowym package. We wszyst- }
dy chcemy wyciągnąć z funkcji tylko jeden argu- kich przedstawionych programach definiowali- }
ment? Do takiego celu służy tzw zmienna anoni- śmy paczkę main, i tak należy robić we wszyst-
mowa, znana np. w Prologu, i w Go posiadająca kich swoich programach, o ile nie definiujemy Listing 5. interface_usage.go
taki sam symbol _ (podkreślenie), oznacza to, że biblioteki. Gdy przekazujemy do kompilatora package main
tego rodzaju zmiennej nie można przypisać żad- program z paczką main, to kompilator wtedy
nej wartości ani używać jej w jakiejkolwiek funk- wie, gdzie znajduje się główna procedura main. type Auto interface {
cji. Jednak doskonale nadaje się ona właśnie do Inaczej jest, gdy piszemy jedynie bibliotekę do Odpal();
celów “pomijania” dowolnych wartości z rezul- szerszego użytku, wtedy nazwa pakietu jest do- }
tatu funkcji. Jeśli chcielibyśmy z naszej funkcji wolna. Można jeszcze wspomnieć o specjalnej
parseComplex wyciągnąć tylko zmienną im, to funkcji inicjalizującej, jaką może posiadać każ- type Mazda struct {
można funkcję wywoływać następująco: dy plik źródłowy, mowa o funkcji init() – nie spalanie float;
zwraca nic ani nie przyjmuje żadnych warto- liczba_drzwi, rocznik int;
_, im := parseComplex(“2+4i”); ści. Jednak jak można się domyślać w przypad- }
ku użycia funkcji z kodu, w którym zdefiniowa-
Definiując funkcję, możemy także użyć zapi- no funkcje init, wywoływana jest najpierw wła- type Audi struct { }
su nazwanego “nazwane parametry zwracane”, śnie ona, a w przypadku paczki main, funkcja
które to parametry możemy nazwać podczas init wywoływana jest nawet przed główną funk- func (m Mazda) Odpal() { }
deklaracji typu zwracanego. Zmienne takie zo- cją main().
staną zainicjowane przez zero, zyskujemy na Zależności to coś, co było jednym z celów pro- func Sprzedaj(a Auto) {
tym tyle, że gdy chcemy zwrócić właśnie te pa- jektowych języka go, może nie same zależności, a.Odpal();
rametry, nie musimy podawać ich nazw (więc ale ich nadmiar i dołączanie bibliotek z funkcja- /* ... */
zyskujemy czas i redukujemy ilość kodu). Po mi, które i tak nie są użyte w programie. Podczas }
raz kolejny posłużymy się naszą “funkcją” do procesu konsolidacji programu dołączane są bi-
liczb zespolonych. blioteki zewnętrzne, których nagłówki zostały func main() {
zdeklarowane w kodzie źródłowym, tak ma to var m Mazda;
func parseComplex(p string) (re float, im miejsce w C/C++, a co za tym idzie, powstaje var a Auto;
float) { coraz większy i większy plik wykonywalny. Jed- // var c Audi;
// ... nak co, jeśli programista załączył nagłówek, któ- a = new(Mazda);
return 0, 0; // bo możemy np tak reagować rego i tak nie używa? Tracone jest miejsce na dys- Sprzedaj(m);
na błędy ku, a także czas uruchamiania takiego progra- Sprzedaj(a);
// ... mu się wydłuża. Co to ma wspólnego z go? Ty- // Sprzedaj(c);
return; // tutaj zostaną zwrócone re i im le, że już podczas procesu kompilacji kompilator }
} sprawdza, jakie paczki zaimportowaliśmy do od-

www.sdjournal.org 37
Języki programowania

najlepiej jest posłużyć się słowem kluczowym ciu słowa kluczowego var, lub przez stworze- nie jest może trochę chaotyczne, ale po przed-
type, którego semantyka jest następująca: nie wskaźnika do nowoutworzonego obiek- stawieniu przykładu powinno się rozjaśnić
tu. Do tego celu służy kolejne nowe słowo klu- Widać, że funkcja Sprzedaj przyjmuje ja-
“type “ + nazwaNowegoTypu + instniejący typ czowe new(), które zwraca wskaźnik na nowo ko argument “obiekt” rodzaju Auto, który choć
utworzony obiekt z zainicjowanymi zerami. sam jest interfejsem, to definiuje minimum, ja-
natomiast strukturę tworzymy w nastepują- Poprawnymi zapisami są więc: kie musi być dostępne w tym “obiekcie”, tutaj
cy sposób: jest nim funkcja Odpal (bo każde auto przed
var liczba Complex; sprzedażą trzeba sprawdzić). Jak widać, obiekt
“struct {“ + pola + “}” liczba1 := new(Complex); typu Mazda posiada funkcję Odpal, jak i również
dodatkowe pola, oczywiście dodatkowe funkcje
więc po raz kolejny odosząc się do liczb zespo- z tą jednak różnicą, że liczba będzie typu także można dodać. Interfejs jednak nie pozwa-
lonych, możemy utworzyć następujący nowy Complex, a liczba1 typu *Complex. Wcześniej la na definiowanie pól, które struktura powinna
typ strukturowy: mówiliśmy, że struktury rekompensują brak posiadać, ponadto możemy zdefiniować zmien-
istnienia klas, jednak można zauważyć, że skoro ną typu interface (w przykładzie var a Auto),
type Complex struct { nie możemy stosować definicji funkcji oddziel- w którą możemy “rzutować” typy bardziej złożo-
im, re float nie z deklaracją, to musielibyśmy zdefiniować ne, ale spełniające wymagania interfejsu. Dlatego
} wszystkie funkcje wewnątrz struktury, co jed- też zakomentowane są linie Sprzedaj(c) i var c
nak nie jest wygodne. Zamiast tego jednak mo- Audi. Pierwszą linię ze względu na to, że struk-
Warto zauważyć, że średnik nie jest wyma- żemy zdefiniować specjalną funkcję, która two- tura Audi nie implementuje funkcji Odpal(),
gany w żadnej z globalnej definicji, gdyż w go rzy jakby rozszerzenie dowolnego typu danych. a drugą dlatego, że zadeklarowaliśmy zmien-
średnik jest separatorem, a nie terminatorem. Przykładowo dla naszej struktury zespolonej ną c, ale nigdzie w procedurze jej nie używamy.
Nowy “obiekt” strukturowy możemy tworzyć funkcje sumy możemy zapisać następująco: Kompilator jest w tym przypadku tak samo re-
na dwa sposoby, tak jak wcześniej, przy uży- strykcyjny jak w przypadku odmowy kompila-
func (a *Complex) dodaj(b Complex) { cji w przypadku nieużywania zaimportowanych
Listing 6. goroutines.go a.im += b.im; pakietów. Jeżeli jednak odkomentujemy te dwie
a.re += b.re; linie, otrzymamy od kompilatora komunikat:
package main }
import “fmt” missing Odpal()
import “time” który dodaje do liczby a wartość z b. Wywoły-
wanie funkcji odbywa się następująco: Co wskazuje na to, że nie zaimplementowa-
func() funkcja() { liśmy funkcji Odpal() dla obiektu Audi. Ze
time.Sleep(10); var a,b Complex; względu na to, że inferfejsy nie mogą posia-
fmt.Println(“funkcja w tle”); // inicjalizacja wartosc dać wymienionych wymaganych pól, dla-
} a.dodaj(b); tego też nie możemy zdefiniować dla in-
terfejsu funkcji dla typu, jak to ma miejsce
func() main() { i to wszystko. Dodaliśmy coś na kształt klasy w przypadku struktury czy innego dowol-
go funkcja(); w C++. Co prawda nie tak elastyczne jak klasy, nie zdefiniowanego typu.
} jednak przy odrobinie gimnastyki można osią-
gnąć prawie tyle samo, co w językach obiekto- Wątki, kanały
Listing 7. chanells.go wych. Największym problemem w tym przy- Oddzielnym tematem w go jest współbieżność
package main padku jest brak możliwości tworzenia różnych aplikacji w nim napisanych. Ten temat także
import “fmt” wersji funkcji, więc można powiedzieć, że mu- był celem projektowym, ze względu na wciąż
simy mieć podejście trochę jak w Obj-C, znów powiększającą się liczbę rdzeni czy też coraz
func odliczaj(ch chan int, from int) { odnosząc się do przykładu z liczbami zespolo- większą ilość procesorów w komputerach biur-
for from >= 0 { nymi, lista “konstruktorów” mogłaby wyglą- kowych i serwerowych. Głównym problemem
ch <- from; dać następująco: współbieżności jest współdzielenie pamięci
from--; i rozwiązywany jest poprzez mutexy, i tego po-
} func Complex_initWithValues(a, b float) (c dejścia jak najbardziej możemy używać, typ
} *Complex) { /* ... Mutex dostępny jest w paczce sync, jednak jest
*/ } to podejście dość niskopoziomowe. W go istnie-
func main() { func Complex_initFromString(a string) (c je rozwiązanie dużo bardziej wysokopoziomo-
ch := make(chan int); *Complex) { /* ... we zwane kanałami, o których za chwile. Ogól-
go odliczaj(ch, 7); */ } nie można przyjąć:
Nie komunikuj się przez pamięć współdzie-
for { Możliwości są na tyle ograniczone, na ile wy- loną, zamiast tego dziel pamięć przez komu-
a := <- ch; obraźnia programisty. nikację.
fmt.Println(a); Interfejs to próba zaadoptowania objektowo- Można przybliżyć tę ideę poprzez przykład.
if a == 0 { ści w go, najprościej mówiąc, interfejsy w go mó- Załóżmy, że mamy jednowątkowy proces, z oczy-
break; wią, że jeżeli coś posiada jakąś własność, to może wistych względów nie wymaga on synchroniza-
} być użyty w jakimś celu. Można to rozumieć ja- cji. Przy uruchomieniu kolejnej instancji progra-
} ko swojego rodzaju dziedziczenie i rzutowanie mu, pamięć nie musi być synchronizowana. Jed-
} w dół/górę hierarchii obiektów, choć sama hie- nak można sprawić, żeby oba te procesy się ze so-
rarchia obiektów w go nie występuje. Wyjaśnie- bą komunikowały, jeżeli teraz komunikat jest

38 04/2010
The Go programming language

synchronizatorem, to synchronizacja pamięci łający komunikat w kanał będzie czekał na od- mentów, do tego celu służy paczka flag. Za po-
procesów nie jest potrzebna. I to jest główny po- biór przesłanych danych. Przez kanały możemy mocą funkcji Parse() parsujemy do programu
mysł kryjący się za kanałami w go. Wątki w go przesyłać dowolne rodzaje zmiennych, zależ- wszystkie podane flagi (argumenty typu “-x war-
zwane są goroutines, wątki go są multipleksowa- nie od rodzaju zdefiniowanego kanału. Kanał tość”), jednak żeby poprawnie je obsłużyć, nale-
ne na wiele wątków systemu operacyjnego, więc możemy oczywiście przekazać jako argument ży zdeklarować, jakich flag oczekujemy. Przykła-
jeżeli któryś z wątków oczekuje na zdarzenie (na funkcji, więc automatycznie narzuca się meto- dowo, żeby pobrać liczbę w fladze (np. jako ilość
przykład oczekiwanie na we/wy), to pozostałe da współdzielenia pamięci i wysokopoziomowe procesorów, którą chcemy przeznaczyć na obli-
wątki nie są blokowane, tylko działają dalej. tworzenie mutex’ów. Prostym przykładowym czenia), użyjemy funkcji flag.Int, która przyj-
Tworzenie wątków w go jest naprawdę pro- programem używającym kanałów i wielowąt- muje trzy artgumenty, pierwszy to nazwa fla-
ste, wszystko, co wystarczy zrobić, to wywołać kowości może być następujący kod. gi (bez poprzedzającego go myślnika), drugi to
dowolną funkcję za słowem kluczowym go. I od Funkcja odliczaj przyjmuje dwa argumen- wartość domyślna, jeśli flaga nie została wybra-
tego momentu tworzony jest wątek, który dzia- ty, pierwszym jest kanał typu całkowitego na, trzeci to tzw usage string, czyli opis, do czego
ła współbieżnie z pozostałymi wytworzonymi (chan int), drugim liczba, od jakiej ma nastę- służy dana flaga. W przypadku podania błędnej
wątkami. Jeżeli jednak wykonywanie funkcji pować odliczanie. Linia ch <- from; przekazu- flagi zostanie wydrukowany standardowy usage
main zakończy się przed zakończeniem funkcji je zmienną from do kanału ch, jak wcześniej by- do programu. Funkcje do pobierania odnoszą
w tle, to funkcja wykonywana w tle zostanie za- ło to opisane w specyfice operatora. się do wszystkich typów wbudowanych. Innymi
kończona bez oczekiwania na rezultat. Przykła- W przypadku kanałów pozostaje omówie- funkcjami są Arg(i int), który zwraca i-ty ar-
dem na wywołanie oraz na powyższe stwierdze- nie wyrażenia select, który jest ściśle związa- gument, po parsowaniu flag. Args(), zwraca ta-
nie, że wykonywanie wątku w tle zostanie prze- ny z przesyłaniem lub odbieraniem komuni- blicę argumentów, bez flag. NArg() zwraca ilość
rwane, będzie następujący kod. katów przez kanały. Select ma budowę podob- argumentów bez flag, NFlag() zwraca ilość flag.
Widać więc, jak proste jest tworzenie wątków. ną do switch, w nim także używamy słów klu- Paczka regexp, jak można się domyślić, obsłu-
Co ważniejsze, możemy dostarczyć do proce- czowych case i default, z tą różnicą, że po każ- guje wyrażenia regularne, najbardziej interesują-
dury dowolne argumenty, w przypadku pthre- dym case ustalamy rodzaj transmisji na lub z ka- cą funkcją z tej paczki jest MatchString, która ja-
ads możemy przekazać tylko jeden argument nałów, która ma się wykonać. Wyrażenie select ko pierwszy argument przyjmuje wzór w forma-
wskaźnikowy typu void. Jednak musimy za- czeka na wystąpienie któregoś z wymienionych cie akceptowalnym dla wyrażeń regularnych, ja-
dbać o to, żeby wątek wykonał swoją pracę, tu- przypadków w porządku od góry do dołu, czy- ko drugi przekazujemy string, który chcemy po-
taj więc należy omówić mechanizm oraz głów- li jeśli przypadek 1 i 2 zajdą jednocześnie, to wy- równać z podanym wzorem. Funkcja ta zwraca
ną koncepcję działania kanałów. Kanały w go słu- konany zostanie przypadek pierwszy. Select nie dwa argumenty, pierwszy typu bool, drugi na-
żą do komunikacji między wątkami, sam język czeka jednak na wystąpienie któregokolwiek ze tomiast typu os.Error, z niego możemy wy-
zapewnia sprawdzanie, czy w czasie oczekiwa- zdarzeń, jeżeli umieścimy część default. czytać, czy coś poszło nie tak (ponieważ nasz
nia na komunikat nie wystąpiło zakleszczenie W tym momencie możemy już pisać pro- regexp może się niepoprawnie skompilować).
(ang. deadlock), co jest naprawdę ważnym pro- gramy współbieżne (np wykonujące jakieś ope-
blemem jeśli chodzi o programowanie wielo- racje na tablicy), problem teraz leży po stronie Podsumowanie
wątkowe. Ze względu na to, że jeśli co najmniej projektantów funkcji. Muszą oni zaprojekto- Poznaliśmy nowy młody język programowania,
dwa wątki czekają na sygnał od siebie nawzajem, wać tak funkcję, żeby stosowne obliczenia od- który oferuje dość spore możliwości. Czas na
to żaden z nich nie może się zakończyć. bywały się na żądanym kawałku danych, jednak krótkie podsumowanie.
Przesyłanie danych poprzez kanały osiąga się aby nie potrzebowały się nawzajem do wyniku Go jest prosty w budowie ze względu na
poprzez operator <-. w swoim przedziale. Np. jeśli mamy tablicę A, ograniczoną ilość słów kluczowych. Programy
Operator przyjmuje dwie postaci, pierwszą, na której musimy wykonać operację B, mając N w tym języku kompilują się naprawdę szybko,
gdzie po lewej stronie występuje kanał, a po pra- procesorów/rdzeni, to trzeba wykonać żądane natomiast prędkość ich działania jest zbliżona
wej zmienna do przesłania. Oraz drugą, w któ- zadanie w następujący sposób: do prędkości programów napisanych w C/C++.
rej po prawej występuje kanał, a po lewej zmien- Go jest językiem bezpiecznym, posiada wskaź-
na, do której mamy przypisać otrzymaną war- ppc := len(A)/N; // parts per cpu niki, jednak nie ma arytmetyki wskaźnikowej.
tość. Same kanały są po prostu rodzajem zmien- for i := 0; i < N; i++ { Język ten wspiera współbieżność poprzez tzw.
nych, jednak wytwarzanie ich odbywa się po- B(A[ (i*ppc) : ((i+1)*ppc) ] ); goroutines. Pozwala on na jednoczesne działa-
przez użycie funkcji wbudowanej, make(chan } nie tysięcy wątków bez przepełnienia stosu. Go
<typ>). Opcjonalnie możemy dołączyć drugi posiada garbage collector, przez co programista
argument typu całkowitego, który ustawi ko- Przydatne biblioteki nie musi się martwić o zwalnianie pamięci. Ca-
lejkę buforowania. Będzie to skutkować tym, Tak naprawdę niewiele zostało powiedziane ły język wspiera Unicode, więc nazwy zmien-
że jeśli komunikat zostanie przesłany na kanał, o dołączonych do go bibliotekach, więc tutaj jest nych mogą być naprawdę dowolne. Go dba o
który nie jest buforowany (wartość 0 drugie- chyba najlepsze miejsce na zrobienie tego. Za- zbędne zależności, więc żadna niechciana bi-
go argumentu lub jego brak), to proces wysy- cznijmy od przekazywania do programów argu- blioteka nie będzie dodana do kodu. A co naj-
ważniejsze, go został wydany na licencji BSDL,
więc każdy może włączyć się w jego rozwój.
W Sieci
• http://golang.org/ – strona domowa języka go; BARTOSZ PRZYBYLSKI
• http://2.7183.pl/art_go_progs/ – stąd można pobrać kody listingów. Student Politechniki Wrocławskiej, Wydziału Pod-
stawowych Problemów Techniki. Zainteresowany
głównie algorytmami i systemami rozproszonymi
Literatura oraz teorią grafów. Chętnie podejmuje współpra-
Abraham Silberschatz, Peter B. Galivn, Greg Gagne – Podstawy systemów operacyjnych – cę w innowacyjnych i interesujących projektach.
z rozdziału 5 można dowiedzieć się, jak działają wątki w różnych systemach operacyjnych. Strona domowa: http://2.7183.pl/
Kontakt z autorem: bart.p.pl@gmail.com

www.sdjournal.org 39
Sztuczna Inteligencja

Sztuczna inteligencja
do gier logicznych
Jak nauczyć komputer gry w szachy
Chciałeś kiedyś napisać sztuczną inteligencję, która umiałaby grać w szachy,
warcaby lub inną, podobną grę? To wcale nie musi być trudne. Dzięki
temu artykułowi nauczysz się jak ją stworzyć, a dodatkowo otrzymasz kod
gotowy do wykorzystania w Twojej własnej aplikacji oraz przykładowy
program grający w warcaby.
W tekście wielokrotnie było już wspomina-
Dowiesz się: Powinieneś wiedzieć: ne drzewo gry, więc najwyższy czas wyjaśnić,
• w jaki sposób komputer umie wybrać ruch • podstawowa znajomość języka C# umożli- czym ono jest. Rozważmy grę w kółko i krzy-
• jak przyspieszyć „myślenie” komputera wiająca czytanie kodu żyk. Przez stan gry będziemy rozumieli po-
• jak unikać powszechnych błędów przy pisa- • ogólna znajomość pojęcia drzewa, tablicy łączenie informacji o rozmieszczeniu kółek i
niu SI haszującej i funkcji skrótu krzyżyków na planszy z informacją o tym, na
którego gracza przypada ruch. Załóżmy, że gra
już się chwilę toczyła i teraz przypada ruch na
losowość istnieje, to nie należy się od ra- gracza stawiającego kółka. Obecny stan gry sta-
zu poddawać. Często na czas działania al- nowi korzeń drzewa. Każdy możliwy ruch w
Poziom gorytmu da się tak uprościć zasady, żeby danym stanie doprowadza do innego stanu gry.
trudności wyeliminować czynnik losowy. Załóżmy Wszystkie stany gry osiągalne ze stanu począt-
na przykład, że tworzymy sztuczną inte- kowego w jednym ruchu stanowią dzieci korze-
ligencję do strategii turowej, w której każ- nia drzewa gry. Każdy z tych stanów może z ko-
da jednostka może zadać od X do Y obra- lei przekształcić się w jakiś inny po drugim ru-

O
d kilkudziesięciu lat umiejętność żeń podczas swojego ataku. Tworząc drze- chu itd. Drzewo rozrasta się tym bardziej, im
grania w gry logiczne, takie jak wo gier, możemy założyć, że jednostka za- więcej kroków naprzód chcemy przewidywać.
szachy czy warcaby, pozostaje jed- daje swoje średnie obrażenia, czyli (X+Y)/ Przykładowe drzewo gry zostało pokazane na
nym z najbardziej pokazowych zastosowań 2. Zauważmy, że jeśli zamiast tego założy- Rysunku 1.
sztucznej inteligencji. Wśród osób, które nie libyśmy, że obrażenia zawsze będą mini- W idealnej sytuacji, gdybyśmy dysponowali
zetknęły się dokładniej z tym tematem, czę- malne, to dostalibyśmy SI grającą bardzo komputerem o nieograniczonej mocy oblicze-
sto pokutuje przeświadczenie, że jest to za- zachowawczo. Z kolei założenie o maksy- niowej, chcielibyśmy rozwinąć drzewo gry tak
gadnienie szczególnie trudne. Poniższy arty- malnej ilości obrażeń stworzyłoby SI grają- daleko, aż dojdziemy do stanów końcowych,
kuł ma na celu pokazanie, że stworzenie cał- cą niezwykle agresywnie. Już na takim po- gdzie wiadomo, zwycięstwem którego gracza
kiem nieźle grającej SI jest prostsze, niż się ziomie mamy zatem do dyspozycji mecha- zakończyła się gra. Gdyby to było możliwe, wy-
może wydawać. nizm pozwalający sterować zachowaniem bór najlepszej strategii byłby zadaniem łatwym.
Najczęściej wykorzystywanym podejściem komputerowego przeciwnika. Niestety, stworzenie takiego drzewa, przy dzi-
pozwalającym komputerowi zagrać w gry lo- • Gracz powinien posiadać pełną informa- siejszych możliwościach sprzętowych, jest moż-
giczne są algorytmy przeszukujące drzewo gry. cję o grze, tzn. nie powinno być sytuacji, liwe tylko dla bardzo prostych gier, np. gry w
Zanim zostaną one opisane, wypunktujmy naj- gdy jeden gracz wie coś, o czym nie wie kółko i krzyżyk. Dla szachów czy warcabów sto-
pierw cechy, jakie musi posiadać gra, aby dało inny. Dlatego też zaprezentowanego po- suje się inne podejście. Przewiduje się jedynie
się w niej wykorzystać tę technikę: dejścia nie można zastosować wprost np. określoną ilość kroków naprzód. Jeżeli natrafi-
do gry w brydża, gdzie gracz nie wie, ja- my na stan końcowy gry, to jego ocena jest łatwa
• Kiedy gracz podejmuje decyzje o swoim kie karty trzymają na ręku jego przeciwni- – generalnie jak najbardziej dążymy do stanów,
ruchu, powinna istnieć skończona ilość cy. No, chyba że pozwolimy sobie na oszu- w których wygrywamy i jak ognia unikamy sta-
możliwości, pomiędzy którymi wybie- stwo i udostępnimy komputerowi infor- nów przegrywających. Problem stanowią stany
ra. W praktyce nie powinno ich być wię- mację o tym, co gracz ma na ręce. pośrednie. Do ich oceny służą specjalne funkcje,
cej niż kilkadziesiąt, ponieważ w przeciw- tzw. heurystyki oceniające. O tym, jak stworzyć
nym wypadku nie da się przewidzieć zbyt Teoria matematyczna co prawda nakłada jesz- taką funkcję, napiszę w dalszej części artykułu.
wielu kroków naprzód. cze pewne dodatkowe ograniczenia, ale w Na razie załóżmy jedynie, że posiadamy funkcję
• W grze nie powinien występować czyn- praktycznych zastosowaniach spełnienie tych zwracającą +INF dla stanu, w którym wygrali-
nik losowy. Jeżeli jednak w naszej grze trzech w zupełności wystarczy. śmy, -INF dla stanu przegrywającego, i wartości

40 04/2010
Sztuczna inteligencja do gier logicznych

pośrednie dla pozostałych stanów – tym wyż- wyrzucenie z kodu nie pogorszyłoby siły gry SI aby to zrobić. Po pierwsze, musi mieć takie za-
sze, im stan gry jest dla nas korzystniejszy. – co najwyżej grałaby nieco brzydziej. Nic bar- sady, że gracz nie może wykonać ruchu, który
Czas przejść do sedna artykułu. Dysponuje- dziej mylnego. Wyobraźmy sobie sytuację w doprowadzi do jego przegranej. Po drugie, funk-
my drzewem gry i funkcją oceniającą. W jaki szachach, gdzie SI może dać mata w jednym ru- cja zwracająca listę możliwych do wykonania
sposób na tej podstawie SI ma zdecydować, któ- chu, przesuwając wieżę. Ale SI widzi też, że mo- ruchów musi być napisana w taki sposób, aby
ry ruch należy wykonać? Najpopularniejszym że się poruszyć skoczkiem i nadal ma zapewnio- zwracała pustą listę możliwych ruchów w sy-
rozwiązaniem są tu algorytmy minimaksowe. ne zwycięstwo. Jeżeli SI nie dążyłoby do jak naj- tuacji, gdy gra się skończyła. Jeżeli oba te wa-
Opierają się one na założeniu, że zarówno SI, szybszego zwycięstwa, mogłoby wykonać ruch runki są spełnione, to sam algorytm jest napisa-
jak i przeciwnik wykonają najbardziej korzystne skoczkiem. Tragedii jeszcze nie ma. Problem w ny w ten sposób, że zwraca -INF, gdy SI nie mo-
dla siebie ruchy. Założenia te są z pozoru oczy- tym, że SI w każdym kolejnym ruchu mogłoby że wykonać ruchu (czyli przegrało), a +INF w
wiste, ale mają pewne mniej oczywiste konse- skakać bezcelowo skoczkiem, ponieważ wie, że przypadku, gdy przeciwnik nie może wykonać
kwencje. Przede wszystkim, algorytmy mini- i tak może w każdej chwili wygrać. W rezultacie swojego ruchu (co oznacza z kolei porażkę prze-
maksowe powodują, że SI gra asekurancko. Nie nigdy nie wykona matującego ruchu. Ten przy- ciwnika). Wartości te są modyfikowane tak, że-
będzie próbowała zastawiać pułapek na prze- kład jasno pokazuje, że wyrzucenie tego mecha- by uwzględnić głębokość przeszukiwania, a za-
ciwnika, ponieważ z góry założy, że każda pu- nizmu w całości nie jest dobrym pomysłem na tem zachowana została funkcjonalność opisana
łapka zostanie przejrzana. Generalnie SI będzie optymalizację kodu. w poprzednim akapicie. Załączony do artyku-
grać bardziej agresywnie dopiero wtedy, gdy Okazuje się jednak, że można wyrzucić z łu program do gry w warcaby pokazujący prak-
przeciwnik nie będzie miał już żadnych szans kodu Listingu 1 fragment sprawdzający, czy tyczne zastosowanie przedstawionych tu algo-
na obronę. W zależności od zastosowań może stan jest wygrywający lub przegrywający (frag- rytmów korzysta z tej optymalizacji, ponieważ
to być wadą lub zaletą tego algorytmu. ment, o którym mowa, został wzięty w osob- warcaby spełniają oba wymienione warunki.
Idea działania algorytmu minimaksowego jest ny blok kodu i opatrzony stosownym komenta- Przedstawiony kod działa i jest wystarczający
zatem następująca. Po stworzeniu drzewa gry rzem). Gra musi jednak spełniać dwa warunki, do stworzenia SI grającej całkiem nieźle. Głów-
oceniamy za pomocą naszej funkcji stany w li-
ściach drzewa gry. Przechodzimy poziom wyżej
i jeżeli ruch przypada na SI, zakładamy, że wy-
kona ona ruch najbardziej korzystny dla siebie,
czyli oceniony najwyżej. W przeciwnym wypad-
ku zakładamy, że wykonany zostanie ruch naj-
bardziej niekorzystny. Innymi słowy, na pozio-
mach drzewa, na których ruch przypada na gra-
cza, wybieramy maksimum z wartości potom-
ków, a na poziomach przeciwnika – minimum.
Stąd zresztą wywodzi się nazwa algorytmu. Na
Rysunku 2 przedstawiono efekt działania tego
algorytmu na prostym drzewie gry.
Praktyczna realizacja algorytmu minimakso-
wego została przedstawiona na Listingu 1. Na
uwagę zasługuje fakt, że przy tej implementa-
cji nie jest potrzebne tworzenie zawczasu całego
drzewa. W trakcie działania rekurencyjnie two-
rzone są potrzebne aktualnie węzły, niszczone
zaraz po tym, jak zostaną przetworzone i prze-
staną być potrzebne. Warto w tym miejscu za-
znaczyć, że dość popularna jest lekka modyfi-
kacja algorytmu, tak aby na każdym poziomie Rysunek 1. Przykładowe drzewo dla gry w kółko i krzyżyk
znajdować jedynie maksimum potomków, co
potrafi sporo skrócić kod. Tak zmodyfikowany
algorytm nosi nazwę Negamax. Zainteresowa- �
nych serdecznie zachęcam do zapoznania się z ���
nim. Zaznaczam jedynie, że aby z niego skorzy- � �
stać, gra musi mieć tę właściwość, że jeśli jakiś
stan jest warty dla jednego gracza X, to dla dru-
giego gracza wartość tego stanu wynosi -X.
� �
Należy też zwrócić uwagę na jeszcze jeden
���
fragment kodu algorytmu. Otóż w przypad-
� � � �
ku, gdy stan gry jest wygrywający lub przegry-
wający, zwracana jest wartość uwzględniają-
ca głębokość przeszukiwania drzewa. Mówiąc
prościej – wygrana po 2 ruchach jest z punktu
widzenia SI więcej warta niż wygrana po 3 ru- � � � �
chach. Z kolei porażka po 5 ruchach jest lepsza
od porażki po jednym ruchu. Teoretycznie wy-
gląda to na czysto kosmetyczny zabieg, którego Rysunek 2. Przykład działania algorytmu min-max

www.sdjournal.org 41
Sztuczna Inteligencja

Listing 1a. Podstawowy algorytm przeszukiwania drzewa gry

using System; mSearchDepth = searchDepth;


using System.Collections.Generic; }
using System.Text;
namespace GameTreeSearch private int GameTreeSearch(IGameState gs, int depth,
{ NodeType nodeType)
//Klasa opisująca pojedynczy ruch w grze {
public class Move //Sprawdźmy czy aby analizowany stan gry nie jest
{ stanem końcowym
//Do zaimplementowania //Poniższy blok kodu można usunać w sytuacjach
} opisanych dokładniej w artykule
{
//Interfejs klasy przechowującej informację o stanie gry //Jeśli gra została zakończone
public interface IGameState : ICloneable if (gs.isGameFinished())
{ {
//Wykonuje ruch opisany poprzez m //Oceń stan gry
void makeMove(Move m); int evaluation = mEvaluator.Evaluate(gs);

//Zmienia gracza, na którego przypada ruch //Jeśli oceną jest +INF, czyli jest to nasza
void changeMovingSide(); wygrana...
if (evaluation == Int32.MaxValue)
//Zwraca listę ruchów możliwych do wykoannia w danym {
stanie //...to obniż wartość tym bardziej im
List<Move> GetPossibleMoves(); więcej ruchów musimy wykonać aby ja
osiągnąć
//Zwraca informację o tym, czy stan gry reprezentuje return Int32.MaxValue - mSearchDepth +
sytuację, w której gra jest skończona depth;
bool isGameFinished(); }
} //Jeśli oceną jest -INF, czyli jest to nasza
przegrana
//Interfejs klasy oceniającej stan gry else if (evaluation == Int32.MinValue)
public interface IGameStateEvaluator {
{ //...to zwiększ wartość tym bardziej im
//Funkcja zwracająca liczbową ocenę stanu gry podanego więcej ruchów dzieli nas od tej porażki
jako parametr funkcji return Int32.MinValue + mSearchDepth
int Evaluate(IGameState b); - depth;
} }
//W przeciwnych przypadkach (remisowych)
public class GameAI else
{ {
//Typ służący trzymaniu informacji o tym, czy //zwróć wyliczoną wartosć bez modyfikacji
znajdujemy się na poziomie minimum czy return evaluation;
maksimum }
public enum NodeType }
{ }
NODE_TYPE_MAX,
NODE_TYPE_MIN
} //Jeżeli nie przeglądamy już głębiej drzewa gry
if (depth == 0)
//Obiekt oceniający stan gry {
private IGameStateEvaluator mEvaluator; //zwróć heurystyczną ocenę zadanego stanu gry
return mEvaluator.Evaluate(gs);
//Głębokość przeszukiwania drzewa gry }
private int mSearchDepth;
//pobierz listę wszystkich możliwych do wykonania
public GameAI(IGameStateEvaluator evaluator, int ruchów
searchDepth) List<Move> possibleMoves = gs.GetPossibleMoves();
{
mEvaluator = evaluator; //Jeśli znajdujemy się na poziomie maksimum

42 04/2010
Sztuczna inteligencja do gier logicznych

Listing 1b. Podstawowy algorytm przeszukiwania drzewa gry

if (nodeType == NodeType.NODE_TYPE_MAX) //Rekurencyjnie wywołaj przeszukiwanie dla


{ poziomu niżej i jeżeli
//Zmienna przechowująca najlepszy uzyskany do //ruch jest lepszy od dotychczasowego, zmień
tej pory rezultat wyszukiwania najlepszą znalezioną wartość na nową
//Uzależnienie początkowej wartości od bestMoveValue = Math.Min(bestMoveValue,
głębokości przeszukiwania pozwala w GameTreeSearch(nextGS, depth - 1,
stanach przegrywających NodeType.NODE_TYPE_MAX));
//na preferowanie takich posunięć, które jak }
najdalej odsuwają porażkę licząc na
błąd przeciwnika //Zwróć wartość najlepszego znalezionego ruchu
int bestMoveValue = Int32.MinValue + return bestMoveValue;
mSearchDepth - depth; }
}
//Dla każdego ruchu z listy
foreach (Move move in possibleMoves) //Zwraca najlepszy ruch w stanie gs
{ public Move GetBestMove(IGameState gs)
//Wyznacz stan, który zostanie osiągnięty {
po wykonaniu ruchu //Zasadniczo jest to ta sama funkcja co
IGameState nextGS = GameTreeSearch, ale zwraca nie ocenę
(IGameState)gs.Clone(); stanu gs,
nextGS.makeMove(move); //ale najlepszy w danej sytuacji ruch
nextGS.changeMovingSide();
//pobierz listę wszystkich możliwych do wykonania
//Rekurencyjnie wywołaj przeszukiwanie dla ruchów
poziomu niżej i jeżeli List<Move> possibleMoves = gs.GetPossibleMoves();
//ruch jest lepszy od dotychczasowego,
zmień najlepszą znalezioną wartość na //Zmienne przechowująca najlepszy uzyskany do tej
nową pory rezultat wyszukiwania
bestMoveValue = Math.Max(bestMoveValue, //oraz najlepszy możliwy ruch
GameTreeSearch(nextGS, depth - 1, int bestMoveValue = Int32.MinValue;
NodeType.NODE_TYPE_MIN)); Move bestMove = null;
}
//Dla każdego ruchu z listy
//Zwróć wartość najlepszego znalezionego ruchu foreach (Move move in possibleMoves)
return bestMoveValue; {
} //Wyznacz stan, który zostanie osiągnięty po
else /*if (nodeType == NodeType.NODE_TYPE_MIN)*/ wykonaniu ruchu
{ IGameState nextGS = (IGameState)gs.Clone();
//Zmienna przechowująca najlepszy uzyskany do nextGS.makeMove(move);
tej pory rezultat wyszukiwania nextGS.changeMovingSide();
//Uzależnienie początkowej wartości od
głębokości przeszukiwania pozwala w //Przeszukiwaj drzewo poziom niżej
stanach wygrywających int result = GameTreeSearch(nextGS, mSearchDepth
//na preferowanie takich posunięć, które - 1, NodeType.NODE_TYPE_MIN);
doprowadzają do jak najszybszego if (result > bestMoveValue)
zwycięstwa {
int bestMoveValue = Int32.MaxValue - bestMoveValue = result;
mSearchDepth + depth; bestMove = move;
}
//Dla każdego ruchu z listy }
foreach (Move move in possibleMoves)
{ //Zwróć wartość najlepszego znalezionego ruchu
//Wyznacz stan, który zostanie osiągnięty return bestMove;
po wykonaniu ruchu }
IGameState nextGS = public static void Main() { }
(IGameState)gs.Clone(); }
nextGS.makeMove(move); }
nextGS.changeMovingSide();

www.sdjournal.org 43
Sztuczna Inteligencja

Listing 2a. Wprowadzenie tablicy transpozycji do algorytmu przeszukującego

public interface IGameState : ICloneable


{ public class GameAI
//Funkcja zwracająca wartość pierwszej funkcji {
skrótu od bieżącego stanu //...
int getHashKey();
//Głębokość przeszukiwania drzewa gry
//Funkcja zwracająca wartość drugiej funkcji skrótu private int mSearchDepth;
od bieżącego stanu
int getHashLock(); //Tablica transpozycji
private Dictionary<int, TTEntry> mTranspositionTable;
//...
} //Maksymalna ilość wpisów w tablicy transpozycji
private const int TRANSPOSITION_TABLE_SIZE = 100000;
//Klasa obiektów przetrzymujących stany gry zapisane w
tablicy transpozycji public GameAI(IGameStateEvaluator evaluator, int
class TTEntry searchDepth)
{ {
//Funkcja skrótu z zapisanego stanu gry //...
private int mGameStateHash; mTranspositionTable = new Dictionary<int,
TTEntry>(TRANSPOSITION_TABLE_SIZE);
//Wartość oceny zapisanego stanu gry }
private int mEvaluation;
private int GameTreeSearch(IGameState gs, int depth,
//Wartosć głębokości przeszukiwania, dla której NodeType nodeType)
wyliczono wartość stanu gry {
private int mDepth; //Sprawdźmy, czy już nie policzyliśmy kiedyś
wartości tego stanu
//Konstruktor
public TTEntry(IGameState gs, int evaluation, int //Pobierz wartosć funkcji skrótu do tablicy
depth) transpozycji i przytnij ją do wymaganego
{ rozmiaru
//Wylicz wartość funkcji skrótu z podanego stanu int ttKey = gs.getHashKey() % TRANSPOSITION_TABLE_
gry SIZE;
mGameStateHash = gs.getHashLock();
//Jesli wpis o tym kluczu jest już w tablicy
mEvaluation = evaluation; if (mTranspositionTable.ContainsKey(ttKey))
mDepth = depth; {
} //Pobierz wpis
TTEntry entry = mTranspositionTable[ttKey];
//Funkcja podająca czy zapisany stan gry nadaje się
do wykorzystania //Jeśli można go wykorzystać
public bool IsEntryApplicable(IGameState gs, int if (entry.IsEntryApplicable(gs, depth))
depth) {
{ //Zwróć wyliczoną wcześniej wartość
//Zwróć true jeżeli zapisany stan gry jest tym, return entry.GetEvaluation();
który chcemy ocenić, }
//a ocena została wykonana co najmniej na }
wymaganej głębokości
return (mGameStateHash == gs.getHashLock() && //...
mDepth >= depth);
} if (nodeType == NodeType.NODE_TYPE_MAX)
{
//Funkcja zwracająca ocenę zapisanego stanu gry //...
public int GetEvaluation()
{ //Zapamiętaj wyliczoną wartość w tablicy
return mEvaluation; transpozycji
} TTEntry ttentry = new TTEntry(gs,
} bestMoveValue, depth);

44 04/2010
Sztuczna inteligencja do gier logicznych

Listing 2b. Wprowadzenie tablicy transpozycji do algorytmu przeszukującego

mTranspositionTable[ttKey] = ttentry; public Move GetBestMove(IGameState gs)


{
//Zwróć wartość najlepszego znalezionego //Pobierz wartosć funkcji skrótu do tablicy
ruchu transpozycji i przytnij ją do wymaganego
return bestMoveValue; rozmiaru
} int ttKey = gs.getHashKey() % TRANSPOSITION_TABLE_
else /*if (nodeType == NodeType.NODE_TYPE_MIN)*/ SIZE;
{
//... //...

//Zapamiętaj wyliczoną wartość w tablicy //Zapamiętaj wyliczoną wartość w tablicy


transpozycji transpozycji
TTEntry ttentry = new TTEntry(gs, TTEntry entry = new TTEntry(gs, bestMoveValue,
bestMoveValue, depth); mSearchDepth);
mTranspositionTable[ttKey] = ttentry; mTranspositionTable[ttKey] = entry;

//Zwróć wartość najlepszego znalezionego //Zwróć wartość najlepszego znalezionego ruchu


ruchu return bestMove;
return bestMoveValue; }
} }
}

nym problemem jest długi czas działania algo- za darmo zwiększyliśmy siłę sztucznej inteli- dziemy w stanie wartym 0. W tym momencie
rytmu. Niestety, w chwili obecnej nie potrafimy gencji! Taką strukturę danych, która zapamię- okazuje się, że można przerwać analizę stanu
zejść z wykładniczej złożoności, ale na całe szczę- tuje wykonane oceny, nazywa się tablicą trans- C i zwrócić 0 jako ocenę tego stanu, nie analizu-
ście istnieje szereg metod optymalizacji pozwala- pozycji. Pozostaje zatem kwestia – jak ją zaim- jąc ruchu f. Dlaczego? Przeanalizujmy dokład-
jących na drastyczne skrócenie czasu przeszuki- plementować? Zazwyczaj wykorzystuje się w niej sytuację. W stanie A przeanalizowaliśmy
wania. Przykładowo, dla warcabów przy analizie tym celu tablicę haszującą. W wielu językach już poddrzewo po wykonaniu ruchu a. Wiemy,
dziesięciu kroków naprzód przedstawione dalej programowania jest ona dostępna, więc odpada że wykonując ten ruch przy najlepszej możliwej
optymalizacje pozwalają skrócić czas działania nam jej kodowanie. Oczywiście, aby móc sko- grze przeciwnika, dojdziemy do stanu o warto-
algorytmu od kilkunastu do nawet kilkuset razy. rzystać z takiej tablicy, musimy stworzyć funk- ści 5. W stanie C wiemy, że wykonując ruch e,
Zdecydowanie jest to gra warta świeczki. cję skrótu, która zwróci nam liczbę, której uży- dojdziemy do stanu o wartości 0. Nie wiemy
Rozważmy kolejną szachową sytuację. Roz- jemy jako klucz do tablicy haszującej. Często dla jeszcze, co byśmy dostali, gdybyśmy wykonali
poczyna się gra. Ruszam się pionem z e2 na e4, oszczędności, zarówno pamięci, jak i czasu dzia- ruch f. Rzecz w tym, że w tym stanie ruch przy-
przeciwnik z e7 na e5, ja z d2 na d4. Gdybym łania aplikacji, w tablicy haszującej nie trzyma- pada na przeciwnika. Jeżeli f byłoby dla nas ko-
najpierw ruszył się z d2 na d4, a dopiero potem my całego stanu gry, a jedynie wartość funkcji rzystniejsze (tak jak pokazuje to rysunek), prze-
z e2 na e4, to nic by to nie zmieniło. Nadal sytu- skrótu z tego stanu. Dla uniknięcia konfliktu ciwnik po prostu wykona ruch e. Jednym sło-
acja na szachownicy byłaby identyczna. Jednak funkcje skrótu użyte do stworzenia klucza ta- wem, wiemy już, że stan C dostanie wartość 0
przedstawiony algorytm minimaksowy kom- blicy haszującej i wartości trzymanej w tej tabli- lub mniej. Ale wykonując a, dostaniemy gwa-
pletnie nie bierze tego pod uwagę. Jeżeli drugi cy powinny być różne. Kod przedstawiony na rantowane osiągnięcie stanu o wartości co naj-
raz natknie się na ten sam stan, po raz drugi roz- Listingu 2 powinien rozwiać wątpliwości doty- mniej 5. Nie mamy zatem żadnego powodu wy-
winie całe poddrzewo tego stanu i żmudnie po- czące implementacji. konywać ruchu b, niezależnie od tego, co by się
liczy, ile on jest wart. Rozwiązanie narzuca się Kolejną bardzo powszechnie stosowaną me- stało, gdyby przeciwnik zagrał f, a zatem nie ma
tutaj samo. Należy zapamiętywać wartości ocen todą optymalizacji algorytmu minimaksowe- potrzeby dalszej analizy tego stanu. Wykonuje-
poszczególnych stanów. Oczywiście, oprócz te- go jest przycinanie alfa-beta. Rozważmy przy- my tzw. przycięcie alfa albo inaczej przycięcie
go trzeba zapamiętać, dla jakiej głębokości zo- kład przedstawiony na Rysunku 3. Rozpoczy- od dołu. Tego typu przycięcia wykonuje się na
stała wyliczona wartość. Jeżeli oceniliśmy jakiś namy w stanie A. Algorytm najpierw spraw- poziomach minimum, kiedy znajdziemy war-
stan gry, patrząc jedynie 1 ruch naprzód od nie- dza, co się stanie, jeśli wykonamy ruch a. Bę- tość mniejszą niż zadana alfa (w naszym przy-
go, a potem musimy ustalić wartość tego stanu, dziemy wtedy w stanie B, gdzie będą do wybo- kładzie alfa wynosiła 5). Jak się już zapewne do-
patrząc 10 kroków naprzód, to ta stara analiza ru dwa ruchy c i d. Okazuje się, że po wykona- myśliliście, przycięcia beta wykonywane są na
na nic nam się nie przyda. Musimy ponownie niu ruchu c osiągniemy stan gry o ocenie 5, a po poziomach maksimum, kiedy znajdujemy ruch
rozwinąć poddrzewo gry od tego stanu i żmud- wykonaniu d – stan o o cenie 7. Ponieważ w sta- zbyt dobry, aby poziom minimum ponad ana-
nie wyliczyć poprawną wartość. Z drugiej jed- nie B ruch przypada na przeciwnika, to zakłada- lizowanym stanem pozwolił nam go wykonać.
nak strony, jeśli kiedyś wyliczyliśmy wartość my, że wykona on lepsze dla siebie posunięcie Dla przykładu. Ruch przypada na przeciwnika i
stanu 10 kroków naprzód, a potem będziemy c. Stan B otrzymuje zatem ocenę 5. Wracamy może zagrać ruch a albo b. Jeśli zagra a, to przy
potrzebować mniejszej dokładności, z raptem do stanu A z tą informacją i analizujemy, co się optymalnej grze obu graczy zostanie osiągnię-
1 ruchem do przodu, możemy śmiało zwrócić stanie, jeśli wykonamy ruch b. Trafiamy do sta- ty stan o wartości 5. Jeśli wykona b, to podczas
zapamiętaną wartość. Nie dość, że skracamy w nu C. Sprawdzamy, co się stanie, jeśli wykona- analizy okazuje się, że istnieje ruch, dzięki któ-
ten sposób czas obliczeń, to jeszcze praktycznie my ruch e. Algorytm zwraca informację, że bę- remu wygrywamy. W tym momencie można

www.sdjournal.org 45
Sztuczna Inteligencja

Listing 3a. Wprowadzenie przycinania alfa-beta do algorytmu przeszukującego

class TTEntry
{ public class GameAI
//Typ wyliczeniowy trzymający informację czy wyliczona {
wartość została wyliczona dokładnie czy //...
została przycięta
public enum EvaluationType private int GameTreeSearch(IGameState gs, int depth,
{ NodeType nodeType, int alpha, int beta)
EVALUATION_ACCURATE, {
EVALUATION_ALPHA_CLAMPED, //...
EVALUATION_BETA_CLAMPED
} if (mTranspositionTable.ContainsKey(ttKey))
{
//Rodzaj wykonanego przycięcia //...
private EvaluationType mEvalType;
if (entry.IsEntryApplicable(gs, depth, nodeType,
//... alpha, beta))
{
//Konstruktor //...
public TTEntry(IGameState gs, int evaluation, int }
depth, EvaluationType evalType) }
{
//... if (nodeType == NodeType.NODE_TYPE_MAX)
mEvalType = evalType; {
} //Zmienna przechowująca najlepszy uzyskany do
tej pory rezultat wyszukiwania
//Funkcja podająca czy zapisany stan gry nadaje się do //Uzależnienie początkowej wartości od
wykorzystania głębokości przeszukiwania pozwala w
public bool IsEntryApplicable(IGameState gs, int stanach przegrywających
depth, GameAI.NodeType nodeType, int //na preferowanie takich posunięć, które jak
alpha, int beta) najdalej odsuwają porażkę licząc na błąd
{ przeciwnika
//Zwróć true jeżeli zapisany stan gry jest tym, int bestMoveValue = Int32.MinValue +
który chcemy ocenić, mSearchDepth - depth;
//a ocena została wykonana co najmniej na
wymaganej głębokości //Dla każdego ruchu z listy
//i nie została zbyt wcześnie przycięta foreach (Move move in possibleMoves)
if (nodeType == GameAI.NodeType.NODE_TYPE_MAX) {
{ //Wyznacz stan, który zostanie osiągnięty po
return (mGameStateHash == gs.getHashLock() && wykonaniu ruchu
mDepth >= depth && IGameState nextGS = (IGameState)gs.Clone();
(mEvalType == EvaluationType.EVALUAT nextGS.makeMove(move);
ION_ACCURATE || (mEvalType == Evalua nextGS.changeMovingSide();
tionType.EVALUATION_BETA_CLAMPED &&
mEvaluation >= beta))); //Rekurencyjnie wywołaj przeszukiwanie dla
} poziomu niżej i jeżeli
else /*if (nodeType == GameAI.NodeType.NODE_TYPE_ //ruch jest lepszy od dotychczasowego, zmień
MIN)*/ najlepszą znalezioną wartość na nową
{ bestMoveValue = Math.Max(bestMoveValue,
return (mGameStateHash == gs.getHashLock() && GameTreeSearch(nextGS, depth - 1,
mDepth >= depth && NodeType.NODE_TYPE_MIN, alpha, beta));
(mEvalType == EvaluationType.EVALUAT
ION_ACCURATE || (mEvalType == Evalua //Zaznacz, że nie interesują nas potomkowie
tionType.EVALUATION_ALPHA_CLAMPED && o wartości mniejszej niż bestMoveValue i
mEvaluation <= alpha))); można ich przyciąć
} alpha = Math.Max(alpha, bestMoveValue);
}
//... //jeśli znaleziona wartość jest większa od
} bety, wykonaj przycięcie

46 04/2010
Sztuczna inteligencja do gier logicznych

Listing 3b. Wprowadzenie przycinania alfa-beta do algorytmu przeszukującego

if (bestMoveValue >= beta) potomkowie o wartości większej niż


{ bestMoveValue i można ich przyciąć
//Zapamiętaj wyliczoną wartość w beta = Math.Min(beta, bestMoveValue);
tablicy transpozycji z zaznaczeniem, że
zostało wykonane przycięcie //jeśli znaleziona wartość jest mniejsza od
TTEntry entry = new TTEntry(gs, alfy, wykonaj przycięcie
bestMoveValue, depth, TTEntry.Evaluatio if (bestMoveValue <= alpha)
nType.EVALUATION_BETA_CLAMPED); {
mTranspositionTable[ttKey] = entry; //Zapamiętaj wyliczoną wartość w tablicy
transpozycji
//Zwróć wyliczona wartość TTEntry entry = new TTEntry(gs,
return bestMoveValue; bestMoveValue, depth, TTEntry.EvaluationT
} ype.EVALUATION_ALPHA_CLAMPED);
} mTranspositionTable[ttKey] = entry;

//Zapamiętaj wyliczoną wartość w tablicy //Zwróć wyliczona wartość


transpozycji return bestMoveValue;
TTEntry ttentry = new TTEntry(gs, }
bestMoveValue, depth, TTEntry.Evaluatio }
nType.EVALUATION_ACCURATE);
mTranspositionTable[ttKey] = ttentry; //Zapamiętaj wyliczoną wartość w tablicy
transpozycji
//Zwróć wartość najlepszego znalezionego ruchu TTEntry ttentry = new TTEntry(gs, bestMoveValue,
return bestMoveValue; depth, TTEntry.EvaluationType.EVALUATION
} _ACCURATE);
else /*if (nodeType == NodeType.NODE_TYPE_MIN)*/ mTranspositionTable[ttKey] = ttentry;
{
//Zmienna przechowująca najlepszy uzyskany do //Zwróć wartość najlepszego znalezionego ruchu
tej pory rezultat wyszukiwania return bestMoveValue;
//Uzależnienie początkowej wartości od }
głębokości przeszukiwania pozwala w }
stanach wygrywających
//na preferowanie takich posunięć, które public Move GetBestMove(IGameState gs)
doprowadzają do jak najszybszego {
zwycięstwa //...
int bestMoveValue = Int32.MaxValue -
mSearchDepth + depth; foreach (Move move in possibleMoves)
{
//Dla każdego ruchu z listy //...
foreach (Move move in possibleMoves)
{ //Przeszukiwaj drzewo poziom niżej
//Wyznacz stan, który zostanie osiągnięty int result = GameTreeSearch(nextGS, mSearchDepth
po wykonaniu ruchu - 1, NodeType.NODE_TYPE_MIN,
IGameState nextGS = bestMoveValue, Int32.MaxValue);
(IGameState)gs.Clone(); //...
nextGS.makeMove(move); }
nextGS.changeMovingSide();
//Zapamiętaj wyliczoną wartość w tablicy
//Rekurencyjnie wywołaj przeszukiwanie dla transpozycji
poziomu niżej i jeżeli TTEntry entry = new TTEntry(gs, bestMoveValue,
//ruch jest lepszy od dotychczasowego, mSearchDepth, TTEntry.EvaluationType.EVAL
zmień najlepszą znalezioną wartość na UATION_ACCURATE);
nową mTranspositionTable[ttKey] = entry;
bestMoveValue = Math.Min(bestMoveValue,
GameTreeSearch(nextGS, depth - 1, //Zwróć wartość najlepszego znalezionego ruchu
NodeType.NODE_TYPE_MAX, alpha, beta)); return bestMove;
}
//Zaznacz, że nie interesują nas }
}

www.sdjournal.org 47
Sztuczna Inteligencja

przerwać analizę, bo wiadomo, że przeciwnik proste – trzeba dodatkowo zapamiętywać w ta- najczęściej korzystne. W szachach na przykład
na pewno zagra a, nie dając nam takiego łatwe- blicy transpozycji, czy ocena jest dokładna, czy warto zaczynać analizę ruchów od posunięć bi-
go zwycięstwa. Warto tu zaznaczyć, że na przy- też zastosowano jakieś przycięcie. Załóżmy, że jących figurę przeciwnika jako potencjalnie da-
kładzie zyskujemy raptem na analizie jednego znajdujemy się na poziomie minimum i alfa jest jących przewagę. Czasami stosuje się też podej-
stanu, ale w rzeczywistych zastosowaniach, je- równa 5. Zaglądamy do tablicy transpozycji, czy ście zapamiętywania wykonanych ruchów i je-
śli analizujemy np. 10 ruchów naprzód, to ta- przypadkiem już tego nie policzyliśmy. Jeśli jest, żeli jest to możliwe, powtarzania w pierwszej
kie przycięcie nie wyeliminowałoby tylko jed- i to na wymaganej przez nas głębokości przeszu- kolejności ruchu już raz wykonanego. Skutecz-
nego stanu, ale całe poddrzewo. kiwania, sprawdzamy, czy wartość została zapa- ność jest tutaj, oczywiście, mocno zależna od
Wszystko to ładnie wygląda w koncepcji, ale miętana z przycięciem. Jeśli go nie było, sprawa gry – tym bardziej, że w wielu nie da się później
jak to teraz zakodować? Okazuje się, że całkiem jest jasna – zwracamy to, co zawiera tablica. Jeśli powtórzyć raz już wykonanego ruchu.
prosto. Po prostu za każdym razem wykonując zostało wykonane przycięcie alfa, nie wszystko Istnieją też bardziej skomplikowane mecha-
rekurencyjne wywołanie, podajemy wartości, z jeszcze stracone. Sprawdzamy, czy zapamiętana nizmy porządkowania ruchów. Ich dokładne
jakiego zakresu nas interesują. W naszym przy- wartość nie jest przypadkiem niższa niż nasza omówienie wykracza poza ramy tego artyku-
kładzie będzie to wyglądać tak: alfa. Jeśli tak jest, zwracamy ją, ponieważ rze- łu, ale warto o nich wspomnieć, chociażby po
czywista wartość jest albo równa temu, co prze- to, aby zainteresowane osoby mogły wyszukać
• Stan A, ruch a, alfa = -INF, beta = INF (nic chowuje tablica, albo niższa – tak czy inaczej zo- informacje na ich temat. Pierwszym z nich jest
nie wiemy o wartości stanów końcowych, stałaby przycięta przez naszą obecną alfę. Co się iteracyjne pogłębianie. Zaczynamy od przeszu-
więc jesteśmy gotowi na każdą wartość); jednak stanie, jeśli wykonane zostały przycię- kiwania z 1 krokiem naprzód. Potem analizuje-
• Stan B, ruch c, alfa = -INF, beta = INF cia beta? To podchwytliwe pytanie. Jeśli zosta- my dwa ruchy naprzód itd. Sztuczka polega na
(nadal nic nie wiemy); ło wykonane przycięcie beta, to znaczy, że anali- tym, że ruchy szeregujemy tak, żeby jako pierw-
• Stan B, ruch d, alfa = -INF, beta = 5 (jeste- za nastąpiła na poziomie maksimum, czyli ruch sze analizowane były te, które w poprzedniej ite-
śmy w stanie minimum; wiemy, że wy- przypadał na SI, a my jesteśmy na minimum i racji były najlepsze. Wydawać by się mogło, że
konując c ,dojdziemy do stanu o wartości ruch przypada na nas. Tymczasem stan gry za- to podejście wydłuża czas przeszukiwania, ale
5, więc nie obchodzą nas żadne wartości wiera informację nie tylko o rozmieszczeniu fi- okazuje się, że w rzeczywistości pozwala przej-
większe od 5); gur na szachownicy itp., ale również o tym, czyj rzeć nawet kilka ruchów naprzód więcej, a do
• Stan A, ruch b, alfa = 5, beta = INF (wie- jest ruch. Tym samym nie powinno nigdy dojść tego ma tę niepodważalną zaletę, że po każdej
my, że robiąc ruch a, dojdziemy do stanu do sytuacji, gdzie na poziomie minimum zosta- iteracji można sprawdzić, ile czasu zajęła nam
o wartości 5, więc nic mniejszego od 5 nas łaby zwrócona wartość z przycięciem beta. Jeśli dotychczasowa analiza i podjąć na tej podsta-
nie obchodzi); tak się stało, zapewne popełniliśmy gdzieś błąd, wie decyzję, czy analizować dalej. Tym samym
• Stan C, ruch e, alfa = 5, beta = INF; np. nie uwzględniliśmy faktu posiadania ru- dostajemy SI mającą określony czas na ruch, a
chu przy generowaniu funkcji skrótu do tablicy nie zadaną głębokość przeszukiwania. Przy tej
W tym momencie następuje przycięcie alfa, bo transpozycji. Na Listingu 3 możecie zobaczyć, ostatniej analizy skomplikowanych sytuacji po-
otrzymana wartość 0 jest mniejsza niż obecna jak zmienił się nasz kod po uwzględnieniu przy- trafią zająć kilkanaście razy więcej niż sytuacji
alfa. Gdyby jednak ruch e zwrócił wartość 9, cinania alfa-beta. prostych i tym samym sprawić, że gracz musi w
kolejnym krokiem byłoby: Stan C, ruch f, alfa Zanim zakończymy kwestię przycinania, za- pewnych sytuacjach czekać na ruch SI znacznie
= 5, beta = 9 (nie obchodzą nas wartości mniej- stanówmy się jeszcze, jak bardzo dodanie tej dłużej niż w innych, co raczej nie jest pożądane.
sze niż 5, bo w stanie A nie wykonamy takie- optymalizacji przyspieszyło nasz kod? Okazu- Z tego podejścia korzysta chociażby algorytm
go ruchu, ale nie chcemy też nic większego od je się, że jest to mocno zależne od tego, w jakiej Negascout będący jednym z najpopularniej-
9, bo w stanie C wykonalibyśmy wtedy po pro- kolejności rozpatrujemy ruchy. Jeśli zaczniemy szych algorytmów przeszukujących w zaawan-
stu ruch e). od najgorszego możliwego i będziemy postępo- sowanych programach do gry w szachy. Bardziej
Uważny czytelnik może w tej chwili stwier- wać w kierunku coraz lepszych, to okaże się, że zainteresowanych zachęcam również do zapo-
dzić, że przycinanie alfa-beta kłóci się z dopie- nie będziemy mieli ani jednego przycięcia. Z ko- znania się z algorytmem MTD(f), który wyko-
ro co omówioną tablicą transpozycji. Jak tu bo- lei zaczynanie od najlepszego możliwego ru- rzystuje przycinanie alfa-beta w nieco inny spo-
wiem korzystać z wykonanych ocen stanów, chu sprawi, że przycięcia będą występowały co sób, jako tzw. okno przeszukiwania.
skoro są one często niepełne – wykonane dla- chwila, a czas przeszukiwania stanie się liniowy To wszystko, jeżeli chodzi o optymalizację.
tego, że nastąpiło przycięcie. Rozwiązanie jest względem głębokości przeszukiwania! Wszyst- Czas rozwiązać zupełnie inny problem z algoryt-
ko pięknie, ale mem minimaksowym, o którym do tej pory nie
jeżeli wiedzieli- wspominałem – problem horyzontu. Rozważ-
� byśmy zawcza- my grę w warcaby. Bardzo często następują w
���
su, który ruch niej wymiany pion za piona, czyli np. ja biję prze-
� �
jest najlepszy, to ciwnikowi jego pionka, a on w swoim kolejnym
po co nam to ca- ruchu bije mojego. Problemem jest to, że taka
łe przeszukiwa- wymiana następuje na przestrzeni dwóch ru-
� � nie? Niby racja, chów. Co się stanie, jeśli głębokość przeszukiwa-
��� ale zawsze mo- nia będzie ustawiona tak, że SI zauważy tylko, że
� � � � żemy próbować bije piona przeciwnika, ale nie zajrzy jeden ruch
się do tego ideału dalej i nie zauważy, że sama potem traci takiego
zbliżyć. Najle- piona? Zapewne będzie myślała, że ruch w tym
piej wykorzystać kierunku jest słuszny. Dopiero jak wykona ruch
� � � � tu zdrowy rozsą- i następnym razem zajrzy o ruch głębiej, przeko-
dek i zastanowić na się, że ta niby genialna zagrywka aż tak dobra
się, jakie ruchy nie jest. Jeśli może się z tego wycofać, to pół bie-
Rysunek 3. Zasada działania przycinania alfa-beta w naszej grze są dy. Gorzej, jeżeli SI już poświęciła piona, żeby w

48 04/2010
Sztuczna inteligencja do gier logicznych

przyszłości zgarnąć piona przeciwnika. Problem reakcję przeciwnika. Oczywiście, jest to pew- się popisać jakimiś większymi umiejętnościami,
horyzontu generalnie nie został rozwiązany, ale na zgadywanka. Często warto byłoby rozwinąć warto zapoznać się z literaturą na temat danej
da się go mocno ograniczyć. Popularną metodą drzewo w stanie, który ocenimy jako cichy i vi- gry albo jeszcze lepiej skorzystać z własnego do-
jest algorytm Quiescence. Jest to kolejna mody- ce versa, ale ponownie stajemy tu przed starym świadczenia. Przykładowo, bardziej zaawanso-
fikacja algorytmu minimaksowego. Polega ona dylematem – gdybyśmy umieli ocenić to do- wane funkcje oceniające w warcabach sprawdzą,
na tym, że jeżeli stwierdzimy, że mamy analizo- kładnie, zapewne nie potrzebowalibyśmy funk- ile pól dzieli nasze piony od pól przemiany, czy
wać 0 kroków naprzód, czyli powinniśmy oce- cji szukającej najlepszego ruchu. Na Listingu 4. nasze figury łatwo jest zbić, jak dużo możliwości
nić stan za pomocą naszej heurystyki, najpierw przedstawiono, jak zmodyfikować nasz kod tak, kolejnego ruchu posiadamy, czy kontrolujemy
sprawdzamy, czy stan jest „cichy”. Jeśli jest, to aby korzystał z algorytmu Quiescence. centrum itd. Prawdopodobnie większość z Was
oceniamy tak, jak to zawsze robiliśmy. W prze- Do omówienia pozostała jeszcze jedna kwe- pomyślała w tym momencie – ale jak ocenić,
ciwnym razie, rozwijamy drzewo w tym stanie stia. Jak stworzyć funkcję oceniającą dany stan jak ważne są poszczególne cechy? Czy ważniej-
jeszcze o krok dalej, do momentu, aż dojdziemy gry? Generalnie zasada jest taka, żeby utrzy- sza jest odległość od pól przemiany swoich pio-
do stanów „cichych”. No dobrze, ale czym jest mać prostotę tej funkcji. Jeżeli policzenie war- nów, czy też fakt bronienia swoimi figurami pól
ten stan „cichy”? To akurat jest mocno zależne tości funkcji oceniającej zajmuje więcej czasu przemiany przed przeciwnikiem? Lepiej kontro-
od gry. W warcabach na przykład bicia są przy- niż przejrzenie drzewa o jeden krok dalej, lepiej lować centrum, czy mieć lepiej bronione figury?
musowe i jeśli w danym stanie bicie jest możli- ją uprościć i zrobić ten krok więcej. Główne py- Niestety nie ma prostej odpowiedzi na te pyta-
we, warto byłoby rozwinąć drzewo poziom da- tanie, jakie musimy sobie zadać, brzmi „Co jest nia. Można co prawda pokusić się np. o zastoso-
lej. Tym samym za „ciche” uznaje się te stany, w najważniejsze do wygrania w grze?” W szachach wanie algorytmów genetycznych i wyznaczenie
których bicie nie jest możliwe. W szachach jest czy warcabach z całą pewnością są to posiadane poszczególnych parametrów w drodze konkuru-
ciut trudniej. Tam prawie zawsze możliwe jest figury. Ciężko wyobrazić sobie funkcję oceniają- jących sztucznych inteligencji, ale tak naprawdę
jakieś bicie, z tym że bicia te zwykle nie są opła- cą w tych grach, która nie brałaby pod uwagę ilo- nie ma w tej chwili dobrego zastępstwa dla za-
calne. Stosuje się zatem inną sztuczkę i rozwi- ści pionów, damek itp. Jeżeli tworzymy SI gra- awansowanej wiedzy eksperckiej. Przykładowo,
ja się drzewo wtedy, gdy w poprzednim ruchu jącą w piłkarzyki, w które zapewne wielu z nas w bardziej skomplikowanych SI grających w sza-
zostało wykonane bicie. Wychodzi się bowiem grało na nudnych lekcjach na kartce papieru, naj- chy stosuje się różne funkcje oceniające w zależ-
z założenia, że bicia mocno zmieniają sytuację prostszą oceną jest odległość od bramki przeciw- ności od tego, czy jest to gra początkowa, środko-
na szachownicy i zwykle powodują gwałtowną nika. Jeżeli chcemy jednak, aby nasza SI umiała wa czy końcowa. Tym samym w większości za-
stosowań należy „ręcznie” próbować poprawiać
Listing 4. Wprowadzenie algorytmu Quiescence swoją funkcję oceniającą tak długo, aż uzyska-
my SI o satysfakcjonującym nas poziomie. Do-
public interface IGameState : ICloneable dam tylko, że przy eksperymentowaniu warto
{ napuszczać nową SI na starą, aby mieć pewność,
//... że nasza poprawka do heurystyki nie pogorszyła
//Zwraca informację czy stan gry jest cichy w rozumieniu algorytmu Quiescence przypadkiem gry SI.
bool isQuiet(); Mam nadzieję, że powyższy artykuł udowod-
} nił, że stworzenie całkiem zaawansowanej SI do
public class GameAI gier logicznych nie jest czymś niemożliwym dla
{ przeciętnego programisty. Oczywiście, temat
//... nie został wyczerpany. Nadal pozostaje kwestia
private int GameTreeSearch(IGameState gs, int depth, NodeType nodeType, int generowania ruchów czy trzymania stanu gry
alpha, int beta) w ekonomicznej postaci, ale są to w większości
{ zagadnienia mocno zależne od gry, którą zamie-
//... rzamy oprogramować. Gorąco zachęcam do
//Jeżeli nie przeglądamy już głębiej drzewa gry wypróbowania i wykorzystania przedstawionej
if (depth == 0) tu wiedzy w swoich grach oraz eksperymento-
{ wania z przedstawionymi algorytmami. Sztucz-
//jeśli stan jest cichy na inteligencja ma to do siebie, że często własne
if (gs.isQuiet()) pomysły dostosowane pod konkretną grę znacz-
{ nie poprawiają rezultaty.
//zwróć heurystyczną ocenę zadanego stanu gry
return mEvaluator.Evaluate(gs); MATEUSZ BOŻYKOWSKI
} Doświadczony programista gier na urządzenia
else mobilne oraz zapalony miłośnik sztucznej inteli-
{ gencji i szeroko pojętych algorytmów. Obecnie pra-
//w przeciwnym przypadku wymuś przeszukanie jeszcze jednego cuje w frmie Gamelion, wchodzącej w skład Gru-
poziomu drzewa py BLStream. Grupa BLStream powstała, by efek-
depth = 1; 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:
mateusz.bozykowski@game-lion.com

www.sdjournal.org 49
Warsztaty

Postprocessing w OpenGL
Dostawca usługi zarządzania komunikatami dla Websphere AS 7

Ogrom mocy obliczeniowej, którą mamy dostępną we współczesnych


kartach graficznych (GPU), sprawia, że metody renderingu, które kiedyś
były bardzo czasochłonne i zajmowały kilka godzin, teraz z powodzeniem
mogą być stosowane w interaktywnych aplikacjach graficznych, jak gry
komputerowe. Jedną z takich metod jest postprocessing obrazów.
• Dodatki.
Dowiesz się: Powinieneś wiedzieć: • Podsumowanie.
• Jak działają podstawowe efekty, jak zmiana • Jak wykonać rendering sceny w OpenGL;
kolorów, rozmycie, wykrywanie krawędzi; • Co to są shadery. Efekty graficzne
• Jak zaimplementować te efekty w OpenGL; Zastanówmy się teraz, czym są efekty na-
• Jak tworzyć dynamiczne tekstury; kładane na obrazy. Żeby nie komplikować
• Jak wykorzystać efekty do wzbogacenia naszych rozważań, możemy przyjąć nastę-
renderingu sceny. pujące założenia:
Obraz to po prostu dwuwymiarowa ta-
blica pikseli, każdy piksel zwykle prze-
Należy jeszcze wspomnieć o najnowszej chowuje trzy składowe koloru (Red, Gre-
grze z serii Prince Of Persia, gdzie zostały en, Blue), sam efekt to specjalna procedu-
Poziom zastosowane efekty, które nadały grze wy- ra, która wykonuje się dla każdego piksela
trudności gląd kreskówkowy: zarysowane krawędzie, tego obrazu.
odpowiednie tonowanie kolorów i cienio- Przyjrzyjmy się, jak może działać efekt
wanie. zmiany obrazu na skalę szarości.

W
skrócie postprocessing ozna- Jak widać na Listingu 1, z każdego pikse-
cza usprawnianie, modyfiko- Plan la obrazu pobieramy jego składowe koloru
wanie obrazów. Od dawna tę i za pomocą odpowiedniego wzoru (śred-
procedurę stosuje się do obróbki zdjęć cy- • Efekty graficzne. nia) otrzymujemy nowy piksel, który już
frowych czy poszczególnych klatek obrazu • Implementacja w OpenGL. posiada kolor w skali szarości.
filmowego. Przykładowo czasem potrze- • Dynamiczne tekstury. Efekty przyjmują na wejściu pojedynczy
ba dodać więcej światła do sceny, usunąć • Nakładanie efektów. piksel, ale swoich obliczeń nie muszą ogra-
szum, zwiększyć kontrast kolorów, a jesz- • Podstawowe efekty – kod: niczać tylko do tego wejściowego piksela,
cze innym razem sprawić, aby z fotografii • Rozmycie. często efekt „zagląda” do sąsiadów, aby uzy-
zrobić obrazek jak z komiksu. • Wykrywanie krawędzi. skać dodatkowe informacje.
W tym artykule chciałbym, abyśmy za-
stanowili się, jak efekty typu rozmycie, wy-
krywanie krawędzi, sepia, negatyw umie-
Digital Image Processing
Cyfrowe przetwarzanie obrazów – zajmuje się wszelkimi algorytmami i technikami po-
li zastosować w aplikacjach opartych o zyskiwania, obróbki i analizy obrazów cyfrowych. Mamy tutaj do czynienia z filtrowa-
OpenGL API. Każdą klatkę wygenerowa- niem, kompresją, rozpoznawaniem wzorców, redukcją szumu, analizą obrazów medycz-
nego obrazu – naszej animacji, gry, aplika- nych.
cji – możemy dostosować do naszych po-
trzeb, sprawić, by wyglądała ona znacznie
lepiej i ciekawiej.
Obecnie postprocessing jest bardzo czę-
Co to są Shadery?
Obecnie rendering interaktywnej grafiki komputerowej w głównej mierze opiera się o
sto wykorzystywany w grach komputero- specjalne mini-programy, tzw. Shadery. Są one uruchamiane bezpośrednio na karcie gra-
wych - szczególnie do efektu nazwanego ficznej.
High Dynamic Range (HDR): jarzenie kolo- Mamy kilka rodzajów Shaderów: VertexShader – przetwarza pojedyncze wierzchołki, Frag-
rów, przebłyski, promienie świetlne. HDR mentShader – odpowiada za pojedyncze fragmenty (piksele).
wpisał się już jako standardowy efekt i ża-
• http://www.lighthouse3d.com/opengl/glsl/ - jeden z najlepszych tutoriali wprowadzają-
den nowy tytuł nie może sobie pozwolić na cych.
zrezygnowanie z takiej techniki.

50 04/2010
Postprocessing w OpenGL

Przykładem takiego zachowania może być


efekt prostego rozmazywania obrazu – po- Listing 2. Rozmycie
kazany na Listingu 2. For i = 1 to picture.width do
Idea jest prosta: do swojego koloru dodaj For j = 1 to picture.height do
kolory czterech sąsiadów i uśrednij wszyst- color = 0.2 * picture[i,j] + // właściwy piksel
ko przez liczbę 5. 0.2 * picture[i-1,j] + // sąsiad po lewej
Bardzo ważne w przetwarzaniu obra- 0.2 * picture[i+1,j] + // sąsiad po prawej
zów jest wykonywanie transformacji zada- 0.2 * picture[i,j-1] + // sąsiad na dole
nej za pomocą tzw. kernela – jest to specjal- 0.2 * picture[i,j+1] // sąsiad na górze
na macierz reprezentująca wagi, które będą newPicture[i,j] = color
uwzględniane przy odczycie sąsiednich pik-
seli. Na przykład efekt rozmycia z Listingu
2 może być zadany przez następujący kernel Listing 3. FBO Podstawy
3x3 widoczny w tabeli numer 1.
Dla każdego piksela, dla którego wyko- // tworzymy fbo
nujemy efekt, będziemy „przykładać” ta- glGenFramebuffersEXT(1, &fbo);
ką macierz tak, aby środek znalazł się w da-
nym pikselu. Następnie pobieramy kolor od // ustawiamy jako aktualny
sąsiednich pikseli z odpowiednimi waga- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
mi. W tym przykładzie pobierzemy pikse-
le z prawej, lewej strony oraz z góry i z dołu // załączamy teksture:
z wagami 0.2. Pikseli po przekątnych może- glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
my nie pobierać, bo mają one wagę 0. GL_TEXTURE_2D, texID, 0);

Implementacja w OpenGL // załączamy renderbuffer (bufor głębi w tym wypadku):


Wyposażeni w podstawową wiedzę dotyczą- glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,
cą obrazów możemy podjąć się zadania prze- GL_DEPTH_ATTACHMENT_EXT,
łożenia teorii na praktykę. Spróbujemy zasto- GL_RENDERBUFFER_EXT, rtID);
sować wyżej opisane efekty do poprawiania
obrazów powstałych z renderingu w naszych // sprawdzenie czy wszystko dobrze zalączone:
aplikacjach graficznych. Każda klatka genero- GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
wana w aplikacjach graficznych to obraz, któ- if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
ry możemy poddać dodatkowej obróbce. Py- {
tanie jednak, jak się do tego zabrać? // error!
}
• Po pierwsze: musimy mieć metodę, któ-
ra pozwoli nam pozyskiwać te obrazy, // ustaw znów bufor systemowy – indeks „0”
zanim zostaną one przesłane przez kar- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
tę graficzną na monitor.

Tabela 1. Kernel 3x3 dla rozmycia


Listing 4. Tekstura i Renderbuffer
0 0.2 0
// tekstura:
0.2 0.2 0.2
GLuint texID;
0 0.2 0
glGenTextures(1, &texID);
glBindTexture(GL_TEXTURE_2D, texID);
Tabela 2. Kernel 3x3 dla wykrywania krawędzi glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-1 -1 -1 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-1 8 -1
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-1 -1 -1
// rozmiar texW na texH – RGBA – 32bity, wypełnij NULL

Listing 1. Skala szarości glTexImage2D(GL_TEXTURE_2D, 0, 4, texW, texH, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);


glBindTexture(GL_TEXTURE_2D, 0);
For i = 1 to picture.width do
For j = 1 to picture.height do // bufor z:
gray = picture[i,j].red * 0.33 + GLuint rtID;
picture[i,j].green * 0.33 + glGenRenderbuffersEXT(1, &rtID);
picture[i,j].blue * 0.33 glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rtID);
newPicture[i,j].red = gray glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, texW, texH);
newPicture[i,j].green = gray glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
newPicture[i,j].blue = gray

www.sdjournal.org 51
Warsztaty

• Po drugie: należy znaleźć sposób, w ja- Krok 1: Dynamiczne tekstury FBO jest dostępne jako rozszerzenie od
ki efektywnie i efektownie możemy Rozwiązaniem naszego pierwszego proble- OpenGL wersji 2.1 – jest to mniej wię-
wdrażać nasze efekty na tych obra- mu są tzw. dynamiczne tekstury lub inaczej cej rok 2005 i obecnie większość kart gra-
zach. RenderTargets (nazwa zwykle stosowana w ficznych powinna wspierać te obiekty.
• Trzecim krokiem będzie wysłanie Microsoft DirectX API). Idea jest prosta: za- Dodatkowo FBO stało się standardem w
przetworzonego obrazu na wyświe- miast renderować obraz na ekran, zapisz da- OpenGL3 i wyżej.
tlacz, monitor. ne do tekstury. FrameBufferObject można utożsamiać z
Jest wiele metod, jak można to osiągnąć, ale paczką buforów, do których można wykony-
Poniżej przedstawiony jest zarys metody, ostatnio w OpenGL standardem stały się bu- wać rendering. To od nas jednak zależy, co do
którą będziemy wykorzystywać: fory ramki, czyli FrameBufferObjects - FBO. tej paczki włożymy i jakie parametry ustawi-
my. OpenGL sam w sobie ma jedną taką pacz-
kę (FBO o indeksie, „0”) – czyli back i front
buffer, do którego renderuje obraz, który
później jest widoczny na ekranie monitora.
Do FBO musimy przekazać, do czego
chcemy renderować. Zwykle będzie to wła-
śnie tekstura. Czasem będziemy potrzebo-
wać także głębię sceny. FBO daje nam na-
wet możliwość renderowania do kilku tek-
stur jednocześnie (odbywa się to za pomo-
Rysunek 1. Efekt wykonywany dla każdego piksela wejściowego obrazu cą tzw. MultipleRenderTagets)!
Do FBO wkładamy tzw. załączniki. Ma-
my ich dwa rodzaje: TextureAttachment oraz
RenderBuffers.
Te pierwsze to są właśnie dynamiczne
tekstury, które później chcemy wykorzy-
stywać w dalszych etapach renderingu. Na-
tomiast RenderBuffers to specjalne bufory
do użytku wewnętrznego przez OpenGL.
Na przykład zwykle chcemy mieć tylko tek-
sturę ze sceną, a już tekstura z głębią nie
jest nam potrzebna. Mimo wszystko bufor
głębi musi być używany poprzez OpenGL
– do sortowania fragmentów! Z tego wła-
śnie powodu do FBO załączymy teksturę, a
później bufor głębi jako RenderBuffer . Za-
uważmy, że do tekstury mamy później do-
stęp i możemy ją wykorzystać, natomiast
RenderBuffer ustawiamy raz i już nie może-
my wykorzystywać jego danych.
Listing 3 przedstawia podstawy odno-
Rysunek 2. Zarys algorytmu do postprocessingu sceny śnie tworzenia i zarządzania FBO. Na li-
stingu widać, że dodaliśmy do FBO tekstu-
rę – texID, (do której będziemy chcieli ren-
Listing 5. GLSL: VS & FS – podstawowe Gray scale
derować scenę) oraz renderbuffer – rtID.
// vertex shader: Listing 4 przedstawia sposób tworzenia za-
void main() { równo tekstury, jak i renderbuffer.
gl_Position = ftransform(); Wszystko może się wydawać dość skom-
// przekształć pozycję wejściową plikowane, ale okazuje się, że wystarczy do-
gl_FrontColor = gl_Color; brze się temu przyjrzeć i większość proble-
gl_TexCoord[0] = gl_MultiTexCoord0; mów ze zrozumieniem się niweluje. Bardzo
} ważne jest, aby wszystkie załączniki w FBO
// fragment shader: miały dokładnie takie same rozmiary (wy-
uniform sampler2D inputTexture; sokość i szerokość) – bez tego OpenGL nie
void main() { będzie wiedział, jak poprawnie wykonać
// odczytaj kolor: rendering, i zwróci błąd. Bardzo przydat-
vec3 col = texture2D(inputTexture, gl_TexCoord[0].st).rgb; ną funkcją jest glCheckFramebufferStatu
// prosta konwersja na skalę szarości: s, która sprawdza stan FBO i czy wszystko
col.rgb = vec3(col.r*0.33 + rol.g*0.33 + col.b*0.33); jest poprawnie ustawione.
// wyślij go na ekran…
gl_FragColor = vec4(col, 1.0); // kolor + alpha Krok 2: Procesory Pikseli
} Gdy mamy już teksturę reprezentującą na-
szą scenę, możemy przejść do wykonywa-

52 04/2010
Postprocessing w OpenGL

nia na niej efektów. Najprościej będzie za- Listing 6. Rendering sceny + postprocessing
przęgnąć do tego zadania procesory pikseli
(lub bardziej dokładnie fragmentów) – czy- // scena – renderujemy do przygotowanej wcześniej tekstury i FBO:
li FragmentShaders. Dzięki nim możemy pi- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, mFBO);
sać proste programy, które będą wykony- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
wane dla pojedynczego docelowego pikse- RenderScene();
la na ekranie.
Na Listingu 5 znajduje się kod Vertex // postprocessing:
i Fragment Shadera, które pobierają kolor // ustaw FBO o numerze 0 – czyli ekran
z tekstury wejściowej (tej, do której wcze- glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
śniej renderowaliśmy scenę), a następnie glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
zmieniają ten kolor na skalę szarości i wy-
syłają go na ekran: // użyj efektu:
glUseProgram(postprocEffectID);
Krok 3: Rendering z nałożonym
efektem. // ustaw teksture do której wcześniej renderowaliśmy scenę:
O ile Fragment Shader z Listingu 5 był, glBindTexture(GL_TEXTURE2D, texID);
mam nadzieję, zrozumiały, o tyle nie wia-
domo, co robił VertexShader. Jakie wierz- // pełnoekranowy prostokąt:
chołki on przetwarzał? glMatrixMode(GL_PROJECTION);
Zauważmy, że mamy teksturę ze sceną. glLoadIdentity();
Na tę teksturę chcemy nałożyć efekt, ale glMatrixMode(GL_MODELVIEW);
chcemy oczywiście, aby nadal scena mia- glLoadIdentity();
ła kształt prostokątnego widoku kamery i glBegin(GL_QUADS);
była ukazana na całym oknie. Dlatego naj- glTexCoord2i(0, 0); glVertex2i(-1, -1);
prostszym rozwiązaniem jest narysować glTexCoord2i(1, 0); glVertex2i(1, -1);
czworokąt, który przykryje cały ekran i na- glTexCoord2i(1, 1); glVertex2i(1, 1);
łożyć na niego teksturę. Listing 6 przedsta- glTexCoord2i(0, 1); glVertex2i(-1, 1);
wia cały pomysł już z wykorzystaniem FBO glEnd();
i renderingiem sceny.
Listing 7. Rozmycie
Efekty – implementacja
Teraz, gdy już mamy technologię do two- uniform sampler2D texture;
rzenia efektów, możemy spróbować napi- const float dx = 0.00125;
sać parę efektów!
Wcześniej udało nam się wykonać efekt // 1/800 – rozmiar jednego texela
zmiany na skalę szarości. Ten typ efektu const float dy = 0.00292; // 1/600 – rozmiar jednego texela
określamy mianem transformacji koloru.
Można wykonywać różnego rodzaju trans- // sasiedzi 3x3 – (0,0) to punkt centralny
formacje, takie jak: negatyw, powiększanie const vec2 offsets[9]=vec2[9](vec2(-dx,-dy),vec2(0.0,-dy),vec2(dx,-dy),
kontrastu, logarytm koloru etc… Wszystko vec2(-dx, 0.0), vec2(0.0, 0.0), vec2(dx, 0.0),
polega na odpowiednich operacjach ma- vec2(-dx, dy), vec2(0.0, dy), vec2(dx, dy));
tematycznych wykonywanych na kolorze void main()
piksela. {
Spróbujmy jednak wykonać bardziej in- // rozklad gaussa N(0,1), dla 3x3
teresujący przykład: rozmycie – czyli blur. onst float kernel[9] = float[9](0.075625, 0.12375, 0.075625,
0.12375, 0.2025, 0.12375,
Rozmycie obrazu 0.075625, 0.12375, 0.075625 );
Pomysł na rozmywanie obrazu jest dość pro- // zbierz dane
sty: dla każdego piksela obrazu weź jego kil- vec4 col[9];

for (int i = 0; i < 9; ++i) {


col[i] = texture2D( texture, gl_TexCoord[0].st+offsets[i]);
}
// zastosuj kernel
vec4 Color = vec4(0.0);
for (int i = 0; i < 9; ++i) {
Color += kernel[i]*col[i];
gl_FragColor = Color;
}
}
Rysunek 3. Rozkład Normalny 2D

www.sdjournal.org 53
Warsztaty

ku sąsiadów i uśrednij ich kolor (efekt na- Listing 7 zawiera implementacje shade- Krawędź można określi, jako nagłą zmia-
zywa się dokładnie BoxBlur). Jak się jednak ra, który wykonuje rozmycie obrazu. Po- nę kolorów, na przykład z białego na czar-
okazuje takie najprostsze podejście nie daje bieranych jest 9 pikseli z pewnego otocze- ny. Zauważmy, że tak zdefiniowany ker-
najlepszych rezultatów – zachęcam czytel- nia. Kernel z listingu zawiera pewne wagi nel będzie działał prawidłowo; gdy dane
nika do sprawdzenia. Obecnie standardem (przybliżone) z rozkładu normalnego. Od sąsiedztwo ma podobny kolor, to filtr nic
w wygładzaniu obrazów jest GussianBlur. razu widać, że im piksel jest odległy od pik- nie zrobi – da kolor równy 0, natomiast
Po lewej jest widoczny wykres rozkładu sela centralnego, będzie miał mniejszą wagę gdy będzie w sąsiedztwie krawędź, to wte-
normalnego – Gaussa. Pobierając kolory są- w obliczeniach. dy filtr uwypukli ją (za sprawą wartości 8
siadów danego piksela, będziemy uwzględ- w kernelu) i sprawi, że krawędź będzie wi-
niać je w średniej z określoną wagą. Waga Wykrywanie krawędzi doczna.
będzie pochodzić właśnie z rozkładu nor- Kolejnym ważnym efektem jest wykrywa- Gdy zamiast 8 użyjemy 9 w tym kerne-
malnego. Dzięki temu największe wagi nie krawędzi. Najprostszy kernel, który lu, to okaże się, że dostaniemy filtr, któ-
otrzymają sąsiedzi, którzy znajdują się naj- opisuje wykrywanie, ma następującą po- ry będzie dodawał krawędzie do istnieją-
bliżej centralnego piksela. stać widoczną w tabeli 2. cej sceny.
Listing 8 zawiera kod efektu wykrywa-
Listing 8. Wykrywanie krawędzi nia krawędzi. Zauważmy jednak, że kra-
wędź jest wykrywana dla każdej składo-
uniform sampler2D texture; wej koloru osobno… dla Red, Green i Blue.
Gdy po prostu zwrócimy kolor, to ca-
const float dx = 0.00125; ły efekt nie będzie wyglądał zbyt dobrze.
// 1/800 – rozmiar jednego texela Dlatego na końcu przekształcam wynik
const float dy = 0.00292; na skalę szarości (w miejscu, gdzie są py-
// 1/600 – rozmiar jednego texela tajniki). Można oczywiście wykonać inne
transformacje, aby zachować kolor i kra-
// sasiedzi 3x3 – (0,0) to punkt centralny wędzie. (jakie? – polecam poeksperymen-
const vec2 offsets[9]=vec2[9](vec2(-dx,-dy),vec2(0.0,-dy),vec2(dx,-dy), tować).
vec2(-dx, 0.0), vec2(0.0, 0.0), vec2(dx, 0.0),
vec2(-dx, dy), vec2(0.0, dy), vec2(dx, dy)); Podsumowanie
To, co udało nam się zaimplementować, to
void main() dopiero wierzchołek góry lodowej! Jest na-
{ prawdę bardzo wiele różnorakich efektów,
const float kernel[9] = float[9]( -1.0, -1.0, -1.0, które można wykorzystać w swoich apli-
-1.0, 9.0, -1.0, kacjach.
-1.0, -1.0, -1.0 ); Efekty podwójnego widzenia, falującego
// zbierz dane ciepłego powietrza, zmiana ostrości widze-
vec4 col[9]; nia, widzenie jak w noktowizorze… wszyst-
for (int i = 0; i < 9; ++i) { ko to jest dostępne właśnie za sprawą post-
col[i] = texture2D( texture, gl_TexCoord[0].st+offsets[i]); processing.
} Nic nie stoi na przeszkodzie, aby dodać
// zastosuj kernel do takiej obróbki animacje, wystarczy do
vec4 Color = vec4(0.0); shadera przekazać czas i na jego podstawie
or (int i = 0; i < 9; ++i) { wykonać jakiś ciekawy efekt.
Color += kernel[i]*col[i]; Mam nadzieję, że udało mi się zachęcić
} do eksperymentów z efektami i shaderami.
// ??? W materiałach dodatkowych (CD Rom)
Color.rgb = vec3(dot(Color.rgb, vec3(0.299, 0.587, 0.114))); jest umieszona aplikacja przykładowa, któ-
Color.a = 1.0f; ra ukazuje cały kod i różne efekty, którymi
można się bawić. Wierzę, że pomoże ona
gl_FragColor = Color; zbudować własny system do postproces-
} sing sceny.

BARTŁOMIEJ FILIPEK
Jest obecnie studentem V roku na wydziale Mate-
matyki i Informatyki UJ, gdzie pracuje nad swo-
Materiały i przydatne linki ją pracą magisterską z metod renderingu Cie-
• http://www.songho.ca/opengl/gl_fbo.html – świetne wyjaśnienie działania FBO i prak- ni w gra�ce 3D. W swojej pracy zawodowej miał
tyczne zastosowanie; do czynienia z projektowaniem interfejsów użyt-
• http://www.gamedev.net/reference/articles/article2331.asp – artykuł z Gamedev.net kownika dla aplikacji multimedialnych, a także z
o FBO budowaniem silników 3D.
• Astle, D. (2006). More OpenGL Programming. Thomson Course Technology;
Wolny czas spędza na basenie, rowerze, przy gi-
• Wright, R. S. (2007). OpenGL SuperBible IV Edition. Addison Wesley.
tarze lub po prostu ze znajomymi.
Strona: www.b�lipek.com

54 04/2010
Warsztaty

Tworzenie galerii zdjęć przy


pomocy ASP.NET MVC, cz. I
Programowanie części galerii zdjęć przeznaczonej dla
użytkownika z wykorzystaniem możliwości platformy .NET
Framework oraz wzorca projektowego MVC.
W pierwszej części artykułu została przedstawiona „krok po kroku” bu-
dowa fragmentu aplikacji przeznaczonego dla użytkownika, tzn. umoż-
liwiającego wyświetlanie listy albumów, zdjęć należących do konkret-
nego albumu, a także pojedynczego zdjęcia oraz dodawanie komen-
tarzy. Ponadto, krótko opisany został framework ASP.NET MVC, a także
sposoby tworzenia aplikacji internetowych korzystających z niego.
który pozwala na tworzenie witryn inter-
Dowiesz się: Powinieneś wiedzieć: netowych, korzystając z zalet wzorca pro-
• Czym jest ASP.NET oraz ASP.NET MVC • Jaka jest składnia języka C# jektowego MVC. Zastosowanie tej techno-
• W jaki sposób utworzyć aplikację korzysta- • Czym jest wzorzec projektowy MVC oraz Re- logii umożliwia projektowanie rozbudowa-
jącą z ASP.NET MVC pository nych aplikacji internetowych korzystających
• Jakie możliwości posiada ASP.NET MVC z mechanizmu routingu, wsparcia dla AJAX
• Jak utworzyć część galerii zdjęć przeznaczoną czy też programowania zgodnego z techniką
dla użytkownika (m.in. jak uzyskać dostęp do TDD (Test–Driven Development). Wykorzy-
danych przechowywanych w bazie danych, stanie wzorca projektowego MVC pozwala
jak można tworzyć kontrolery, akcje i widoki) twórcom stron internetowych na rozdzie-
lenie poszczególnych części projektu: mo-
delów, widoków i kontrolerów. Elemen-
Artykuł przeznaczony jest przede wszyst- ty te posiadają zróżnicowane zasady dzia-
kim dla programistów, którzy wcześniej nie łania i współpracy z innymi częściami apli-
Poziom tworzyli aplikacji internetowych przy po- kacji. Zastosowanie tego wzorca projekto-
trudności mocy ASP.NET MVC. W obu częściach wego ma pozytywny wpływ na jakość kodu
Czytelnicy będą mieli okazję poznać zasto- tworzonego przy projektowaniu aplikacji in-
sowanie języka C# do programowania apli- ternetowej, między innymi poprzez umoż-
kacji internetowych przy pomocy tego fra- liwienie późniejszej prostszej modyfikacji

W
itam w pierwszej części artykułu meworka. Korzystanie z niego umożliwia poszczególnych elementów projektu, która
poświęconego tworzeniu aplika- tworzenie stron internetowych z wykorzy- może polegać np. na zmianie rodzaju stoso-
cji internetowej, pełniącej funk- staniem bardzo dużych możliwości platfor- wanej bazy danych. W przypadku popraw-
cję galerii zdjęć, przy pomocy frameworka my .NET Framework, a ponadto zgodnych nie zaimplementowanego wzorca projekto-
ASP.NET MVC. z wzorcem projektowym Model – View wego MVC, modyfikacja ta może spowodo-
W tej części zostanie przedstawiona „krok – Controller (MVC). wać jedynie konieczność zmiany kodu zwią-
po kroku” budowa fragmentu aplikacji prze- zanego z modelem, przy równoczesnym po-
znaczonego dla użytkownika, tzn. umoż- Czym jest ASP.NET zostawieniu bez zmian zarówno kodu kon-
liwiającego wyświetlanie listy albumów, i ASP.NET MVC? trolerów, jak i widoków.
zdjęć należących do konkretnego albumu, a Na początku artykułu zastanówmy się, cze-
także pojedynczego zdjęcia oraz dodawanie go dotyczy nazwa umieszczona w tytule. Opis galerii zdjęć
komentarzy. Ponadto, krótko opisany zosta- Zarówno ASP.NET, jak i ASP.NET MVC Wiemy już, czym jest wzorzec MVC i ja-
nie framework ASP.NET MVC, a także spo- są frameworkami utworzonymi przez Mi- kie może on mieć znaczenie dla programi-
soby tworzenia aplikacji internetowych ko- crosoft, które umożliwiają programowa- stów aplikacji internetowych. Przed przy-
rzystających z niego. nie dynamicznych aplikacji internetowych, stąpieniem do pracy nad galerią zdjęć war-
W kolejnym numerze magazynu przed- przy wykorzystaniu rozbudowanych moż- to się najpierw zastanowić, jakie funkcje ma
stawione zostanie tworzenie panelu admi- liwości platformy .NET Framework, a tak- ona posiadać.
nistracyjnego umożliwiającego zarządzanie że m.in. języka C#. Jak sama nazwa wska- Galeria zdjęć utworzona w tej oraz kolej-
albumami, zdjęciami i komentarzami. zuje, ASP.NET MVC jest frameworkiem, nej części artykułu będzie umożliwiała wy-

56 04/2010
Tworzenie galerii zdjęć przy pomocy ASP.NET MVC, cz. I

świetlanie listy albumów, listy zdjęć należą- listę reguł kaskadowego arkusza stylów, któ- formacje dotyczące źródeł danych, obsłu-
cych do konkretnego albumu oraz pojedyn- re są stosowane w celu określania wyglądu gi błędów czy też autentykacji użytkow-
czego zdjęcia. Pozwoli także na dodawanie poszczególnych elementów strony interne- ników.
komentarzy przez użytkowników, a także towej. W folderze tym można umieścić tak- Możemy już teraz uruchomić automa-
zarządzanie albumami, zdjęciami i komen- że np. grafiki, które są wyświetlane na pod- tycznie wygenerowaną aplikację, wybiera-
tarzami z poziomu panelu administracyjne- stronach witryny. jąc z menu środowiska programistycznego
go dostępnego po zalogowaniu. Dodawanie Katalog Controllers zawiera z kolei pli- opcję Debug oraz Start Debugging (bądź też
komentarzy będzie z kolei możliwe bez ko- ki .cs (z kodem w języku C#) z definicjami korzystając ze skrótu F5). W komunikacie,
nieczności logowania się użytkowników. klas, które pełnią rolę kontrolerów w aplika- który powinien się pojawić podczas pierw-
cji internetowej, wykonywanej przy pomocy szej próby uruchomienia nowo utworzo-
Utworzenie nowej aplikacji ASP.NET MVC. nej aplikacji, warto wybrać opcję Modify the
ASP.NET MVC W folderze Models powinny znaleźć się Web.config file to enable debugging.
Galeria zdjęć zostanie utworzona przy po- elementy projektu, które są związane z mo-
mocy środowiska programistycznego Micro- delem ze wzorca projektowego MVC, czyli Tworzenie bazy danych
soft Visual Web Developer 2008 Express np. klasy, które umożliwiają mapowanie za- Nasza aplikacja będzie przechowywała da-
Edition, które można pobrać ze strony http: wartości tabeli z relacyjnej bazy danych do ne dotyczące założonych albumów, znajdu-
//www.microsoft.com/exPress/. Ponadto wy- postaci obiektów. jących się w nich zdjęć oraz dodanych przez
magane jest zainstalowanie frameworka Katalog Scripts zawiera zbiór skryptów użytkowników komentarzy. Do zapisywa-
ASP.NET MVC (dostępnego na stronie: http: w języku JavaScript, w tym jQuery oraz Mi- nia tych danych możemy wykorzystać np.
//www.asp.net/mvc/download/). Listingi oraz crosoft Ajax. Klasy zgromadzone w tych pli- pliki XML albo skorzystać z relacyjnej ba-
omówienie poszczególnych elementów apli- kach mogą zostać wykorzystane w projek- zy danych. W ramach artykułu przedsta-
kacji dotyczą ASP.NET MVC w wersji 1.0. tach w celu dodania skryptów działających wione zostanie wykorzystanie bazy danych
Po uruchomieniu środowiska programi- po stronie klienta oraz obsługi technolo- SQL Server.
stycznego należy utworzyć nowy projekt gii AJAX. Jak widać na przedstawionym diagramie
ASP.NET MVC, korzystając z opcji menu Ostatni folder (Views) zawiera katalo- ERD, dane albumów oraz zdjęć będą prze-
File oraz New Project, a następnie zazna- gi oraz pliki, w których znajduje się kod chowywane w dwóch tabelach: Albums oraz
czając w oknie, które się pojawi, ASP.NET XHTML określający wygląd strony inter- Photos. Przyjmujemy założenie, że do albu-
MVC Web Application oraz podając nazwę netowej. Zastosowanie widoków jest zwią- mu może należeć wiele zdjęć, zaś zdjęcie
tworzonego projektu. zane z kontrolerami, o czym będzie się musi należeć do dokładnie jednego albu-
można przekonać w dalszej części tego ar- mu. Obie tabele będą zawierać podobne po-
Struktura aplikacji ASP.NET MVC tykułu.
Zostanie wygenerowany projekt zawierający Oprócz wygenerowanych katalogów mo-
odpowiednią strukturę katalogów, a także żemy również znaleźć przygotowane pliki,
kilka przykładowych plików, w których za- które umożliwiają np. konfigurację apli-
warty jest kod m.in. umożliwiający logowa- kacji internetowej tworzonej przy pomo-
nie i wylogowywanie się użytkowników. cy ASP.NET MVC. Szczególnie istotnym
W katalogu Content znajduje się automa- plikiem jest Web.config, w którym zdefi-
tycznie wygenerowany plik CSS zawierający niowane są ustawienia aplikacji, w tym in-

Rysunek 2. Struktura nowo utworzonego


projektu ASP.NET MVC (środowisko
Rysunek 1. Tworzenie nowego projektu (środowisko programistyczne Microsoft Visual Web Developer programistyczne Microsoft Visual Web Developer
2008 Express Edition) 2008 Express Edition)

www.sdjournal.org 57
Warsztaty

la, w tym klucze główne (AlbumId oraz Pho- można to wykonać, zostanie opisany w dru- sko programistyczne Microsoft Visual Web
toId), a także tytuły, opisy, daty dodania i giej części artykułu. Developer 2008 Express Edition umożliwia
ostatniej modyfikacji, a w przypadku zdję- Przy tworzeniu większego projektu ta- szybkie i wygodne tworzenie struktury ba-
cia także ścieżkę do niego. Część z tych pól kie rozwiązanie prawdopodobnie okazało- zy danych przy pomocy graficznego interfej-
musi być uzupełnionych przy dodawaniu by się niewystarczające, dlatego też można su. Aby dodać nową tabelę do bazy danych,
(bądź też modyfikacji) danych, zaś niektó- wówczas wykorzystać możliwości platfor- należy wyświetlić okno Database Explorer, a
re z nich są opcjonalne. Dane dotyczące ko- my .NET Framework ułatwiające zarządza- następnie z menu kontekstowego, dostępne-
mentarzy będą z kolei przechowywane w ta- nie i autentykację użytkowników, zarządza- go po kliknięciu prawym przyciskiem my-
beli Comments, której kluczem głównym jest nie grupami czy też profilami. Warto wspo- szy na elemencie drzewa Tables, wybrać Add
pole CommentId. Zawiera ona także pola mnieć, że ich konfiguracja jest przeprowa- New Table.
związane z nazwą autora komentarza, jego dzana w pliku Web.config. W tym artykule Przy dodawaniu tabel do bazy danych na-
adresem e–mail, treścią komentarza, a tak- nie zostaną one jednak omówione. leży zwrócić uwagę na ustanowienie pól Al-
że datą dodania. Warto zauważyć, że zgod- Przygotowany wcześniej diagram ERD po- bumId, PhotoId oraz CommentId kluczami
nie z diagramem ERD, do zdjęcia może być służy nam do utworzenia odpowiednich ta- głównymi, które będą ponadto automatycz-
wystawionych wiele komentarzy, jednak po- bel w bazie danych i powiązania ich ze so- nie inkrementowane. W tym celu należy, z
jedynczy komentarz musi dotyczyć dokład- bą związkami. Wcześniej jednak należy do- menu kontekstowego dostępnego po zazna-
nie jednego zdjęcia. dać do projektu nową bazę danych. W tym czeniu pola, wybrać Set Primary Key, a na-
Jak zostało zaznaczone w opisie funkcjo- celu, z menu kontekstowego dostępnego stępnie w zakładce Column Properties, w gru-
nalności galerii zdjęć, ma ona umożliwiać dla elementu reprezentującego nasz pro- pie Identity Specification, wartość (Is Identity)
także logowanie się administratora. W przy- jekt w oknie Solution Explorer, wybieramy ustawić na Yes.
padku tej aplikacji zostanie przedstawiona Add oraz New Item. W oknie, które się po- Następnym krokiem jest utworzenie
możliwość autentykacji użytkownika przy jawi, wystarczy wybrać SQL Server Databa- związków pomiędzy trzema nowo dodany-
pomocy nazwy i hasła, które są zdefiniowa- se oraz podać nazwę pliku bazy danych (np. mi tabelami.
ne w pliku konfiguracyjnym. Sposób, w jaki Db.mdf). W przypadku pojawienia się ko- Aby dodać związek pomiędzy tabelami
munikatu mówiącego o tym, że plik bazy da- Photos oraz Albums, należy w oknie Data-
Listing 1. Kod z pliku IAlbumRepository.cs nych powinien znajdować
się w katalogu App_Data,
using System.Collections.Generic; warto go zaakceptować.
Następnym istotnym
namespace GaleriaZdjec.Models.Reposit krokiem jest utworzenie
ories tabel w nowo dodanej
{ bazie danych. Środowi-
interface IAlbumRepository
{
Album GetById(int albumId);
IEnumerable<Album> GetAll();
}
}

Listing 2. Kod z pliku IPhotoRepository.cs

namespace GaleriaZdjec.Models.Reposit
ories
{
interface IPhotoRepository
{
Photo GetById(int photoId);
}
}

Listing 3. Kod z pliku


ICommentRepository.cs

namespace GaleriaZdjec.Models.Reposit
ories
{
interface ICommentRepository
{
void Add(Comment comment);
}
} Rysunek 4. Dodawanie tabel do bazy danych (środowisko
Rysunek 3. Diagram ERD programistyczne Microsoft Visual Web Developer 2008 Express
projektowanej galerii zdjęć Edition)

58 04/2010
Tworzenie galerii zdjęć przy pomocy ASP.NET MVC, cz. I

base Explorer dwukrotnie kliknąć na tabeli o


nazwie Photos, a następnie z menu Table De- Listing 4. Kod z pliku AlbumRepository.cs
signer wybrać opcję Relationships. Po kliknię- using System.Collections.Generic;
ciu przycisku Add należy wybrać pole znaj- using System.Linq;
dujące się przy etykiecie Tables and Columns namespace GaleriaZdjec.Models.Repositories
Specification i kliknąć na mały przycisk, któ- {
ry pojawi się po prawej stronie pola teksto- public class AlbumRepository : IAlbumRepository
wego. Następnie wystarczy jeszcze tylko wy- {
brać odpowiednie pola tabel Albums i Pho- private DataClassesDataContext _db = new DataClassesDataContext();
tos, które będą kluczem podstawowym oraz
obcym. W tym przypadku kluczem podsta- public Album GetById(int albumId)
wowym będzie pole AlbumId z tabeli Al- {
bums, zaś kluczem obcym pole AlbumId z return _db.Albums.Single(a => a.AlbumId == albumId);
tabeli Photos. W analogiczny sposób należy }
zdefiniować związek pomiędzy tabelami
Photos oraz Comments. public IEnumerable<Album> GetAll()
Struktura bazy danych zostanie zapisana {
po zamknięciu okien umożliwiających kon- return _db.Albums;
figurację związków, zapisaniu wprowadzo- }
nych zmian i ich potwierdzeniu (przy po- }
mocy dodatkowo wyświetlonego okna przed }
zapisem).
Na koniec dodajmy jeszcze przykładowe Listing 5. Kod z pliku CommentRepository.cs
dane do tabel w bazie danych. Jest to spo- using System;
wodowane tym, że w tej części artykułu namespace GaleriaZdjec.Models.Repositories
nie będzie opisywane tworzenie panelu ad- {
ministracyjnego umożliwiającego zarządza- public class CommentRepository : ICommentRepository
nie danymi. {
W tym celu można w oknie Databa- private DataClassesDataContext _db = new DataClassesDataContext();
se Explorer zaznaczyć odpowiednią tabelę
i z menu kontekstowego wybrać Show Ta- public void Add(Comment comment)
ble Data. Następnie możemy już, przy po- {
mocy środowiska programistycznego, doda- comment.AdditionDate = DateTime.Now;
wać dane do tabeli. W przypadku tworzonej _db.Comments.InsertOnSubmit(comment);
galerii zdjęć należy podać adres do pliku ze _db.SubmitChanges();
zdjęciem względem katalogu Uploads (np. }
łańcuch znaków 1.jpg będzie dotyczył pli- }
ku o nazwie 1.jpg znajdującego się w katalo- }
gu Uploads). Datę możemy z kolei podać w
formacie RRRR–MM–DD GG:MM:SS (np. Listing 6. Kod z pliku AlbumController.cs
2009–12–26 11:00:00). using System.Web.Mvc;
using GaleriaZdjec.Models.Repositories;
Model namespace GaleriaZdjec.Controllers
Po dodaniu bazy danych oraz utworzeniu {
tabel przejdziemy do tworzenia interfejsów public class AlbumController : Controller
oraz klas, które będą służyć do uzyskania do- {
stępu do danych zgromadzonych w bazie da- private IAlbumRepository _albumRepository = new AlbumRepository();
nych. Do tego celu wykorzystany zostanie
LINQ to SQL oraz wzorzec projektowy Re- public ActionResult Index()
pository. {
Pierwszą czynnością jest dodanie do kata- return View(_albumRepository.GetAll());
logu Models nowego elementu – klas LINQ }
to SQL. Aby to uczynić, należy zaznaczyć
ten folder w oknie Solution Explorer i z me- public ActionResult View(int id)
nu kontekstowego wybrać opcję Add oraz {
New Item, a w oknie, które się pojawi, zazna- var album = _albumRepository.GetById(id);
czyć LINQ to SQL Classes. W przypadku te- if (album != null) { return View(album); }
go projektu, zmieńmy nazwę pliku na Da- return RedirectToAction("Index");
taClasses.dbml. Po jego otworzeniu wystar- }
czy przeciągnąć do jego okna tabele przedsta- }
wione w Database Explorer. Po zapisaniu pli- }
ku warto przebudować cały projekt (wybie-
rając z menu Build opcję Rebuild).

www.sdjournal.org 59
Warsztaty

Kolejną czynnością będzie utworzenie nia różnych metod rozszerzających (Me- Jak można zauważyć, przy domyślnej kon-
trzech interfejsów, z których każdy będzie thod Syntax). figuracji mechanizmu routingu nie osią-
dotyczył innej tabeli: IAlbumRepository, gniemy zamierzonego efektu, dlatego
IPhotoRepository oraz ICommentRepository. Kontrolery i akcje też w dalszej części tego artykułu zosta-
Pliki odpowiadające tym klasom (IAlbumRepo- Trzy klasy oraz interfejsy utworzone wcze- nie przedstawiona modyfikacja pliku Glo-
sitory.cs, IPhotoRepository.cs oraz ICommentRe- śniej umożliwiają nam już uzyskanie dostę- bal.asax, w którym mamy możliwość zde-
pository.cs) umieścimy w katalogu Models/Re- pu do danych, jakie będą nam potrzebne w finiowania dodatkowych reguł, według
positories. Będą one zawierać informacje na te- pierwszej części artykułu. których będzie tłumaczony wprowadzony
mat metod (służących do pobrania czy też do- Następną czynnością, związaną z tworze- adres strony.
dawania określonych danych do bazy), które niem galerii zdjęć przy pomocy ASP.NET Przejdźmy więc do pisania kodu klas re-
będą musiały znajdować się w klasach imple- MVC, będzie modyfikacja automatycznie prezentujących poszczególne kontrolery.
mentujących te interfejsy. wygenerowanych kontrolerów oraz utwo- Na początku usuńmy plik HomeCon-
W tej części artykułu, w klasach tych znaj- rzenie własnych, wraz z akcjami, które bę- troller.cs oraz AccountController.cs, a w za-
dą się jedynie metody, które umożliwią wy- dą umożliwiały obsługę różnych żądań użyt- mian dodajmy plik zawierający kod klasy
świetlanie albumów, zdjęć i komentarzy, kownika, spowodowanych poprzez próbę AlbumController (w tym celu wystarczy
a także dodawanie komentarza. Pozostałe wyświetlenia jakiejś podstrony naszej gale- zaznaczyć folder Controllers w oknie Solu-
metody, związane z zarządzaniem poszcze- rii zdjęć. tion Explorer, a następnie z menu kontek-
gólnymi elementami galerii zdjęć, zostaną Warto jednak wcześniej zastanowić się, stowego wybrać opcję Add oraz Controller).
przedstawione w drugiej części artykułu. jakich kontrolerów będziemy potrzebowa- Nie ma także potrzeby, aby środowisko pro-
Klasy implementujące interfejs li oraz jakie podstrony chcemy opracować. gramistyczne automatycznie wygenerowało
IAlbumRepository muszą posiadać metody Zastosowanie kontrolerów (oraz akcji) jest nagłówki metod odpowiedzialnych za doda-
GetById oraz GetAll, o typach zwracanych i związane z formatem wyświetlania adresu wanie, edycję i wyświetlanie danych.
listach parametrów takich samych, jakie po- internetowego aplikacji internetowej. Do- Klasa AlbumController będzie zawiera-
dane są w interfejsie. W przypadku klas im- myślnym ustawieniem ASP.NET MVC jest ła 2 metody. Index umożliwia wyświetle-
plementujących interfejs IPhotoRepository controller/action/id. Oznacza to, że mając nie listy albumów, zaś View – zdjęć nale-
konieczne będzie posiadanie metody o na- część adresu strony internetowej (pominię- żących do konkretnego albumu, przy po-
zwie GetById, zaś dla klas implementują- to domenę) /Album/Show/1, próba wyświe- mocy metod wcześniej opracowanej klasy
cych interfejs ICommentRepository koniecz- tlenia takiej podstrony spowoduje wywoła- AlbumRepository.
ne będzie umieszczenie kodu metody Add. nie metody o nazwie Show z parametrem id Typem zwracanym metod, które odpowia-
Utworzymy teraz klasy implementują- o wartości 1, z kontrolera reprezentowanego dają akcjom, jest ActionResult. Ta klasa abs-
ce te interfejsy, których kod będzie zapi- przez klasę o nazwie AlbumController. trakcyjna jest dziedziczona m.in. przez klasy
sany w plikach znajdujących się w kata- W przypadku opracowywanej aplikacji ViewResult oraz RedirectToRouteResult.
logu Models/Repositories. W tym celu do- internetowej będziemy potrzebowali jedy- Określa ona sposób działania akcji, czyli np.
dajmy trzy nowe klasy: AlbumRepository, nie kontrolerów, które umożliwią (w nawia- prezentację dokumentu HTML (ViewRe-
PhotoRepository oraz CommentRepository. sach podano istotną część adresów z punktu sult), przekierowanie do określonej stro-
Kod dwóch z tych klas przedstawiony jest widzenia ich tłumaczenia): ny internetowej (RedirectResult) czy też
na listingach. Warto zauważyć, że posiada- do podstrony naszej aplikacji, korzystając ze
ją one pole _db, które jest instancją klasy • Wyświetlenie listy albumów (prezento- ścieżek zdefiniowanych w pliku Global.asax
DataClassesDataContext, związanej z kla- wane jako strona główna). (RedirectToRouteResult). Oprócz wymie-
sami LINQ to SQL, które wcześniej zostały • Wyświetlenie zdjęć z pojedynczego albu- nionych tutaj możliwości, umożliwia ona
przez nas utworzone. mu (Album/id, np. Album/1). także zwracanie wyników w inny sposób,
W celu zaimplementowania metod stoso- • Wyświetlenie pojedynczego zdjęcia np. przy pomocy JSON.
wana jest technologia LINQ (Language Inte- (Photo/id, np. Photo/1). W przypadku kontrolera Photo warto
grated Query), która umożliwia nam wyko- zwrócić szczególną uwagę na drugą meto-
nywanie różnych operacji na obiektach, ko- W celu zaimplementowania takiego rozwią-
rzystając ze składni zintegrowanej z języ- zania użyjemy 2 kontrolerów, reprezentowa-
kiem programowania. Wyróżnia się dwie nych przez klasy o nazwach:
różne składnie, z których jedna przypomi-
na swoim wyglądem tę znaną z zapytań SQL • AlbumController
(Query Syntax), a druga stanowi wywoła- • PhotoController

Rysunek 6. Dodawanie widoku Index


Rysunek 5. Wygenerowany schemat klas LINQ to SQL (środowisko programistyczne Microsoft Visual (środowisko programistyczne Microsoft Visual
Web Developer 2008 Express Edition) Web Developer 2008 Express Edition)

60 04/2010
Tworzenie galerii zdjęć przy pomocy ASP.NET MVC, cz. I

dę, oznaczoną atrybutem AcceptVerbs. Po- Widoki i HtmlHelper’y nych w niej zgromadzonych, kontrolery
woduje to, że metoda ta zostanie wywołana Przed nami ostatni już fragment pierw- z akcjami umożliwiające obsługę żądań
jedynie dla żądań POST. Ponadto w nagłów- szej części artykułu poświęconego tworze- użytkowników, a także ścieżki, które po-
ku tej metody można zauważyć trzy dodat- niu galerii zdjęć przy pomocy ASP.NET zwalają nam na określenie adresów po-
kowe parametry: authorName, authorEmail MVC. Mamy już opracowaną bazę da- szczególnych podstron zgodnie z naszy-
oraz text. Nie oznacza to, że parametry te nych, klasy umożliwiające dostęp do da- mi ustaleniami.
będą przekazywane poprzez adres strony
internetowej (jak w przypadku parametru Listing 7. Kod z pliku PhotoController.cs
id), lecz będą one pochodzić z formularza,
przesyłanego przy pomocy metody POST. using System;
W dalszej części następuje sprawdze- using System.Web.Mvc;
nie poprawności danych wczytanych z for- using GaleriaZdjec.Models;
mularza. Gdyby okazało się, że dane nie using GaleriaZdjec.Models.Repositories;
są poprawne, informacja o tym zostanie
wyświetlona użytkownikowi. Informacje namespace GaleriaZdjec.Controllers
o błędach są zgłaszane przy pomocy akce- {
sora ModelState znajdującego się w kla- public class PhotoController : Controller
sie abstrakcyjnej Controller, z której z ko- {
lei dziedziczy opracowana przez nas klasa private IPhotoRepository _photoRepository = new PhotoRepository();
PhotoController. private ICommentRepository _commentRepository = new CommentRepository();
Jeśli okaże się, że wszelkie dane są po-
prawne, wówczas zostanie utworzony no- public ActionResult View(int id)
wy obiekt Comment, który następnie zostanie {
przekazany jako parametr do metody Add var photo = _photoRepository.GetById(id);
klasy CommentRepository w celu dodania if (photo != null) { return View(photo); }
wiersza do tabeli w bazie danych. return RedirectToAction("Index", "Album");
Oczywiście sprawdzanie danych pocho- }
dzących z formularza jest w tym przypadku
niewystarczające. Warto by było np. spraw- [AcceptVerbs(HttpVerbs.Post)]
dzić, czy adres e-mail jest poprawny. Zagad- public ActionResult View(int id, string authorName, string authorEmail,
nienie walidacji danych zostanie także omó- string text)
wione w drugiej części artykułu, poświęco- {
nej tworzeniu panelu administracyjnego. var photo = _photoRepository.GetById(id);
if (photo == null) { return RedirectToAction("Index", "Album"); }
Routing
Jak już zostało wcześniej wspomniane, do- // Sprawdzenie poprawności danych z formularza
myślne ustawienia ścieżek nie zapewnią // umożliwiającego dodanie komentarza
nam obsługi takich adresów, jakie zostały if (String.IsNullOrEmpty(authorName)) { ModelState.AddModelError("autho
przez nas zaplanowane. Z tego też powodu rName", "Należy podać nazwę autora"); }
zmodyfikujemy plik Global.asax. Okazuje if (String.IsNullOrEmpty(authorEmail)) { ModelState.AddModelError("auth
się, że wystarczy jedynie usunąć fragment orEmail", "Należy podać adres e–mail autora"); }
związany z domyślną regułą i w jej miejsce if (String.IsNullOrEmpty(text)) { ModelState.AddModelError("text",
dodać poniższy kod: "Należy podać treść komentarza"); }

routes.MapRoute("Album", "Album/{id}", new { // Jeśli dane są poprawne


controller = "Album", action = if (ModelState.IsValid)
"View", id = "" }); {
routes.MapRoute("Photo", "Photo/{id}", new { Comment comment = new Comment()
controller = "Photo", action = {
"View", id = "" }); AuthorName = authorName,
routes.MapRoute("Default", "{controller}/ AuthorEmail = authorEmail,
{action}/{id}", new { controller = Text = text,
"Album", action = "Index", id = "" }); PhotoId = id
};
Na powyższym wycinku kodu widzimy _commentRepository.Add(comment);
wywołania metody MapRoute, które umoż- return RedirectToAction("View", new { id = id });
liwiają dodawanie dodatkowych ścieżek, }
przy pomocy których uzyskamy obsługę ta- return View(photo);
kich adresów jak np. Photo/1 albo Album/2. }
Warto jeszcze zauważyć, że ścieżka o na- }
zwie Deafult umożliwi wyświetlenie li- }
sty wszystkich albumów na głównej stro-
nie galerii.

www.sdjournal.org 61
Warsztaty

Teraz przejdziemy do tworzenia war-


stwy prezentacyjnej galerii zdjęć. W artyku- Listing 8. Kod z pliku Index.aspx (Views/Album/Index.aspx)
le nie zostanie przedstawiona budowa kodu
XHTML oraz CSS, lecz wykorzystany zosta- <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
nie wygląd aplikacji internetowej automa- Inherits="System.Web.Mvc.ViewPage<IEnumerable<GaleriaZdjec.M
tycznie wygenerowany przy tworzeniu no- odels.Album>>" %>
wego projektu ASP.NET MVC.
Utwórzmy więc najpierw stronę star- <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent"
tową naszej galerii zdjęć. W tym ce- runat="server">Galeria zdjęć</asp:Content>
lu można w kodzie metody Index klasy <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
AlbumController kliknąć prawym przyci-
skiem myszy, a z menu kontekstowego wy- <h2>Lista albumów</h2>
brać opcję AddView.
Przy tworzeniu widoków warto zaznaczać <ul>
opcję Create a strongly–typed view, o ile oczy- <% foreach (var item in Model) { %>
wiście to jest w danym przypadku możliwe. <li><%=Html.ActionLink(String.Format("{0} – {1} zdjęć", item.Title,
Opcja ta umożliwia nam stosowanie ścisłego item.Photos.Count), "View", "Album", new { id = item.AlbumId
typowania w pliku z widokiem. Dostęp do }, null) %></li>
danych w widoku jest możliwy przy pomo- <% } %>
cy właściwości Model. </ul>
Możemy już ponownie uruchomić na- </asp:Content>
szą aplikację i powinniśmy zobaczyć auto-
matycznie wygenerowany spis albumów
(przedstawiony w postaci tabeli). W ramach artykułu przedstawię jeszcze dyncze zdjęcie. Kod tej strony przedstawio-
Po wprowadzeniu modyfikacji w tym sposób tworzenia formularza przy pomocy ny jest na listingu.
pliku uzyskujemy przedstawienie albu- HtmlHelper’ów, których zastosowanie mo- Jak widać na przedstawionym obok zrzu-
mów w postaci listy wraz z informacja- gliśmy już zauważyć na listingu kodu z pli- cie ekranu, wyświetlanie pojedynczych
mi o liczbie zdjęć w poszczególnych al- ku Index.aspx. Poprzez wywoływanie metod zdjęć oraz dodanych komentarzy działa po-
bumach. na akcesorze Html, będącym instancją klasy prawnie.
Warto zauważyć, że poszczególne pod- HtmlHelper, mamy moż-
strony aplikacji tworzone są w oparciu o liwość generowania po-
szablon, który zdefiniowany jest w pliku Si- szczególnych fragmentów
te.master. Poprzez jego edycję możemy zmo- kodu strony takich jak np.
dyfikować np. tekst wyświetlany w nagłów- linki (zgodnie ze ścieżka-
ku czy też elementy menu. mi zdefiniowanymi przez
W analogiczny sposób jak przy tworzeniu nas wcześniej) czy też ele-
widoku dla głównej strony naszej aplikacji, menty formularzy.
można utworzyć plik .aspx do wyświetlania Formularz umożliwia-
listy zdjęć należących do albumu. jący dodawanie komen-
Możemy także usunąć katalogi Acco- tarzy będzie wyświetla-
unt oraz Home znajdujące się w katalogu ny na stronie, na której
Views. przedstawione jest poje-

Rysunek 8. Strona z pojedynczym zdjęciem i formularzem


Rysunek 7. Strona główna galerii zdjęć umożliwiającym dodanie komentarza

62 04/2010
Tworzenie galerii zdjęć przy pomocy ASP.NET MVC, cz. I

Podsumowanie
Zbliżamy się do końca pierwszego artyku- Listing 9. Kod z pliku View.aspx (Views/Photo/View.aspx)
łu poświęconego tematyce tworzenia ga-
lerii zdjęć przy pomocy ASP.NET MVC. <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherit
Przedstawiłem w nim ogólne informacje na s="System.Web.Mvc.ViewPage<GaleriaZdjec.Models.Photo>" %>
temat tego frameworka, a także jego możli-
wości oraz opis czynności, jakie należy wy- <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent"
konać w celu opracowania fragmentu pro- runat="server">Galeria zdjęć</asp:Content>
stej aplikacji internetowej przeznaczone-
go dla użytkownika. W artykule zawar- <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
te zostały także listingi oraz zrzuty ekra- <h2>Zdjęcie<% if (!String.IsNullOrEmpty(Model.Title)) { Response.Write(": " +
nu wykonane podczas tworzenia aplikacji. Model.Title); } %></h2>
Niemniej jednak, w treści artykułu nie zo- <% if (!String.IsNullOrEmpty(Model.Description)) { Response.Write("<p>" +
stał przedstawiony cały kod, dlatego też za- Model.Description + "</p>"); } %>
chęcam do samodzielnego zapoznania się z <div style="text–align: center"><img src="<%=ResolveClientUrl("~/Uploads/" +
tą aplikacją na podstawie kodu źródłowe- Model.Url)%>" alt="Zdjęcie" style="border: 1px solid #000"
go dostępnego na dołączonej do magazy- /></div>
nu płycie.
Jak można zauważyć, tworzenie aplika- <h2>Komentarze</h2>
cji przy pomocy tej technologii jest przy- <% foreach (var comment in Model.Comments) { %>
jemne i dość szybkie. Możemy korzystać <div>
z wielu zaawansowanych możliwości do- <p style="font–size: 9px; margin–bottom: 0"><%=String.Format("{0} ({1}):",
stępnych na platformie .NET Framework. Server.HtmlEncode(comment.AuthorName), comment.AdditionDate.
Warto także wspomnieć o zaletach środo- ToString("dd.MM.yyyy HH:mm:ss"))%></p>
wiska programistycznego Microsoft Visual <p style="margin–top: 0"><%=Server.HtmlEncode(comment.Text)%></p>
Web Developer 2008 Express Edition, któ- </div>
re może znacznie uprościć pracę programi- <% } %>
sty, nie tylko poprzez automatyczne gene-
rowanie fragmentów kodu, lecz także dzię- <fieldset>
ki mechanizmowi umożliwiającemu pod- <% using (Html.BeginForm()) { %>
powiadanie składni. <table>
W tym artykule zawarta została jedynie <tr>
bardzo niewielka ilość informacji związa- <td>Nick: </td>
nych z tworzeniem aplikacji internetowych <td><%=Html.TextBox("authorName") %></td>
przy pomocy ASP.NET MVC. Mam jednak </tr>
nadzieję, że udało mi się zainteresować Czy- <tr>
telników tym frameworkiem. <td>Adres e–mail: </td>
Zachęcam do samodzielnego ekspery- <td><%=Html.TextBox("authorEmail") %></td>
mentowania i poszerzania wiedzy doty- </tr>
czącej zarówno języka C#, platformy .NET <tr>
Framework, jak i frameworków ASP.NET <td>Treść: </td>
czy też ASP.NET MVC, a także do prze- <td><%=Html.TextArea("text") %></td>
czytania następnej części artykułu, w ko- </tr>
lejnym numerze magazynu. Zostanie </table>
w niej opisane tworzenie panelu admini- <div style="text–align: center"><input type="submit" value="Wyślij
stracyjnego umożliwiającego zarządzanie komentarz" /></div>
galerią zdjęć. <% } %>
</fieldset>
</asp:Content>

MARCIN JAMRO
W Sieci Student III roku informatyki na Politechnice Rze-
Informacje na temat ASP.NET oraz ASP.NET MVC: szowskiej. Interesuje się tworzeniem aplikacji
przy wykorzystaniu możliwości platformy .NET
• http://www.asp.net/ Framework, szczególnie związanych z ASP.NET
• http://www.asp.net/mvc/ MVC. Posiada certy�kat "Microsoft Certi�ed Tech-
• http://weblogs.asp.net/scottgu/default.aspx
nology Specialist" (MCTS: .NET Framework 3.5,
• http://codeguru.pl/
ASP.NET Applications oraz MCTS: .NET Frame-
Środowisko programistyczne: work 3.5, Windows Forms Applications). Więcej
informacji znajduje się na stronie internetowej
• http://www.microsoft.com/exPress/ autora: http://jamro.biz.
Kontakt z autorem: marcin@jamro.biz

www.sdjournal.org 63
Aplikacje biznesowe

Modelowanie
procesów biznesowych
Praktyczne wykorzystanie BPMN
Analiza i opis procesów biznesowych jest chlebem powszednim
analityka IT. Rezultaty prac, diagramy i opisy można przedstawiać
w dowolny zrozumiały sposób, jednak najlepszym podejściem jest
wykorzystanie w tym celu standardów, jak BPMN (Business Process
Modeling Notation), notacja zrozumiała dla większości odbiorców
prac analitycznych. W artykule przedstawiam najważniejsze pojęcia,
elementy i praktyczny przykład procesu biznesowego.
narzędzi, metodyk i notacji (takich jak
Dowiesz się: Powinieneś wiedzieć: przykładowo BPMN).
• Co to jest BPMN i do czego służy; • Co to jest proces biznesowy i jak istotny jest • Analiza i modelowanie – po przygoto-
• Jakie są elementy notacji i główne wzorce; dla firmy; waniu ogólnej definicji i opisu proce-
• Jak przedstawiać proces biznesowy na przy- • Jak używać oprogramowania wspomagają- su biznesowego jest wykonywana bar-
kładzie; cego projektowanie w BPMN. dziej szczegółowa analiza i uzupełnie-
• Jakie są korzyści ze stosowania tej notacji. nie o brakujące wcześniej elementy i za-
gadnienia istotne dla jego przebiegu, w
tym zestawu metryk KPI.
ność, zdolność do lepszego realizowania ce- • Symulacja – ten etap służy weryfikacji
lów i większej konkurencyjności firmy. Za- modelu procesu biznesowego, symula-
Poziom let jest wiele, pomimo wymaganego solidne- cji wykonania procesu i obliczenia me-
trudności go nakładu pracy na wdrożenie i utrzymy- tryk KPI – wszystkie te czynności po-
wanie takiego podejścia. zwalają zidentyfikować potencjalne
Ogólny cykl życia procesu biznesowego problemy, wąskie gardła, punkty pro-
przedstawiam na Rysunku 1, wykorzystując blemowe i ulepszyć proces biznesowy.

O
mawianie BPMN wypada roz- i prezentując przy okazji niektóre elementy • Realizacja – po zamodelowaniu i okre-
począć od zagadnienia zarządza- BPMN. Oczywiście spotykane są inne wizu- śleniu metryk procesu może nastą-
nia procesami biznesowymi, czyli alizacje procesu na bazie czterech lub pięciu pić jego praktyczna realizacja polegają-
BPM. Jest to bardzo szerokie zagadnienie, etapów, jednak wszystkie podejścia są do sie- ca na odniesieniu poszczególnych jego
które pozwala organizacjom i firmom funk- bie zbliżone i zakładają podobne sekwen- elementów do wspierających rozwiązań
cjonować wydajnie na rynku i odpowiadać cje czynności, pozwalające uzyskać te same technicznych (systemów, usług) – naj-
na potrzeby klientów. W poniższym roz- efekty i produkty. lepiej przy wykorzystaniu architektury
dziale nieco informacji na ten temat. Na rysunku widać wszystkie etapy cyklu zorientowanej na usługi (Service Orien-
wraz z obiektami danych – produktami da- ted Architecture – SOA).
Zarządzanie procesami nych faz. • Uruchomienie – gotowy już proces biz-
biznesowymi – BPM Poszczególne etapy cyklu życia procesu nesowy wraz ze wspierającymi rozwią-
BPM (Business Process Management) jest po- biznesowego obejmują grupy działań w skró- zaniami technicznymi może być uru-
dejściem do zarządzania, którego głównym cie przedstawiające się jak poniżej: chomiony i przekazany końcowym
celem jest dopasowanie organizacji do po- użytkownikom – ten etap obejmu-
trzeb klientów i obejmującym powiązane ze • Identyfikacja – na tym etapie jest prze- je czynności, które dostarczą funkcjo-
sobą etapy czynności – identyfikację, anali- prowadzane rozpoznanie nowego lub nujący proces biznesowy na bazie roz-
zę, modelowanie, symulację, realizację, uru- istniejącego procesu biznesowego i je- wiązań technicznych przygotowanych
chomienie, monitorowanie i optymalizację go funkcjonalności w ogólnym zakresie wcześniej.
– będące jednocześnie głównymi elementa- (kolejności zdarzeń, zależności pomię- • Monitorowanie i optymalizacja – działa-
mi typowego cyklu życia procesów bizneso- dzy nimi, właścicieli zdarzeń oraz gene- jący i wykorzystywany proces bizneso-
wych. Założeniem jest także ciągłe uspraw- rowanych i przekazywanych informa- wy nie jest doskonały – z pewnością w
nianie procesów, co zapewnia ich aktual- cji) i dokumentacja procesu z użyciem trakcie użytkowania wychodzą na jaw

64 04/2010
Modelowanie procesów biznesowych z BPMN

problemy, wąskie gardła i propozycje • top-down (od ogółu do szczegółu) – dzie lepiej odpowiadał potrzebom biz-
zmian, których nie dało się przewidzieć specyfikacja procesu biznesowego roz- nesowym.
wcześniej – takie zagadnienia mogą być poczyna się od rozpoznania ogólnych • Inside-out (rozprzestrzenianie) – pew-
wychwycone w trakcie stałej obserwacji elementów, a następnie jest on w coraz nym kompromisem jest to podejście,
procesu i weryfikacji metryk KPI. Na większych detalach i na coraz niższym polegające na skupieniu się na głównych
bazie zauważonych nieprawidłowości poziomie definiowany. Zaletą tego po- procesach, określeniu ich w pierw-
mogą zostać podjęte działania napraw- dejścia jest skoncentrowanie na potrze- szej kolejności i na bazie tej podstawo-
cze i optymalizujące – przykładowo bach biznesowych i podporządkowanie wej struktury obudowywanie kolejny-
może być to zarówno zmiana założeń i rozwiązań szczegółowych, jednak wy- mi wspierającymi elementami, podpro-
metryk procesu, jak i też jego przepro- maga to dużej wiedzy i przy złożonych cesami i zadaniami. Problemem może
jektowanie. W razie potrzeby zmian procesach drobne błędy popełnione na być najważniejsza kwestia – określenie,
rozpoczyna się kolejny cykl życia pro- poziomie ogólnym mogą wygenerować które procesy i podprocesy są najważ-
cesu biznesowego i wszelkie modyfika- duże problemy przy szczegółowym opi- niejsze – w przypadku błędu może się
cje są wprowadzane, przechodząc przez sywaniu struktury procesu. okazać, że model procesu biznesowego
wszystkie jego fazy. • bottom-up (od szczegółu do ogółu) – musi być zmieniony lub wykonany od
to podejście jest przeciwieństwem po- nowa, jeżeli projekt będzie zmierzał w
Być może zastanawiacie się, dlaczego do przedniego i polega na rozpoczęciu spe- złym kierunku.
zrealizowania obsługi procesu biznesowe- cyfikacji elementów procesu i podpro- • Mixed (mieszana) – tu stosuje się
go wymagane jest złożone i metodyczne cesów, które są łączone i finalnie tworzą wszystkie wymienione powyżej strate-
podejście – przecież można wszystko zro- całościowy proces biznesowy. Dzięki gie tak, aby osiągnąć jak najlepsze rezul-
bić szybciej, bez dzielenia na fazy, unika- temu możliwe jest dokładne zaprojek- taty i optymalne modelowanie procesu
jąc tego czasem dużego nakładu pracy, a towanie systemu z naciskiem na szcze- biznesowego, a przy tym minimalizację
uzyskać można to samo. Częściowo jest to gółowe zadania, podprocesy i produk- wpływu wad osobnego stosowania stra-
prawda – można podejść do zagadnienia ty, ale istnieje pewne ryzyko, że spe- tegii omówionych powyżej.
bardziej konkretnie, jednak często warto cyfikacja będzie zbyt szczegółowa, za-
poświęcić nieco czasu i zasobów, aby pro- brnie w zbyt duże detale i skomplikuje Niezależnie od wybranej strategii, analiza i
ces biznesowy był dobrze rozpoznany, za- proces biznesowy, który przy podejściu modelowanie procesu biznesowego powin-
projektowany i uruchomiony. Trudno bę- odwrotnym omówionym wcześniej bę- na uwzględnić wszelkie zagadnienia, któ-
dzie uzyskać maksymalną korzyść bez roz-
ważenia wszystkich zagadnień, problemów
i przetestowania różnych scenariuszy alter-
natywnych.
Najbardziej istotnym elementem zarzą-
dzania procesami biznesowymi jest ich do-
bre i wyczerpujące przeanalizowanie i za-
modelowanie. Inne będzie potraktowanie
nowego procesu, a inne kryteria są waż-
ne przy optymalizacji czy zmianach proce-
sów działających. Samo modelowanie i roz-
poznanie procesów powinno też angażować
jak największą liczbę osób i ról (przykłado-
wo: analityk, ekspert, właściciel procesu biz-
nesowego), na które proces ma wpływ lub
są one bezpośrenio w niego zaangażowane.
Uwzględnianie potrzeb, możliwości i ogra-
niczeń wszystkich stron pozwoli na zapro-
jektowanie i uruchomienie procesu bizne-
sowego odpowiadającego potrzebom, wpa-
sowanego w warunki firmy i optymalnego
w działaniu. Zignorowanie potrzeb jednej Rysunek 1. Typowy cykl życia procesu biznesowego
ze stron może prowadzić do procesu, który
będzie niewygodny lub nawet trudny w uży-
ciu, a problemy i ograniczenia mogą być na
tyle dotkliwe, że po pewnym czasie zostanie
wycofany z użycia i zastąpiony innym roz-
wiązaniem lepiej dopasowanym, co nieste-
ty, jak się łatwo domyślić, wygeneruje kosz-
ty, zmarnowany zostanie cenny czas, a w naj-
gorszym przypadku uniemożliwi rozwój fir-
my czy bieżące wsparcie działalności.
W tym miejscu warto wspomnieć o strate-
giach modelowania procesów biznesowych.
Można wyróżnić cztery podstawowe: Rysunek 2. Przykład procesu obsługi klienta

www.sdjournal.org 65
Aplikacje biznesowe

re mogą mieć wpływ na jego kształt. Przy- cesie i jakie mają być jego wymierne re- Zdefniowanie wszystkich powyższych za-
kładowo: zultaty; gadnień z pewnością ułatwi stworzenie do-
• opis procesu – bardziej obszerny opis pro- brego modelu procesu biznesowego i będzie
• potrzeby biznesowe, klientów i osób cesu, jego przebiegu, podprocesów i wszel- dobrą podstawą do dalszych prac z użyciem
oraz ról powiązanych z procesem; kich innych kluczowych elementów; wybranej notacji, przykładowo BPMN, do
• struktura organizacyjna; • dane wejściowe – informacja o tym, jakie której wprowadzam w dalszej części arty-
• powiązania z innymi procesami; dokumenty i produkty proces powinien kułu poniżej.
• wymagane wsparcie techniczne; otrzymać, aby poprawnie wykonać posta-
• produkty i dokumenty będące wyni- wione zadanie i w których miejscach pro- Wprowadzenie do BPMN
kiem procesu i ich ścieżka w procesie; cesu powinny być wprowadzone; BPMN (Business Process Modeling Notation)
• role użytkowników, powiązania mię- • produkty wyjściowe – informacja o do- jest opracowanym przez Business Process Ma-
dzy nimi i zakresy odpowiedzialności. kumentach i produktach, które zosta- nagement Initiative (BPMI) sposobem gra-
ną wygenerowane w trakcie działania ficznego przedstawiania procesów w trak-
Powyżej zamieszczam tylko przykłado- procesu wraz z określeniem miejsca ich cie ich modelowania, zgodnym z podejściem
we zagadnienia, jako że dla różnych proce- wygenerowania; SOA (Service Oriented Architecture). Elemen-
sów inna może być lista. Za każdym razem • zdarzenia – informacja o sytuacjach, w ty i zasady używania wszystkich elementów
jednak trzeba się zastanowić, jakie kwestie których następuje przejście do następ- graficznych są na tyle przejrzyste i nieskom-
mogą mieć wpływ na kształt procesu i na nej czynności, zakończenie lub prze- plikowane w tworzeniu diagramów i ich ro-
co sam proces będzie oddziaływał. Bez do- rwanie procesu; zumieniu, że zarówno specjaliści technicz-
brego zrozumienia otoczenia procesu trud- • wynik – co jest efektem działania procesu; ni, jak i przedstawiciele biznesu mogą dzię-
ne lub niemożliwe będzie jego odpowiednie • uczestnicy – jakie role lub osoby biorą ki wykorzystaniu tej notacji znaleźć wspól-
zamodelowanie. w procesie udział i jakie mają odpowie- ny język i zrozumieć się wzajemnie. Jest to
Jako dobry początek można wykorzystać dzialności; bardzo cenna właściwość, a niezwykle waż-
poniższą listę przedstawiającą podstawowe • powiązania z innymi procesami – in- ne jest zrozumienie potrzeb biznesu przez
informacje ważne dla zdefiniowania proce- formacja o procesach korzystających z specjalistów, a ogólnych zawiłości i zagad-
su biznesowego: wyników działania danego procesu lub nień konstrukcyjnych przez przedstawicie-
dostarczających dane wejściowe; li biznesu. Dzięki temu można wypracować
• cel procesu – krótko, acz treściwie • reguły biznesowe będące częścią proce- model procesu możliwy do realizacji w kon-
określający, co chcemy uzyskać w pro- su i mające na niego wpływ. kretnych specyficznych warunkach infra-
struktury, a jednocześnie wspierający dzia-
łalność organizacji i zaspokajający oczekiwa-
nia wszystkich zainteresowanych stron.
Bardzo ważną cechą BPMN jest zgodność
z innymi standardami wykorzystywanymi
w architekturach zorientowanych na usługi
Rysunek 3. Przykład prywatnego (wewnętrznego) procesu biznesowego (SOA) – językami XML używanych w wy-
konywaniu procesów biznesowych, przykła-
dowo BPEL4WS (Business Process Execution
Language for Web Services), który może być
w prosty sposób wygenerowany z gotowych
modeli BPMN.

Rysunek 4. Przykład procesu abstrakcyjnego (publicznego)

Rysunek 5. Przykład procesu współpracy (globalnego) Rysunek 6. Rodzaje zdarzeń

66 04/2010
Modelowanie procesów biznesowych z BPMN

Przykładowy proces Kolejnym rodzajem procesu jest abstrakcyj- obiektów między sobą z innymi infor-
Jako wstęp do elementów BPMN, na Rysun- na (publiczna) odmiana. W tym przypadku macjami;
ku 2 przedstawiam niewielki przykładowy przedstawiane są wszystkie interakcje pomię- • Miejsca realizacji przebiegu – pule lub
proces, który obejmuje ogólny proces ob- dzy procesem prywatnym (wewnętrznym), „tory wodne” (swimlanes) – są wykorzy-
sługi klienta sieci komórkowej. Oczywiście a innymi procesami lub uczestnikami (użyt- stywane do przedstawienia uczestni-
w rzeczywistości jest on dużo bardziej roz- kownikami). W odróżnieniu od poprzednie- ków procesu biznesowego;
budowany, tu tylko prezentuję jego wyci- go rodzaju procesu, nie są ujmowane w mo- • Artefakty (artifacts) – nie są częścią
nek, aby zobrazować, jak może wyglądać ta- delu wewnętrzne czynności zachodzące w ra- przebiegu procesu, a dostarczają infor-
ki proces. Więcej przykładów procesów znaj- mach procesu, ale tylko i wyłącznie elementy macji uzupełniających.
dziecie w literaturze, do której linki znaj- biorące udział w komunikacji ze światem ze-
dziecie poniżej. W dalszej części artykułu wnętrznym dla procesu. Podobnie jak w po- Wszystkie powyższe kategorie elementów
omawiam wszystkie elementy przedstawio- przednim przypadku, procesy publiczne są omawiam w detalach w poniższej części ar-
ne na tym przykładzie i pozostałą większość także zawarte w ramach jednego toru. tykułu.
tu niewykorzystanych symboli. Rysunek 4 przedstawia przykład takie- Grupa obiektów przebiegu, pierwsza na po-
go procesu. wyższej liście, jest jednocześnie, jak wspomnia-
Typy procesów biznesowych Ostatnim rodzajem są procesy współpra- łem wyżej, głównym elementem umożliwiają-
BPMN został zaprojektowany w taki sposób, cy (globalne) obrazujące interakcje pomiędzy cym przedstawienie zachowania danego pro-
aby umożliwić budowanie modeli wszelkich dwoma (lub więcej) procesami abstrakcyjny- cesu biznesowego, a w jej skład wchodzą:
procesów biznesowych spotykanych w orga- mi komunikującymi się ze sobą. To podejście
nizacjach i firmach w standardowy, ustruk- skupia się na przedstawieniu punktów styku • Zdarzenia (events) – przedstawiające sy-
turalizowany sposób. Wyróżnić można trzy łączących uczestników. Podobnie jak w przy- tuacje występujące w danym miejscu
podstawowe typy procesów biznesowych, padku procesów publicznych, na takich dia- procesu biznesowego (wszystkie rodzaje
które można w łatwy sposób zamodelować gramach niewiele jest szczegółów czynno- zdarzeń przedstawiam na Rysunku 6);
w tej notacji: ści wewnątrz procesów, które nie są istotne • Czynności (activities) – reprezentują-
z punktu widzenia połączeń pomiędzy nimi. ce zadania wykonywane w danej orga-
• Prywatne (wewnętrzne) procesy bizne- Przykład takiego procesu na Rysunku 5. nizacji jako zadania, procesy lub pod-
sowe; procesy (przedstawione na Rysunku 7)
• Procesy abstrakcyjne (publiczne); Elementy BPMN – mogą być także opatrzone specjalny-
• Procesy współpracy (globalne). Notacja BPMN składa się z zestawu elemen- mi atrybutami w dowolnej ich kombi-
tów graficznych umożliwiających zamodelo- nacji (możliwe atrybuty przedstawiam
Prywatne (wewnętrzne) procesy biznesowe wanie dowolnego procesu biznesowego. Ze- na Rysunku 8);
są, jak sama nazwa wskazuje, wewnątrz kon- staw można podzielić na cztery następujące
kretnej organizacji. Jeżeli diagramy BPMN podstawowe kategorie:
wykorzystują miejsca realizacji przebiegu –
„tory wodne” (swimlane – omówione w dal- • Obiekty przebiegu (flow objects) – głów-
szej części artykułu), to cały proces jest za- ne elementy określające zachowanie
warty w granicach jednego toru i nie może procesu biznesowego;
poza jego obręb wykraczać. Rysunek 3 przed- • Obiekty łączące (connecting objects) –
stawia przykład takiego rodzaju procesu. umożliwiają przedstawienie połączeń

BPMN kontra UML


Wielu specjalistów wykorzystuje BPMN do modelowania procesów biznesowych, jednak
jest też wiele osób, które w tym celu wykorzystują UML (Unified Modeling Language). Przy
pomocy obu z nich można zrealizować główny cel, czyli wykonanie modelu procesu biz-
nesowego, ale wybór konkretnego narzędzia należy do was. Warto w tym miejscu wspo-
mnieć o różnicach pomiędzy tymi narzędziami jako swego rodzaju sugestii:

• UML jest językiem zaprojektowanym do obiektowo zorientowanego modelowania


samych aplikacji i ich konstrukcji technicznej, a nie procesów. Dlatego też jego ele- Rysunek 7. Elementy przedstawiające czynności
menty są bardziej złożone, jest ich więcej i nie zawsze będą poprawnie rozumiane w BPMN
przez przedstawicieli biznesu,
• Głównym zadaniem BPMN jest procesowo zorientowane modelowanie systemów,
a zestaw elementów i zasady budowania modeli będą z reguły rozumiane przez nie-
techniczne osoby. Nie posiadając wiedzy na temat konkretnych elementówm, można
nawet intuicyjnie zrozumieć, jak działa proces.
• BPMN skupia się na procesach biznesowych i ich wsparciu przez informatyczne roz-
wiązania, nie dociekając, jak one są zbudowane (a z reguły zawierają elementy za-
projektowane z wykorzystaniem UML) i pozostawiając architektury systemów innym
notacjom i językom opisu, takim jak właśnie UML,

BPMN i UML są idealną kombinacją wzajemnie uzupełniających się narzędzi budowania mo-
deli i projektów. Jeżeli ktoś jest zdecydowany, może użyć tylko UML w obu celach, jednak
najbardziej optymalne jest zastosowanie specjalistyczne każdego z nich, a zbudowane mo-
dele będą rozumiane przez konkretnych odbiorców i pomogą zbudować optymalny proces
biznesowy wspierany przez rozwiązania techniczne zbudowane w zgodzie ze standardami.
Rysunek 8. Specjalne atrybuty czynności

www.sdjournal.org 67
Aplikacje biznesowe

• Bramki (gateways) – używane do roz- • Uczestnicy, Pule – baseny (pools) – re-


dzielenia lub połączenia wielu niezależ- prezentują uczestników procesu;
nych przebiegów procesu (ich rodzaje • Tory (lanes) – zawierające się w danej
przedstawione są na Rysunku 9). puli i używane do grupowania zdarzeń.

Nie jest możliwe przedstawienie przebiegu Powyższe elementy prezentuję w przykła-


procesu biznesowego powyższymi elemen- dowym procesie na Rysunku 13.
tami bez zastosowania połączeń obrazują- Ostatnią grupą elementów są artefakty
cych relacje pomiędzy obiektami lub z in- (zaprezentowane na Rysunku 14), czyli:
nymi informacjami i danymi, czyli obiek-
tów łączących, drugich na liście powyżej, a • Obiekty danych (Data objects) – infor-
obejmujących: macje o dokumentach wejściowych i
wyjściowych
• Przebieg sekwencji (sequence flow) – • Grupy (Groups) – grupowanie elemen-
Rysunek 11. Reguły przebiegu sekwencji
określa kolejność wykonania obiektów tów procesu;
przepływu; • Adnotacje (Annotations) – dodatkowe
• Przebieg komunikatu (message flow) – informacje.
prezentuje przebieg komunikatów po-
między dwoma uczestnikami; W poniższej części artykułu postaram się
• Połączenie (association) – jest wykorzy- bardziej szczegółowo przedstawić wszyst-
stywane do prezentowania powiązania kie elementy BPMN, ale z uwagi na obję-
informacji z obiektami przebiegu. tość artykułu nie jestem w stanie zrobić te-
go w dogłębny sposób. Zapraszam do zapo-
Na Rysunku 10 przedstawiam powyższe znania się z dodatkowymi materiałami wy-
typy połączeń, w tym trzy rodzaje przebie- mienionymi w ramce „W sieci” – tam znaj-
gu sekwencji (normalny, warunkowy, do- dziecie wszystko, co potrzeba, aby dokład-
myślny). nie poznać notację BPMN.
Wszystkie powyższe elementy po ich
wspólnym zastosowaniu umożliwiają zbudo- Zdarzenia
wanie modelu pojedynczego procesu bizneso- Jak już wspomniałem wcześniej, te elemen- Rysunek 12. Reguły przebiegu komunikatów
wego, a dla pełnego modelowania konieczne ty umożliwiają wizualizację zachowania
często jest zastosowanie elementów grupują- procesów, pokazanie, co się wewnątrz nich • Ogólne (General) – zdarzenie począt-
cych procesy i pozwalające w bardziej czytel- dzieje, w jakich momentach, jakie są czyn- kowe prezentuje miejsce rozpoczęcia
ny i zrozumiały sposób przedstawić przebiegi niki wyzwalające wystąpienie zdarzeń i ja- konkretnego procesu, zdarzenie po-
procesów. Do elementów tych należą miejsca ki jest skutek ich wystąpienia. średnie występuje pomiędzy zdarze-
realizacji przebiegu obejmujące: Zdarzenia można podzielić na trzy grupy: niem początkowym i końcowym, zda-
rzenie końcowe prezentuje moment za-
• Początkowe – proces kończenia procesu.
rozpoczyna się w momencie • Komunikatu (Message) – komunikat
ich wystąpienia; pochodzący od uczestnika procesu wy-
• Pośrednie – wykorzy- zwala zdarzenie, powodując rozpoczę-
stywane na dwa sposoby – cie, kontynuację lub zakończenie proce-
w przebiegu normalnym do su w sytuacji oczekiwania na dany ko-
chwytania i wyzwalania zda- munikat. Może zmieniać też przebieg
rzeń pośrednich (gdy proces procesu w razie wystąpienia wyjątku.
jest kontynuowany po wy- Komunikat końcowy jest generowany
stąpieniu zdarzenia) oraz na zakończenie procesu.
w przebiegu wyjątkowym • Czasu (Timer) – umożliwia zdefiniowa-
(przykładowo w momencie nie znaczników czasu zaczynających
Rysunek 9. Rodzaje bramek
wystąpienia błędu); lub kontynuujących proces, może być
• Końcowe – zachodzące też używany do sterowania opóźnienia-
na zakończenie procesu. mi w procesie.
• Warunkowe (Conditional) – to zdarze-
Rysunek 6 przedstawia nie jest wyzwalane, gdy spełniony jest
wszystkie możliwe rodzaje dany warunek, a może być wykorzysty-
zdarzeń z ich podziałem na wane do obsługi wyjątków i sterowania
powyższe grupy oraz pre- kontrolowanym zakończeniem procesu.
zentuje, które z nich mogą • Sygnału (Signal) – prezentuje sygnały
występować w roli chwyta- pochodzące od innych procesów wy-
jącej lub wyzwalającej i jakie zwalające rozpoczęcie danego procesu,
symbole są wtedy używane. lub generowany w tym momencie sy-
Poniżej omawiam poszcze- gnał analogicznie wpływający na inne
Rysunek 10. Typy obiektów łączących gólne rodzaje zdarzeń: procesy.

68 04/2010
Modelowanie procesów biznesowych z BPMN

• Błędu (Error) – używany jest do prezen- lowania wsparty specjalnym protoko- • Wykluczająca sterowana danymi (Da-
tacji generowania i obsługiwania wyjąt- łem zapewniającym zgodność uczestni- ta-based exclusive gateway – XOR) – ta
ków lub błędów występujących w trak- ków co do przebiegu transakcji. bramka, podobnie jak poniższa stero-
cie procesu. • Podproces (Subprocess) – wydzielona wana zdarzeniami, ogranicza przebieg
• Anulowania (Cancel) – jest wykorzy- część procesu, może być przedstawio- do jednej z opcji wybieranej w trakcie
stywany w podprocesie będącym trans- na jako zwinięta (ukrywając wewnętrz- działania procesu, a w tym przypadku
akcją (podprocesem wspartym specjal- ny przebieg – przydatna opcja umożli- decyzja jest podejmowana na podsta-
nym protokołem zapewniającym zgod- wiająca skupienie się na ogólnym prze- wie wyrażeń warunkowych. Jest to do-
ność wszystkich uczestników, że proces pływie procesów) lub rozwinięta poka- myślny rodzaj bramki przy wybraniu
musi być albo zakończony, albo anulo- zująca pełną zawartość podprocesu. symbolu bez żadnego znacznika we-
wany – bez innych możliwości). wnątrz, a oba pokazane na Rysunku 8
• Kompensacji (Compensation) – wykorzy- Powyższy atrybut (znacznik) podprocesu zwi- symbole są możliwe do zastosowania w
stywany do równoważenia efektu zda- niętego w postaci plusa na dole elementu jest tym przypadku.
rzenia innymi czynnościami (przykłado- jednym z pięciu określonych w BPMN i może • Wykluczająca sterowana zdarzeniami
wo anulowaniem obciążenia konta). być łączony z czterema pozostałymi w dowol- (Event-based exclusive gateway – XOR)
• Zakończenia (Terminate) – przedstawia- ny sposób. Jedyny wyjątek to brak zastosowa- – ta bramka zakłada wybranie jednej
jące miejsce natychmiastowego zakoń- nia połączenia pętli i wieloinstancyjności. z opcji, ale, w przeciwieństwie do po-
czenia procesu bez możliwości kom- Rysunek 8 przedstawia te znaczniki, a po- przedniczki, decyzja jest podejmowa-
pensacji czy obsługi wyjątków. niżej zamieszczam ich listę z krótkim wyja- na na podstawie wystąpienia zdarzenia
• Łącznik (Link) – umożliwia połączenie śnieniem zastosowania: (przykładowo otrzymania wiadomości).
końca procesu (rezultatu) z początkiem • Niewykluczająca (Inclusive gateway –
innego procesu (wyzwalania). • Pętla (Looping) – zadanie lub podproces OR) – w tym przypadku możliwe jest
• Zbiorcze (Multiple) – przedstawia kilka jest powtarzany w pętli; wybranie żadnej lub wszystkich opcji.
możliwości przepływu i uruchomienia • Ad-hoc – czynności znajdujące się we- • Złożona (Complex gateway) – ta bram-
procesu, z których tylko jedna jest wy- wnątrz procesu nie są realizowane w ka umożliwia sterowanie przebiegiem
magana, a inne opcjonalne. określonym porządku i nie mogą zostać procesu przy użyciu złożonych warun-
połączone w przebieg na etapie projek- ków i może być wykorzystywana do łą-
Czynności towania; czenia funkcji kilku innych podstawo-
Są to działania realizowane przez firmę lub • Wieloinstancyjność (Multiple instances) wych bramek.
organizację – mogą być pojedynczymi zada- – zostanie utworzone wiele instancji • Równoległa (Parallel gateway – AND)
niami, całymi procesami lub podprocesami. zadania lub podprocesu; – są używane do synchronizacji wielu
Zadania są wykorzystywane do przedstawia- • Kompensacja (Compensation) – zadanie przebiegów, zarówno wchodzących, jak
nia najmniejszych elementów działalności, a lub proces ma rolę kompensacji i ozna- i wychodzących.
procesy obejmują większe przebiegi prac. cza się nim element wywoływany w
Rysunek 7 przedstawia podstawowe ele- zdarzeniu kompensacji. Obiekty łączące
menty BPMN używane w modelowaniu Ta grupa elementów jest wykorzystywana
czynności. Są to: Bramki do przedstawiania połączeń obiektów prze-
Kolejną grupą elementów są Bramki (gate- biegu (zdarzeń, czynności i bramek) ze sobą
• Zadanie (Task) – najmniejsza czynność ways) używane w kontrolowaniu przepływu i z innymi informacjami. Jak wspomniałem
w procesie używana, jeżeli nie da się procesu, jego rozgałęzień i łączeń. Rodzaje wcześniej, na Rysunku 10 przedstawione są
lub nie jest celowe rozbicie tej czynno- bramek przedstawiam na Rysunku 9, a po- trzy podstawowe typy obiektów:
ści na mniejsze elementy. niżej zamieszczam krótkie omówienie każ-
• Transakcja (Transaction) – proces z dego z nich z odpowiadającymi funkcjami • Przebieg sekwencji (sequence flow);
możliwością jego zakończenia lub anu- logicznymi w nawiasach: • Przebieg komunikatu (message flow);
• Połączenie (association).

Poniżej omawiam wszystkie pokazane na


rysunku rodzaje obiektów łączących:

• Normalny przebieg sekwencji (Normal


sequence flow) – wykorzystywany jest

Rysunek 13. Przykładowe wykorzystanie pul i torów Rysunek 14. Rodzaje artefaktów

www.sdjournal.org 69
Aplikacje biznesowe

do przedstawienia kolejności realizowa- Dwa kolejne rysunki przedstawiają dozwolo- • Przebiegi sekwencji są niedozwolone
nia czynności. ne połączenia pomiędzy obiektami przebie- pomiędzy pulami;
• Warunkowy przebieg sekwencji (Con- gu z użyciem dwóch głównych obiektów łą- • Przebiegi komunikatów są niedozwolo-
ditional sequence flow) – przebieg jest czących – przebiegu sekwencji (Rysunek 11) ne pomiędzy torami w puli;
uzależniony od wyrażeń warunkowych i przebiegu komunikatu (Rysunek 12). Ele- • Każdy proces zawarty w torze lub puli
sprawdzanych w trakcie działania pro- ment w odpowiednim miejscu tabeli wska- musi w tym miejscu mieć swój począ-
cesu i na tej podstawie jest podejmowa- zuje, że połączenie jest możliwe, brak ele- tek, koniec i być pulą lub torem ograni-
na decyzja o użyciu przebiegu. mentu na przecięciu dwóch obiektów prze- czony.
• Domyślny przebieg sekwencji (Default biegu zabrania używać danego przebiegu.
sequence flow) – jest wykorzystywa- Artefakty
ny dla decyzji wykluczających sterowa- Miejsca realizacji przebiegu Ostatnią grupą elementów BPMN są arte-
nych danymi (XOR) lub decyzji niewy- Elementy z tej grupy (pule – pools i tory fakty, nie związane bezpośrednio z przebie-
kluczających i określa przebieg wyko- – lanes) pomagają w modelowaniu i orga- gami sekwencji i komunikatów, a umożli-
rzystywany, jeżeli wszystkie wychodzą- nizacji procesów biznesowych i wyznacza- wiające przedstawianie dodatkowych infor-
ce alternatywy nie mogą być użyte. niu uczestników, osobnych procesów lub macji w modelach procesów biznesowych.
• Przebieg komunikatu (Message flow) – grup procesów. Przedstawione są na Rysunku 14 i omówio-
prezentuje przebieg komunikatów po- Rysunek 13 przedstawia te elementy, po- ne poniżej:
między dwoma uczestnikami, którzy kazując jednocześnie przykładowy proces
mogą być przedstawieni jako osobne zrealizowany z wykorzystaniem obu ele- • Obiekt danych (Data object) – zawiera-
pule. mentów. Przy wykorzystaniu puli i torów jący informację o dokumentach (pro-
• Połączenie (Association) – może być obowiązuje kilka zasad: duktach) wejściowych i wyjściowych
przedstawione w wersji skierowanej, niezbędnych dla przebiegu procesu.
jak i bez tego znacznika i prezentuje po- • Pula musi zawierać najwyżej 1 proces • Grupa (Group)– wykorzystywana do
wiązanie obiektów przebiegu z dodat- biznesowy; przedstawiania podobnych lub powią-
kowymi informacjami uzupełniającymi • Pula może zawierać 1 lub więcej torów, zanych elementów procesu.
model. może jednak w ogóle ich nie mieć; • Adnotacja (Annotation) – umożliwiają-
ca opisanie diagramów dodatkowymi
informacjami.

Wzorce procesów
Podobnie jak w innych dziedzinach projek-
towania rozwiązań, w trakcie modelowania
procesu biznesowego, oprócz znajomości po-
Rysunek 15. Wzorzec procesu - sekwencja szczególnych elementów BPMN i zasad ich

Rysunek 16. Wzorzec procesu – rozgałęzienie równoległe – wychodzące Rysunek 18. Wzorzec procesu - rozgałęzienie równoległe – rozwinięty
przebiegi sekwencji podproces

Rysunek 17. Wzorzec procesu - rozgałęzienie równoległe – bramka Rysunek 19. Wzorzec procesu – synchronizacja
równoległa – bramka równoległa

70 04/2010
Modelowanie procesów biznesowych z BPMN

stosowania, warto znać podstawowe wzor-


ce stosowane w procesach, aby tworzyć mo-
dele optymalne i oparte na dobrych prakty-
kach i wytycznych twórców notacji. Takie po-
dejście ułatwia zrozumienie projektów w ze-
spole i z udziałem odbiorców prac, eliminuje
większość nieporozumień, a dzięki typowym
konstrukcjom umożliwia ponowne wykorzy-
stanie rezultatów w innych projektach, a na-
wet budowanie bibliotek procesów i efektyw-
ne dzielenie się wiedzą.
Poniżej przedstawiam wybrane wzorce
spośród wielu dostępnych przykładów i mo- Rysunek 22. Wzorzec procesu – proste łączenie – bramka wykluczająca
deli, a jeśli chcecie zapoznać się z większą
ich ilością, zapraszam do lektury innych ma- Rozgałęzienie równoległe (Parallel Split) • Z użyciem rozwiniętego podprocesu
teriałów, gdzie znajdziecie ich więcej, wraz W tym wzorcu ścieżka rozdziela się na dwie (Rysunek 20).
ze szczegółowymi omówieniami ich funk- lub więcej równolegle realizowanych ście-
cji i przebiegu. W tak krótkim artykule nie żek, niezależnych od siebie. Ten wzorzec jest przydatny w procesach
sposób wymienić i opisać wszystkich wzor- wymagających zakończenia wszystkich
ców, a poszukiwanie w innych opracowa- • Wykorzystanie kilku wychodzących ścieżek istotnych dla danego procesu i je-
niach wzbogaci z pewnością Waszą wiedzę przebiegów sekwencji z obiektu prze- go kontynuacji już w jednej ścieżce. Przy-
na temat BPMN. pływu (Rysunek 16); kładem przedstawionym na ilustracji jest
• Użycie bramki równoległej do rozdzia- wysyłka towaru uzależniona od obciążenia
Sekwencja (Sequence) łu przebiegu sekwencji na kilka osob- konta i przygotowania wysyłki - bez zakoń-
Pierwszy z wzorców przedstawiony jest na Ry- nych (Rysunek 17); czenia obu z tych ścieżek proces nie może
sunku 15 i jest chyba najczęściej stosowanym • Użycie rozwiniętego podprocesu obej- dalej przebiegać.
i najprostszym. Na pewno wiele razy widzieli- mującego czynności wykonywane rów-
ście podobne konstrukcje na diagramach. nolegle (Rysunek 18) Wybór wykluczający (Exclusive choice)
Definiuje on serię zadań, które są wykony- W tym wzorcu przebieg rozdziela się na
wane jedno po drugim, kolejne zadanie jest Wzorzec ma zastosowanie w procesach dwie lub więcej niezależnych ścieżek, z któ-
realizowane po zakończeniu poprzedniego. rozdzielających się na kilka wątków, które rych tylko jedna może być wybrana do kon-
Zadania połączone są ze sobą kolejno przebie- przebiegają rozdzielnie i mogą zawierać zu- tynuacji przebiegu. Dalsze zadania są reali-
gami sekwencji. pełnie inne zadania i kończyć się w zupeł- zowane z wybranej ścieżki, a druga zostaje
Ten wzorzec pomaga w wizualizacji pro- nie innych miejscach. Uzupełnieniem mo- porzucona i nie bierze udziału w procesie.
stych procesów, które nie mają rozgałęzień, że być kolejny omawiany wzorzec synchro- Wzorzec jest odpowiednikiem funkcji lo-
lub bardziej złożonych przebiegów na więk- nizacji, który może połączyć takie równole- gicznej OR. W tym przypadku wykorzystuje
szym poziomie ogólności. gle przebiegające ścieżki. się bramkę wykluczającą. Wzorzec prezen-
tuję na Rysunku 21.
Synchronizacja Wybór wykluczający jest niezwykle przy-
(Synchronization) datny przy procesach zależnych od dokony-
Wzorzec synchroni- wanych wyborów, lub rozmaitych warun-
zacji umożliwia łącze- ków. Pozwala tworzyć duże modele z wielo-
nie ścieżek równole- ma niezależnymi procesami i ścieżkami i zbu-
głych, które przebie- dowanie funkcjonalnych modeli..
gały wcześniej nieza-
leżnie od siebie lub po- Proste łączenie (Simple merge)
wstały w wyniku za- Ten wzorzec służy połączeniu dwóch lub
Rysunek 20. Wzorzec procesu – synchronizacja – rozwinięty podproces
stosowania poprzed- więcej alternatywnych ścieżek w jedną kon-
niego wzorca – rozga- tynuującą przebieg. Możliwe są dwa sposo-
łęzienia równoległego. by jego przedstawienia, z bramką wyklucza-
Warunkiem koniecz- jącą lub bez niej. Na Rysunku 22 przedsta-
nym jest zakończenie wiam podejście z wykorzystaniem bramki,
realizacji wszystkich na Rysunku 23 pokazana jest alternatyw-
ścieżek wchodzących na opcja.
do węzła synchroniza- W przeciwieństwie do wcześniej omawia-
cji przed kontynuacją nego wzorca synchronizacji, tu nie jest wy-
przebiegu. magane zakończenie każdej ze ścieżek przed
Wzorzec ten można kontynuacją procesu, a wystarczy realizacja
ująć na dwa sposoby: jednej z nich. Wzorzec jest uzupełnieniem
wcześniej omawianego wyboru wykluczają-
• Z wykorzysta- cego, gdzie tylko jedna ze ścieżek jest wybie-
Rysunek 21. Wzorzec procesu – wybór wykluczający niem bramki równo- rana do realizacji, a obie z dostępnych wcho-
ległej (Rysunek 19); dzą do węzła prostego łączenia.

www.sdjournal.org 71
Aplikacje biznesowe

Wybór wielokrotny (Multiple choice) żek w trakcie wykonania procesu. Możliwe są Łączenie wielokrotne (Multiple merge)
W odróżnieniu od wcześniej omawianego dwa podejścia do tego wzorca – z bramką nie- Ostatni ze wzorców przezentowanych prze-
wzorca wyboru wykluczającego, ten wzorzec wykluczającą (Rysunek 24) i z warunkowymi ze mnie w niniejszym artykule jest stosowa-
umożliwia wybranie jednej lub więcej ście- przebiegami sekwencji (Rysunek 25). ny w łączeniu kilku równoległych ścieżek,
z których wszystkie naraz mogą być wy-
brane (w przeciwieństwie do wzorca Pro-
ste Łączenie). Wzorzec prezentuję na Ry-
sunku 26.
Powyżej omawiane wzorce są tylko wy-
cinkiem z wielu znanych i stosowanych w
praktyce. Wcześniej wspominałem, że wzor-
ców jest naprawdę dużo, dlatego też nie za-
mieszczam w tym artykule wszystkich. Tak
krótki artykuł nie pozwala na wyczerpanie
tematu, a poza tym nie było to moim ce-
lem. Polecam lekturę innych opracowań i
mam nadzieję, że wybrane powyżej przy-
Rysunek 23. Wzorzec procesu – proste łączenie – przebieg niekontrolowany
kłady zachęcą Was do poszukiwania kolej-
nych w dostępnej literaturze zamieszczonej
w ramce W sieci. Szczególnie polecam stro-
nę www.bpmn.org, która jest oficjalną stroną,
zawierającą szczegółowe informacje i specy-
fikacje wszystkich wersji BPMN, nawiąza-
nia do specjalistycznych książek, opracowań
i artykułów. Znajdziecie tam także przedsta-
wienie elementy graficzne aktualnej wer-
sji notacji. Kolejnym miejscem w sieci, któ-
re naprawdę warto odwiedzić, jest strona
http://refcardz.dzone.com/, na której znajdzie-
cie wiele podręcznych ściąg na rozmaite te-
maty, w tym BPM i BPMN, a cyklicznie po-
Rysunek 24. Wzorzec procesu – wybór wielokrotny – bramka niewykluczająca jawiają się opisy nowych technologii, narzę-
dzi czy standardów.
Te opracowania są publikowane przez spe-
cjalistów w swojej dziedzinie, a wiedza w
nich zawarta często pozwala lepiej przyswo-
ić temat lub odwołać się w razie wątpliwości
od książek czy artykułów.

Podsumowanie
BPMN jest w tej chwili powszechnym i ak-
ceptowanym standardem wykorzystywanym
w analizie i modelowaniu procesów bizne-
sowych, a wiedza o nim jest najczęściej nie-
zbędnym wymogiem na stanowisku anali-
tyka biznesowego. Notacja jest łatwa w na-
uce, przyjazna, a także zrozumiała dla więk-
Rysunek 25. Wzorzec procesu – wybór wielokrotny – warunkowe przebiegi sekwencji
szości osób zaangażowanych w procesy biz-
nesowe, zarówno od strony technicznej, jak
i zarządu firmy czy przedstawicieli klientów.
Uważa się, że modelowanie procesów z uży-
ciem UML jest także możliwe, jednak ten ję-
zyk nie dla wszystkich jest wystarczająco zro-
zumiały i nie zawsze modele nim zbudowa-
ne będą optymalne, tak jak z wykorzystaniem
omawianego tu BPMN.
Idealnym zestawieniem jest wykorzysta-
nie UML w kombinacji z BPMN, co pozwala
skupić się z jednej strony na budowaniu od-
powiednich modeli procesów biznesowych,
a z drugiej strony modelować architektu-
ry systemów realizujących wsparcie danych
Rysunek 26. Wzorzec procesu – łączenie wielokrotne procesów. Każdy z tych standardów w zamy-

72 04/2010
Modelowanie procesów biznesowych z BPMN

śle miał służyć innym celom i z pewnością Z pomocą odpowiednich narzędzi możliwe technicznej wspierającej procesy (chociaż-
korzystniej wykorzystywać je do tych zadań, jest także wydajne projektowanie systemów by z pomocą BPEL4WS) bez konieczności
w których sprawdzają się najlepiej. i procesów i budowanie odniesień pomiędzy żmudnego kopiowania, przepisywania i po-
W takim zestawieniu tych narzędzi za- modelami na tych dwóch poziomach. Po- wielania informacji.
równo strona techniczna, jak i biznesowa maga to także w takim opracowaniu modeli Niniejszy artykuł co prawda opisuje
będzie usatysfakcjonowana, a potrzeby zo- procesów, aby można było dalej je wykorzy- BPMN w pewnym zakresie, jednak niewy-
staną zrealizowane w odpowiedni sposób. stywać w procesie budowania architektury konalne jest opisanie wszystkich zależno-
ści, wzorców i zasad niezbędnych przy ko-
rzystaniu z tej notacji. Wyczerpujące uję-
Narzędzia wspomagające cie tematu pochłonęłoby co najmniej jeden
Modelowanie procesów biznesowych może być realizowane bez żadnych dodatkowych na-
rzędzi, nawet przy pomocy kartki i ołówka. Każdy sposób jest dobry, pod warunkiem dobre- cały numer SDJ, co jest niewykonalne. Po-
go opisania procesów i ich przebiegów, a także zrozumiałości dla innych zainteresowanych za tym nie ma sensu kopiowanie zawartości
stron. Są oczywiście lepsze sposoby, szczególnie gdy modele procesów mają być dalej uży- innych materiałów, które z łatwością może-
wane do definiowania i uruchamiania architektur zorientowanych na usługi przy użyciu BPE- cie pobrać ze stron, które podałem w ramce.
L4WS. W takiej sytuacji od razu najlepiej użyć jednego z narzędzi znakomicie usprawniają-
Moim celem było pokazanie głównych ele-
cych tworzenie modeli i umożliwiających późniejsze ich wykorzystanie do innych celów. Jest
wiele takich narzędzi, najbardziej popularne przedstawiam na liście poniżej, a kompletną li- mentów i pewnego wycinka wiedzy, na pod-
stę rozwiązań odnajdziecie na stronie www.bpmn.org: stawie którego będziecie w stanie rozpo-
cząć pracę bez wertowania dziesiątek stron.
• Borland Together Architect® 2006 i Together Designer® 2006 – www.borland.com; Mam nadzieję, że wzbudziłem Wasze zain-
• Corel iGrafx™ – www.igrafx.com; teresowanie, zawarłem najważniejsze frag-
• Intalio n³ Designer™ – www.intalio.com;
• No Magic MagicDraw UML – www.nomagic.com; menty i będzie to dobra podstawa do dal-
• Sparx Systems Enterprise Architect – www.sparxsystems.com; szych poszukiwań.
• Sybase PowerDesigner® – www.sybase.com/developmentintegration; W podanych przeze mnie linkach do
• Tibco Business Studio ™ – www.tibco.com; stron znajdziecie też opracowania na bar-
• Visual Paradigm Visual Architect™ – www.visual-paradigm.com. dziej zwięzłym poziomie, niestety w języ-
ku angielskim. Co do strony językowej stara-
łem się w miarę przystępnie przełożyć obce
terminy na język polski, zakładając, że nie-
Pojęcia które z pewnością można przetłumaczyć in-
• BPMN – Business Process Modeling Notation – notacja używana w modelowaniu i przygo- aczej. Spotkałem się jednak z tyloma wersja-
towywaniu diagramów procesów biznesowych; mi polskich zwrotów poszczególnych wyra-
• Diagram Procesu Biznesowego (Business Process Diagram – BPD) – diagram zdefiniowany żeń angielskich, że musiałem się na coś zde-
przez BPMN i wykorzystujący elementy zdefiniowane w tej notacji; cydować. Utwierdzicie się w przekonaniu
• Kluczowy Wskaźnik Wydajności (Key Performance Indicator – KPI) – metryka (nie tylko finan- co do języka i zalecanych zwrotów w trakcie
sowa) używana w procesie realizacji celów organizacji, pozwalająca ocenić postępy ich
używania praktycznego elementów BPMN i
realizacji i zastosować ewentualne korekty;
• Monitorowanie Czynności Biznesowej (Business Activity Monitoring – BAM) – ciągła i aktyw- projektów wraz z innymi analitykami, któ-
na obserwacja Kluczowych Wskaźników Wydajności; rzy często już mają ugruntowany słownik i
• Proces – każda czynność wykonywana w danej firmie lub organizacji, w BPMN przedsta- wiedzą jak nazwać poszczególne elementy w
wiona jako sieć elementów przepływu (Flow Objects), czyli zestawów drobniejszych czyn- naszym ojczystym języku.
ności i sekwencjonujących elementów kontrolnych;
Mam nadzieję, że przedstawione podsu-
• Proces Biznesowy – zestaw czynności, zadań i zdarzeń realizowanych przez rozwiązania
systemowe i użytkowników w celu osiągnięcia celu biznesowego; mowanie BPM i BPMN zachęci Was do za-
• Reguła Biznesowa (Business Rule) – zdanie lub określenie opisujące zasady postępowania, poznania się ze szczegółami tej notacji i wy-
definicje i ograniczenia w działalności biznesowej mające wpływ na przebieg procesów korzystania jej w praktyce i oczywiście do
biznesowych; pogłębiania wiedzy ogólnodostępnymi i ko-
• Zadanie (Task) – pojedyncza czynność będąca elementem Procesu i pozwalająca na zo- mercyjnymi materiałami i szkoleniami. Te-
brazowanie pojedynczego elementu pracy w Procesie;
mat jest na tyle złożony i szeroki, że można
• Zarządzanie Procesami Biznesowymi (BPM – Business Process Management) – podejście
zarządzania obejmujące rozpoznanie, projektowanie i wdrażanie Procesów Biznesowych, spędzić wiele czasu na nauce, jednak myślę
dodatkowo zapewniające kontrolę wykonania, administracji i nadzoru nad nimi. że nie będzie to czas stracony.
Życząc powodzenia w dalszych stara-
niach, dziękuję bardzo za cenny czas poświę-
cony na przeczytanie tego artykułu.
W Sieci
• http://www.bpmn.org/ – oficjalna strona BPMN;
• http://www.bpmi.org/ – oficjalna strona BPMI;
• http://www.itposter.net/itPosters/bpmn/bpmn.htm – zebrane elementy BPMN w postaci MARCIN SAŁACIŃSKI
arkusza w różnych formatach gotowych do wydruku do wersji 1.0 BPMN; Kierownik projektu i analityk IT z ponad 10-let-
• http://bpt.hpi.uni-potsdam.de/pub/Public/BPMNCorner/BPMN1_1_Poster_EN.pdf – po-
nim doświadczeniem w branży. Specjalizuje się
dobny do powyższego arkusz do wersji 1.1 BPMN i zawierający nieco mniej informacji;
• http://www.DiveIntoBPM.org/ – strona z dużą ilością ciekawych informacji i przykładów z w zarządzaniu projektami, analizie biznesowej
BPMN; z wykorzystaniem BPMN i projektowaniu syste-
• http://refcardz.dzone.com/refcardz/bpm-bpmn – podsumowanie BPMN w łatwo strawnej mów wielowarstwowych w architekturze SOA.
formie ściągi – oprócz tego opracowania jest tam wiele innych ciekawych informacji; Obecnie pracuje na stanowisku kierownika pro-
• http://work�owpatterns.com/ – strona z rozmaitymi wzorcami przebiegów. jektu w RWE IT Poland.
Kontakt z autorem: marcin.salacinski@gmail.com

www.sdjournal.org 73
Programowanie gier

Tworzenie gry Flash


w pigułce
Programowanie gier to jedne z ciekawszych rodzajów projektów
programistycznych. Dzięki platformie Flash'owej stworzenie ciekawej
(i przynoszącej niezłe zyski) gry nie jest większym problemem.

ting, co znacznie przyspiesza jakość animacji


Dowiesz się: Powinieneś wiedzieć: i prędkość działania. Dość mocno upraszcza-
• Jak używać silnika Pixel Blitz; • Jak programować w AS 3.0. jąc proces – PB tworzy w pamięci bitmapę,
• Jak zarobić na swoich Flash'owych grach; na którą nanosi wszystkie wykorzystywane
• Skąd wziąć grafikę i dzwięki do swojej gry. w grze sprite'y, tak że flash renderuje jedynie
pojedynczy obrazek. Skok wydajności przy
użyciu PB jest spory – silnik bez problemu
potrafi płynnie animować 200-300 porusza-
sy, gdzie tacy utalentowani osobnicy prze- jących się obiektów (więcej niż potrzebuje-
Poziom trudności bywają – linki w ramce). Są też w sieci arty- my w naszej grze). Wykorzystując PB, ma-
ści, którzy tworzą grafikę do gier, a następ- my też do dyspozycji gotowe klasy do detek-
nie udostępniają ją zwykłym śmiertelnikom cji kolizji na poziomie pikseli, pełnej obsłu-
na licencji creative commons (linki również gi klawiatury (np. do wykrywania jednocze-
w ramce). śnie naciśniętych klawiszy).

W
tym artykule postaramy się przy- Zdecydowanie odradzalibyśmy zrzynanie
bliżyć Wam proces tworzenia grafiki z innych gier – choćby bardzo starych Edytor Poziomów
gry – od pomysłu, przez progra- i bardzo zapomnianych. Pomijając etyczny Chcieliśmy, aby w naszej grze znalazło się
mowanie do dystrybucji. wymiar takiego działania – w razie wydania dużo zróżnicowanych poziomów. Rozważa-
naszej gry i zdobycia przez nią popularności liśmy dwie możliwe opcje – napisanie gene-
Pomysł – możemy mieć przykrą niespodziankę w po- ratora losowych poziomów lub stworzenie
Zdecydowanie najważniejsza część całej gry staci roszczeń prawowitych autorów. do naszej gry edytora. Zdecydowaliśmy się na
– to właśnie doskonały pomysł (lub jego Przy tworzeniu Asteroid Raid'a mieliśmy prostsze, szybsze, ciekawsze i zgodne z pier-
brak), który decyduje o powodzeniu naszej to szczęście, że w zespole znalazł się dosta- wowzorem rozwiązanie i napisaliśmy edytor
produkcji. tecznie utalentowany grafik, który zapro- poziomów.
Wzięliśmy na warsztat klasyczną pozycję – jektował wszystkie niezbędne elementy gry Poziom w naszej grze podzieliliśmy na li-
klon ogromnie popularnego (swego czasu) 8- w pixel-art'owym stylu nawiązującym do nie, a te na segmenty. Każda linia składa się
bitowego hitu River Raid. Przenieśliśmy kon- pierwowzoru. z 20 segmentów o wymiarach 20x20 pik-
wencję w świat statków kosmicznych, i tak Podobnie ma się sprawa z efektami dźwię- seli (co daje 400 pikseli szerokości całej gry
powstał pomysł na Asteroid Raid. kowymi i muzyką – jest sporo miejsc w sie- – w sam raz do osadzenia Flasha na stronie
ci, gdzie programiści mogą znaleźć gotowe z grami czy na Facebook'u).
Grafika i Dzwięk (i darmowe) dźwięki. Na początku ustaliliśmy format zapisu po-
Na samym początku tworzenia gry progra- ziomu – z uwagi na ilość niezbędnych infor-
mista może trafić na pierwszą poważną prze- Biblioteki macji stworzyliśmy dość upakowany format
szkodę – skąd wziąść grafikę? Co prawda możemy napisać naszą grę od zapisu:
Opcji jest kilka – jeżeli dysponujemy mi- podstaw sami – ale są na to dużo sprytniej-
nimalnym talentem, można pokusić się sze metody. [ilość_segmentów, ilość_wolnej_przestrzeni,
o stworzenie grafiki samemu (kilka zna- Silników i bibliotek do detekcji kolizji i fi- ilość_segmentów (,ilość_wolnej_
nych Flashowych tytułów posiada taką „ba- zyki jest dość sporo – w przypadku Asteroid przestrzeni,
zgrołkową” konwencję). Najlepszym wyj- Raid'a korzystaliśmy z biblioteki o nazwie Pi- ilość segmentów)][id_obiektu, pozycja]
ściem jest połączenie sił ze znajomym gra- xel Blitz (PB).
fikiem (jeżeli żaden z Waszych znajomych PB to silnik stworzony przez Norm'a So- Jak widać, standardowa linia opisana jest
nie posiada takich talentów – można poszu- ule'a i Richarda Davey (photostorm.com). Jak przez trzyelementową tablicę. W momen-
kać chętnych do współpracy poprzez serwi- sama nazwa wskazuje, PB wykorzystuje blit- cie gdy pojawiają się bardziej skomplikowa-

74 04/2010
Tworzenie gry flash w pigułce

ne linie (z „wyspą” w środku lub z miejsca- var str:String = map.generateTxt() xelBlit'zu, to takie rozwiązanie byłoby fatal-
mi, gdzie znajdują się przeciwnicy) jest tych var file:FileReference = new ne – PB umożliwia konstruowanie gry w ten
danych więcej, niemniej jednak cały poziom FileReference() sposób). Klasa Level Animator dba również
(założyliśmy poziomy po ok. 2000 lini) mie- file.save(str,”level.txt”); o umieszczenie mostów dzielących poziomy
ści się w pliku tekstowym o wielkości kilku oraz umieszcza w odpowiednich miejscach
kilobajtów. Część generująca tekst korzysta przeciwników oraz zbiorniki z paliwem (po-
z metody row.generateTxt() oraz przez wywołanie meotdy generateObject()
Implementacja edytora poziomów tile.genereateTxt(). Tak stworzony tekst klasy ObjectAnimator).
Prace nad edytorem zaczynamy od zapro- jest przekazywany do metody file.save().
jektowania klas. Za pomocą klasy Map ope- Otwiera ona natywne okno dialogowe do za- SpaceShipAnimator
rujemy całością mapy, przechowujemy dane pisania pliku. Ładowanie zapisanej wcześniej Klasa, która tworzy nasz statek kosmiczny, do-
o wszystkich dodanych rzędach oraz doda- mapy odbywa się z wykorzystaniem zwykłej daje go na scenę, obsługuje reakcję na klawisze,
jemy rzędy do listy wyświetlania. Klasa Row klasy URLLoader. zmienia położenie statku, generuje pociski
opisuje każdy rząd (wszystkie obiekty Tile, oraz animuje piękną katastrofę po wykryciu
jakie się w nim znajdują). Klasa Tile określa Engine Gry kolizji z innymi obiektami. Ponieważ po wy-
rodzaj i typ każdego elementu rzędu. Klasa Nasza gra oparta jest o 4 klasy: ryciu kolizji musi minąć pewien czas do mo-
Tile powiązana jest z odpowiednio przygo- mentu przejścia do ekranu końcowego (gracz
towanymi elementami graficznymi zgrupo- LevelAnimator powinien mieć możliwość nacieszenia się wi-
wanymi w jednym MovieClip'ie w bibliotece. Zajmuje się budowaniem wierszy tworzą- dokiem płonących resztek swojego statku),
Proces tworzenia mapy wygląda następująco: cych poziom oraz zmianą położenia segmen- używamy metody dispatchEvent (ta metoda
tów. Budowane jest tylko tyle wierszy, ile po- jest dostępna, ponieważ SpaceShipAnimator
• Tworzymy obiekt Map na scenie – var trzeba na zapełnienie ekranu (sceny). W każ- dziedziczy po klasie EventDispatcher). Re-
map:Map = new Map();addChild(map); dej klatce wszystkie segmenty przesuwają się akcją na zakończenie animacji jest wykonanie
• Ustawiamy atrybut określający ilość rzę- o zadaną wartość (gdyby gra oparta była np. metody EventListener'a (czyli w naszym przy-
dów – map.rowNum = x. na obiektach klasy Spirte zamiast na Pi- padku – przejście do ekranu końcowego).
• Atrybut rowNum jest setterem, czy-
li przypisanie mu wartości wywołu-
je funkcję. W naszym przypadku przy-
pisanie wartości do rowNum powodu-
je automatyczne dodanie rzędów do Di-
splayList.
• Dla każdego dodanego na sce-
nę rzędu wywołujemy funkcję
row.createTiles(), która tworzy w da-
nym rzędzie obiekty Tile.

Po stworzeniu mapy czas na umieszczenie


na niej obiektów oraz układanie skał. Każ-
dy element mapy (Tile) nasłuchuje event'u
MouseEvent.MOUSE _ DOWN. Event ten odpa-
la metodę setID, która przypisuje do dane-
go elementu Id aktualnie wybranego „narzę-
dzia”, np:.

tile.setID(Tile.GROUND)

Warto wspomnieć, że dodawanie obiek-


tów jest zabezpieczone przed niepożąda-
nymi efektami, jak nakładanie się elemen-
tów lub umieszczenie elementu na nie-
odpowiednim podłożu (określone rodza-
je przeciwników mogą poruszać się jedy-
nie po określonym podłożu). Na początku
metody setID wywołujemy metodę klasy
Row.validateObject. Metoda ta przepro-
wadza testy, aby sprawdzić, czy dany obiekt
może być dodany w wybrane miejsce.
Kolejną funkcjonalnością edytora jest za-
pisywanie i ładowanie map. Mapę, nad któ-
rą pracujemy, możemy zapisać, klikając przy-
cisk SAVE. Wywołuje to funkcję, która zapi-
suje informacje o mapie w postaci pliku tek-
stowego. Rysunek 1. Tak wygląda nasza gra

www.sdjournal.org 75
Programowanie gier

Elementem, który wymaga nieco zabiegów, 3.0 ta klasa została usunięta. Jedną z możli- StarsAnimator
jest poprawne sterowanie rakietą. Założyliśmy, wości rozwiązania tego problemu jest ustawie- Klasa odpowiedzialna za animację tła, opar-
że sterowanie będzie się odbywało za pomocą nie flagi dla każdego klawisza i nasłuchiwanie tą o dwa obiekty klasy RenderLayer() silnika
klawiszy „strzałek”. Naturalnym rozwiązaniem na eventach KEY_UP i KEY_DOWN. Jest to sposób PixelBlitz. Pixel Blitz umożliwia stworze-
jest wykorzystanie KeyboardEvent do obsługi dość prymitywny i wykrywanie dużej ilości na- nie kilku "powłok", które dają możliwość od-
naciskania klawiszy. Jednak w przypadku na- ciśniętych klawiszy wymaga dodania sporej ilo- dzielenia od siebie różnych elementów i uła-
szej gry pojawia się problem z wykrywaniem ści zmiennych globalnych. W Asteroid Raid twiają animację i symulowanie głębi.
jednoczesnego naciśnięcia dwóch klawiszy (na wykorzystaliśmy klasę Key napisaną w AS 3.0
przykład „w prawo” i spacji). W ActionScrip- (dostępną na http://kirupa.com), która pozwala ObjectAnimator
t'cie 2.0 dzięki klasie Key mogliśmy sprawdzać w elegancki sposób obsługiwać naciśnięcie wie- Opisuje właściwości wrogich statków, zbior-
każdy klawisz indywidualnie, w ActionScript lu klawiszy naraz. ników z paliwem oraz zajmuje się ich ani-
macją. Dodawanie obiektów jest inicjowane
przez klasę levelAnimator w momencie two-
rzenia nowego wiersza. Metoda opisu pozio-
mu, jaką wybraliśmy, wprowadza ogranicze-
nie w postaci jednego obiektu na wiersz.
Ponieważ każdy z obiektów jest opisany
osobną klasą, możemy zamiast zwykłego:

switch(a) {
case 1: object = new Class1(); break;
case 2: object = new Class2(); break;
}

wprowadzić nieco bardziej eleganckie roz-


wiązanie, polegające na trzymaniu referen-
cji do wszystkich klas obiektów w tablicy:

var classes:Array = new Array(Class1,


Class2, Class3)
var class:Class = classes[a]
object = new class()

AsteroidRaidMain
Główna klasa gry, w której zaimplementowa-
no inicjowanie gry, łączenie z serwerem Mo-
chi (o tym za chwilę) oraz reakcje na kolizje
obiektów.
W każdej klatce wykonywana jest rów-
nież metoda render() na obiekcie klasy
Renderer2D silnika PixelBlitz (która, jak ła-
two się domyślić, renderuje obraz).

Rysunek 2. Kreator poziomów Money


makes the world go round
Mimo że tworzenie gier jest niezwykle przy-
Co to jest blitting? jemne samo w sobie, staje się jeszcze ciekaw-
Bliting (Bit-Block Image Transfer) to operacja, w trakcie której kilka bitmap łączonych jest w jed-
ną całość przy użyciu operatorów bitowych. Najprostszym przykładem jest nadpisanie jednej sze w momencie, kiedy możemy na tym za-
bitmapy przez drugą, jednak w procesie możemy wykorzystywać również bitmapy o różnych robić.
rozmiarach i stosować różne operatory. Blitting jest wykorzystywany głównie przy dwuwymia- Jedną z opcji jest wstawienie do naszej gry
rowych animacjach wykorzystujących dużą ilość różnorodnych bitmap (np. w grach 2D). reklam. Na rynku funkcjonuje kilka serwi-
sów oferujących reklamy (żeby wspomnieć
choćby Google AdWords dla Flash'a). My
zdecydowaliśmy się na wykorzystanie w na-
Linki do stron z grafiką i dzwiękiem szym projekcie serwisu MochiMedia (http:
• http://www.lostgarden.com/ (genialna strona z dużą ilością ciekawych grafik); //www.mochimedia.com/). Mochi jest łatwe
• http://www.pixeljoint.com/ (serwis dla twórców pixel-art'u); w implementacji, reklamy z tego serwisu ma-
• http://www.pix.art.pl (polski odpowiednik pixeljoint'a); ją dość dobre ceny, a dodatkowo Mochi oferu-
• http://www.istock.com (można tutaj kupić zdjęcia i grafikę w umiarkowanych cenach); je kilka bardzo ciekawych opcji dodatkowych
• http://blackmoondev.com (zdarza się, że publikujemy grafiki);
(o tym za chwilę). Nie bez znaczenia jest rów-
• http://www.freesound.org/ (biblioteka dzwięków Creative Commons);
• http://www.�ashkit.com (można tam znaleźć zarówno dzwięki, jak i grafikę). nież fakt, że wysyłając swoją grę do serwisu
Mochi, jest ona automatycznie dystrybuowa-
na na ponad 10 tysięcy stron z grami.

76 04/2010
MochiAds API oferuje nam trzy typy re- jąc loginu ze strony Mochi lub (co jest o wiele
klam, jakie możemy umieścić w naszej grze. bardziej rozpowszechnione) swojego loginu
z facebook.com. Implementacja Mochi Scores
• Pre-game :
Reklamy tekstowe, flashowe jest równie banalna co innych komponentów
lub video pokazywane w czasie ładowa- Mochi – jedyne, co trzeba zrobić, to w odpo-
nia gry. Tak naprawdę preload jest jedy- wiednim miejscu wkleić kilka linijek kodu.
nie symulowany – sami ustalamy, przez
ile sekund reklama ma być wyświetlana Mochi Coins
(licząc na to, że nasza gra się w tym cza- Ostatnim modułem Mochi, jaki chcieliśmy
sie załaduje); pokazać, jest Mochi Coins (mimo że nie wy-
• Inter-level: Reklamy pokazywane korzystaliśmy tej opcji w Asteroid Raid jest
w czasie naturalnych przerw w grze (np. to wyjątkowo ciekawe i nowatorskie rozwiąza-
pomiędzy poziomami). Minimalny czas nie). Koncepcja Mochi Coins polega na „wir-
trwania reklamy to 10 sekund; tualnym portfelu”, który użytkownik może
• Click-away: Reklama pokazywana zapełnić Mochi Coins na stronie MochiMedia
w czasie przerw w grze, którą grający (zapełnić oczywiście, płacąc za nie rzeczywistą
musi usunąć samodzielnie – gra będzie gotówką). Gracz może wydać swoje „mone-
wyświetlana tak długo, dopóki nie zosta- ty” w każdej grze wykorzystującej Mochi Co-
nie „od-kliknięta”. ins. Pozwala to developerom na stworzenie na-
no-płatności za odblokowanie nowych pozio-
Implementacja wstawiania reklam jest wy- mów, nowe rodzaje broni etc. (w naszym przy-
jątkowo prosta. Należy jedynie zaimporto- kładzie mogłoby to być np. sprzedawanie lep-
wać odpowiednie klasy i w miejscu, gdzie szych rodzajów broni za przysłowiowego cen-
ma się wyświetlać reklama, umieścić kod ta). Aby używać systemu Mochi Coins, nasza
(przykład dla Pre-game, reszta wygląda bar- gra musi być przesłana do Mochi w celu jej do-
dzo podobnie): głębnego przetestowania.

MochiAd.showPreGameAd({clip:root, id:”id_ Sponsoring


naszej_gry”, res: Poza umieszczaniem w naszej grze reklam
”wymiary_naszej_ możemy również poszukać sponsora, któ-
gry”}); ry wyłożyłby twardą gotówkę w zamian za
umieszczenie w naszej grze swojego loga i lin-
Id gry otrzymamy po bezpłatnej rejestracji ku. Część sposobów pozwala na dodatkowe
na stronie mochimedia.com – tam również umieszczenie reklam, część jest zaintereso-
będziemy mogli śledzić strumień napływa- wana jedynie pełną wyłącznością.
jących do nas pieniędzy. Najłatwiej skontaktować się z potencjalny-
mi sponsorami na stronie www.flashgameli-
Mochi Analytics cense.com.
Jeżeli jesteśmy zainteresowani bardziej roz-
budowanymi statystykami na temat naszych Podsumowanie
graczy, możemy zainstalować w naszej grze Tworzenie gier Flash może być bardzo cieka-
Mochi Analytics. Po darmowej rejestracji na wym i inspirującym zajęciem. Dzięki serwi-
stronie http://mochibot.com otrzymamy snip- som takim jak MochiMedia programiści nie
pet kodu, który wklejamy do naszej gry – od muszą zmagać się z problemami dystrybu-
tego momentu możemy śledzić, ilu graczy cji i monetyzacji (mądre słowo na „zarabianie
odpaliło naszą grę. na”) swoich produkcji. Łatwość, z jaką Flash
umożliwia zarządzanie grafiką, gigantycz-
Mochi Scores ny stopień penetracji przeglądarek interneto-
Jedną ze wspomnianych dodatkowych opcji wych, obsługa 3D, możliwość tworzenia de-
Mochi jest system Highscore (Tablicy wyni- sktopowch aplikacji dzięki AIR oraz (dostęp-
ków). Mochi oferuje całe hostingowanie na- na wkrótce) możliwość tworzenia natywnych
szej tablicy wyników, a dodatkowo gracze aplikacji na iPhone'y bezpośrednio we Flashu
mogą porównywać swoje wyniki bez wzglę- sprawiają, że w tej chwili Flash jest najłatwiej-
du na to, na jakiej stronie grali w naszą grę. szym sposobem na tworzenie gier dostępnych
Mochi Highscore pozwala na zarejestrowanie na wielu platformach.
wyniku gracza, który zalogował się, używa-
ROBERT PODGÓRSKI,
BARTEK INDYCKI,
W Sieci MICHAŁ WRÓBLEWSKI
• http://mochimedia.com; BlackMoon Design to studio z Poznania zajmują-
• http://kirupa.com; ce się projektowaniem stron internetowych, ilu-
• http://blackmoondev.com. stracją oraz tworzeniem gier i aplickacji Flash.
Kontakt: http://blackmoondev.com

www.sdjournal.org 77
Efektywność pracy

Ile to zajmie?
Rzecz o szacowaniu zadań programistycznych

W artykule skoncentrowaliśmy się na jednym z częściej powtarzanych


pytań w zespołach programistycznych: ile to zajmie? Pytanie to
nieustannie spędza z oczy sen zarówno liderom, jak i programistom.
Skoro jest ono tak ważkie, to przyjrzyjmy mu się dokładniej!

kładne, może bardzo szybko Cię zniechęcić do


Dowiesz się: Powinieneś wiedzieć: podejmowania jakichkolwiek prób estymowa-
• Jak szacować zadania programistyczne; • Tylko to, co już wiesz. nia swojej pracy.
• Jak uzasadniać swoje szacowania;
Po kawałku...
Po sieci krąży wyeksploatowana już anegdota:
Zastanów się teraz, na jakiej podstawie do- Jak się je słonia? Odpowiedź: Po kawałku!Aneg-
konałeś oszacowania. Czy była to intuicja, do- dota daje bardzo prostą metodę postępowania
Poziom świadczenie, wyczucie, analiza? Zapamiętaj tę z zadaniami. Jej użyteczność jest nie do prze-
trudności liczbę. Przyda się nam jeszcze w dalszej części cenienia. Metoda mówi tyle: jeśli zabierasz się
artykułu. za złożone zadanie, to najpierw podziel je na
kolejne kroki (zdekomponuj problem). W me-
Dlaczego szacowanie? todykach zarządzania projektami technika de-

N
a początek mamy dla Ciebie ćwi- Gdyś wybierał się na wycieczkę samochodową, komponowania problemu nazywa się Work
czenie rozgrzewkowe. Wyobraź so- np. z Krakowa do Frankfurtu, to co chciałbyś Breakdown Structure. W podejściu Agile zna-
bie, że bierzesz udział w tworzeniu wiedzieć, zanim wyjedziesz? Z pewnością za- na jest jej odmiana Feature Breakdown Structu-
aplikacji webowej w Twojej ulubionej tech- pytałbyś o: długość trasy, czas trwania całej wy- re. Jak więc ma się to do szacowania?
nologii. Aplikacja ta ma wspomagać pracę bi- cieczki, noclegi, atrakcje turystyczne itp. Waż- Główny kłopot, z którym boryka się pro-
blioteki, a Tobie przypadło zaimplementowa- niejsze jest jednak dlaczego chciałbyś to wie- gramista próbujący oszacować swoje zadanie,
nie następujących funkcjonalności: dzieć? Być może dlatego, żeby: określić, ile ben- można streścić następująco: nie wiem, ile to zaj-
zyny zatankować, ile prowiantu ze sobą zabrać, mie, może dzień, może tydzień. Kłopot ten bie-
• Czytelnik może zapisać się do bibliote- ile cała podróż będzie kosztować. Podobnie jest rze się ze zbyt dużej ilości zmiennych (różnych
ki; z projektami informatycznymi. Każdy projekt możliwości, przypadków, potencjalnych pro-
• Czytelnik może wyszukać książki ze to pewna podróż, która ma określony cel (le- blemów), które bierze pod uwagę, szacując za-
względu na: autora/ów, tytuł; wydaw- piej, żeby miała!) i do której chcemy się jak najle- danie. Z pomocą przychodzi dekomponowa-
nictwo, słowa kluczowe; piej przygotować. Z drugiej strony każda podróż nie problemu. Wracając do metafory z podró-
• Czytelnik może wypożyczyć książkę; niesie w sobie element niepewności i nawet naj- żowaniem: trudno od razu oszacować czas jaz-
• Czytelnik może przedłużyć wypożycze- lepiej przygotowanego poszukiwacza przygód dy z Krakowa do Frankfurtu. Gdy jednak po-
nie książki; rzeczywistość może niejeden raz zaskoczyć. dzielimy trasę na kilometrowe odcinki, to zna-
• Czytelnik może oglądać stan swojego Metafora podróży dobrze obrazuje popular- jąc czas przejazdu jednego kilometra, bez pro-
konta (wypożyczone książki, kary za ny mit na temat szacowania – że musi być do- blemu oszacujemy czas podróży.
przetrzymanie); kładne. Równie dobrze możesz oczekiwać, że Jest jeden haczyk w powyższym rozumowa-
• System nalicza kary za przekroczenie podczas wspomnianej podróży odwiedzisz za- niu. O ile czas dziesięciokilometrowej podróży
terminu zwrotu książki; planowane miejsca o ustalonej godzinie z do- można w miarę dokładnie oszacować, to jed-
• Bibliotekarz dodaje książki do katalogu; kładnością co do minuty. Być może byłoby to nak osiemset kilometrów przez dwa różne pań-
• Bibliotekarz usuwa z katalogu zniszczo- możliwe w idealnym świecie, gdzie nie ma kor- stwa sprawia więcej kłopotów. Każdy kierowca
ne egzemplarze książek. ków na ulicach, a autobusy się nie spóźniają. wie, że inaczej jeździ się po polskich i niemiec-
To jest właśnie sedno sprawy: podczas prac kich drogach, inaczej jeździ się o różnych po-
Następnie odpowiedz na pytanie: jak długo nad projektem wydarzają się rzeczy, które są rach dnia. Można powiedzieć, że trasa podróży
zajmie Ci zaimplementowanie powyższych od nas niezależne. Szacowanie ma dać Ci na przebiega przez zróżnicowane konteksty środo-
funkcjonalności (z dokładnością do osobod- tyle realny obraz sytuacji, abyś mógł spokojnie wiskowe, które mają istotny wpływ na szacowa-
nia)? Daj sobie tyle czasu, ile potrzebujesz, wykonywać swoje zadania. Oczekiwanie, że nie. Z tego względu rozsądniej byłoby podzielić
aby udzielić wiarygodnej odpowiedzi. szacowanie będzie zawsze stuprocentowo do- trasę na takie kolejne części, które będzie moż-

78 04/2010
Ile to zajmie?

na wiarygodnie oszacować biorąc pod uwagę Tabela 1. Szacowanie zdekomponowanego zadania


ich kontekst. Części te nie mogą być zbyt drob- Podzadanie Opt. Real. Pes. Wynik
ne, np. kilometry, gdyż wtedy niebezpiecznie
Opracować model (...) 2 2 5 2,5
uśredniamy informacje o kontekście. Nie mogą
być również zbyt duże, np. cała trasa, gdyż w ta- Przygotować formularz (...) 3 5 7 5
kim przypadku jest zbyt dużo rzeczy, które mo- Zaimplementować (...) 8 8 20 10
gą utrudnić podróż, a o których nie wiemy. W … … … … …
ten nieco metaforyczny sposób staramy się od-
Razem 33 45 72 47,5
powiedzieć często powtarzane pytania: jak bar-
dzo dekomponować zadanie, skąd wiadomo, że
jest zbyt szczegółowo lub zbyt ogólnie, kiedy w • Przetestować integracyjnie całość funk- konywało powtarzalne zadania. W takim wy-
ogóle warto dekomponować zadania? cjonalności. padku wystarczy uwzględnić czynniki związa-
• Napisać skrypt uaktualniający bazę pro- ne z poziomem kompetencji, ilością wykony-
Przykład dukcyjną. wanych zadań, by za pomocą mniej lub bardziej
Zajmijmy się zadaniem Czytelnik może zapi- skomplikowanych wzorów określić czas wyko-
sać się do biblioteki z początkowego przykładu. Zdekomponowanie zadania: nywania projektu. Szybko okazało się, że to, co
Rozkładamy je na następujące kroki: przede wszystkim charakteryzuje projekty (w
• Daje logiczny ciąg czynności, po wyko- tym projekty IT), to nieprzewidywalność i zróż-
• Opracować model danych i konfigurować naniu których zadanie zostanie zrealizo- nicowane ryzyko. Potrzeba nieco innego podej-
warstwę dostępu do danych. wane; ścia do szacowania. Jednym z nich może być
• Przygotować formularz zapisywania się • Wyodrębnia fragmenty większego zadania Metoda ekspercka, która polega na tym, że szaco-
użytkownika do biblioteki. łatwiejsze do oszacowania; wania dokonuje doświadczony ekspert. Odmia-
• Zaimplementować mechanizm zapisywa- • Pomaga „wyciągnąć na światło dzienne” ną tego podejścia jest Metoda Delphi, gdzie sza-
nia się do biblioteki i mailowego potwier- rzeczy, o których moglibyśmy zapomnieć. cowania dokonuje grupa ekspertów, a ostatecz-
dzania zapisania się w bibliotece (wraz z ny wynik jest wartością uśrednioną. Minusem
testami jednostkowymi). Metoda szacowania tej metody jest oczywiście konieczność zaanga-
• Zaimplementować mechanizm zakłada- Wiele metod szacowania ma swoje korzenie w żowania ekspertów. Co więc może zrobić jeden
nia konta czytelnika po potwierdzeniu czasach industrializacji, gdy tysiące robotni- programista, aby wiarygodnie oszacować swoje
rejestracji. ków skupionych wokół linii produkcyjnej wy- (zdekomponowane) zadania?

R E K L A M A

www.sdjournal.org 79
Efektywność pracy

Magiczna liczba wał „ugrać” coś na szacowaniach. W końcu mu- szy – to jest jej istotą. Szacowanie za pomocą
W wielu przypadkach estymowanie zadania si zmieścić się z projektem w określonych grani- trzech liczb pozwala nam patrzeć na całą tar-
przebiega następująco: cach, a niektórych terminów (choćby Bożego czę do rzutek, do której porównywaliśmy sza-
Narodzenia) nie da się przesunąć, żeby nie wiem cowanie. Zamiast celować na chybił trafił, pró-
• [PM] Zajmij się wymaganiem nr 142. Osza- co. Szacując, będziemy chcieli w miarę precyzyj- bujemy ogarnąć zadanie w szerszej perspekty-
cuj je do 13.00. nie określić wielkość tarczy, to da nam o wiele wie. W tym sensie nie rozsądzamy, że używa-
• [Programista] Ok. więcej informacji niż jedna magiczna liczba. nie trzech liczb zamiast jednej jest lepsze. Jed-
• (dwie godziny później) nopunktowe szacowanie ma również swoich
• [PM] I jak? Ile Ci to zajmie? Szacowanie trzypunktowe zwolenników. Twierdzimy jedynie, że w przy-
• [Programista] Dwanaście dni. Koncepcja szacowania za pomocą trzech liczb padku zadań programistycznych metoda trzy-
• [PM] Oj, to za dużo. Moim zdaniem najwy- (Three-point estimation) została zaczerpnięta z punktowa jest bardziej użyteczna, ponieważ
żej pięć. metodyki zarządzania projektami PERT. Polega niesie ze sobą więcej informacji. Na przykład z
• [Programista] yyyyyy... ona na uwzględnieniu trzech punktów widze- szacowania przedstawionego w Tabeli 1 moż-
• [PM] No, to ustalone. Czekam na wieści o po- nia: optymistycznego, pesymistycznego oraz re- na wyciągać następujące wnioski:
myślnym zakończeniu. alistycznego.
• [Programista] @!$#&$%#^! Szacując optymistycznie, załóż, że wszyst- • Całe zadanie powinno zająć 47,5 jednost-
kie czynności związane z zadaniem zostaną ki, ale istnieją okoliczności, które mogą
Częstym problemem, z którym się borykamy, wykonane szybko i sprawnie. Przy uwzględnia- sprawić, że prace przeciągną się do 72 jed-
jest uzasadnianie naszych szacowań. Ponieważ niu wariantu pesymistycznego myśl tak, jakby nostek;
jesteśmy bardzo blisko kodu, mamy świado- wszystkie systemy sprzysięgły się przeciw To- • Podzadanie nr 1 jest znane i typowe, po-
mość często występujących problemów, więc bie. Weź pod uwagę problemy, które mogą wy- nieważ poszczególne szacowania nie róż-
możemy założyć, że potrafimy ocenić czaso- stąpić. Gdy określasz najbardziej prawdopo- nią się znacząco od siebie;
chłonność zadania. Z drugiej strony właśnie dobny wariant, odwołaj się do swojego doświad- • Podzadanie nr 3 na zastanawiająco duży
dlatego, że jesteśmy bardzo blisko kodu, mo- czenia. Zastanów się, ile czasu zajmowały po- wariant pesymistyczny możliwe powody:
żemy mieć trudności z przyjrzeniem się zada- dobne zadania. programista jest niedoświadczony w tym
niu z szerszej perspektywy. Gdy jesteśmy znu- Tabela 1 przedstawia przykładowe szacowa- obszarze i rezerwuje czas na niezbędną
żeni projektem i stale powtarzającymi się kło- nia dla wybranych kroków omawianego wcze- naukę, programista ma trudności z szaco-
potami, grubo przeszacowujemy zadania. Gdy śniej zadania Czytelnik może zapisać się do bi- waniem w ogóle, zadanie zostało niewy-
jesteśmy w dobrym nastroju, rozentuzjazmo- blioteki. starczająco dookreślone i programista ma
wani nowym ciekawym projektem, zdarza się zbyt mało informacji, aby wiarygodnie je
nam zapominać o istotnych szczegółach i sza- Co wynika z trzech liczb? oszacować.
cujemy zbyt optymistycznie. Być może przychodzą Ci do głowy pytania:
Mówiliśmy wcześniej o tym, że szacowanie dlaczego estymacja za pomocą trzech liczb Rozłożenie zadania na poszczególne kroki, a
to pewne przypuszczenie odnośnie przyszłości. jest lepsza niż za pomocą jednej? Które szaco- następnie oszacowanie każdego z nich zwięk-
Możesz wyobrazić sobie tę przyszłość jako tar- wania należy uznać za obowiązujące: optymi- szy prawdopodobieństwo, że utrzymasz swo-
czę (taką jak do gry w rzutki). Pole powierzchni styczne realistyczne, czy pesymistyczne? Jeśli je szacowanie. Dodatkowo przeprowadzona
tarczy to obszar możliwości, to wszystkie poten- chodzi o drugie pytanie, to metoda podaje for- analiza będzie doskonałym uzasadnieniem
cjalne wartości szacowań. Gdy szacujemy jed- mułę: (opt + 4*real + pesym)/6 ,za pomo- dla podawanych przez Ciebie estymacji. Lide-
nopunktowo, to tak jakbyśmy rzucali do tarczy cą której możesz uśrednić trzy perspektywy do rzy, z którymi pracowaliśmy, zgodnie twier-
z zamkniętymi oczami – czasem trafiamy. Czy jednej. Więc gdyby ktoś pytał o ostateczny wy- dzą, że chętnie biorą pod uwagę i zgadzają
nie korzystniej jest widzieć całą tarczę, zanim się nik, to łatwo go obliczysz. się z propozycjami programisty, jeśli tylko są
rzuci? Gdy nie dostarczamy menadżerowi po- Siła tej metody leży przede wszystkim w przekonani, że programista wie, co robi, i po-
parcia na naszych szacowań, to będzie on próbo- rozważaniu trzech alternatywnych scenariu- trafi swoje propozycje uzasadnić.
Jako ćwiczenie na zakończenie artykułu
proponujemy Ci Czytelniku, abyś oszacował
Czym nie jest, a czym jest szacowanie? każde z zadań dotyczących systemu biblio-
Szacowanie nie jest:
tecznego. Pamiętaj, żeby najpierw zdekom-
• dokładnym wynikiem ponować każde zadanie. Następnie porównaj
końcowy wynik z szacowaniem, które prze-
Szacowanie jest: prowadziłeś na początku artykułu. Czy po-
• przypuszczeniem, które daje wystarczająco wyraźny obraz realności zadania tak, aby równanie Cię zaskoczyło?
programista mógł podejmować dobre decyzje w trakcie jego realizacji (Steve McConnell,
Szacowanie oprogramowania).
MICHAŁ BARTYZEL,
MARIUSZ SIERACZKIEWICZ
O czym często zapominamy ? Trenerzy i konsultanci w �rmie BNS IT. Badają
Starając się przygotować zestaw kroków zmierzających do wykonania zadania, zdarza nam i rozwijają metody psychologii programowania,
się zapominać o uwzględnieniu: pomagające programistom lepiej wykonywać ich
pracę. Na co dzień qutorzy zajmują się zwiększa-
• Tworzenia diagramów UML i niezbędnej dokumentacji technicznej; niem efektywności programistów poprzez szkole-
• Testowania funkcjonalności, tworzenia testów jednostkowych;
• Zmian w plikach konfiguracyjnych; nia, warsztaty oraz coaching i trening.
• Skryptów migracyjnych i pomocniczych. Kontakt z autorami:
m.bartyzel@bnsit.pl, m.sieraczkiewicz@bnsit.pl

80 04/2010
KLUB PRO Volantis jest uznanym na całym świecie
nych dla firm udostępniających informa
(operatorów telefonii komórkowej, stacji
Dzięki zasadzie „stwórz raz, uruchamia
zania zmniejszają złożoność, koszt i cza
INFOTEX SP.J TTS Company Sp. z o.o.
wisów i aplikacji. Volantis jest członkiem
Śmietanowski i Wsp. Sprzedaż i dystrybucja oprogramowania komputero-
klientami są światowi potentaci telekomu
Dystrybutor XP Unlimited – Serwer Terminali dla wego. Import programów na zamówienie. Ponad 200
Windows XP i VISTA. Program umożliwia łącze- producentów w standardowej ofercie. Chcesz kupić
nie się z dowolnego klienta Windows, Linux z wy- oprogramowanie i nie możesz znaleźć polskiego do-
korzystaniem protokołu RDP. Cena wersji Classic stawcy? Skontaktuj się z nami – sprowadzimy nawet
dla 5 użytkowników - 165€, dla nieograniczonej
liczby - 235€. Ponadto oferujemy opiekę serwiso- pojedyncze licencje.
wą i aplikacje internetowe na zamówienie.

http://www.infotex.com.pl http://www.OprogramowanieKomputerowe.pl

IT SOLUTIONS Softline rozwiązania mobilne


Wdrożenia i szkolenia z zakresu: Wiodący producent systemów mobilnych, do-
• SQL Server stawca aplikacji użytkowych dla biznesu (Sym-
• SharePoint Services bian OS, Windows Mobile, J2ME ) zaprasza do
IT SOLUTIONS • MS Project / Server
• Tworzenie aplikacji w technologii .NET
współpracy. Zostań naszym partnerem. Dołącz
do zespołu.

http://www.itsolutions.biz.pl
marcin.pytlik@itsolutions.biz.pl http://www.softline.com.pl

Proximetry Poland Sp. z o.o. Systemy bankowe, ISOF


Proximetry Poland Sp. z o.o. jest polskim od- HEUTHES istnieje na rynku od 1989 r. Obok
działem amerykańskiej firmy Proximetry Inc. – systemów informatycznych dla banków, ofe-
dostawcy systemów zarządzania sieciami bez- ruje nowoczesne oprogramowanie do obsługi
przewodowymi opartymi na technologiach WiFi
i WiMAX. Naszą misją jest dostarczenie klien- firm. System ISOF jest udostępniany klientom
tom rozwiązań poprawiających jakość usług w trybie SaaS lub licencji. Pracuje na platfor-
(QoS) dostarczanych drogą radiową. Dołącz do mie Linux i zawiera m.in. takie moduły jak
najlepszych i zostań członkiem naszej ekipy! CRM, DMS, Magazyn, Sprzedaż, Logistyka
oraz Rachunkowość.
http://www.proximetry.com http://www.isof.pl

Opera Software Architektury systemów IT


Opera Software’s vision is to deliver the best In- Twórca frameworków JUVE i serwera aplikacji
ternet experience on any device. We are offering AVAX oferuje usługi, doradztwo, rozwiązania do
browser for PC/desktops and embedded pro- tworzenia nowoczesnych, dużych systemów i roz-
ducts that operates across devices, platforms wiązań informatycznych/internetowych, integrujące
and operating systems. Our browser can deliver architektury ery post-J2EE/.NET, wykorzystujące
a faster, more stable and flexible Internet expe- MDD/MDA dla dziedzin – bankowość, telekomuni-
rience than its competitors. kacja, handel, e-commerce, ERP/Workflow/CRM,
rozwiązania internetowe, portalowe.
http://www.opera.com www.mpsystem.com mpsystem@mpsystem.com

Kei.pl Future Processing


Kei.pl działa na rynku usług hostingowych od 2000 Future Processing to dynamiczna firma technolo-
roku. Do naszych zadowolonych Klientów z du- giczna działająca na globalnym rynku oprogramo-
mą możemy zaliczyć wiele przedsiębiorstw sekto- wania. Jesteśmy zespołem wysokiej klasy specja-
ra MSP, instytucji oraz osób prywatnych. W ofer- listów posiadających wiedzę i doświadczenie nie-
cie Kei.pl znajdują się pakiety hostingowe, a także zbędne do realizacji ambitnych projektów informa-
usługi dla wymagających Użytkowników – platfor- tycznych. Jeśli programowanie to Twoja pasja do-
my e-Biznes oraz serwery fizyczne. łącz do nas! (możliwość pracy zdalnej).

http://www.kei.pl http://www.future-processing.pl

Playsoft WSISiZ w Warszawie


Playsoft jako lider portowania aplikacji na plat- INFORMATYKA ZARZĄDZANIE
formy mobilne wciąż powiększa bazę swo- studia stopnia I i II (stacjonarne i niestacjonar-
ich klientów: EA Mobile, Sega, THQ, Kona- ne) specjalności: inżynierskie, magisterskie
mi. W ramach rozszerzania swojej działalno- i licencjackie. Szczegółowe plany studiów, opi-
ści, poszukujemy doświadczonego programi- sy poszczególnych specjalności – zapraszamy
sty, który byłby odpowiedzialny za tworzenie na stronę uczelni.
aplikacji na platformy Iphone, Windows Mobi-
le, Android.
http:// www.playsoft.fr http://www.wit.edu.pl

You might also like