Pokročilý editor grafů

Transkript

Pokročilý editor grafů
České vysoké učení technické v Praze
Fakulta elektrotechnická
Katedra počítačů
Diplomová práce
Pokročilý editor grafů
Bc. Martin Šach
Vedoucí práce: Ing. Ivan Šimeček, Ph.D.
Studijní program: Elektrotechnika a informatika, strukturovaný, Navazující
magisterský
Obor: Výpočetní technika
12. května 2010
Poděkování
Chtěl bych vyjádřit poděkování vedoucímu své diplomové práce panu Ing. Ivanu Šimečkovi, Ph.D.
za cenné připomínky a rady k obsahu práce. Dále bych chtěl poděkovat svým nejbližším za velkou
trpělivost a podporu při studiu a tvorbě práce. Děkuji!
Prohlášení
Prohlašuji, že jsem práci vypracoval samostatně a použil jsem pouze podklady uvedené v
přiloženém seznamu.
Nemám závažný důvod proti užití tohoto školního díla ve smyslu §60 Zákona č. 121/2000
Sb., o právu autorském, o právech souvisejících s právem autorským a o změně některých
zákonů (autorský zákon).
V Praze dne 6. 5. 2010
...........................................................
Abstract
This work represents the result of many and many days and nights of thinking over the problem
of realization of an advanced editor of graphs and the realization itself. This editor enables
simulation of graph algorithms.
Abstrakt
Tato práce prezentuje výsledek mnoha dnů a nocí strávených úporným přemýšlením o problému
realizace pokročilého editoru grafů a realizací samotnou. Tento editor umožňuje simulování
grafových algoritmů.
Obsah
1 Úvod.................................................................................................................................................1
2 Popis problému, specifikace cíle......................................................................................................4
2.1 Hlavní cíle práce.......................................................................................................................4
2.2 Požadavky na program..............................................................................................................4
3 Analýza.............................................................................................................................................7
3.1 Požadavky na realizaci..............................................................................................................7
3.1.1 Použití programovacího jazyku Java................................................................................7
3.1.2 Systémové nároky.............................................................................................................8
3.1.3 Spouštění z různých médií a OS.......................................................................................9
3.1.4 Využívání OOP..................................................................................................................9
3.1.5 Strukturalizování kódu....................................................................................................10
3.1.6 Odolávání výjimkám.......................................................................................................10
3.1.7 Vytváření omezení...........................................................................................................11
3.2 Požadavky na práci s grafy.....................................................................................................11
3.2.1 Kreslení grafů..................................................................................................................11
3.2.2 Editace nakreslených grafů.............................................................................................12
3.2.3 Ukládání grafů do souborů..............................................................................................13
3.2.4 Načítání grafů ze souborů...............................................................................................14
3.2.5 Generování grafů.............................................................................................................15
3.2.6 Algoritmy pro práci s grafy.............................................................................................17
3.2.7 Zobrazování grafů...........................................................................................................19
Požadavek: Grafy lze zobrazovat různými způsoby.............................................................19
Požadavek: Stejný graf může být najednou zobrazen různými způsoby.............................20
Požadavek: Změna učiněná na jednom zobrazovači grafu se projeví na ostatních
zobrazovačích grafu.............................................................................................................20
3.3 Požadavky na jazyk a nastavení .............................................................................................21
3.3.1 Pamatování si uživatelského nastavení ..........................................................................21
3.3.2 Lokalizace programu.......................................................................................................22
3.3.3 Výchozí nastavení...........................................................................................................23
Obecně k výchozímu nastavení............................................................................................23
Shlukování výchozího nastavení .........................................................................................24
3.3.4 Základní text...................................................................................................................24
3.4 Požadavky na GUI..................................................................................................................24
3.4.1 Základní funkcionality v nástrojové liště........................................................................24
3.4.2 Různé způsoby zobrazování obsahu...............................................................................25
3.4.3 Stavový řádek..................................................................................................................26
3.4.4 Startovní splash screen....................................................................................................26
3.5 Další požadavky......................................................................................................................27
3.5.1 Podpora modifikovatelnosti sebe sama...........................................................................27
3.5.2 Podpora rozšířitelnosti sebe sama...................................................................................27
3.5.3 Podpora přidání nové funkcionality do menu.................................................................29
3.5.4 Přidání nápovědy od uživatele .......................................................................................29
3.5.5 Výpis zpráv o chybách / výjimkách................................................................................30
3.6 Java a reflexe..........................................................................................................................31
4 Popis implementace........................................................................................................................33
4.1 Třídy, soubory.........................................................................................................................33
4.2 Spuštění Programu..................................................................................................................38
4.3 Hlavní okno.............................................................................................................................38
4.4 Ukládání uživatelského nastavení...........................................................................................40
4.5 Načítání uživatelského nastavení............................................................................................41
4.6 Jazykové nastavení.................................................................................................................43
4.6.1 Řešení lokalizace.............................................................................................................43
4.6.2 Lokalizování do nového jazyka......................................................................................45
4.7 Stavy programu.......................................................................................................................45
4.7.1 Přidání uzlu.....................................................................................................................46
4.7.2 Stisknutí tlačítka myši nad plátnem................................................................................47
4.8 Logování.................................................................................................................................47
4.9 Stavový řádek.........................................................................................................................48
4.10 Sebereflexe............................................................................................................................49
4.10.1 Seznam tříd, které jsou v programu dostupné...............................................................49
4.10.2 Vytvoření instance konkrétní třídy................................................................................51
4.11 Možnosti zobrazování obsahu...............................................................................................51
4.12 Grafy a jejich zobrazování....................................................................................................53
4.12.1 Princip řešení.................................................................................................................53
4.12.2 Grafický vs. textový pohled..........................................................................................55
4.12.3 Manuální vs. automatické vykreslování........................................................................56
4.12.4 Zobrazení grafu v jiném pohledu..................................................................................57
4.13 Manage okno.........................................................................................................................57
4.14 Nápověda..............................................................................................................................59
4.15 Rozšíření...............................................................................................................................60
4.15.1 Princip řešení.................................................................................................................60
4.15.2 Jak přidat nový add-on..................................................................................................61
4.16 Přidání položky do menu......................................................................................................62
4.17 Ukládání a načítání grafů......................................................................................................63
4.17.1 Ukládání grafů...............................................................................................................63
4.17.2 Načítání grafů................................................................................................................64
4.18 Simulace algoritmů...............................................................................................................65
4.18.1 „Needs“ metody............................................................................................................66
4.18.2 Krok pro inicializaci prostředí......................................................................................66
4.18.3 Další přípravné kroky....................................................................................................67
4.18.4 Třída Algorithm a její konstruktor................................................................................67
4.18.5 Krok pro start samotného kroku....................................................................................68
4.18.6 Metoda createMySteps()...............................................................................................68
4.18.7 Kroky............................................................................................................................69
4.18.8 Panel pro simulování algoritmů....................................................................................69
4.18.9 Příklad algoritmu pro simulaci......................................................................................71
4.18.10 Další příklady..............................................................................................................73
4.18.11 Příznaky a staré hodnoty uzlů a hran..........................................................................74
4.19 Generátor grafů.....................................................................................................................74
4.19.1 Základní principy..........................................................................................................74
4.19.2 Dostupné generátory.....................................................................................................76
5 Testování.........................................................................................................................................77
5.1 HW nároky..............................................................................................................................77
5.2 Spouštění v různých OS a JDK...............................................................................................77
5.3 Testování funkčnosti SystemInfoFunctions............................................................................77
5.3.1 Vytváření nových projektů..............................................................................................78
5.3.2 Spouštění z konzole / příkazové řádky...........................................................................78
5.4 Testování funkčnosti ostatních částí programu.......................................................................79
5.5 Testy použitelnosti..................................................................................................................81
6 Podobné programy..........................................................................................................................82
6.1 Pokročilý editor grafů.............................................................................................................82
6.2 uDraw(Graph) 3.1...................................................................................................................83
6.3 yEd Graph Editor 3.5..............................................................................................................83
6.4 Applety....................................................................................................................................83
6.5 Graph Magics 2.1....................................................................................................................83
6.6 Platforma NetBeans................................................................................................................84
7 Závěr...............................................................................................................................................85
7.1 Náměty na vylepšení...............................................................................................................85
7.2 Zhodnocení.............................................................................................................................87
8 Seznam použité literatury...............................................................................................................88
A Seznam použitých zkratek..............................................................................................................90
B Uživatelská příručka.......................................................................................................................91
Spouštění programu.......................................................................................................................91
C Obsah CD........................................................................................................................................92
Seznam obrázků
Obrázek 1.1: Ukázka zachycující práci s programem vyvinutým v rámci BP (převzato z [2])...........2
Obrázek 3.1: Různé způsoby reprezentace grafu (převzato z [10])....................................................11
Obrázek 3.2: Indikace, kde je kurzor myši.........................................................................................12
Obrázek 3.3: Graf pro uložení (převzato z [11])................................................................................14
Obrázek 3.4:Nabídka tříd, které implementují dané rozhraní\...........................................................19
Obrázek 3.5: Možný způsob načtení nastavení..................................................................................23
Obrázek 3.6: Panel s grafem zobrazeným v záložce a okno pro výběr barvy zobrazené ve vnitřním
okně....................................................................................................................................................25
Obrázek 3.7: Obecné schéma, jak při skládání řešit podkomponenty................................................27
Obrázek 3.8: Pohled na nastavení plug-inů v NetBeans IDE 6.7.1....................................................28
Obrázek 3.9:Deaktivování stavového řádku může řešit jen záměna objektů.....................................31
Obrázek 4.1: Ilustrace k popisu třídy GraphicalGraphView..............................................................36
Obrázek 4.2: Načtení programu.........................................................................................................38
Obrázek 4.3: Struktura hlavního okna................................................................................................39
Obrázek 4.4: Jak hlavní okno dědí od javax.swing.JFrame...............................................................39
Obrázek 4.5: Interface Settable..........................................................................................................40
Obrázek 4.6: Načítání nastavení.........................................................................................................41
Obrázek 4.7: Příklad tříd, pomocí kterých se načte nastavení pro GraphViewCircles......................42
Obrázek 4.8: Třídy související s načítáním........................................................................................43
Obrázek 4.9: Stavy AddNodeState a AddNodeNoState.....................................................................46
Obrázek 4.10: TimeLog jako jedna z tříd, kterými lze realizovat log................................................48
Obrázek 4.11: Metody třídy SystemInfoFunctions............................................................................50
Obrázek 4.12: Třídy pro vizualizaci obsahu pomocí záložek............................................................52
Obrázek 4.13: Graf a jeho pohledy.....................................................................................................53
Obrázek 4.14: Třídy potřebné pro realizaci překreslovačů u GraphicalGraphView..........................54
Obrázek 4.15:Graf a uzly v něm.........................................................................................................55
Obrázek 4.16: Hierarchie tříd pohledů...............................................................................................56
Obrázek 4.17: Třída okna pro nastavení komponent..........................................................................57
Obrázek 4.18: Třídy realizující okno pro správu komponent.............................................................58
Obrázek 4.19: Registr add-onů...........................................................................................................58
Obrázek 4.20: Téma nápovědy: Grafy...............................................................................................59
Obrázek 4.21: Třída okna pro správu add-onů...................................................................................61
Obrázek 4.22: Okno pro správu doplňků...........................................................................................62
Obrázek 4.23: Třídy související s položkami v menu........................................................................63
Obrázek 4.24: Třídy používané při ukládání grafů.............................................................................64
Obrázek 4.25: Předci třídy BFS..........................................................................................................65
Ilustrace 4.26: ....................................................................................................................................65
Obrázek 4.27: Třída reprezentující.....................................................................................................66
Obrázek 4.28: Panel pro krokování algoritmů...................................................................................70
Obrázek 4.29: Třídy, které se podílí na realizaci generátoru..............................................................75
1
Úvod
Informatika, stále populárnější pojem, který lze v podstatě považovat stále za pojem moderní, je
věda o zpracování informací. A to nejenom na počítačích. Jako počítačová věda je to pojem, který
zahrnuje řadu důležitých věcí, disciplín a oborů. A objevit tu můžeme i teorii grafů, disciplínu, která
by měla stát jako takový hlavní zastřešovací pojem nad touto prací.
O teorii grafů se v [1, str. 18] píše: „Bez pojmů a postupů teorie grafů si lze informatiku jen
stěží představit. Využívání grafů a grafových algoritmů prostupuje nejen teoretický základ oboru,
ale tvoří neodmyslitelnou součást i tak prakticky zaměřených oblastí, jakou představují např.
programovací techniky nebo počítačové sítě“.
Tedy teorie grafů není jen zastřešujícím pojmem nad touto prací. Je to hlavně i jedna
z důležitých věd, s kterými by měl student informatiky přijít do styku. A pokud možno si toho
z jejího studia odnést co nejvíce.
Základem této teorie jsou grafy. Jde o matematické modely, kterých je hojně využíváno právě
v informatice. Většinou bývají zakreslovány jako shluk koleček propojených čárkami. Kolečkům
se říká uzly, čárkám hrany. Vzniklý útvar v řadě případů nepůsobí vůbec složitě.
Grafy bývají využívány k abstrakci reálného problému. Skutečný problém se namapuje
na graf, ve kterém uzly najednou představují např. města, a hrany silnice mezi takovými městy.
Problém, který pro takové město (graf) chceme řešit, tu pak představuje grafový algoritmus, který
by měl být nad naším grafem vykonán..
Teorie grafů tu ale není jen od toho, aby nám říkala, že reálné problémy by šlo abstrahovat
pomocí grafů. Kromě grafů, jak je zakreslovat či reprezentovat, bylo vymyšleno již nespočetně
grafových algoritmů, pomocí kterých lze řešit nejrůznější problémy. Jmenovat můžeme např.
Jarníkův algoritmus pro nalezení minimální kostry grafu, Dijkstrův algoritmus pro nalezení
nejkratší cesty z jednoho uzlu do uzlů ostatních či Fordův-Fulkersonův algoritmus pro výpočet
maximálního toku v síti.
Když si dokážeme náš problém namapovat na graf, můžeme se dostat ke zjištění, že pro řešení
takového problému lze využít již vymyšlený algoritmus. U toho budeme znát jeho asymptotickou
složitost a pseudokód, což nám usnadní vyřešení našeho problému. Vše se nese v duchu: „Proč
vymýšlet znova něco, co už bylo dávno vymyšleno?“
To by bylo několik úvodních vět k problematice teorie grafů. Více např. v [1]. Nyní bude ještě
následovat několik vět k účelu této práce. Cíle konkrétněji budou ještě rozebrány v kapitole 2.
1
Jak již bylo řečeno tato práce se věnuje teorii grafů. Cílem je poskytnutí podpory pro studium
teorie grafů, o což jsem se snažil již v roce 2007 ve své práci bakalářské.
Ano. V této diplomové práci navazuji na svou práci bakalářskou [2], která se věnovala
stejnému tématu. Tímto tématem bylo vytvoření softwaru, pro „hraní“ si s grafy. Slovo hraní
je v uvozovkách, jelikož ho nelze chápat doslova. Uživatel v takovém programu mohl vytvářet
grafy a simulovat si na nich nabídnuté algoritmy.
Obrázek 1.1: Ukázka zachycující práci s programem vyvinutým v rámci BP (převzato z [2])
2
Hlavním cílem této diplomové práce je zrovna tak vytvoření softwaru pro takové „hraní si“
s grafy. Napadá mě spojení „Škola hrou“. Idea je taková, že když si uživatel vzniklého programu
vytvoří graf, něco v něm změní a má možnost sledovat, jaké taková změna vyvolala konsekvence,
tak to na něj může míti výchovný efekt.
Přitom platí, že se předešlá práce nekopíruje. Ani se na ní nenavazuje v implementačním
slova smyslu. Tři roky odstupu, sebereflexe, ujasnění si, co jsou klady a zápory předešlé práce,
to jsou ty základní stavební kameny, které vedly k rozhodnutí, že program, který bude
vytvářený v rámci této diplomové práce (dále DP), bude vytvářen úplně od znova.
Záměrem je být obecnější. Bylo by chybné předpokládat, že je možné vytvořit nějaký
dokonalý a nepřekonatelný program sloužící jako učební pomůcka pro studium grafů. Vždycky
je možné vytvořit něco lepšího.
Za prvé se může např. něco změnit. Za druhé může být zapomenuto nebo nestiženo v takovém
programu něco implementovat. Za třetí implementace některých částí může být chybná, nepřesná,
časově neoptimální, nebo jen mírně jiná, než by člověk po čase chtěl. Člověk je bytost chybující
a stane se, že se v hotovém programu objeví zásadní nedostatek, i když se testuje sebevíc. A konec
konců ještě za čtvrté: V průběhu času se může vyskytnou potřeba po přidání nějaké nové
funkcionality.
Všechny tyto úvahy mě vedly k názoru, že je vhodnější program pro tuto DP koncipovat více
obecnějším způsobem. Jako takovou platformu, kterou by nemělo být tak těžké pozměňovat nebo
do ní něco přidávat. To o softwaru napsaném v rámci BP vůbec neplatí a změny by se v něm
prováděly poměrně složitě.
A to by bylo pro úvod asi vše. Uvedli jsme si několik vět k problematice teorie grafů, uvedli
jsme si několik vět k účelu této práce, a konkrétnější budeme v následujících kapitolách. Co se týče
související terminologie, tak nejzákladnější používané termíny tu na rozdíl od BP vysvětlovány
nebudou. Snahou je se držet terminologie použité v [1]. Tudíž pokud čtenář například neví, co je to
ohodnocená hrana nebo co je to smyčka, tak viz. zde.
3
2
Popis problému, specifikace cíle
2.1
Hlavní cíle práce
Úplně nejzákladnějším cílem této práce je vytvoření programu, který by mohl být v ideálním
případě využívaný jako učební pomůcka ke studiu teorie grafů. To už bylo uvedeno v úvodu. V této
kapitole vše dostatečně rozvedeme.
Implementovaný program bude v rámci tohoto textu nazýván jednoduše programem. Hlavní
funkcionality programu pravděpodobně budou zjevné ze zadání této DP. Přesto si je v kapitole 2.2 v rámci všech učiněných požadavků vyjmenujeme a v kapitole 3 podrobně probereme.
Co se týče textu této DP, tak zde je hlavním cílem přiblížit řešení programu. Tedy jeho
implementaci. Uváděny tu pochopitelně nebudou zdrojové kódy, protože ty by samy o sobě čtenáři
nic zásadního neřekly. A ani by tu pro ně nebyl dostatek prostoru, protože zdrojového kódu bylo
napsáno skutečně dost.
Mnohem zásadnější je pro mě nastínit logiku řešení jednotlivých části. Jak je program
myšlenkově uchopen. Jak je navrhnut a jak je sestaven. Jaký v něm panuje vnitřní řád. Co je pro
jeho obsluhu potřeba. Jak ho nastavovat, měnit či ovládat. A proč to tak je. Rád bych tu nastínil,
v čem se takové řešení vyznačuje, jaké má výhody a jaké z něj plynou důsledky.
A to všechno by mělo být obsahem následujících řádek.
2.2
Požadavky na program
Tato kapitola vyjmenovává ve strukturalizované formě požadavky / cíle, o kterých bylo rozhodnuto,
že je program bude splňovat. Jde tedy o funkcionality, které program i splňuje. Tyto funkcionality
plynou jednak ze zadání této DP, a pak také z požadavků, které jsem si učinil sám. Učinil jsem tak
po zkušenostech z BP a po analýze celého problému. Tyto požadavky budou v kapitole 3 blíže
rozvedeny a analyzovány. Zde je pouze jejich seznam, aby bylo možné si snadno učinit přehled
o základních možnostech programu.
Jestli se tu vyjmenovává požadavků zbytečně moc, nebo jestli tu měla býti zmíněna ještě
nějaká další funkčnost, která by mohla býti považována za požadavek na program, těžko posoudit.
Jde o záležitost subjektivního vnímání. V následujícím seznamu jsem se snažil mimo jiné
o vyjmenování všech základních a nejdůležitějších věcí, jejichž diskutování by mohlo pomoci
k pochopení vnitřní logiky výsledku. Celé uchopení programu je samozřejmě od učiněných
požadavků dosti odvislé.
4
Požadavky na realizaci:
1.
Program je naprogramovaný v programovacím jazyku Java
2.
Program zbytečně neplýtvá výpočetními prostředky.
3.
Předpokládá se spouštění z různých operačních systémů a z různých úložných médií.
4.
Program využívá možností objektově orientovaného programování.
5.
Program se snaží obecně použitelné / společné funkcionality vyčleňovat do zvláštních
tříd / metod.
6.
Program není ukončen kvůli jednoduché chybě / výjimce, která se vyskytne během
běhu programu.
7.
Program zbytečně nevytváří nějaká omezení, která by znesnadňovala budoucí vývoj.
Požadavky na práci s grafy:
8.
Uživatel může kreslit grafy.
9.
Uživatel může editovat nakreslené grafy.
10.
Uživatel může uložit graf do souboru.
11.
Uživatel může načíst graf ze souboru.
12.
Uživatel si může nechat vygenerovat graf.
13.
Uživatel může graf ovlivnit zadaným algoritmem napsaným v programovacím jazyku
Java.
14.
Grafy lze zobrazovat různými způsoby.
15.
Stejný graf může být najednou zobrazen různými způsoby.
16.
Změna učiněná na jednom zobrazovači grafu se projeví na ostatních zobrazovačích
grafu.
17.
Program lze rozšiřovat o podporu nových formátů, do kterých lze grafy ukládat, či ze
kterých lze grafy načítat.
Požadavky na jazyk a nastavení:
18.
Program si pamatuje uživatelské nastavení.
19.
Program je možné spustit v různých světových jazycích.
20.
Program bude spuštěn ve výchozím nastavení, pakliže načtení uživatelského nastavení
selže.
21.
Výchozí nastavení je pokud možno shluknuto do jednoho místa.
22.
Program poskytne základní text v situacích, kdy se nepodaří načíst text lokalizovaný.
5
Požadavky na GUI:
23.
Program zpřístupňuje základní funkcionality v nástrojové liště.
24.
Zobrazovaný obsah, kterým v našem případě jsou vykreslené grafy, lze uživateli
zpřístupňovat různými způsoby – např. jako jednu ze záložek v panelu záložek, či jako
okénko.
25.
Uživateli se může zobrazovat stavový řádek, který obsahuje základní informace o tom,
co se právě v programu děje.
26.
Před startem programu se zobrazuje splash screen (okno indikující načítání programu).
Další požadavky:
27.
Program se snaží podpořit uživatelskou modifikovatelnost sebe sama.
28.
Program se snaží podpořit uživatelskou rozšířitelnost sebe sama.
29.
Program usnadňuje přidání nové funkcionality do menu.
30.
Program může umožnit přidání nápovědy pro uživatele.
31.
Program umožňuje výpis zpráv o chybách / výjimkách.
6
3
Analýza
Účelem této kapitoly je podrobně zanalyzovat požadavky, které byly heslovitě vyjmenovány v kapitole 2.2. Stejnojmenná kapitola byla i v mé BP, kde mimo jiné stojí věta: „Analýza a návrh řešení
nebývá jednoduchý úkol, protože její úspěšné vykonání vyžaduje detailní znalost řešené
úlohy, dobrou znalost prostředků, které by bylo možné k řešení úlohy využít, a konec konců i talent
pomocí těchto vědomostí vyfiltrovat vhodné řešení, toto řešení dokázat popsat a tento popis umět
obhájit.“
Asi nebude žádným překvapivým tvrzením, když uvedeme, že tato věta platí i zde. Má-li
se zvýšit pravděpodobnost, že jakýkoli implementovaný program bude kvalitní, analýza řešení
je potřebná vždy. Jinak pak vznikají různá ad hoc řešení, pomocí kterých se programátor snaží vyřešit problém, který se objevil. Když se nad vším člověk zamyslí dopředu, na řadu případných problémů se přijde už během analýzy.
Jak už bylo uvedeno v úvodu, v analýze a návrhu řešení jsem mohl vycházet ze zkušeností
s implementací podobného programu, který vznikl v rámci mé BP. Tehdy se člověk zabýval ještě
takovými věcmi, jako bylo například zvolení vhodného programovacího jazyku, v kterém práci
implementovat. Tehdy vyhrál programovací jazyk Java (konkrétně Java 5.0 od tehdejších Sun
Microsystems) v kombinaci s obecným značkovacím jazykem XML.
3.1
Požadavky na realizaci
3.1.1 Použití programovacího jazyku Java
Požadavek 1: Program je napsaný v programovacím jazyku Java. Zvolit Javu či jiný
programovací jazyk? Od této volby jsem byl v rámci této práce odstíněn, protože volba Javy je součástí zadání. Asi netřeba připomínat skutečnosti, že Java je po celém světě hodně rozšířená a že
programy vytvořené v Javě jsou nezávislé na architektuře, přenositelné a objektově orientované.
I proto bych Javu znovu volil i dnes. Více o ní případně viz. v [3].
V rámci analýzy a návrhu řešení bych především pojednal o verzi Javy, kterou jsem se
rozhodl použít. Stejně jako v případě BP jsem myslel na Javu 5.0 od Sun Microsystems. Již několik
let je sice dostupná i verze 6, ale tu případný uživatel nemusí mít nainstalovánu. K Javě 6 jsem
se chtěl uchýlit jen v případě, pokud by mě k tomu nutila nějaká šikovná knihovna, kterou by šlo
v programu využít. Nestalo se tak.
Další skutečností, která souvisí přímo s programovacím jazykem, je ta, že jsem se v rámci
7
analýzy rozhodl, že nebudu využívat deprecated metody. To jsou takové metody, které je doporučeno nepoužívat, protože k nim nejspíš existují lepší alternativy. V Javě jsou přítomny hlavně kvůli
zpětné kompatibilitě. Díky té programy napsané pro určitou verzi Javy lze spouštět i ve verzích
novějších. To je podstatná skutečnost, která měla na volbu Javy 5.0 nemalý vliv.
Finální stav je tak takový, že program je napsaný v 5. verzi Javy, spustitelný v jejím JRE,
a použité metody nejsou označeny jako deprecated ani v Javě 6.
3.1.2 Systémové nároky
U požadavku 2 bylo uvedeno, aby program zbytečně neplýtval výpočetními prostředky. Jde o poměrně obecný požadavek, který by měl signalizovat, že jsem si vědom toho, že počítače, na kterých
program poběží, mají limitované možnosti. Dostupná paměť tu nebude nekonečná, takže není
vhodné si do ní ukládat každou zbytečnost. A počet instrukcí, které počítač bude schopen za časový
úsek provést, bude také konečný.
V rámci BP jsem v souvislosti se systémovými nároky napsal několik vět o rozdílu mezi
interpretovanými a kompilovanými jazyky. Tentokrát bych tu místo toho jen odkázal na systémové
nároky využitého virtuálního stroje [4]. Od těch by mělo býti snahou se příliš nevzdalovat. Objekty
se v programu mohou vytvářet až v případě potřeby. Po použití se mohou hned odstranit, aniž by se
čekalo až na konec programu. A některé časté časově náročné operace mohou být naopak urychleny
ukládáním před vypočtených hodnot.
Mohou, mohou, mohou – slova vypovídající o tom, že se o systémových nárocích bavíme
v obecnější rovině. Důvod je ten, že na vyvíjený program nahlížím jako na „knihovnu rozumných
řešení“, kde není prvořadým cílem efektivnost, ale nabízená funkcionalita, poskytnutá míra rozšiřitelnosti atd.
Jako příklad lze uvést následující kód (předpokládá existenci třídy A s bezparametrovým konstruktorem):
A a1 = new A();
A a2 = (A) A.class.newInstance();
Jak se uvádí v [5], je výkonnostní rozdíl mezi tím, jestli vytvoříme instanci třídy pomocí
operátoru new, nebo jestli k tomu použijeme metodu newInstance() třídy Class. Použití new je
rychlejší a pro většinu situací naprosto správné. Na druhou stranu, jak bude diskutováno v textu
ještě dále, při použití newInstance() můžeme za běhu načíst i třídy, které při spuštění programu ani
neexistovaly. To může být např. třída nějakého plug-inu. A to je velice zajímavé chování. Hodí se
8
a tím nás nutí do výkonnostního kompromisu. A podobných příkladů bychom tu mohli uvádět ještě
několik.
V této souvislosti bych tu dále připomenul skutečnost, že cílem programu je býti učební
pomůckou. V takových programech bývají spouštěny spíše menší úlohy, a víc než rychlost se cení
názornost. Nám tak stačí, aby se program při běžné práci na běžných počítačích zbytečně nesekal.
Tolik k systémovým nárokům.
3.1.3 Spouštění z různých médií a OS
Mohlo by se zdát, že je tady tento bod zmiňován zbytečně, protože jednou ze základních vlastností
Javy je, že se snaží býti platformově nezávislá. Přesto se jen s takovým tvrzením nemůžeme
spokojit. Existují totiž skutečnosti, na které je nutné v době implementace pamatovat, pokud
chceme maximalizovat míru bezkonfliktnosti při spouštění na různých OS a z různých médií.
A proto tento speciální požadavek. Mezi možné problémy patří následující:
•
Odlišné znaky pro odřádkování.
•
Odlišný typ lomítka, které se využívá k popisu cesty ke konkrétnímu souboru v souborovém
systému.
•
Reálná možnost toho, že program bude uložen na takovém médiu, na kterém nebude možné
uložení dalších souborů.
V Javě jsou k dispozici funkce, které nám umí vrátit správný znak pro odřádkování a správné
lomítko do cesty souboru. Nemusíme se přitom starat o detekci toho, s jakým OS se vůbec pracuje.
Mechanismus vyhazování výjimek zase umožňuje, že programátor na každém rizikovém místě
v programu nemusí testovat, jestli platí určitá skutečnost. Místo toho předpokládá korektní chování,
a na zvláštním místě ošetří možnost, že ke korektnímu chování nedošlo. Např. ve zmiňované situaci
s ukládáním souborů zkusíme soubor uložit, a když to nebude možné, zareagujeme na vyhozenou
výjimku.
3.1.4 Využívání OOP
Java je objektově orientovaný jazyk a proto se může zdát jako zbytečné, když si vytváříme
požadavek 4, aby se využívalo možností OOP. Záleží ale na chápání celého problému. V Javě lze
krásně napsat program, který moc z možností, které OOP nabízí, nevyužívá. To jsou např.
abstrakce, zapouzdření, skládání, dědičnost a polymorfismus. O nich více např. v [6].
Když jsem se zamýšlel nad programem, který vznikl v rámci BP a hledal v něm nedostatky,
9
jedním z učiněných závěrů bylo právě to, že nebylo využito možností, které OOP nabízí. V programu se např. neustále pomocí podmínek testovalo, jestli platí nějaký stav, aby se případně provedla
určitá operace. Takové věci šlo ale řešit např. jen lepší strukturalizací kódu a využitím
polymorfismu.
A takto by se dalo pokračovat dále. Proto jsem zavedl tento požadavek. Mělo by se více
využívat dědění k tomu, aby se nové funkcionality vytvořily pozměněním funkcionalit starších.
Dále komplexnější celky mohou být jednoduše tvořeny pomocí skládání z celků jednodušších apod.
Pokud chceme mít program obecně navržen, aby ho bylo možné co nejjednodušeji modifikovat
a rozšiřovat, jsou podobné postupy nadmíru vhodné.
3.1.5 Strukturalizování kódu
U požadavku 5 se píše o vyčleňování obecně použitelných funkcionalit do zvláštních metod. Jde
o další z obecných požadavků, který by se dal zařadit do rad, jak vhodně programovat. Zde ho uvádíme jako jednu ze skutečností, na které by měl člověk v rámci implementace pamatovat. Pokud
bude člověk stejný kód shlukovat (v případně parametrizované podobně), má to pro něj mimo jiné
následující důsledky:
•
Nemusí se psát stejná věc vícekrát.
•
V případě chybné implementace lze provést změny jen na jednom místě.
•
Jednodušeji se testuje funkčnost.
3.1.6 Odolávání výjimkám
V Javě je přítomný mechanismus výjimek [7], díky kterému lze lépe reagovat na nestandardní
a chybové chování programu. Výjimka může být vyvolána např. v případě, když není na zadané
adrese nalezen požadovaný soubor, když se pokoušíme dělit nulou nebo když se v poli snažíme přistoupit k hodnotě na indexu, který v poli vůbec není.
K výjimce za běhu programu zkrátka může dojít poměrně snadno. A nebylo by pochopitelně
vhodné, aby v takovém případě program hned skončil. Tedy pokud existuje alternativa, jak se
zachovat v případě nezdaru. A ta existuje v podstatě vždy, když budeme uvažovat i takové
možnosti, jako jsou dialogová okna s varovným výpisem. Proto můžeme požadovat, aby si pokud
možno žádná výjimka nevynutila ukončení programu.
V Javě platí, že vzniklé výjimky lze odchytávat a ve speciálních blocích na ně reagovat.
Snahou by mělo býti takového chování využít a výjimky odchytit. A je-li to nutné, nabídnout
10
alternativní možnost pro běh programu. Program by při vzniklé výjimce nemusel korektně fungovat
v určité své části, ale sám o sobě by měl neustále zůstávat běhu schopný.
3.1.7 Vytváření omezení
U požadavku 7 se píše o tom, aby nebyla zbytečně vytvářena omezení, která by
znesnadňovala budoucí vývoj. Z našeho pohledu jde o další z požadavků, které jsou poměrně
obecné. Skrývat se pod ním může psaní kódu, které lze samo o sobě označit za obecné. Ke komponentám, u kterých by byla představitelná i jiná implementace, lze vytvářet rozhraní, pomocí kterých
by se z vnějšku skrývalo, s instancí jaké třídy se konkrétně pracuje.
Dále není nutné hned využívat prostředků / knihoven, které by samy od sebe dosti omezovaly
možnosti, které byly do doby jejich využití dostupné. Pokud se nějaká taková knihovna vyskytne,
nešlo by k ní nalézt méně náročnou alternativu? Nešlo by pro uložení použít formát, který je
dostupný na všech platformách? A nešlo by nabídku možností např. v nějaké roletce odvodit od
stavu v systému či od uživatelsky definovaných mezních hodnot - tak aby nabídka nemusela být vytvořena napevno?
To všechno jsou otázky, který si člověk musí během vývoje pokládat, a také si je pokládal.
Podařilo se mi tak např. zachovat možnost spouštět program s pomocí JRE 5.0, i když jsem kvůli
tomu musel oželet několik řešení funkčních jen v Java 6 a vytvořit si místo toho řešení jiné.
3.2
Požadavky na práci s grafy
3.2.1 Kreslení grafů
U požadavku 8 na kreslení grafů nelze uvést nic zásadně jiného, než bylo uvedeno již v BP. Aby si
uživatel mohl kreslit, je potřeba, aby zvolený programovací jazyk podporoval grafický výstup na
obrazovku. To Java umožňuje prostřednictvím knihoven AWT a JFC (Swing), které jsou součástí
Java Core API (sada základních API). Dostupné jsou základní grafické prvky jako je např. elipsa,
úsečka a obdélník. Ty nám usnadňují vykreslování a další základní práce. Programátor se tak
sám nesnaží pomocí jednotlivých bodíků „vybodíkovat“ útvar, který by měl uživateli připomínat
např. zmíněnou elipsu.
Toto je ale základní chování, které
bychom čekali od každého programovacího Obrázek 3.1: Různé způsoby reprezentace grafu
(převzato z [10])
11
jazyku, kde lze něco kreslit. Zmínit bych chtěl proto hlavně takové funkcionality, díky kterých nám
grafický objekt umí sdělit, jestli se zadaný bod v grafickém kontextu [8] nachází uvnitř nebo vně
jeho plochy. Díky tomu tak např. u instance třídy java.awt.geom.Ellipse2D (elipsa) umíme relativně
jednoduše zjistit, jestli na nad ní bylo stisknuto tlačítko myši. Vyjadřovat si to sami, jestli zadaná
souřadnice leží uvnitř obsahu nějakého objektu, který nemá tvar zrovna čtverce, by byl již poněkud
obtížnější matematický problém.
O AWT a Swingu více např. v [8]. O kreslení geometrických útvarů v Javě pojednává [9].
3.2.2 Editace nakreslených grafů
Požadavek 9: Uživatel může editovat nakreslené grafy. Zde začněme tím, co si konkrétně pod tímto
požadavkem představit.
Tak za prvé jde už o samotnou možnost, že si uživatel
může grafy nakreslit ručně. Tedy že má nějaké plátno, v něm
si zvolí libovolný bod, nad ním klikne myší, v místě kliknutí
se objeví např. kolečko reprezentující uzel apod. To je základ.
A k tomu chceme, aby uživatel, lidský tvor, který může
chybovat a rozmýšlet se, měl možnost nakreslené objekty
odstraňovat nebo nějak modifikovat. Např. u uzlu můžeme
chtít změnit jeho pozici, velikost, barvu, ohodnocení. Tedy
pokud takové parametry uzel vůbec má. Grafy lze totiž
uživateli vizuálně prezentovat různými způsoby [10], což se
snaží naznačit obrázek 3.1. O zobrazování grafů různými
Obrázek 3.2: Indikace, kde je
kurzor myši
způsoby bude dále pojednáno v kap. 3.2.7.
Jestli v textu kapitoly 3.2.1 nebylo patrno, proč byly oslavovány funkcionality, pomocí
kterých může uživatel relativně jednoduše zjistit, jestli nebylo např. kliknuto myší nad daným
grafickým objektem, tak nyní by už to snad býti vidět mohlo. Podobné funkcionality se pro potřeby
grafické editace hodí náramně. Při kliknutí na plátno, kde je graf nakreslen, nebudeme vědět, zda
bylo kliknuto na nějaký grafický útvar nebo ne. Plátno pouze vykresluje obrazy daných grafických
objektů, a neví o nich nic detailního. My si proto musíme ukládat, které objekty chceme na plátno
vykreslit, a když je nad plátnem např. stisknuto tlačítko myši, tak se objektů zeptáme, jestli náhodou
kurzor myši nebyl v době stisknutí nad jejich plochou. Když ano, vykonáme potřebnou akci.
12
3.2.3 Ukládání grafů do souborů
Požadavek na ukládání vytvořeného obsahu je jedním z nejzákladnějších požadavků, se kterým se
člověk setká skoro u každého programu. Proč plýtvat nad něčím časem, když veškeré vynaložené
úsilí bude ztraceno v době ukončení programu? Tato úvaha asi nikoho nepřekvapí, jen jsem ji uvedl
jako úvod k celému problému.
S problémem ukládání vytvořených grafů se člověk samozřejmě potýkal už v rámci BP. Byla
potřeba vytvořit nebo najít již standardizovaný formát, pomocí kterého by bylo možné zachytit co
nejvíce z vytvořeného obsahu, aby si pak takový obsah mohl uživatel znovu (pokud možno
v nezměněné podobě) načíst.
V rámci BP jsem čtenáře seznámil s formáty GraphML [11], GXL [12], XGMML [13]
a GML [14]. První tři jmenovaní zástupci jsou XML formáty s jasně definovanou strukturou, čtvrtý
je jeden ze zástupců starších formátů, které vznikly ještě před příchodem XML.
Jak lze možná z vět uvedených výše vycítit, formátů pro ukládání grafů bylo vytvořeno
relativně dost. Zmíněné formáty patří k jedněm z těch významnější formátů. A minimálně jeden ze
zmíněných XML formátů by měl program podporovat. Ideálně GraphML, který je nejnovější. To je
jediný požadavek na podporovaný formát, který jsem v rámci analýzy a návrhu řešení učinil. Měl
jsem k tomu důvod, který snad bude patrný z následujících odstavců.
Jedním z cílů programu je uživatelská rozšířitelnost. Když člověk přemýšlí nad tímto
termínem, může dojít stejně tak jako já k závěrům, že by se takováto rozšířitelnost měla týkat
i formátů, do kterých by bylo možné graf ukládat.
Když jsem přišel k tomuto názoru, další na řadě byly úvahy, jak zařídit, aby měl program
nějakou základní nabídku formátů, do kterých by umožnil ukládat. Tuto nabídku by bylo možné
případně rozšiřovat, či dokonce podporu některého formátu odstraňovat. Tyto úvahy vedly
až k závěrům, že v Javě půjde s využitím Java Reflection API zařídit, aby uvažování, který formát
je v základní nabídce a který je nějakým speciálním způsobem načítaný doplněk, splynulo. Program
může být naprogramovaný i tak, že nebude uživatelský ani programátorský rozdíl v tom, jestli
člověk pracuje s formátem (třídou, která daný formát zastupuje), který byl v programu přítomný už
v době publikování této práce, a nebo byl dopsán až někdy pak. Takového chování lze
s Java Reflection API dosáhnout snadno. Jak je vše konkrétně řešené, si lze přečíst v kapitole 4.17.
Krátké pojednání k Java Reflection API lze najít v kap. 3.6. Příklad uložení grafu do GraphML
(převzatý z [11]) následuje:
13
Uložení grafu z obrázku 3.3 do formátu GraphML:
<?xml version="1.0" encoding="UTF-8"?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns
http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="d0" for="node" attr.name="color" attr.type="string">
<default>yellow</default>
</key>
<key id="d1" for="edge" attr.name="weight" attr.type="double"/>
<graph id="G" edgedefault="undirected">
<node id="n0">
<data key="d0">green</data>
</node>
<node id="n1"/>
<node id="n2">
<data key="d0">blue</data>
</node>
<node id="n3">
<data key="d0">red</data>
</node>
<node id="n4"/>
<node id="n5">
<data key="d0">turquoise</data>
</node>
<edge id="e0" source="n0" target="n2">
<data key="d1">1.0</data>
Obrázek 3.3: Graf pro uložení
</edge>
(převzato z [11])
<edge id="e1" source="n0" target="n1">
<data key="d1">1.0</data>
</edge>
<edge id="e2" source="n1" target="n3">
<data key="d1">2.0</data>
</edge>
<edge id="e3" source="n3" target="n2"/>
<edge id="e4" source="n2" target="n4"/>
<edge id="e5" source="n3" target="n5"/>
<edge id="e6" source="n5" target="n4">
<data key="d1">1.1</data>
</edge>
</graph>
</graphml>
3.2.4 Načítání grafů ze souborů
Když může uživatel ukládat grafy do speciálních formátů, které umožní zpětnou rekonstrukci
ukládaného grafu, mělo by býti možné umožnit i samotnou rekonstrukci takto uloženého grafu.
Takové chování se může zdát intuitivní, přesto mu věnuji stejně jako v rámci BP speciální kapitolu.
Připadá mi totiž vhodné ještě zmínit několik skutečností.
14
Tak za prvé mi v rámci různých případů užití, které jsem si během analýzy chování
programu zkoušel navrhnout, připadalo rozumné, aby načítání grafů a ukládání grafů byly na sobě
nezávislé. Tím myslím skutečnost, že by teoreticky mohlo býti možné i to, že by graf uměl ukládat
do úplně jiných formátů, než z kterých by uměl načítat.
Tento příklad je po funkční stránce samozřejmě úplně nevhodný, ale de facto by mohl být
v obecně koncipovaném programu reálný. Vše lze demonstrovat na celkem reálné situaci, kdy by
program uměl ukládat grafy do pomyslných formátů A, B a C, z těchto formátů by grafy uměl
i načítat, a navíc by uměl načítat grafy i z nějakého speciálního formátu, kterému zde budeme
říkat formát D.
Jde de facto o možnost pro import, se kterým se můžeme v některých programech setkat jako
s oddělenou funkčností od otevírání. Náš program by proto nějaké speciální funkčnosti jako Export
do a Import z nezaváděl. Místo toho tu budeme mluvit jen o formátech, do kterých lze ukládat
a o formátech, z kterých lze načítat. Je přitom vhodné, aby obě tyto množiny pokrývaly stejné
formáty. Pokud to tedy bude možné.
Druhou skutečnost, kterou bych zde chtěl uvést, můžeme nazvat např. GUI podporou načítání, která se tu hodí ještě více než u ukládání, ale i tam by měla býti dostupná. Myslí se tím skutečnost, aby se grafy po stisknutí tlačítka Uložit samy neukládaly s výchozím názvem do nějakého
implicitního úložiště, kde by případně automaticky přepsaly starší stejnojmenné soubory. Stejně tak
by bylo krajně nepohodlné, kdyby jedinou možností, jak by mohl uživatel uložit nebo načíst graf do
konkrétního místa úložného prostoru, byla nutnost zapsat ručně cestu k takovému nově vytvářenému souboru.
V GUI aplikacích jsou uživatelé zvyklí na chování, že při kliknutí na uložit / otevřít, se jim
zobrazí okénko, v kterém snadno mohou nadefinovat jméno souboru a cestu k takovému souboru.
A právě takové chování budeme chtít stejně jako v BP i nyní. V Javě k podpoře vytváření
takovýchto okýnek slouží třída JFileChooser z package javax.swing.
3.2.5 Generování grafů
Vygenerování grafu lze dosáhnout různými způsoby. Tím se myslí, že pro takovou funkcionalitu
může existovat samostatně fungující program, stejně tak jako nějaká funkcionalita uvnitř hlavního
programu.
Dále se může lišit podoba generovaného obsahu a úroveň takového generovaného obsahu.
Generováním lze například vytvořit soubor s obsahem, s kterým by si uměl poradit některý
15
z „načítačů“ grafů diskutovaných v kapitole 3.2.4. Jinou možností je v rámci běhu programu
vygenerovat graf, a hned ho zobrazit. U pokročilého generátoru by vygenerované grafy neměly být
úplně náhodné a měly by obsahovat rozumně rozvržené uzly rozmístěné např. v nějakém
navoleném tvaru. Zde by bylo možné definovat si počty hran a uzlů, jejich tvar, barvu, ohodnocení,
a jmenovat by se tu dalo dál.
Možností, jak si hrát s generátorem, je zkrátka mnoho. U programu, jehož snahou je
obecnější návrh, je problematické určovat, jak by takový generátor měl vypadat a co všechno by
měl umět. Ideální by mohlo býti, kdyby uměl všechno, ale něco podobného jsme už řešili v úvodu.
Co je všechno? A nevyskytne se jednou něco, co by bylo potřeba do generátoru přidat? A co kdyby
se po čase v generátoru objevila nějaká chyba? Bude snadné ji opravit?
Ne, myšlenka nějakého komplexního generátoru, v jehož možnostech by prostřednictvím
zaškrtávání různých možností bylo nepřesně řečeno všechno, se mi už od samotného začátku
nelíbila. Takový generátor by se implementoval složitě, nebyl by tak uživatelsky přívětivý pro
člověka, kterému by stačila jen drobnost, a o obecnosti a rozšířitelnosti také těžko něco konkrétního
soudit. Ty by byly plně odvislé od implementačního zvládnutí takového komplexního generátoru.
Místo komplexního generátoru jsem proto začal uvažovat o něčem, co by šlo nazvat jako
okno pro výběr generátorů. Myšlenka je de facto stejná jako v případě ukládání a načítání grafů.
Neměl by být činěn žádný konkrétní požadavek, co by měl takový generátor umět. Jednomu
uživateli by se grafy mohly hodit v podobě takové, druhému v podobě jiné. Jenom v rámci
předmětu Paralelní systémy a algoritmy by studenti určitě ocenili řadu různých generátorů – jeden
by mohl umět generovat mřížky o zadaných rozměrech, druhý krychle o zadaných dimenzích apod.
Místo toho jsem proto shledal jako vhodnější, aby si šlo vytvářet generátory libovolně, a jak
by se takový generátor choval a co by generoval, by bylo jen a jen na něm. Uživatel by si mohl
otevřít (jak bylo výše označeno) okno pro výběr generátorů, zde by si vybral konkrétní generátor,
v něm by využil možnosti, které tento generátor nabízí a vygeneroval si s ním nový graf.
To by bylo okno pro výběr generátorů. Z předešlého povídání vyplývá další skutečnost.
Uživatel by měl mít možnost si dopsat vlastní generátor. Ten by mělo jít co nejjednodušeji připojit
k programu takovým způsobem, aby se nový generátor zobrazoval v okně pro výběr generátorů.
Když si člověk vytvořil tento cíl, znělo to už poměrně složitě, ale stále se tu jedná o to samé
jako v případě ukládaní grafů. Tam se uvádělo využití Java Reflection API. Zde lze celý problém řešit obdobně. Uživatel si např. ve vývojovém nástroji NetBeans vytvoří nový projekt, do knihoven si
přidá JAR soubor našeho programu, a aniž by v něm musel něco měnit, vytvoří si novou javovskou
16
třídu, která implementuje zadané rozhraní. K této třídě dopíše chování metod, a když projekt spustí,
tak se mu pro výběr generátorů objeví i nově vytvořený generátor. Ten lze normálně použít. V pozadí za vším je vyhledání tříd, které jsou v programu dostupné.
V případě takového řešení opět těžko hovořit o tom, který z nabízených generátorů je pevnou
částí programu a který přidaný doplněk. Všechny generátory si budou rovnocenné, jen jeden
se v nabídce zobrazí jako prvně vybraný. Odstranění třídy, která daný generátor představuje, pak
znamená, že se takový generátor v nabídce generátorů již nebude nabízet. K podobnému chování by
mohlo stačit i udělat třídu jako abstraktní, a myslitelné jsou i jiné možnosti. Konkrétní chování je
popsáno v kapitole 4.19, kde se již uvažuje vytvořený kód.
3.2.6 Algoritmy pro práci s grafy
Požadavek 13: Uživatel může graf ovlivnit zadaným algoritmem v programovacím jazyku Java.
K této funkcionalitě úvodem asi tak, že by to měla býti jedna z nejdůležitějších změna oproti
programu implementovanému v rámci BP. To, že se objevila již v zadání, plyne z určité základní
analýzy, která proběhla před konkretizováním zadání této práce.
Původní program implementovaný v rámci BP byl takového ražení, že jsme tu měli plátno
pro kreslení grafů, a vedle něj byl k dispozici panel simulátoru, který nabízel několik základních
algoritmů, které bylo možné na vytvořeném grafu simulovat. Všechny tyto algoritmy byly zapsány
přímo jako součást panelu tohoto simulátoru. Přidání nového algoritmu pro simulování pak bylo
možné takovým způsobem, že si uživatel potřebný kód napsal třeba do nějaké funkce, a následně
ho ručně přidal do potřebných míst. Tak, aby se v roletce s nabídkou algoritmů zobrazoval i algoritmus nový a bylo možné ho v pořádku spouštět. Takových vynucených změn kvůli přidání nového
algoritmu nebylo vůbec málo.
Dále se tu programátor musel při některých operacích starat o nastavení správných hodnot
v různých místech programu. Pro vývojáře tedy nic příjemného. Byla tu ale ta výhoda, že si nově
přidávaný algoritmus šlo zapsat přímo v Javě. O té jsme tu již uváděli, že ji ovládá spousta
programátorů.
Zauvažujme se na chvíli, že by simulační algoritmy byly programovány v něčem jiném, než
v Javě a že by s celým programem nesdílely jedno stejné běhové prostředí. Úvahy učiněné v rámci
analýzy podobné myšlenky vedly k následujícím závěrům:
•
Programátorům algoritmů je vhodné poskytnout programovací jazyk s pokud možno známou syntaxí, aby se nemuseli učit něco nového. Nutnost učit se pracovat s novým
17
programovacím jazykem, by mohla řadu lidí odradit, aby vůbec takovou práci zkoušeli.
•
Vytvoření vlastního programovacího jazyku (jedno s jakou syntaxí) pro tvorbu simulačních
algoritmů by programátora odstínilo od samotného běhové prostředí. Vznikla by nutnost si
vytvořit vlastní syntaktický analyzátor a další související prostředky, přičemž by vlastně ve
výsledku jen docházelo k mapování příkazů z takového programu na volání metod našeho
programu. Čím jednodušší by takový programovací jazyk byl, tím bychom pravděpodobně
byly odstíněni od více dostupných funkcionalit v programu, protože by chybělo potřebné
mapování. Některé věci by tak nemusely býti možné a při potřebě přidání nového chování,
které nebylo pokryto, by mohla vyvstat nutnost modifikace jazyku a pravděpodobně
i nemalého množství s ním souvisejícího kódu. Že při takovém doplňování dojde
k vytvoření chyb, je reálné.
•
Řešení použité v BP, tedy řešení, kdy simulační kód využívá stejné běhové prostředí,
protože sám je částí programu, je výhodné v tom, že programátor simulačního algoritmu
není v ničem omezen. Dostupné mu jsou všechny veřejně přístupné třídy a metody
programu, a dále i samotné možnosti Javy a vývojových prostředí, které jsou pro ni
dostupná..
•
Řešení použité v BP má řadu nevýhod souvisejících s úpravou kódu, jak již bylo diskutováno. Není vhodné kvůli novým algoritmům měnit cokoli ve zdrojových kódech
programu. Když si píšeme program v Javě, tak taky kvůli němu neděláme změny ve
vývojovém nástroji, v kterém takový program píšeme (např. v NetBeans).
To by byly základní podněty, díky kterým mi volba použití samotné Javy pro zapisování
simulačních algoritmů připadala rozumná. Snahami dalších analýz a návrhů chování proto bylo
zajistit, aby takové simulování netrpělo stejnými (zmiňovanými) nedostatky, které se vyskytly
v rámci BP.
V úvahách nad výsledným chováním jsem se tak postupně dostal k řešení, kde se opět
využívá Java Reflection API. O tom, pro připomenutí, byla řeč již v souvislosti s ukládáním grafů
(kap.3.2.3) a generováním grafů (kap. 3.2.5). Simulační algoritmus může být třída, která
implementuje konkrétní rozhraní. V nabídce dostupných algoritmů, které lze v programu simulovat,
pak lze nabízet všechny třídy, které implementují zadané rozhraní a jsou aktuálně v programu
dostupné. Díky tomu by se programátor takových algoritmů nemusel vůbec starat o přidávání svých
výtvorů do potřebných míst tak, aby se zobrazily v nabídce. Tato nabídka by se namísto toho
generovala automaticky.
18
Zároveň (druhá důležitá věc) celý program by měl být koncipován tak, aby se
uživatel/programátor, mohl soustředit převážně na algoritmus samotný, a o ostatní věci, jako je
vykreslování, různé stavové proměnné programu apod. se nemusel starat. I když i tyto možnosti by
tu byly.
Samotný program lze tak chápat jako takové běhového prostředí pro simulaci. Programátor
píšící simulační algoritmy v něm nemusí řešit, jak implementovat potřebné struktury, jak zajistit,
aby se mu na obrazovce vykreslila hrana, na jejímž jednom konci je šipka atd. Místo toho se soustředí se hlavně na to, jak co nejvýchovněji zapsat kód, pomocí kterého by šlo ilustrovat, jak např.
funguje algoritmus pro prohledávání grafu do šířky.
Obrázek 3.4:Nabídka tříd, které implementují dané rozhraní\
3.2.7 Zobrazování grafů
Požadavek: Grafy lze zobrazovat různými způsoby.
Grafy lze zobrazovat různými způsoby. Toto je obecně napsaný požadavek, za kterým se skrývá
možná nejvíce práce. Záleží, co všechno si člověk pod takovým požadavkem představí. O programu
vytvořeném v rámci BP se takto mluvit nedalo. A přitom lze nad takovým chováním uvažovat jako
o jednom z těch, u kterých by se v případě vydařené implementace dalo mluvit o výchovném
efektu.
Ale konkrétněji. Byla tu řeč o tom, že grafy lze zobrazovat různými způsoby. Uváděn byl
graf vykreslený s pomocí čárek a koleček, stejně tak graf, který je reprezentovaný maticí
sousednosti. Možností je nespočet. V pozadí za vším je graf tvořený množinou uzlů, množinou hran
a informacemi o tom, že daná hrana se nachází mezi uzlem tím a tím, a že je orientovaná a ohodno19
cená hodnotou 7.Údaje, jak je který uzel široký, tu už chybí, proto v každém vyobrazení grafu může
být taková šířka úplně jiná.
Pakliže si uživatel bude moci graf zobrazit v různých podobách, je reálná taková situace, že
prostřednictvím změn činěných v jednom zobrazení, může studovat principy, na kterých funguje
zobrazení jiné. Zobrazení grafů budou dále v textu nazývána termíny jako pohledy, vizualizace či
vykreslovače.
Modelový příklad: Nakreslím si graf jako směsici čar a koleček (tedy uzlů a hran) a zobrazím
si jeho matici sousednosti, která představuje jen jiné zobrazení toho samého. Při pohledu
na takovou matici vidím např. tabulku, v které jsou nuly a jedničky. Následně ze zobrazení
tvořeného čárkami a kolečky vymažu jednu čárku (hranu). Když si nyní pro takový graf znovu
nechám zobrazit matici sousednosti, vidím, že se tu jedna jednička změnila na nulu.
A právě takové chování budeme chtít po programu. Konkrétnější budeme v kapitole 4.12.
Požadavek: Stejný graf může být najednou zobrazen různými způsoby
Uváděli jsme, že by bylo vhodné, aby bylo možné graf zobrazovat různými způsoby. Nic ale
nebylo vyřčeno o tom, co se skrývá pod různými způsoby. Nemuselo tak jasně vyplynout, že takto
zobrazovat by mělo být možné různé pohledy najednou. Tedy aby uživatel nemusel zavřít jednu
vizualizaci grafu, aby mohl vidět jinou vizualizaci grafu apod.. Hlavním důvodem je pochopitelně
výchovný efekt. Abychom mohli mít různé vizualizace zobrazené vedle sebe a porovnávat je.
Požadavek: Změna učiněná na jednom zobrazovači grafu se projeví na
ostatních zobrazovačích grafu.
A nyní k poslední důležité věci, která souvisí s vizualizací grafů a která by mohla zesilovat
výchovný efekt na uživatele programu. Jak plyne z názvu tohoto požadavku, grafy nechceme
vizualizovat jen různými způsoby a zobrazovat si tyto vizualizace najednou, ale budeme chtít, aby
se změny učiněné v jedné vizualizaci okamžitě projevovaly v ostatních vizualizacích stejného grafu.
Jak je vše řešeno konkrétně, bude diskutováno až v rámci řešení. Nyní už jen několik vět
o tom, na jaké změny by vizualizace grafů měly být schopné vzájemně reagovat.
Když jsem se nad vším zamýšlel, připadalo mi rozumné, nic konkrétního v rámci tohoto
požadavku nespecifikovat. Pohledy na grafy mohou být různé, může být časem přidán nějaký nový,
ten může být specifický něčím, co nebylo dosud uvažováno atd. Proto je potřeba s tímto počítat.
Konkrétnější požadavky ale činit nebudeme. Každá vizualizace grafu si jakoby rozhodne sama, na
co chce reagovat, a na co ne.
20
3.3
Požadavky na jazyk a nastavení
3.3.1 Pamatování si uživatelského nastavení
Další z požadavků, který se vyskytl a byl řešen již v rámci BP, je ukládání uživatelského nastavení.
Vždyť je to uživatelsky příjemné, když si program zapamatuje nějaké moje poslední funkční
nastavení a při dalším spuštění mi ho nabídne. Například proč pokaždé uživatele nutit, aby si okno
programu velikostně nastavil na přibližně polovinu velikosti monitoru, a místo toho mu pokaždé
nabízet okno přes celou obrazovku? Je relativně jednoduché zařídit, aby si program při ukončování
zapamatoval, že jeho hlavní okno mělo naposledy pozici [x ; y], šířku š a výšku v, a při dalším
spuštění si tyto údaje načíst a hlavní okno podle nich nastavit.
Takového ukládání lze docílit např. s využitím XML. Aplikační data se uloží do zvláštního
souboru, který má definovanou strukturu, a pak si je odsud zase načteme. Odlišit od sebe jednotlivá
data lze např. unikátními názvy jednotlivých elementů, které je obsahují. O Javě lze říci, že pro
práci s XML obsahuje poměrně dobrou podporu.
Program vzniklý v rámci BP ukládal nastavení rovněž do XML souboru, ale situace tam byla
taková, že tento XML soubor byl pro celý program pouze jeden a s přibývajícím počtem údajů,
u kterých vyvstávala potřeba po jejich ukládání, se stával čím dál tím více nepřehledný. Hlídání
unikátnosti názvů elementů tu tak bylo komplikovanější. Po startu programu docházelo hned
k načítání všech dat, i když některá z nich byla potřebná jen v hodně řídkých případech. Byla tedy
většinou načítaná úplně zbytečně.
Použít i nyní podobné chování u programu, kde se předpokládá jeho větší míra modifikovatelnosti a náhrady některých částí řešením jiným, mi připadalo problematické. Zvlášť, když
uvážíme, že vznikat mohou i přídavné funkcionality, se kterými se původně vůbec nepočítalo.
V takovém případě používat pro ukládání nastavení jen jeden soubor, není výhodné.
Místo toho, pokud má být řešení dostatečně obecné, mi připadá vhodné, aby si načítání
nastavení zajišťovala každá komponenta, která může být z programu nějak vyčleněna, sama. Když
se taková komponenta načítá, načte si svoje a jen svoje nastavení, nastaví se podle něj,
a v okamžiku, když se taková komponenta ukončuje / zavírá, si hned svoje nastavení uloží,
abychom si ho nemuseli pamatovat např. až do doby, kdy bude ukončován celý program. Vede to
sice k tomu, že se během běhu programu otevírá řada malých souborů a čte se / ukládá se jejich
obsah, ale výhodou je, že se každá komponenta stará jen o své vlastní nastavení a přidává-li uživatel
komponentu novou, nemusí víceméně řešit situaci ve zbytku programu.
21
3.3.2 Lokalizace programu
Pokud máme program, který si lze přeložit do jiného světového jazyka, než pro který byl napsán,
aniž bychom museli provádět změny ve zdrojovém kódu, je zajisté možné takové chování
okomentovat pozitivními výrazy. A takové chování chceme i od našeho programu.
Když se člověk nad vším zamyslí, tak situace je tu podobná jako u ukládání nastavení. Opět
se tu dostáváme k řešení, kdy máme texty ve zvláštních souborech, z kterých si je načítáme pomocí
unikátních klíčů.
Když bych zase odkázal na program vzniklý v rámci BP, situace tu byla taková, že tu
existoval jeden soubor pro všechny texty, které se v daném jazyce mohly v programu objevit
(i v některých méně běžných případech). Výjimkou byly jen texty pro jednotlivé simulační
algoritmy, které byly umístěny ve vlastních souborech. Vše tedy vedlo k tomu, že na začátku byly
do paměti načteny všechny texty, a ty se pak v případě potřeby vypisovaly. U menších programů lze
podobné řešení tolerovat. U větších programů a u programů, které jsou složeny z menších celků,
které se mohou různě zaměňovat, přidávat a odstraňovat, mi opět připadá vhodnější a obecnější,
když si každá komponenta bude zajišťovat lokalizaci svého textu sama. Situace je tu obdobná jako
u načítání nastavení, takže veškeré mínusy vyjmenované v kap. 3.3.1 zde znovu jmenovat nebudu.
Místo toho bych uvedl dvě věci, které tu jsou jinak:
1. Světové jazyky jsou různé. V případě ukládání nastavení bylo navrhováno, aby každá
samostatná komponenta měla vlastní XML soubor, do kterého by si své nastavení ukládala.
Zde každá komponenta má vlastní soubor s jazykovým nastavením, ale jazyků je ale mnoho.
Každá komponenta by při takovém řešení měla tolik jazykových souborů, kolik jazyků
podporuje. V programu by přitom nemělo být nijak definováno, které jazyky to mohou být,
protože v průběhu času může vzniknout požadavek po podpoře nového světového jazyku.
A měnit kvůli tomu zdrojové kódy by bylo opět nešťastné. Rozumnější je si upravovat
aktuální jazyk pomocí některého souboru s uloženým nastavením. Při startu programu by se
načetl údaj o tom, jaký jazyk se má používat pro výpis zpráv, a dle tohoto údaje by si pak
jednotlivé komponenty načítaly lokalizované výpisy.
2. I když by šlo pro tuto funkcionalitu opět využít XML, Java pro podobné potřeby poskytuje
mocnější mechanismus. Tím je Internacionalizace zvaná jako i18n. Jde o skupiny tříd,
pomocí kterých lze přizpůsobovat jazyk a chování aplikace zvyklostem, které jsou pro daný
jazyk a danou zem běžné. Například v různých státech se formát zapsaného časového údaje
může lišit. My až takové detaily v rámci práce na programu řešit nebudeme. To už je takové
22
chování, které lze zdokonalovat v budoucnu. Ale pro lokalizaci výpisů i18n využijeme. Bylo
tak činěno už v BP.
3.3.3 Výchozí nastavení
Obecně k výchozímu nastavení
Mluvili jsme o načítání uživatelského nastavení a o načítání lokalizovaných textů programu ze
zvláštních souborů. Jenomže nic nebylo řečeno o tom, jak by měl program fungovat v případech,
kdy by takové načítání selhalo. Zde jsem se nakonec rozhodl neměnit způsob chování oproti způsobu, kterým se choval program vytvořený v rámci BP. Ale konkrétně:
1. Před prvním spuštěním programu není žádné uživatelské nastavení dostupné. Nebylo by to
ani logické. Jednotlivé komponenty se pokusí načíst soubor se svým nastavením, ale nenajdou ho, a tak se spustí s nastavením výchozím. Výchozí nastavení je tedy dostupné vždy,
když není načten soubor s uživatelským nastavením.
2. Pokud je dostupný soubor s uživatelským nastavením, ale jsou v něm jen některé z údajů,
kterými lze ovlivnit vzhled a chování spouštěné komponenty, tak části, pro které je dostupné
uživatelské nastavení, se načtou s tímto nastavením, a části, pro které uživatelské nastavení
není dostupné, se načtou s nastavením výchozím.
3. Každá komponenta si zajišťuje načtení a uložení vlastního nastavení sama, takže oba body
1) a 2) uvedené výše nemusí pro některou z komponent platit.
Obrázek 3.5: Možný způsob načtení nastavení
23
4. Obdoba bodů 1 – 3 ) platí i pro jazykové nastavení. V programu je vhodné poskytovat
i základní text, kdyby náhodou někdo některý ze souborů s lokalizovanými výpisy odstranil,
nebo z něj odstranil jen některou z dvojic klíč – text.
Vše nastiňuje obrázek 3.5. Ten byl vytvořen v našem programu.
Shlukování výchozího nastavení
Byla řeč o výchozím nastavení. Jak se má komponenta chovat a vypadat, když není ještě uloženo
uživatelem modifikované nastavení? Uvádělo se, že jako odpověď na tyto problémy slouží
nastavení výchozí. To samozřejmě musíme nějakým způsobem v programu definovat. V rámci
tohoto požadavku, který chápu jako jeden z těch obecných, chci jen zmínit skutečnost, že je vhodné,
když se takové výchozí nastavení bude shlukovat na co nejméně míst. Při implementaci je potřeba
takto uvažovat.
Důvod? Usnadnění případných změn takového nastavení. Vzhledem k tomu, že se různé části
programu mohou měnit, je takové usnadnění na místě. Konkrétní hodnoty některých parametrů
v závislosti na aktuálním stavu může vracet třeba i funkce. Kromě nějakých proměnných
s výchozími hodnotami proto můžeme mít i funkce, jejíchž chování bychom mohli nazývat jako
výchozí chování apod.
3.3.4 Základní text
Cílem této kapitoly je opět zdůraznit, že něco podobného, co bylo zmiňováno u výchozího
nastavení, platí pro výchozí texty. Význam takového chování spočívá v tom, že kdyby se uživateli
nepodařilo načíst jeho lokalizované výpisy, tak stále uvidí aspoň „něco“. Když toto „něco“ bude
v Angličtině, je tu šance, že nejspíše středoškolsky či vysokoškolsky vzdělaný uživatel programu
bude stále nabízenému textu rozumět. Tedy v rámci toto odstavce zmiňme hlavně tu skutečnost, že
by výchozím jazykem měl být jazyk anglický. Jinak pro problematiku základního textu neplatí nic
zvláštního oproti problematice výchozího nastavení.
3.4
Požadavky na GUI
3.4.1 Základní funkcionality v nástrojové liště
Toto je jeden z požadavků, který by mohl zpříjemnit uživatelovu práci s programem. Už je to
zvyklostí, že programy, s kterými pracujeme, poskytují nejpodstatnější funkcionality v nástrojových
lištách, aby je uživatelé nemuseli hledat např. někde v menu, kde je dostupných funkcionalit
mnohem více. A to je i motivace k tomuto požadavku. V Javě se nástrojové lišty vytvářejí např.
24
pomocí třídy javax.swing.JToolBar, se kterou se pracuje podobně jako s jinými swingovskými
třídami.
Co se týče toho, jaké panely nástrojů mají být dostupné a co všechno by v nich mělo být, tak
to neupřesňujeme. Nutno ale počítat s tím, že počet takových panelů a jejich obsah by mohl být
časem měněn.
3.4.2 Různé způsoby zobrazování obsahu
V programu, který vznikl v rámci BP, se grafy zobrazovaly v záložkách, jejichž přidávání v Javě
umožňuje instance třídy JTabbedPane z balíku javax.swing. Šlo o řešení, kdy se všechny grafy
zobrazovaly na jednom místě. Při vhodném pojmenování v nich byl celkem přehled, i když jich
bylo vytvořeno např. deset najednou. Jen ale nebylo možné vidět dva různé grafy najednou. A to je
problematické, když si vzpomeneme na požadavky diskutované v kap. 3.2.7 – tedy že grafy lze
vizualizovat více způsoby najednou.
Proto se člověk dostal k úvahám, že grafy bude nutné zobrazovat spíše v oknech. Mají to být
ale okna hlavní, nebo budou stačit jen vnitřní? A nebude těžší si v zobrazeném obsahu udržovat
pořádek, když si uživatel bude muset okna různě posouvat, zvětšovat a zmenšovat, aby vidět to, co
zrovna chce?
Obrázek 3.6: Panel s grafem zobrazeným v záložce a okno pro výběr barvy zobrazené ve vnitřním
okně
25
A tak jsem se i díky těmto úvahám dostal k závěru, ke kterému je tu nyní psáno několik vět.
Ideálnější by totiž mohlo být, kdyby si uživatel v programu vybral, jak se mu vizualizovaný obsah
bude zobrazovat. Jestli to bude v záložce, nebo jestli to bude v okně. A jaké případně má toto okno
být?
Aby bylo něco takového možné, tak se s tím musí samozřejmě dopředu počítat a musí k tomu
být postaveny vhodné programové základy, protože takové chování není jen problematikou přidání
do menu jednoho tlačítka a napsání akce, co se v případě kliknutí na toto tlačítko má provádět.
Blíže v kapitole 4.11, která pojednává o konkrétní realizaci.
3.4.3 Stavový řádek
Stavový řádek je jakási obdoba logu (kap. 3.5.5), která se vyznačuje tím, že se do ní nevypisují
zprávy chybové, ale zprávy informační. Ty by převážně novému uživateli mohly pomoci
v počátečním zorientování s prací v programu. V nástrojích Microsoft Office se v podobné úloze
vyskytuje např. ona známá mluvící sponka.
Na podobnou funkcionalitu došlo už v rámci BP. Uživateli se v malé lištičce ve spodní části
programu zobrazovaly informace, že je právě možné přidávat uzly, že je nyní možné přidávat hrany
apod. Program implementovaný jako součást DP, by se měl chovat stejně. Rozdíl bude v tom,
že v DP by mělo býti možné si jednoduše zaměnit objekty, pomocí kterých se výpisy zpráv
realizují. Při vhodném strukturování kódu to není nic těžkého a z programu se díky tomu částečně
stává skládačka. Toto chování bude ještě diskutováno v textu dál u jiných funkcionalit.
3.4.4 Startovní splash screen
Splash screeny, tedy okna s indikací, že dochází k načítání programu, poskytuje nejeden z dnešních
programů, s kterými v rámci práce s počítačem přijde běžný uživatel do styku. Příkladem může být
spouštění samotného OS, kde informace o tom, že dochází ke spouštění programu, bývají běžně
přítomné. Někteří uživatelé si již na podobné chován zvykli.
Kdyby se program dlouho načítal, na obrazovce se nic opticky neměnilo a nebyla by
přítomná informace o tom, že dochází k načítání, mohlo by dojít k závěru, že program nefunguje.
To samozřejmě nechceme, a proto přítomnost splash screenu bude příjemným uživatelským
doplňkem s čistě informativními účely. V programu bude, ten ale bude běhu schopný i bez něco.
V Javě je pro tyto účely v základním API dostupná třída JProgressBar z balíku javax.swing.
26
3.5
Další požadavky
3.5.1 Podpora modifikovatelnosti sebe sama
O rozšiřitelnosti zde již bylo napsáno nemálo vět. Připomeňme, že se např. předpokládají situace
jako u ukládání grafů, kdy si programátor připíše vlastní „ukládač“ a ten bude dostupný, aniž by
bylo nutné na některých místech zdrojového kódu programu něco měnit.
Vyvstat samozřejmě ale může i situace, kdy si uživatel poví, že se mu např. současné řešení
stavového řádku nelíbí a že si napíše třídu, s jejíž pomocí si vytvoří stavový řádek vlastní
(kap. 3.4.4). V takovém případě již bude jistý zásah do zdrojového kódu, nebo například jen do
souborů s uživatelským nastavením, nutný.
Implementačními snahami by mělo být vytvářet program takovým způsobem, aby se kvůli
případným změnám muselo modifikovat co nejméně míst v programu. Ideálně by šlo o záměnu
instance jedné třídy instancí třídy jiné – a to na co nejméně místech v programu. Když se např. od
zmiňovaného stavového řádku bude vyžadovat, aby implementoval dané rozhraní, a v jiných
místech kódu se s ním pracovalo právě přes toto rozhraní, je to dobrý základ k řešení takového
problému.
Další věcí, co by mohl program poskytovat, by mohla být komponenta, pomocí které by si
podobné náhrady těch nejzákladnějších komponent / objektů mohl uživatel jen naklikat, díky čemuž
by byl odstíněn od nudnosti provádět v kódu programu změny pomocí přepisu několika málo míst
v kódu. V kapitole 4.13 je nazvána jako Manage okno. Při ručních přepisech totiž může snadno dojít k nějakému opomenutí, a vytvoření chyb / nekonzistentností. Díky přítomnosti nastavovacího
okénka by se uživatel případných změn nemusel tolik bát.
Obrázek 3.7: Obecné schéma, jak při skládání řešit podkomponenty
3.5.2 Podpora rozšířitelnosti sebe sama
Mohlo by se zdát, že tento požadavek byl již diskutován v kap. 3.5.1, ale není tomu úplně tak. Jde
tu totiž o to, aby bylo umožněno přidávat a v programu nějak jednoduše aktivovat / spouštět
27
funkcionality, se kterými původně nebylo vůbec počítáno. Jednou z možností samozřejmě je, že si
uživatel otevře zdrojový kód programu, na pár míst dopíše kód vlastní, a nová funkcionalita je na
světě.
Takový postup je ale v podstatě možný u každého programu, ke kterému je dostupný
zdrojový kód, takže asi nepřekvapí konstatování, že zrovna toto pod tímto požadavkem skryto není.
Tam se skrývá chování, díky kterému do programu novou funkcionalitu (de facto třídu) přidáme či
zaregistrujeme, aniž by bylo nutné ve zdrojovém kódu něco měnit.
Potom, co tu už několikrát bylo zmíněno Java Reflection API, asi již nepřekvapí, že
i takového chování je možné s jeho pomocí dosáhnout. V programu musí být komponenta (okno
s nabídkou možností), které umí vyhledat a nabídnout třídy, které lze využít. U simulace algoritmů
takto byly nabízeny k simulaci algoritmy, které byly napsány v třídách, jež rozšiřovaly dané
rozhraní. U ukládání grafů byly dle stejného principu vyhledány a nabídnuty třídy, které umožňují
uložení grafu (rozšiřují jiné rozhraní). A tady v rámci diskutovaného požadavku 28 mluvíme
o funkcionalitě, která by de facto uměla to samé, jen by zahrnovala všechno ostatní, co se nedalo
vyčlenit do nějakých speciálních funkčních kategorií. Vzniklé třídy, které se v ní budou využívat,
tak nebudeme nazývat jako simulační algoritmy či jako „ukládače grafů“, ale například jako
rozšíření / plug-iny. A to je pojem, se kterým se setkáme v nejednom programu, a jestli někdo
z dosavadního povídání nepochopil, o čem je řeč, tak nyní snad již chápe.
Dále, co se ještě plug-inů týče, pokládám za vhodné, aby u nich bylo možné definovat, jestli
jsou zrovna aktivní nebo ne. Aby nemuselo docházet např. k odstranění / modifikace třídy, když
bychom plug-in, který se s její pomocí realizuje, nechtěli mít po startu programu aktivní.
Aktivní plug-iny by se přitom po spuštění programu automaticky spustily. V oknu, které by
nabízelo všechny dostupné plug-iny, by měla být možnost nastavit, který z plug-inů chceme mít
aktivovaný a který ne. Něco podobného je např. v NetBeans IDE.
Obrázek 3.8: Pohled na nastavení plug-inů v NetBeans IDE 6.7.1
28
3.5.3 Podpora přidání nové funkcionality do menu
Mluvili jsme o modifikovatelnosti a rozšířitelnosti. Psalo se tu o tom, že se počítá s tím, že si
uživatel bude moci dopisovat vlastní třídy a zakomponovat je do programu. Díky tomu lze na celý
tento SW nahlížet jako na spustitelnou knihovnu tříd. A v takové je možné hned několik možností,
jak přidávat nové funkcionality do menu.
Za prvé nás asi napadne možnost, že např. třída, pomocí které by bylo realizované hlavní
okno programu, by uměla vrátit instanci třídy javax.swing.JMenu. Tou se v javovských programech
menu běžně realizuje. Když by se uživatel k takovému objektu dostal, využíval by stejné metody,
pomocí kterých by jinde v kódu docházelo k přidání defaultních položek do menu.
Takové chování lze nazvat jako základní, ale asi bych u něj místo slovíčka usnadňuje použil
jen slovo umožňuje. Pod slovem usnadňuje si představím jakoukoli možnost, díky které bych jako
programátor musel napsat méně kódu, než by bylo nutné, kdybychom postupovali jako v případě
popsaném u základní možnosti.
Když jsem si toho uvědomil, začal jsem o takových funkcionalitách uvažovat jako
o speciálních případech plug-inů, o kterých pojednala kapitola 3.5.2. Možné jsou pochopitelně i jiné
možnosti. Přídavné položky do menu by tedy byly taky takové plug-iny. Plug-iny, které navíc do
menu přidávají tlačítko, pomocí kterého se vyvolává zadaná akce.
V souvislosti s plug-iny připomínám, že byla řeč i o komponentě, kde by si uživatel naklikal,
jaké plug-iny chce mít v programu aktivní. Pro přídavné položky do menu by tedy platilo obdobné.
3.5.4 Přidání nápovědy od uživatele
Nápověda je funkcionalita, u které není zásadní, aby se v programech vyskytovala, ale bylo by
zdvořilé, kdyby pro ní program poskytoval podporu. Důvod je ten, že i kdyby se program snažil
o intuitivním ovládání, které by spočívalo v napodobování chování a vzhledu od podobných
funkcionalit v některých známých programech, tak i přesto uživatelé nemusí tušit, jak program
ovládat. Každý uživatel se totiž během své práce s počítači setkal s různými programy a jako
intuitivní by označil jiné chování. Lidské vnímání je subjektivní, a právě proto poskytování aspoň
menší nápovědy není od věci.
V rámci vývoje programu by proto mělo být snahou jakousi podporu pro nápovědu vytvořit.
Jako primární přitom nepokládám skutečnost, aby každá funkcionalita měla k dispozici přehlednou
nápovědu, ale možnost takovou nápovědu k programu přidávat a různě si jí modifikovat. Důvod?
I pod pojmem přehledná nápověda si každý představí něco jiného.
29
Kdybych byl konkrétnější, tak jde hlavně o to, aby se např. při stisknutí klávesy F1 otevřelo
okno s dostupnými tématy k nápovědě. Tato témata a jejich obsah mohou být různě přidávány,
uzpůsobovány a odstraňovány. Opět je vlastně ve všem využito Java Reflection API, o které již byla
řeč například u požadavku na ukládání grafů.
V případě nápovědy zde přibývá skutečnost, že i texty s nápovědou by mělo býti možné nějakým
způsobem lokalizovat. Přitom nelze spoléhat jen na soubory properties využívané v rámci i18n, protože realizovat nějaký pokročilejší text s nápovědou je spíše vhodné pomocí HTML souboru.
V HTML souborech se mohou vyskytovat různé ilustrační obrázky apod. Konkrétněji v kapitole
4.14, která popisuje výsledné řešení.
3.5.5 Výpis zpráv o chybách / výjimkách
Pod požadavkem na výpis zpráv o chybách a výjimkách si můžeme představit funkcionalitu, která
je v některých programech nazývaná jako log nebo logovací soubor. V našem případě se budeme
držet jen pojmu log, protože ukládání obsahu nemusí být nutností. Motivací k logu je skutečnost, že
může dojít k nějakému nekorektnímu chování, aniž by bylo patrné, z jakého důvodu k němu došlo.
Pakliže by člověk v kódu jen ošetřoval možné výjimky a neposkytoval informace o tom, že
k vyvolání nějaké výjimky vůbec došlo, může být enormně obtížné identifikovat zdroj problémů.
Na druhou stranu zase není možné někde hodně viditelně vypisovat informace o tom, že došlo
k nestandardní situaci / nekorektnímu chování / chybě. Uživatel by v takovém případě mohl nabít
dojmu, že je program nefunkční, i když by došlo např. k nějaké drobnosti. To, že někde chybí
přídavný soubor není ani chyba samotného programu.
Když se podíváme do jiných programů, kde je log dostupný, tak tam je většinou
implementovaný takovým způsobem, že k logování dochází na pozadí a uživatel se s výpisem
obsahu logu, takové pomyslné černé skříňky programu, seznámí až po jeho vyžádání. A to např.
kliknutím na danou možnost v menu nebo otevřením konkrétního souboru na disku. Takové chování
pokládám osobně za rozumné.
Na druhou stranu uváděli jsme zde snahy, ve kterých se snažíme býti obecní. Že by uživatel
měl mít možnost ovlivňovat, jak se bude výsledný program chovat. A log je příkladem
funkcionality, která stojí poměrně stranou od zbytku programu, takže by tam možnost
zaměnitelnosti / změny chování měla být dostupná tím spíše.
Po těchto úvahách mi přišlo vhodné na log nahlížet jen jako na objekt, který budou mít
ostatní objekty realizující jednotlivé komponenty programu k dispozici, a kterému budou moci
30
posílat text k zaznamenání do logu. Text by to teoreticky mohl být jakýkoli. Ideálně ale upozorňující na výjimky. Pro informativní účely slouží stavový řádek diskutovaný v kap. 3.4.3.
Jak bude log vypadat, kam si bude zaslané zprávy ukládat a jak je bude zobrazovat, by mělo
býti až na konkrétní realizaci. Krásně díky takové realizaci pak můžeme řešit situaci, kdy chceme
mít logování vypnuté. Místo toho, abychom testovali, jestli je logování zapnuté, může funkci logu
plnit objekt, který de facto nic nedělá (přijme text a ignoruje ho). Obdobného je možno dosáhnout
i u stavového řádku.
Obrázek 3.9:Deaktivování stavového řádku může řešit jen záměna objektů
3.6
Java a reflexe
Součástí Javy je Java Reflection API [15]. Jde o třídy a metody, díky kterým můžeme za běhu
získávat informace o třídách a jejich proměnných a metodách, aniž by bylo nutné znát tyto
informace v době kompilace. Stejně tak je možné vytvářet za běhu instance od zadaných tříd.
Reflexe je poměrně pokročilý rys Javy.
Několik faktů k tomuto tématu, které lze v rámci implementace využít a kterých skutečně
využito bylo, v bodovité formě následuje (plus související):
•
Všechny referenční typy dědí v Javě od třídy java.lang.Object.
•
Pro každý objekt JVM vytváří instanci třídy java.lang.Class, prostřednictvím které lze za
běhu zjišťovat informace o daném objektu.
•
Prostřednictvím Class objektu lze vytvořit novou instanci třídy, kterou Class objekt
reprezentuje – stačí zavolat jeho metodu newInstance().
•
U každého objektu lze volat metodu getClass(), díky čemuž jsme schopni zjistit, od jaké
třídy je objekt instancí.
•
Pokud máme k dispozici místo instance typ, lze Class objekt získat pomocí volání .class.
Např. pro třídu MainFrame získáme Class objekt takto: Class c = MainFrame.class;
31
•
Známe-li celý název třídy, lze pro ní získat Class objekt pomocí statické metody
forName(String název) třídy Class. Příklad pro třídu MainFrame:
Class c = Class.forName(“windows.MainFrame”);
•
Instance třídy Class nereprezentuje pouze třídu v běžícím programu. Reprezentovat může
i rozhraní.
•
Třídy do JVM načítá classloader [16], přičemž standardní classloader hledá třídy
v CLASSPATH. To je argument nastavitelný přes příkazový řádek či proměnné prostředí.
Tento argument JVM informuje o tom, kde má hledat uživatelem definované třídy.
•
Class objekt mimo jiné poskytuje metodu boolean isAssignableFrom(Class C), která
odpovídá na to, jestli daná třída je stejná jako třída C. Případně, jestli není jeho nadtřídou.
Pojem třída v této větě zahrnoval i rozhraní.
•
Další metodou, kterou Class objekt nabízí, je metoda String getName(). Tato metoda vrací
celé jméno třídy – tedy i včetně názvů balíčků.
32
4
Popis implementace
Účel této kapitoly je snad zřejmý z jejího nadpisu. Budeme se bavit o tom, jak je program
implementován. Kódu v rámci implementace bylo napsáno hodně, tak tu samozřejmě nebudeme
rozebírat úplně vše. Zaměříme se hlavně na to nejdůležitější, přičemž se dotkneme požadavků, které
byly diskutovány v rámci kapitoly 3.
V třetí kapitole jsem udělali obecný úvod do mnoha souvisejících problémů. To, že tam
o mnoha věcech bylo nastíněno, jak by mohly býti řešeny, znamená, že tak řešeny skutečně byly.
Proto si diskusi k některým menším problémům už odpustíme. O této a o předešlé kapitole by mělo
platit, že to jsou nejdůležitější kapitoly v rámci této práce. Tyto kapitoly by případnému
rozšiřovaleli funkčností implementovaného programu měly pomoci nejvíce.
4.1
Třídy, soubory
V kapitole nazvané Rozhraní a třídy, kterou jsem v rámci BP [2] psal, jsem použil větu: „Program
se skládá z 20 veřejných tříd a 3. rozhraní“. Tato věta už může znít z dnešního pohledu komicky.
Nový program totiž naproti tomu obsahuje 27 neprázdných balíčků (packages). Balíčky
v Javě jsou jakési složky pro shluknutí tříd a jiných souborů určených pro podobné účely.
Tříd a rozhraní je jinými slovy v novém programu mnohem víc. Popisovat účel jednotlivých,
stejně tak jako dalších souborů, proto nebudeme. Bylo by to na dlouho a jako čtení by to nemuselo
býti vůbec záživné. V rámci této kapitoly proto bude učiněn komentář jen k nejdůležitějším třídám
a balíčkům.
Soubory programu jsou obsaženy např. v následujících balících:
•
algorithm – Balík určený pro třídy, které souvisí se simulací grafových algoritmů. Nachází
se tu mimo jiné třídy:
◦ AlgorithmPanel – panel, který nabízí roletku s nabídkou algoritmů, které jsou pro
simulování dostupné. Kromě této roletky tu dále jsou tlačítka, která je nutno při simulaci
využívat, a „posouvátko“ pro nastavování délky časového kroku.
◦ DFS – Třída, pomocí které se zajišťuje simulování procházení grafu do hloubky. Každý
algoritmus má třídu vlastní, toto je jen typický zástupce. V programu mohou existovat
i jiné třídy, pomocí kterých se simuluje stejný problém. To kdybychom chtěli stejnou věc
simulovat / vysvětlovat jinak.
33
◦ Algorithm – Abstraktní třída, od které je vhodné dědit, pakliže si píšeme algoritmus
vlastní. Při použití této třídy je programátor odstíněn od spousty potřebných věcí, které
už v rámci této třídy zařízené jsou.
•
api – Balíček pro rozhraní, která se v programu vyskytují. Umístění některých by bylo
logické i v jiných balíčcích. Např. v tomto balíčku je rozhraní Algorithm určené pro simulační algoritmy,místo toho aby bylo v balíčku algorithm.. Ke konečnému umístění vedlo
rozhodnutí, že by všechna rozhraní měla býti shluknuta do jednoho balíčku - právě tohoto.
Cíl: Usnadnit přemýšlení, kde se které rozhraní nachází. Jejich účel by přitom měl být
patrný z názvů, o což jsem se obecně pokoušel v celém programu.
•
basic – Package, ve kterém jsou nejzákladnější třídy programu, které se nehodilo umístit
jinam. Jmenovat lze hlavně následující třídy:
◦ FactoryDefault – V této třídě je shluknuta velká část výchozího nastavení. Pod tím si
můžeme představit takové věci jako výchozí locale, výchozí třídu, která se používá pro
vytvoření stavového řádku, nebo například i takový údaj, jakým je úhel pro šipku použitou u vykreslené orientované hrany.
◦ GlobalData – Třída s funkčností úložiště základních dat. Když při startu dojde k vytvoření hlavního okna programu, tak reference na objekt, který ho realizuje, je uložena
právě zde. Přes tuto třídu se k hlavnímu oknu i dostáváme, pakliže k němu potřebujeme
přistoupit z jiných částí programu. Dále jsou tu umístěny např. log a locale objekt. Locale objekt je objekt, který nás informuje, jaký světový jazyk v programu právě používáme.
◦ Menu – Jak název této třídy napovídá, jde o třídu, jejíž instance pak v hlavním okně
programu představuje menu. Dochází tu mimo jiné k seskládání menu z jednotlivých
položek, které jsou pak uživateli nabízeny.
◦ Start – Třída s metodou main, u které se předpokládá, že přes ní program bude spouštěn.
Main metodu má v programu hned několik tříd.
•
components.statusPanel – V této package jsou umístěny třídy, pomocí kterých lze
v programu realizovat stavový řádek. V tuto chvíli jsou takové třídy dostupné tři
(NoStatusPanel, StatusPanelSimple, StatusPanelWithHistory). Kromě těchto tříd je tu
umístěna ještě abstraktní třída StatusPanel, která zařizuje základní věci a od které ostatní
třídy určené pro stavovou řádku mohou dědit. Podobný postup, kdy je vytvořena abstraktní
34
třída pojímající základní vlastnosti a chování, je ve zdrojových kódech využit hned
několikrát.
•
components.splitpanel – Obdoba balíku components.statusPanel. V tomto balíku jsou
umístěny třídy, které lze používat pro splitpanel. To je panel, pomocí kterého lze okno
programu rozdělit až na dvě části. Možné je mít i jen jednu část, jak použité slovíčko až napovídá.
•
components.toolbars – Další obdoba balíku components.statusPanel. V tomto balíku jsou
umístěny třídy, pomocí kterých se realizují lišty v nástrojové liště, a další související třídy.
Dále tu jsou umístěny obrázky, které se používají pro ikony tlačítek, která se do nástrojové
lišty vkládají.
•
functions – Balík s třídami, které poskytují statické metody zajišťující různé funkce, které
lze využít v různých částech programu. Příkladem je např. třída ColorFunctions, jejímž
účelem je pomáhat při práci s barvami. Tato třída obsahuje např. metodu getRandomColor()
která umí vrátit náhodnou barvu.
•
generate – Balík sdružující třídy pro realizaci generátoru grafů. Obsaženy tu jsou mimo jiné
třídy následující:
•
GeneratorWindow – Okno, které nabízí nabídku možných generátorů a z kterého se
vygenerování příslušného grafu vyvolává.
•
ShapeGenerator -Příklad generátoru. Tento obsahuje detailní obrázky grafů, které
může případně vygenerovat.
•
graph – V této package jsou třídy, které souvisí již se samotnými grafy. Typickými zástupci
jsou např. tyto třídy:
◦ NodeBasic – představuje datovou reprezentaci uzlu. Obdobou je třída EdgeBasic, jejíž
instance mají představovat datovou reprezentaci hrany grafu. Bude ještě konkretizováno
v kapitole 4.12.
◦ EdgeQuad – Nadstavba nad datovou reprezentaci hrany, která je již spojená s vizualizací. Pomocí EdgeQuad lze vykreslovat hrany, které mohou být zaoblené. Jde tu o třídu,
která dědí od java.awt.geom.QuadCurve2D. Díky tomu jsou k dispozici metody, díky
kterým lze zjistit, jestli zadaný bod z prostoru leží pod vykreslenou hranou. EdgeQuad
jako grafická reprezentace hrany musí implementovat rozhraní EdgeGraphical. Pro
grafické reprezentace uzlů existuje obdoba v podobě rozhraní NodeGraphical.
35
◦ GraphBasic – Třída určená pro realizaci modelu grafu. Obsahuje seznamy objektů
implementujících rozhraní Edge a Node. Takovými objekty jsou objekty NodeBasic
a EdgeBasic. Jde tedy o datovou reprezentaci grafu, ve které se neříká nic o tom, jak graf
vykreslovat.
◦ GraphPresenterBasic – Třída pro realizaci presenteru. To je objekt, který řídí správné
vykreslování
stejného
grafu
v
různých
pohledech.
Podrobněji
bude
diskutováno v kap. 4.12.
◦ GraphViewCircles – Objekt, který umí graf vykreslovat jako kolečka propojená
čárkami. Kolečka jsou uzly, čárky hrany. Konkrétně k tomuto účelu využívá instance tříd
EdgeQuad a NodeCircle.
◦ GraphViewRectangles – Obdoba třídy GraphViewCircles. Rozdíl mezi těmito třídami
je v tom, že zde jsou uzly vykreslovány jako čtverečky. Je to díky tomu, že místo
instancí třídy NodeCircle tu jsou využívány instance třídy NodeRectangle. Obě tyto třídy
implementují zmiňované rozhraní NodeGraphical.
◦ GraphViewAdjacencyMatrix – Zobrazovač grafu, který graf zobrazuje jako matici
sousednosti. Jde o tzv. negrafický pohled na graf, ve kterém se graf nevykresluje
obrázkem, ale kde se vypisuje jako vhodně formátovaný text. Možné je pro tyto účely
využít i tabulky.
◦ GraphicalGraphView – Abstraktní třída, od které dědí třídy GraphViewCircles
a GraphViewRectangles. Sdružuje operace, které jsou společné pro grafické
vykreslovače grafů. Seznamy pro grafické reprezentanty hran a uzlů jsou právě zde.
Můžeme si to dovolit díky tomu, že se v těchto seznamech očekává cokoli, co implementuje rozhraní EdgeGraphical (v případě hran) a NodeGraphical (v případě uzlů).
Obrázek 4.1: Ilustrace k popisu třídy GraphicalGraphView
36
◦ AbstractGraphView – Abstraktní třída, od které dědí třídy GraphicalGraphView
a GraphViewAdjacencyMatrix. Sdružuje nejzákladnější operace, které jsou společné pro
zobrazovače grafů. Grafické zobrazovače lze proto chápat jako nadstavbu k negrafickým
(textovým) zobrazovačům.
◦ NodeCircle – Obdoba EdgeQuad pro uzly. Dědí se od java.awt.geom.Ellipse2D, díky
čemuž by mohlo býti možné uzly zobrazovat jako elipsy. V tuto chvíli je využíváno pro
vykreslování uzlů s kruhovým obvodem.
◦ Repainter – Třída, pomocí které si zobrazovače zajišťují vyvolání vykreslení jejich
obsahu.
•
help - Balík pro umístění tříd, které slouží k realizaci okna s nápovědou i pro realizaci
konkrétních nápověd.
•
help.web – Do tohoto balíku se umísťují HTML soubory, které obsahují text s konkrétní
nápovědou
a
které
se
uživateli
na
vyžádání
zobrazují
v
instanci
třídy
javax.swing.JEditorPane, kterou obsahuje okno pro výpis nápověd.
•
io.graph.formats – Třídy určené pro ukládání grafů a pro jejich načítání, jsou ukládány
právě sem. Pro příklad lze jmenovat třídu AdjacencySaver, která umí uložit vybraný graf
do souboru s příponou .adjacency. Obsahem tohoto souboru je text vhodně proložený mezerami – matice sousednosti.
•
languages – Package, kam byly umístěny properties soubory s lokalizovanými texty, které
se v programu používají.
•
log – Package pro třídy, které souvisí s realizací logu. Diskutováno je v kapitole 4.8.
•
manage – V tomto balíku jsou třídy, které souvisí s realizací manageru tříd. O tom
pojednává kapitola 4.13. Zmínit lze např. třídu ManageClassesWindow, pomocí které se
realizuje okno obsahující možnosti pro nastavování.
•
states – Program se během svého běhu může nacházet v různých stavech. Třídy související
s jejich realizací jsou ukládány právě do tohoto balíčku. Ze zástupců jmenujme např. třídy
AddNodeState a AddNodeNoState.
•
tests – V době vzniku programu byly vytvářeny různé standardní třídy, pomocí kterých byly
testovány potřebné skutečnosti. Tyto třídy byly ve zdrojových kódech programu ponechány,
i když v rámci jeho běhu nebudou vůbec využity. Umístěny jsou právě zde.
37
•
visualise – Balíček určený pro třídy, které souvisí s požadavkem 24. Obsah lze díky nim
zobrazovat různými způsoby – v záložkách, ve vnitřním okně či v hlavním okně.
•
windows – Package vytvořené pro různá okna, která se nehodilo umístit jinam. Je zde např.
třída MainFrame, pomocí které se realizuje hlavní okno programu.
•
windows.components -V tomto balíku jsou umístěny třídy určené pro různé menší
komponenty okna – např. pro ikonu pro přidání uzlu.
4.2
Spuštění Programu
Program se spouští prostřednictvím třídy Start, kde je statická metoda
main. Hned na začátku dochází k zobrazení splash screenu, který
informuje o tom, že probíhá načítání. Následuje pokus o načtení
základního uživatelského nastavení, které je využito při vytvoření
hlavního okna a jeho součástí. Po vytvoření hlavního okna se ještě
načtou a spustí případné doplňky. O těch více v
kap. 4.15. Po
spuštění doplňků se splash screen zavírá a uživateli je zobrazeno
hlavní okno.
Pokud bychom se zaměřili konkrétně na první spuštění programu, tak při něm dojde k výpisu řady textů do logu. Důvod? V tuto
chvíli neexistující soubory s uživatelským nastavením. To se
vytvoří až po prvním spuštění programu.
4.3
Hlavní okno
Hlavní okno programu je tvořeno instancí třídy MainFrame a je
složeno z několika komponent. Ty mohou ve výsledku být
realizovány instancemi různých tříd. Obrázek 4.3 vše znázorňuje
Obrázek 4.2: Načtení
programu
z důvodů, aby byla ulehčena případná orientace v kódu.
Když bychom se podívali na jednotlivé součásti okna, tak k těm lze uvést následující:
•
ToolBarsPanel je panel, do kterého jsou umisťovány nástrojové lišty. Jejich pořadí je odvislé
od toho, v jakém pořadí byly přidány.
•
SplitPanel je panel, který může být rozpůlen. Vkládány do něj jsou mohou být i vnitřní
okna. Např. okno pro výběr barvy, okno s nabídkou algoritmů pro simulaci, či samotné okno
s kreslenými grafy. U těch už oproti předešlým možnostem záleží, jak si přejeme zobrazovat
obsah – jestli jako jako panel v panelu záložek, nebo jako vnitřní / hlavní okno.
38
•
StatusPanel je panel s funkcí stavového řádku.
•
Desktop je panel pro umísťování hlavního obsahu. Vložen je do něj SplitPanel.
Obrázek 4.3: Struktura hlavního okna
Poslední informace, která tu v souvislosti s hlavním oknem bude uvedena, se zabývá tím, díky
jaké třídě z Java Core API je okno vůbec realizováno. Tou třídou je třída javax.swing.JFrame. Od té
nedědí přímo třída MainFrame, ale její vnitřní třída InnerMainWindow. Instance této vnitřní třídy je
pak využíváno.
Takového strukturování kódu je využito i na jiných místech v programu. Důvodem je navenek
skrytí metod, které jsou pro objekty JFrame dostupné, a zprostředkované zpřístupnění metod jen
některých. U tříd, které implementují zadané rozhraní, si tak můžeme ohlídat, aby zvenčí byly
volány jen ty metody, které vnucuje rozhraní. Takové hlídání se hodí, pokud využití jedné třídy
může být nahrazeno využitím jiné třídy. Množství možných problémů klesá.
Obrázek 4.4: Jak hlavní okno dědí od javax.swing.JFrame
39
4.4
Ukládání uživatelského nastavení
Jak bylo rozváděno v rámci kapitoly 3.3, v programu je možné
ukládat uživatelské nastavení. Děje se tak do souboru, ale ne
jednoho. Nastavení se ukládá do více souborů, jelikož si každá
komponenta zajišťuje správu svého nastavení sama. Sama si při
svém vytváření nastavení načte, a sama si ho při konci své
činnosti uloží. Třídy, jejichž instance se takto chovají,
povětšinou implementují rozhraní Setable, které třídám vnucuje
metody loadSettings() a saveSettings().
Co se týče ukládání, tak to probíhá do XML souborů.
Využívány jsou k tomu (prostřednictvím třídy functions.XML)
třídy z package org.w3c.dom. Při ukládání si komponenta, která
se chystá některá svá data uložit, z těchto dat sestaví DOM
(Document Object Model) [17]. Ten je následně uložen. DOM je
objektově orientovaná reprezentace XML. Jde o API, které
Obrázek 4.5: Interface Settable
umožňuje přístup k obsahu XML dokumentu a jeho modifikaci.
Příklad typického použití následuje:
1. Je zavolána metoda saveSettings() u objektu, jehož nastavení chceme uložit (např.
MainFrame).
2. V metodě je vytvořena instance třídy Document, ve které dojde k sestavení stromu údajů,
které pak budou namapovány do XML souboru. Ten má rovněž stromovou strukturu.
Typicky se ve zdrojových řádcích setkáme s přiřazením:
org.w3c.dom.Document doc=XML.newDomXmlDocument();
3. Po vytvoření objektu Document dochází k vytvoření elementů a k nastavování jejich hodnot.
Tyto elementy jsou přidány do stromu.
4. Volá se: XML.saveDOMIntoXMLFile(„název souboru“,doc);
Co se ukládání nastavení dále týče, tak zde může dojít k situaci, kdy je program uložen na
médiu, kam není povolen zápis. Např. na CD. V takovém případě ukládání jeho nastavení selže.
Výsledkem pak jsou upozorňující informace v logu, které zůstanou uživateli utajeny, pokud si
obsah logu nenechá zobrazit. Program ale běží úspěšně dál, jako kdyby k žádným výjimkám
nedošlo.
40
4.5
Načítání uživatelského nastavení
K načítání nastavení by šlo využít opět API pro práci
s DOM. Při jeho použití by dle načtených dat byla vytvořena stromová struktura objektů, které by reprezentovaly
původní XML dokument. K jednotlivým uzlům této
struktury by pak šlo přistupovat a získávat či modifikovat jejich hodnoty.
Použití takového řešení by bylo sice programátorsky
jednoduché, ale existuje zde nevýhoda, spočívající ve
vysokých požadavcích na systémové zdroje – a to hlavně
v případě rozsáhlých dokumentů. Navíc my potřebujeme
uživatelská data pouze načíst, abychom si je následně
uložili jinam, takže vytváření nějaké stromové struktury
objektů je pro nás zbytečné.
A tak je pro ukládání využíván SAX (Simple API for
XML) [18]. To je API, které umožňuje sériový přístup
k XML. Dokument je při použití tohoto API zpracováván
jako
proud
dat,
během
jehož
načítání
dochází
k vyvolávání událostí. Například když se narazí na
novou počáteční značku elementu, tak je vyvolána
Obrázek 4.6: Načítání nastavení
obecně
událost, že byla načtena nová počáteční značku elementu. Obdobné pro koncovou značku elementu,
obsah elementu, komentář atd. Programátorovi stačí potřebné z těchto událostí ošetřit.
A právě takto to při načítání v programu funguje. Konkrétně si stačí hlídat jen koncovou značku
elementu. Tedy pokud XML dokument obsahuje hodnoty pro nastavení v tělu elementů a ne např.
v hodnotách případných atributů. V takovém případě lze pro načítání s výhodou využít napsané
třídy basic.LoaderOfSettings, která pokrývá společné operace a od které stačí dědit.
Jak probíhá načítání nastavení konkrétně, se snaží zastihnout obrázky 4.6 a 4.7. Máme tu jako
příklad načítání nastavení zobrazovač GraphViewCircles. Algoritmus pro načítání lze slovně popsat
takto (místo konkrétních názvů tříd se uvádí jména zkrácená):
1. Při vytváření objektu G dochází k zavolání jeho metody loadSettings()
2. V těle metody loadSettings() je vytvořena instance třídy S. Tato instance objektu S poslouží
k tomu, že objektu G vrátí hodnoty s nastavením. Tyto hodnoty jsou nyní nastaveny na
výchozí hodnoty.
41
3. V konstruktoru objektu S je vytvořena instance od vnitřní třídy M. Třída M dědí od třídy
LoaderOfSettings, což je parser XML dokumentu, ve kterém je obsah elementů postupně
ukládán pro proměnné s názvem text.
4. V překryté metodě endElement(...), kterou objekt M vlastní, jsou dle názvů značek
koncových elementů přepisovány konkrétní hodnoty z nastavení, přičemž se počítá
s možností vyskytnutí výjimek.
5. Poté, co je načítání dokončeno, dochází v metodě loadSettings() objektu G k využívání
načtených (a přes metody objektu S vrácených hodnot).
6. Načtení dokončeno. Pakliže došlo k vyskytnutí nějaké nestandardní situace, projeví se to jen
na obsahu logu.
Obrázek 4.7: Příklad tříd, pomocí kterých se načte nastavení pro GraphViewCircles
42
Dále ještě připomeňme, že:
•
•
4.6
Při prvním spuštění programu soubory s nastavením neexistují, tudíž není odkud načítat.
Pokud je program uložen na nezapisovatelném médiu, tak se nezdaří ukládání načítání, tudíž
ani není odkud načítat.
Jazykové nastavení
4.6.1 Řešení lokalizace
V programu lze změnit jazyk, ve kterém se uživateli zobrazují textové výpisy, aniž by byl nutný zásah do zdrojového kódu. Částečně o tom byla řeč v 3.3. V této kapitole se na vše podíváme
konkrétně. V principu se tu střetáváme se stejným chováním jako v případě načítání nastavení.
Místo SAX parseru, o kterém byla řeč u nastavení, zde pracujeme s ResourceBundle objektem, který je základním kamenem i18n v Javě.
Obrázek 4.8: Třídy související s načítáním
jazyku u menu
Vše funguje tak, že existuje obyčejný plain text soubor s příponou properties, ve které jsou
řádky dvojic klíč – hodnota. Tedy např.:
43
title = Výběr barvy
new = NOVÁ
A právě takovýto soubor si načteme pomocí ResourceBundle objektu, a pak se pomocí
metody getString snažíme získávat prostřednictvím klíčů jednotlivé hodnoty.
Postup načítání je obdobný jako u nastavení. Máme rozhraní Languable, které třídám vnucuje
metodu loadLanguage(). V této metodě může např. docházet k vytvoření objektu, který vrátí texty
pro komponentu. Ty buď budou základní (výchozí), nebo lokalizované. Záleží na tom, jestli se podaří načíst lokalizované zprávy. Možná je i varianta, kdy v lokalizovaném souboru chybí potřebná
řádka / klíčové slovo. To by pak výsledné texty byly částečně lokalizované a částečně původní
(tj. uvedené přímo ve zdrojovém kódu).
U takového řešení lokalizace programu se setkáváme mimo jiné s objektem Locale, což je
objekt, který obsahuje kódy zvoleného státu a jazyku. Využíván je při získání konkrétního
ResourceBundle objektu. Dle Locale a zadaného jména souboru dochází k načtení zpráv ze
správného properties souboru.
Co se týče načítání zpráv ze správného properties souboru, tak situace zde je poněkud složitější. Když neexistuje properties soubor pro konkrétní Locale a tím pádem není pro něj možné
vytvořit ResourceBundle, tak není ještě vše ztraceno. Automaticky se totiž ještě zkouší vytvořit
ResourceBundle pro podobná Locale. Příklad z [19] následuje. Autoři se v tomto příkladu snažili
v prostředí, jehož výchozí Locale bylo en_US, vytvořit ResourceBundle objekt pro Locale
fr_CA_UNIX. Název souboru měl být ButtonLabel. Potřebné soubory neexistovaly, takže postupně
docházelo k pokusům o vytvoření ResourceBundle objektu se zprávami z těchto souborů:
ButtonLabel_fr_CA_UNIX.properties
ButtonLabel_fr_CA.properties
ButtonLabel_fr.properties
ButtonLabel_en_US.properties
ButtonLabel_en.properties
ButtonLabel.properties
Pokud by neexistoval ani jeden z uvedených souborů, tak by došlo k vyvolání výjimky. Na ty
v programu pamatujeme. Výjimka se odchytí, do logu je uložena informace o neúspěšném načítání
44
zpráv, a uživatel uvidí zprávy základní.
4.6.2 Lokalizování do nového jazyka
Předpokládejme, že chceme lokalizovat program do Francouzštiny. Podaří se nám to, když se
přidržíme následujících kroků:
1. Najdeme si umístění stávajících properties souborů. Pakliže program máme k dispozici jako
JAR balík, musíme do něho. Properties soubory s jazykem jsou ve složce languages. V tuto
chvíli by tam měly být převážně soubory pro Locale cs nebo cs_CZ.
2. Postupně si budeme otevírat soubory, které jsou nyní dostupné pouze pro Češtinu a budeme
si k nim vytvářet ekvivalenty pro francouzské Locale. Klíče, které jsou povětšinou v Angličtině, zůstanou v těchto souborech identické, ale český text nahradí jeho francouzský překlad.
3. Pakliže už byl jednou program spuštěn, měl by existovat ve složce settings soubor
GlobalData.xml. V tomto souboru jsou elementy language a country. Obsahem elementu
language může být např. text cs, a obsahem elementu country text CZ. Text v language
přepíšeme na fr, a element country může třeba vymazat.
4. Po opětovném spuštění programu budou texty načítány pro Locale fr.
4.7
Stavy programu
Stavy programu jsou mechanismem, díky kterému lze v programu signalizovat a detekovat, v jakém
kontextu ho chceme právě používat. Např. můžeme mít stav slovně vyjádřitelný jako „V tuto chvíli
lze přidávat uzly“, což má za příčinu, že „V tuto chvíli nelze přidávat hrany“ (slovně vyjádřený jiný
stav).
Stavy programu lze v programu využívat dvěma možnými způsoby:
1. Můžeme si pomocí statické metody getActiveState() třídy states.State zjistit, jaký je aktuální
stav. V podmínce si pak ověřit, jestli konkrétní stav je např. stav pro přidávání uzlů,
a pakliže ano, provede se potřebná operace.
2. Lze využít mapy s názvem states ve třídě GlobalData. V té se nachází všechny stavy, ve
kterých se může program někdy vyskytnout. Pro každý stav přitom platí, že má aktivní
a neaktivní variantu. Ty jsou realizovány 2 speciálními třídami, přičemž jedna dědí od
druhé. Tyto třídy mají několik stejnojmenných metod, které se ale liší tělem.
45
Obrázek 4.9: Stavy AddNodeState a AddNodeNoState
Nyní si k oběma možným způsobům využití stavů uvedeme modelový příklad:
4.7.1 Přidání uzlu
Máme grafický vykreslovač grafu a chceme do něj přidat nový uzel. Popis situace, ve které se
využívá mapa stavů, následuje:
1. Uživatel klikne na ikonu uzlu a aktivuje tím v celém programu přidávání uzlů. Metoda
getActiveState() třídy states.State nyní vrací referenci na nový objekt AddNodeState. Stejný
objekt je nyní dostupný i pomocí klíče add_node v mapě states ve třídě GlobalData. V této
mapě není jediný. Ostatní možné stavy tu mají svou neaktivní variantu – tedy např.
46
AddEdgeNoState či MoveNoState. Neaktivní varianty ostatních stavů se vytváření automaticky při aktivování jakéhokoli stavu.
2. Uživatel uvolnil tlačítko myši nad kreslícím plátnem. V kódu, který v tuto chvíli událost
ošetřuje, si z mapy ve třídě GlobalData vyžádáme stav pro přidávání uzlů a pošleme zprávu
jeho metodě addNode. Kdyby tento stav byl ve své neaktivní variantě (AddNodeNoState),
tak se nic neděje (metoda má prázdné tělo), ale vzhledem k tomu, že máme k dispozici
aktivní variantu, tak v metodě addNode dojde k vytvoření uzlu a jeho přidání do příslušného
grafu.
Pozn.: Mapa se používá kvůli tomu, aby bylo možné do programu přidávat nové stavy, aniž
by se ve zdrojových kódech programu muselo něco měnit. Statická metoda getStateAddNode()
v GlobalData tak slouží jen ke zjednodušení přístupu ke stavu určenému pro přidávání uzlů. Dostat
se k němu tedy lze i jinak.
4.7.2 Stisknutí tlačítka myši nad plátnem
Druhý příklad, jak je možné stavy používat, se používá opět u plátna. A to v době, kdy se nad
plátnem stiskne tlačítko myši. V tuto chvíli je možných několik možností podle toho, jaký je
aktuální stav. Všechny tyto možnosti pracují s plátnem. Proto se využije statická metoda třídy
states.State, která nám vrátí aktuální stav. Následuje větvení, kde podle třídy, od níž je stav instancí,
vykonáváme různé operace. Např. pokud je stav typu AddEdgeState, tak je započato přidávání
hrany.
4.8
Logování
O logu již bylo řečeno dosti. Např. i to, že není nutno si pod ním hned představovat logovací soubor, protože ukládat obsah logu není vůbec nutností. V této kapitole si jen krátce představíme jeho
řešení.
Řešení logu, konkrétně jednoho jeho zástupce v podobě třídy TimeLog, zobrazuje
obrázek 4.10. Je na něm zakreslena situace, kde máme objekt log, na nějž je dostupná reference ze
třídy GlobalData. Tomuto objektu jsou pomocí volání GlobalData.addToLog(zpráva) předávány
z libovolných míst programu varovné výpisy.
Co se týče konkrétní realizace, tak platí, že log může být poměrně jednoduše realizován
různými způsoby. Je to zástupce komponent programu, u kterých toto jde, a tak si na jeho příkladu
vše nastíníme.
47
Abychom si vytvořili vlastní log, tak si stačí vytvořit třídu, uvést si u ní implements api.Log, napsat si
těla vnucených metod, a máme log. To, která třída se
pro realizaci logu finálně použije, je uvedeno v uživatelském / výchozím nastavení.
Když bychom chtěli změnit chování programu
takovým způsobem, aby se pro vytváření logu používala právě naše třída, lze toho docílit několika způsoby:
•
Změnou ve zdrojovém kódu, a to ideálně ve
třídě FactoryDefault, přes kterou lze ovlivňovat
výchozí chování.
•
Změnou
v
souboru
s
nastavením
pro
GlobalData. V souboru settings/GlobalData.xml
je element logclass, jehož obsahem je název
třídy, která se pro realizaci logu používá. Do
těchto míst můžeme zadat celé jméno úplně jiné
třídy.
•
Změnou prostřednictvím okna pro správu
komponent, kde si lze třídu pro realizaci logu
změnit prostřednictvím roletky s nabídkou
dostupných možností. O oknu pro správu
Obrázek 4.10: TimeLog jako jedna z tříd,
kterými lze realizovat log
komponent pojednává kapitola 4.13.
4.9
Stavový řádek
Stavový řádek je na tom podobně jako log. Slouží k výpisu stavových informací a informativních
zpráv. Oproti logu jde o komponentu obsaženou v hlavním oknu, v němž se při povolení
také zobrazuje. Případná záměna jinou realizací se proto děje prostřednictvím souboru s nastavením
hlavního okna. Tedy souboru settings/MainFrame.xml. Možné jsou ale i ostatní způsoby uvedené
v kapitole 4.8. Těmito způsoby jsou úprava kódu ve třídě FactoryDefault, či (ideálně) změna
prostřednictvím okna pro správu komponent.
Stavový řádek je komponenta, která nebude v hlavní okně vidět, pokud pro její realizaci
použijeme třídu obdobnou třídě components.statusPanel.NoStatusPanel. Tato třída je využívána
48
z menu, kde existují možnosti Zobrazit / Skrýt stavový řádek. Ve skutečnosti se ale použitím těchto
možností nemění viditelnost stavového řádku, ale objekt, kterým je stavový řádek realizován
(obrázek 3.9). Programu to vnucuje nutnost si pamatovat, která třída byla pro realizaci stavového
řádku použita, když byl stavový řádek ještě viditelný. V tomto se stavový řádek od logu liší.
V souvislosti s uvedeným názvem souboru MainFrame.xml uveďme ještě krátkou poznámku,
že název souboru plyne z používané konvence. Podle té se soubory XML a properties
pojmenovávají stejně jako třída, které se týkají. Dosti se tím usnadňuje testování a případné změny,
protože v souborech je větší přehled. Většina objektů realizujících stavové řádky žádné takové
soubory nepotřebuje.
4.10 Sebereflexe
V rámci analýzy bylo mnohokrát zmíněno Java Reflection API, o kterém nakonec pojednala
kapitola 3.6. Jde o pokročilou možnost Javy, které bylo v rámci implementace využito takovým
způsobem, že na ní stojí většina důležitých funkcionalit. V této kapitole odkryjeme k tomuto tématu
další informace.
4.10.1 Seznam tříd, které jsou v programu dostupné
Ve zdrojových kódech je dostupná třída functions.SystemInfoFunctions. V této třídě se mimo jiné
nachází statická metoda getClasses(), která vrací seznam názvů tříd, které jsou v programu
dostupné. Jde o třídy, které byly napsány nad rámec standardního API a vyhledané v místě, kam
ukazuje CLASSPATH. Nabízeny jsou i třídy z JAR souborů, které se tu nachází.
K
hledání
tříd
dochází
konkrétně
v
adresářích,
které
nám
vrátí
volání
System.getProperty("java.class.path"). Cesta k těmto adresářům by měla býti uvedená jako
absolutní. Uvádí se to tu z toho důvodu, že při nevhodných údajích v CLASSPATH nebudou
v programu dostupné všechny možnosti, které jsou postaveny na údajích, které vrací metody třídy
SystemInfoFunctions. Těmito údaji jsou povětšinou plné názvy tříd dostupných v systému, nebo jim
odpovídající Class objekty.
Uvedená třída System, plným názvem java.lang.System, je součástí Java Core API. Její
metoda getProperty slouží ke zjišťování systémových vlastností. Parametrem je klíč.
Když bychom se opět věnovali metodám třídy SystemInfoFunctions, tak za zmínku stojí
hlavně tyto:
•
getSystemProperties() - Vypíše dostupné informace o systému.
49
•
getSortedClasses() - Vrátí abecedně seřazené (dle názvů) uživatelem vytvořené třídy.
Uvažuje i obsah JAR souborů.
•
getSortedClassesExcept(List<Class> třídy) - Vrací to samé jako getSortedClasses, až na
třídy, které obsahuje seznam třídy.
•
getSortedClassesThatImplements(Class rozhraní) – Seřazené dle názvů vrátí všechny třídy,
které implementují zadané rozhraní. Uvažuje i obsah JAR souborů. Jde z vnějšku o nejvíce
využívanou metodu z této třídy. Když čtenář upomene na řadu funkcionalit diskutovaných
v části s analýzou,kde bylo zmiňováno Java Reflection API, možná si uvědomí, že přesně
toto je ta metoda, co se k realizaci takového chování hodí.
•
getClassesFromJARs(List<File> jars) – vrátí třídy, které jsou obsaženy v zadaných JAR
souborech.
Obrázek 4.11: Metody třídy SystemInfoFunctions
50
4.10.2 Vytvoření instance konkrétní třídy
Některé třídy programu realizují komponenty, u kterých se předpokládá, že by je mělo být možné
jednoduše nahradit. A to jinou realizací toho samého. Příkladem je např. zmiňovaný stavový řádek,
který je součástí objektu MainFrame. V něm se vytváří potom, co si MainFrame načte
vlastní nastavení. Celý algoritmus lze slovně popsat takto:
1. Při načítání nastavení je načten název třídy, pomocí které se má stavový řádek realizovat.
2. Zkouší se získat Class objekt pro načtený název třídy (Class.forName(název)).
3. Kdyby cokoli selhalo, použije se výchozí třída, kterou vrací metoda
FactoryDefault.getStatusPanelClass(). Tato třída mezi třídami skutečně existuje.
4. Class objekt, který jsme získali, zavolá svou metodu .newInstance(). Takový postup
vyžaduje, aby měla třída, od níž instanci vytváříme, bezparametrový konstruktor.
5. Vytvořený objekt se přetypuje na api.StatusPanel a použije se.
6. Případné výjimky se ošetřují takovým způsobem, aby se v logu zobrazila informační zpráva
a aby byl stavový řádek aspoň realizován třídou, která je uvedená jako výchozí
Popsaný algoritmus je s různými modifikacemi ve zdrojovém kódu programu přítomný hned
na několika místech.
4.11 Možnosti zobrazování obsahu
V kapitole 3.4.2 jsme hovořili o možnosti, že by zobrazovaný obsah mohl být zobrazován různými
způsoby – ve vnitřním okně, ve vnějším okně nebo v záložce. V této kapitole popíšeme, jak je vše
realizováno. Využijeme k tomu obrázku č. 4.12.
Obsah, který lze v programu zobrazovat, implementuje interface s názvem Content, za
kterým se většinou skrývají instance tříd JPanel a JLabel z balíku javax.swing.
Vizualizace obsahu probíhá přes factory objekt (tovární objekt), který se nachází v objektu
MainFrame. Factory objekt si od hlavního okna vyžádáme a prostřednictvím jeho metody
addContent mu předáme náš obsah, který chceme zobrazit.
Další chování je odvislé od toho, jaký factory objekt se v hlavním okně nachází konkrétně
(TabVisualiserFactory, MainFrameVisualiserFactory, InternalFrameVisualiserFactory), ale v principu i tak jsou následující kroky podobné. Factory objekt vytvoří zobrazovač (budeme ho nazývat
vizualizérem) a předá mu obsah. V případě TabVisualiserFactory tak dojde k přidání nové záložky
do panelu záložek, který se v tuto chvíli v hlavním okně zobrazuje. V případě
51
InternalFrameVisualiserFactory dojde k vytvoření vnějšího okna a k jeho přidání do desktop panelu
hlavního okna. A v případě MainFrameVisualiserFactory dojde k vytvoření hlavního okna (představovaného MainFrameVisualiserem) a k jeho zobrazení. Factory objekty si musí udržovat informace o tom, jaké vizualizéry zobrazují, aby mohla být umožněna případná záměna factory jednoho
typu za factory jiného typu.
Co se týče různého vycentrování obsahu, nebo aby příliš velký obsah byl scrollovatelný, tak
takové chování si ošetřují vizualizéry, takže se o něj u námi přidávaného obsahu nemusíme starat.
U factory objektů by ještě stálo za zmínku, že si evidují, který vizualizér je právě vybraný.
Takové chování se hodí pro ukládání grafů a pro simulaci grafových algoritmů. Zde je factory
objekt požádán, aby nám vrátil aktuálně vybraný vizualizér, a s tím pak pracujeme, pokud došlo ke
splnění základních podmínek.
Obrázek 4.12: Třídy pro vizualizaci obsahu pomocí záložek
Obrázek 4.13: Třídy potřebné pro realizaci překreslovačů u GraphicalGraphView
52
4.12 Grafy a jejich zobrazování
4.12.1 Princip řešení
A nyní ke grafům. U těch jsme si říkali, že je chceme zobrazovat různými způsoby, přičemž toto
zobrazování různými způsoby by mělo být možné najednou a změna v jednom pohledu by se měla
případně projevit i pohledech ostatních.
Popsané chování je možné a je řešeno způsobem, který naznačuje obrázek. 4.13. Graf, který
můžeme nazývat modelem, je oddělen od formátovacích dat. Ty obsahuje pohled, což je
komponenta, která graf zobrazuje, přičemž možnosti zobrazování jsou různé (grafický vs. textový
výstup).
Při řešení, kdy se graf zobrazuje jako kolečka propojená čárkami, pohled obsahuje objekty,
které zapouzdřují uzly a hrany z modelu a přidávají k nim formátovací informace, což jsou především výška, šířka a pozice v prostoru. Tyto objekty obohacené o formátovací data obsahují
reference na své neformátované protějšky, a to především z důvodů provádění změn.
Obrázek 4.14: Graf a jeho pohledy
53
Nad pohledy stojí jakýsi řadič (nazývaný presenter), který synchronizuje obsah jednotlivých
pohledů s modelem. Model je součástí tohoto presenteru.
Pakliže v jednom pohledu dojde ke změně v zobrazení (např. bylo pohnuto uzlem A), tak
dojde k provedení změny v daném pohledu P a k informování presenteru, že v pohledu P došlo
k posunu uzlu A na pozici [x;y] (předává se neformátovaný uzel, a ne jeho formátovaná varianta).
Presenter v takovém případě informuje všechny ostatní pohledy, že došlo k posunu uzlu A na
pozici [x;y].
A teď pozor. Je jen na pohledu, jak na takovéto upozornění zareaguje – jestli uzel, který
v jeho vykreslení odpovídá uzlu A, posune na pozici [x;y], nebo jestli neprovede např. vůbec nic.
Na příkladu posunu uzlu bylo demonstrováno chování ukázkové chování presenteru. To je
podobné pro spoustu dalších interakcí s pohledy. A právě v reakcích na jednotlivá upozornění se
mohou různé pohledy lišit, i když vizuálně mohou působit úplně stejně. Uživatel si tak může
s využitím dědění od některého ze současných pohledů napsat pohled vlastní, kde si upraví reakci
na nějaké upozornění dle svých potřeb.
Obrázek 4.15:Graf a uzly v něm
54
4.12.2 Grafický vs. textový pohled
Termíny použité v nadpisu tohoto odstavce mohou být zmatečné, proto radši uveďme: Grafickým
pohledem na graf se myslí např. graf vykreslený pomocí čar a koleček, a textovým (negrafickým)
graf zobrazený prostřednictvím matice sousednosti. Ta může být také vykreslena, ale vizuálně bude
na uživatele spíš působit tak, že někdo vzal klávesnici a napsal na ní pár čísel. Tolik k těmto
pojmům.
Pokud se podíváme na zobrazovače grafu obecně, tak pro ty platí, že implementují rozhraní
GraphView. V programu je přítomná abstraktní třída AbstractGraphView, ve které je doplněno tělo
k těm metodám, které jsou společné pro grafické i textové pohledy. Textové pohledy by měly právě
od této třídy dědit. Je to i případ třídy GraphViewAdjacencyMatrix, která by měla sloužit jako
ukázkový příklad.
Co se týče grafických pohledů, tak u těch pro pohledy přibývá ještě jeden předek. A to třída
GraphicalGraphView, od které dědí pohledy, které v rámci použitého programového slangu lze
označovat za pohledy grafické. V programu od této třídy dědí např. třída GraphViewCircles.
Realizováno je tu plátno, na jehož plochu se kreslí. U tohoto plátna jsou ošetřeny události myši,
pomocí kterých se často informuje presenter o různých změnách. Ty presenter zpracuje a vyvolává
potřebné metody u ostatních pohledů, aby se aktualizovaly.
Obrázek 4.16: Hierarchie tříd pohledů
55
4.12.3 Manuální vs. automatické vykreslování
Vykreslovače grafů představované instancemi tříd, které implementují rozhraní api.GraphView,
umožňují přepínání mezi automatickým a manuálním překreslováním obsahu. Důvod? Např.
při generování grafu chceme vytvořit několik uzlů a hran najednou, a nechceme, aby se po každém
přidání jedné hrany (uzlu) graf zbytečně překresloval.
Vše vypadá tak, že každý zobrazovač grafu obsahuje objekt Repainter (překreslovač), přes
který se volá překreslování plátna s grafem, když dojde k nějaké změně. Pokud je tento objekt
automatickým překreslovačem, tak se plátno s grafem skutečně překreslí. Pokud ne, nic se nestane.
Kromě překreslovacího objektu obsahují třídy zobrazovačů ještě metodu repaint(), pomocí
které lze graf překreslit úplně vždy, protože s překreslovacím objektem nemá vůbec nic společného.
Díky této metodě můžeme tedy grafy překreslovat i tehdy, když je aktivováno manuální
překreslování.
4.12.4 Zobrazení grafu v jiném pohledu
U zobrazených grafů se nachází tlačítko, prostřednictvím kterého se uživateli nabídne popup menu,
přes které si lze specifikovat třídu, jejíž instance zobrazí aktuální graf také. Využito je tu
statických metod zmiňované třídy SystemInfoFunctions diskutované v kap. 4.10. Uživateli jsou
nabídnuty aktuálně dostupné pohledy, v kterých je možné graf zobrazit (včetně těch, v kterých graf
už zobrazen je).
Obrázek 4.17: Registr add-onů
56
4.13 Manage okno
Manage okno neboli okno pro správu komponent. Jde o okno, ve kterém si uživatel myší najede na
komponentu, kterou chce spravovat, a v nabídnuté roletce si vybere třídu, prostřednictvím které
chce, aby se taková komponenta realizovala. Opět je tu využíváno metod ze třídy
SystemInfoFunctions. V nabídce jsou nabízeny pouze aktuálně dostupné třídy, pro které platí, že
implementují zadané rozhraní a nejsou abstraktní.
Obrázek 4.18: Třída okna pro nastavení komponent
Této funkcionality se týká celý balík manage. ManageClassesWindow, které nastavovací
okno realizuje, obsahuje instanci třídy javax.swing.JTabbedPane. To je panel pro záložky. Ty jsou
v oknu v tuto chvíli dvě. Obsahem jedné je instance třídy GraphPanelForManage a obsahem druhé
OtherPanelForManage. Jde o třídy, které dědí od třídy javax.swing.JSplitPane, takže jejich instance
jsou zobrazeny jako panel rozpůlený na dvě poloviny. V levé polovině je nabídka možností, v pravé
polovině detail zvolené možnosti.
Třída ManageClassWindow má realizovat vnitřní okno, u kterého stačí, když bude zobrazeno
maximálně jednou. Proto je jeho konstruktor privátní a nová instance se vytváří pomocí veřejné
statické metody jen tehdy, pokud již nějaká instance neexistuje (návrhový vzor Singleton). Podobně
jsou na tom i jiná okna v programu.
57
Obrázek 4.19: Třídy realizující okno pro správu komponent
58
4.14 Nápověda
Nápověda aneb Poskytnutí pomocných informací uživateli. Po vzoru jiných programů tu jde
o položku v menu, pomocí které lze vyvolat okno s možnostmi, které lze nazývat tématy nápovědy.
Témata nápovědy jsou vyhledána pomocí metod z třídy SystemInfoFunctions. Jde tedy o využití
Java Reflection API. Jako téma nápovědy
poslouží každá neabstraktní třída, která
implementuje rozhraní Help (nebo toto rozhraní implementuje některý z předků takové
třídy).
Při vytváření vlastního tématu nápovědy
lze
využít
dědění
od
třídy
AbstractHelp, která celé vytváření zjednodušuje. Tato třída je napsána takovým způsobem, že předpokládá načítání textů s nápovědou z HTML souborů.
Obecně existují dvě možnosti, jak
řešit téma nápovědy. Lze buď text načítat ze
zadaného HTML souboru, nebo po vzoru
standardního načítání zpráv lze využít
properties souborů a ResourceBundle objektu. Okno pro výpis textu s nápovědou totiž
obsahuje instanci třídy JEditorPane, která
umožňuje jak zobrazování HTML souborů,
tak výpisy obyčejných textů. U témat s nápo-
Obrázek 4.20: Téma nápovědy: Grafy
vědou se s tím počítá a tvůrce nápovědy si
může vybrat, jak chce objekt JEditorPane využít.
Třída AbstractHelp je řešena takovým způsobem, že obsahuje metody protected boolean
isHTMLFile() a protected boolean isSimpleText(). Metoda isHTMLFile() tu vrací hodnotu true,
a druhá zmiňovaná metoda vrací false. Hodnota true v isHTMLFile() říká, že do objektu
JEditorPane chceme načíst HTML soubor s nápovědou, a true v isSimpleText() říká, že do objektu
JEditorPane chceme načítat nápovědu z properties souboru. V třídách, které od AbstractHelp
podědí, lze chování těchto metod změnit. Dokonce je možné to, aby v obou metodách byla hodnota
59
true. V takovém případě má přednost zobrazení HTML souboru s nápovědou jako pokročilejší
z obou možností.
Co se týče textů s nápovědou, tak ty lze lokalizovat. U řešení s properties soubory se neděje
nic jiného, než bylo popisováno v kapitole 4.6. U řešení s HTML soubory je vše již zajímavější.
HTML soubor poskytuje statický text, proto musí pro každý podporovaný jazyk existovat zvlášť.
Názvy takovýchto souborů se shodují, jen se mění jejich umístění dle aktuálního Locale. Je-li
aktuální Locale cs_CZ, tak soubory s nápovědou budou hledány v adresáři help > web > cs.
Neuspěje-li načtení zde, tak po vzoru práce s properties soubory dochází k pokusu o načtení
požadovaného souboru ze složky help > web. Pokud i zde dojde k nezdaru, je dané téma bez textu
s nápovědou.
4.15 Rozšíření
4.15.1 Princip řešení
Rozšíření aneb Add-ony, jak jsme zavedli v textu dříve. Jde o funkcionality, které se budou
s programem automaticky spouštět, pakliže je uvedeme do stavu „Aktivní“. Jak je vše konkrétně
řešeno, si vysvětlíme s pomocí obrázku 4.20.
Když bychom vše zjednodušeně okomentovali:
Program obsahuje registr add-onů (dále registr). Ten je dostupný přes třídu GlobalData.
•
K vytváření tohoto registru dochází při startu programu.
•
Registr obsahuje seznam add-onů, které mohou být ve stavu aktivní / neaktivní. Aktivní
add-on běží, neaktivní neběží, ale jako instance od určité třídy je v programu přítomen.
•
V registru si během běhu programu můžeme zaregistrovat nový add-on.
•
Prostřednictvím registru lze všechny add-ony najednou aktivovat / deaktivovat.
•
Když dochází k ukončení běhu registru, což se děje s koncem běhu programu, dochází
k uložení nastavení registru. Konkrétně se děje přesně ve stylu, jakým probíhá ukládání
nastavení jiných částí programu (kapitola 4.4). Do XML souboru s nastavením jsou uloženy
názvy těch tříd, od kterých jsou v registru přítomné aktivní instance.
•
Během spouštění registru dochází přesně k opačnému procesu – dojde k načtení XML
souboru s nastavení, načteme si názvy tříd, od kterých budeme chtít vytvořit instance, a tyto
instance vytvoříme. Máme tak objekty, které představují jednotlivé add-ony. Tyto add-ony
60
aktivujeme.
4.15.2 Jak přidat nový add-on
V jednotlivých bodech uvedených výše bylo nastíněno, jak probíhá načítání add-onů. Tedy add-onů,
co už byly v programu nějak dříve registrovány. V rámci této podkapitoly bude popsáno, jak se
v programu registruje úplně nový add-on. Nejdůležitější třídy, pomocí kterých je takového chování
dosaženo, znázorňuje obrázek 4.21.
Obrázek 4.21: Třída okna pro správu add-onů
Když opět vše rozvedeme:
•
V programu je možné otevřít okno, které slouží ke správě add-onů. Toto okno představuje
instance třídy RegisterWindow.
•
RegisterWindow si prostřednictvím metod, které nabízí třída SystemInfoFunctions, zjistí,
jaké třídy, které implementují rozhraní api.AddOn, jsou dostupné. Dále si z registru add-onů
vyžádá add-ony, které registr registruje. Výsledkem sjednocení a vyhodnocení těchto údajů
je nabídka, kterou zachycuje obrázek 4.22.
•
Změna učiněná v nabídnutém okně se odpovídajícím způsobem projevuje v registru.
61
Obrázek 4.22: Okno pro správu doplňků
4.16 Přidání položky do menu
V rámci analýzy byla v kapitole 3.5.3 diskutována možnost přidání si nové položky do menu,
přičemž byla uvedena motivace k takovému chování a bylo popsáno, jak by vše mělo přibližně
fungovat. Pozorný čtenář tak tuší, že umístnění této kapitoly právě pod kapitolou popisující
doplňky, není náhodné.
Ale konkrétněji. V package api se nachází interface MenuItem. Pokud libovolná třída tento
interface implementuje, lze o jejích instancích mluvit jako o položkách do menu. Tyto položky jsou
určeny k tomu, aby uživatel prostřednictvím nich mohl ze spuštěného programu vyvolat nějakou
blíže nespecifikovanou akci.
Jak se snaží nastínit obrázek 4.23, tak pro položky menu platí:
•
MenuItem je pouze specializovaný typ add-onu, kterému se přidává akce, která se vykoná,
když se klikne na odpovídající položku v menu.
•
Položky menu je možné aktivovat / deaktivovat jak v oknu pro správu add-onů
(RegisterWindow), tak i ve speciálním okně, které je obdobou RegisterWindow, ale oproti
němu zobrazuje jen dostupné třídy rozšiřující rozhraní MenuItem. V programu toto okno
realizuje instance třídy MenuItemWindow.
•
Jak je vidět i na obrázku 4.22, položky menu se zobrazují v menu pod možností Přídavky.
Text Přídavky se v menu nezobrazuje, pokud není v programu aktivní žádný přídavek.
62
Obrázek 4.23: Třídy související s položkami v menu
4.17 Ukládání a načítání grafů
4.17.1 Ukládání grafů
Jak přibližně ukládání grafů funguje, bylo nastíněno v kapitole 3.23, proto se v rámci kapitoly podíváme hlavně na zúčastněné třídy a na funkce, které plní. Vše zachycuje obrázek 4.24.
Základem pro ukládání jakéhokoli obsahu v programu je třída functions.IOFunctions. Když
uživatel klikne např. v nástrojové liště na tlačítko Uložit, zavolá se statická metoda save()
zmiňované třídy. Zde se zavolá metoda save() u vybraného obsahu (pokud nějaký je). Následuje
otevření okna, prostřednictvím kterého si lze naklikat, kam chceme obsah uložit a jaké má být
jméno souboru. V případě grafů je využíváno objektu GraphFilter, který vrátí přípony podporovaných formátů.
63
Obrázek 4.24: Třídy používané při ukládání grafů
GraphFilter obsahuje 2 mapy. Klíčem do nich je přípona souboru (malými písmeny)
a obsahem reference na objekt, který s daným formátem umí pracovat.
Jednou z map je mapa pro objekty, prostřednictvím kterých lze grafy ukládat. Během běhu
programu dochází obdobně jako u jiných funkcionalit k vyhledání dostupných tříd. Ty musí
implementovat rozhraní GraphSaver. Od těchto tříd se vytvoří instance a uloží se do mapy. Z té se
konkrétní objekt, prostřednictvím kterého se graf uloží, vybere výhradně na základě přípony
souboru.
Co se týče podporovaných formátů, dostupná je podpora pro formát GraphML. Uložit lze
graf i jako matici sousednosti (přípona .adjacency) a jako matici incidence (přípona incidence).
4.17.2 Načítání grafů
Načítání grafů je na tom podobně jako ukládání grafů. Opět se využívá tříd IOFunctions
a GraphFilter. Objekty, prostřednictvím kterých lze grafy načítat, jsou instance třídy implementující
rozhraní GraphLoader. To stejně tak jako rozhraní GraphSaver dědí od rozhraní GraphFormat. Načtené grafy se zobrazují v zobrazovači, který je nastaven jako výchozí. To je takový zobrazovač,
který se zobrazí, když klikneme v nástrojové liště na možnost Vytvořit nový graf. V tuto chvíli by to
měla být instance třídy GraphViewCircles.
64
4.18 Simulace algoritmů
V této kapitole se konečně dotkneme samotného
simulování algoritmů. Jako jeho základní kameny
lze jmenovat následující skutečnosti:
Neexistuje
•
žádný
speciální
jazyk,
prostřednictvím kterého se grafové algoritmy
zapisují.
Grafové algoritmy jsou obyčejné třídy,
•
které využívají možností tříd, na kterých je celý
program postaven.
Nabídka algoritmů k simulaci není vytvo-
•
řena napevno, ale generuje se automaticky
podle aktuálně dostupných tříd s algoritmy pro
simulaci.
A k těmto základním kamenům nyní
Ilustrace 4.26:
podrobněji.
Každý algoritmus, který lze prostřednictvím
programu simulovat, představuje třída, která
implementuje rozhraní api.Algorithm. Rozhraní
třídám
mimo
runNextStep(),
jiné
vnucuje
prostřednictvím
metodu
které
se
algoritmus krokuje.
Interface Algorithm sám o sobě příliš
zajímavý není a slouží hlavně k vygenerování
nabídky všech dostupných algoritmů. Po vzoru
dříve popisovaných problémů se k simulování
nabídnou pouze třídy, které tento interface
implementují.
Co se týče metod, tak těch má tento interface jen několik. Ty souvisí převážně s krokováním,
a ne se simulací samotnou.
Obrázek 4.25: Předci třídy BFS
65
Taková jednoduchost je ale nevhodná, pakliže po
uživateli / programátorovi nechceme, aby se musel
detailně seznamovat s celým prostředím, nad kterém jeho
programy poběží. A proto vznikla třída Algorithm, která
rozhraní Algorithm implementuje. Od té je při vytváření
vlastní třídy, která by měla představovat algoritmus pro
simulování, doporučeno dědit.
Třída Algorithm poskytuje uživateli řadu metod,
které by se mu mohly při programování jeho algoritmu
hodit. Na obrázku 4.25 nejsou ani zdaleka vidět všechny.
Cílem těchto metod je odstiňovat uživatele od úkonů,
které by si musel složitěji napsat sám. V rámci následujících podkapitol bude uvedeno několik příkladů.
4.18.1 „Needs“ metody
Jednou z metod, které třída Algorithm obsahuje, je např.
metoda protected boolean needsStartNode(). Jde o zástup-
Obrázek 4.27: Třída reprezentující
jeden krok algoritmu
ce metod, které zjednodušeně můžeme nazývat jako needs metody. Smyslem těchto metod je
informovat o potřebě na konkrétní vlastnost grafu. Např. metoda needsStartNode() odpovídá na
otázku, jestli algoritmus, který se prostřednictvím instance třídy simuluje, potřebuje znát startovní
uzel. Needs metody ve třídě Algorithm mají jednoduché tělo – pouze vrací false. V třídách, které od
třídy Algorithm dědí, lze některé z těchto metod v případě potřeby překrýt.
4.18.2 Krok pro inicializaci prostředí
Krok pro inicializaci je stav, který bude každý algoritmus (napsaný v třídě dědící od třídy
Algorithm) automaticky obsahovat, pakliže si nepřekryje metodu protected boolean isInitStep().
V rámci tohoto kroku se zkontroluje, jestli je vybrán nějaký graf a jestli má potřebné vlastnosti
(přihlíží se např. k některým needs metodám). V tomto kroku se nastaví členské proměnné jako
visualiser, graph, view, graphPresenter atd., pomocí kterých máme v rámci algoritmu přístup
k vybranému pohledu či ke grafu, který zobrazuje.
V rámci tohoto kroku se dále aktivuje stav programu nazývaný Prealgorithm, během kterého
nelze měnit graf, ale lze v něm vybírat uzly a hrany. Dále se tu aktivuje ruční překreslování
vizualizovaných grafů. O tom byla zmínka v kapitole 4.12.3. Ruční překreslování je potřeba před
66
další prací s programem zase změnit na automatické, proto program uživatele nutí k tomu, aby
ukončil simulování algoritmu, než začne program používat k jiným činnostem.
4.18.3 Další přípravné kroky
Kromě kroku pro inicializaci prostředí lze automaticky vytvořit ještě několik dalších kroků.
V konstruktoru třídy Algorithm se volá metoda addPrepareSteps(), v jejímž těle se testují návratové
hodnoty zbylých needs metod a případně se s jejich pomocí přidávají nové kroky. Testována je tu
např. hodnota, kterou vrací metoda needsStartNode(). Pokud je návratovou hodnotou true, volá se
metoda addStateNeedsStartNode(), která zařídí přidání kroku, v rámci kterého se kontroluje, jestli
je vybraný v grafu jeden uzel. Pokud ne, zobrazuje se dialogové okno s informací, že je potřeba
vybrat uzel.
Zmiňování názvů zúčastněných metod by se mohlo zdát jako přílišné zacházení do
podrobností, ale rád bych na těchto metodách zdůraznil překrývání metod, na kterém je celá
simulace postavena. Překrývat lze totiž na několika místech, abychom dosáhli požadovaného
chování. Například se místo metody needsStartNode() může překrýt metoda addPrepareSteps(). Ta
nemusí dělat vůbec nic, a programátor si místo ní může např. jen zavolat metodu
addStepNeedsStartNode(). A nebo si může krok, během kterého dojde k výběru startovního uzlu,
napsat úplně sám.
4.18.4 Třída Algorithm a její konstruktor
Díky předešlé kapitole může využívání možností třídy Algorithm působit složitě, ale smysl třídy
Algorithm je tím poměrně dobře nastíněn. Ta je zkrátka jen pomocník, který uživateli může ušetřit
psaní spousty kódu. A to různými způsoby. Je ale jen na uživateli, jak ji využije, a jestli vůbec. Je to
ale doporučeno i za cenu překrývání některých metod.
Cílem při realizaci simulování algoritmů nebylo vytvářet nějaká zbytečná omezení. Ani
nebyly učiněny snahy, aby byly prostřednictvím třídy Algorithm pokryty úplně všechny funkčnosti,
které by se při psaní nějakého nového algoritmu mohly objevit. Celé simulování totiž může vypadat
jakkoli. Pro program je potřebné jen to, aby šlo algoritmus pro simulaci krokovat. Jak si ale v rámci
některých kroků algoritmu uživatel ošetří, aby byl zvolen např. počáteční uzel, je jen na uživateli.
Třída Algorithm nabízí řešení v podobě diskutované metody addStepNeedsStartNode() a je
dostupno několik možností, jak tuto metodu zavolat.
Při využití třídy Algorithm je vhodné vědět, jak vypadá tělo jejího jediného konstruktoru, aby
programátor nebyl překvapen nečekaným chováním instancí odvozené třídy. Konstruktor třídy
67
obsahuje kód, který je uvedený za tímto odstavcem. Metoda createMySteps(), jejíž volání je v kódu
obsaženo, je diskutována v samostatné kapitole.
Konstruktor třídy Algorithm:
public Algorithm(){
this.loadLanguage();
if(isInitStep()) addInitStep();
addPrepareSteps();
createMySteps();
}
4.18.5 Krok pro start samotného kroku
V rámci třídy Algorithm se můžeme setkat ještě s jedním důležitým stavem. Jde o startovní stav ,
a vyvolává se voláním metody protected void addStartAlgorithmState(). Tato metoda se volá na
konci metody prepareSteps(). Tento krok má návratovou hodnotu ve skipMe() nastavenou na true
a jeho smysl je takový, že program uvádí do stavu Algorithm. V tomto stavu již s vizualizovaným
grafem nelze provádět vůbec nic. Důvod? Můžeme mít např. označeny nějaké uzly a hrany, a když
bychom myší klikli někam na plátno, tak by se při stavech Prealgorithm a Move mohli odznačit.
Na obrázku 4.28 tento krok představuje možnost s řadou pomlček napsaných za sebou.
Napsáno tu není nic konkrétního, protože krokování se na tomto kroku nezastaví. A to z toho
důvodu, že zmiňovaná metoda skipMe() vrací hodnotu true.
4.18.6 Metoda createMySteps()
Metoda createMySteps() je abstraktní metoda třídy Algorithm. V odvozených třídách je vhodné
právě zde zařídit vytvoření vlastních kroků, z kterých se bude algoritmus pro simulaci
skládat. A to prostřednictvím volání metod, kde každá metoda zařídí vytvoření jednoho kroku.
Takové řešení se hodí z toho důvodu, že můžeme chtít vytvořit simulaci, která se bude lišit od jiné
simulace jen v jednom kroku. Je-li původní simulace realizována prostřednictvím třídy A, a nová
simulace má být realizována prostřednictvím třídy B, tak jen stačí, aby třída B dědila od
třídy A a překryla některou její metodu / metody. Vše bude demonstrováno na příkladech
v kapitole 4.18.10.
68
4.18.7 Kroky
Jak v případě třídy Algorithm a jejích potomků vypadá jeden krok, lze vidět na obrázku 4:27. Jde
o instance anonymních tříd, jejichž základem je třída Step. Všechny instance třídy Step (dále
označované jen jako kroky) se samy přidají do ArrayListu (seznam realizovaný pomocí pole), který
obsahuje instance třídy Algorithm. Krok zná svoji pozici v tomto seznamu, a kromě toho obsahuje
text, který informuje o tom, co se v rámci tohoto kroku provádí.
Co se v kroku provádí je v těle metody code(). Z dalších stojí za zmínku následující:
•
public boolean skipMe() - Pokud vrací true, tak se po proběhnutí tohoto kroku automaticky
zavolá krok další. Defaultně vrací false.
•
public boolean stopTimerAfterCode() - Pokud vrací true, tak se zastaví automatické
krokování (pokud je zapnuté). Defaultně vrací false.
4.18.8 Panel pro simulování algoritmů
K simulaci algoritmů dochází prostřednictvím komponenty, kterou můžeme nazývat jako
panel pro simulování algoritmů Jde o panel, který se skládá ze 4 základních částí:
•
bloku kroků, ze kterých se průběh algoritmu skládá
•
roletky pro výběr algoritmu,
•
tlačítek pro krokování
•
posuvníku pro nastavení časové prodlevy mezi 2 kroky při automatickém krokování
algoritmu.
Několik poznámek k vyjmenovanému následuje:
Blok kroků, ze kterých se průběh algoritmu skládá
Jde o instanci třídy javax.swing.JList. Obsah tvoří textové zprávy jednotlivých kroků, ze kterých se
vybraný algoritmus skládá. O tom, jaká položka je vybrána, vypovídá index kroku, který je zrovna
na řadě.
Roletka pro výběr algoritmu
Tato roletka obsahuje název třídy, od které se vytvoří instance, když se v roletce změní hodnota.
V nabídce jsou pouze třídy, které implementují rozhraní Algorithm. Z vytvořeného objektu se
vytáhnou zprávy pro blok kroků a jako krok, který je nyní na řadě, se označí krok s indexem 0.
69
Co se týká obsahu roletky, tak tím jsou skutečně jen názvy tříd, od kterých se vytvoří
instance. Pokud bychom tu chtěli mít nějaké „hezčí“ výpisy, museli bychom sem vkládat „nějaké“
objekty s překrytou metodou toString(). Tím si vše děláme složitější.
Tlačítka pro krokování
Funkčnost těchto tlačítek nebude asi překvapivá, přesto ji uveďme.
•
Pomocí tlačítka Krok se vyvolá provedení kroku, který je v bloku kroků zrovna označen.
•
Pomocí tlačítka Auto-krok se spouští automatické provádění kroků – jako kdyby počítač po
určité době klikl na tlačítko Krok za uživatele. Ke krokování je využita instance třídy
javax.swing.Timer.
•
Pomocí tlačítka Konec se ukončuje provádění algoritmu. Algoritmy mohou končit krokem,
kde se volá metoda end(), pomocí které se zajišťuje obdobné. Po stisknutí tohoto tlačítka se
v programu aktivuje stav Přesunů a u vizualizátorů grafu se opět aktivuje automatické
vykreslování.
Obrázek 4.28: Panel pro krokování algoritmů
70
4.18.9 Příklad algoritmu pro simulaci
Zdrojový kód
Následující kód je přepisem obsahu třídy BFS, která se v balíku algorithm nachází. Jak název
napovídá, jde o třídu, prostřednictvím jejíž instance se simuluje procházení grafu do šířky. Kód
uvádím bez komentářů a importů. Naopak jsem do něj přidal číslování řádků, aby šlo do zdrojového
kódu v dalším textu snáze odkazovat.
1
public class BFS extends Algorithm{
2
public Color openColor=Color.CYAN;
3
public Color freshColor=Color.WHITE;
4
public Color closedColor=Color.BLACK;
5
6
@Override
7
protected boolean needsStartNode(){
8
9
return true;
}
10
11
@Override
12
public void createMySteps(){
13
step3();
14
step4();
15
step5();
16
step6();
17
step7();
18
}
19
20
protected void step3(){
21
new Step("Nevybrané uzly se označí jako FRESH"){
22
public void code(){
23
startNode.setColorInAlgorithm(openColor);
24
openNode(startNode,openColor);
25
setAllNodesAsFresh(freshColor);
26
nextStep();
27
}
28
29
};
}
30
31
32
33
protected void step4(){
new Step("Expandování vybraného OPEN uzlu"){
public void code(){
34
openNodeNeightbourReflectEdgeOrientation(openColor);
35
repaint();
71
36
nextStep();
37
}
38
};
39
}
40
41
protected void step5(){
42
new Step("Uzavření prozkoumaného uzlu"){
43
public void code(){
44
closeNode(closedColor);
45
repaint();
46
nextStep();
47
}
48
};
49
}
50
51
protected void step6(){
52
new Step("Vytáhnutí nejstaršího uzlu z OPEN k prozkoumání"){
53
public void code(){
54
List<Node> opens=getOpenNodes();
55
if(opens.isEmpty()) nextStep();
56
else{
57
selectNode(opens.get(0));
58
repaint();
59
goToStep(getExpandSelectedIndex());
60
}
61
}
62
};
63
}
64
65
protected int getExpandSelectedIndex(){
66
return 4;
67
}
68
69
protected void step7(){
70
new Step("Neexistuje žádný OPEN uzel. Konec."){
71
public void code(){
72
end();
73
}
74
};
75
76
}
}
72
Několik poznámek k uvedenému kódu
•
Na obrázku 4.28, kde je okno s panelem pro simulování algoritmů, je v panelu vybraný
právě tento algoritmus.
•
V kódu si lze na řádcích 6 – 9 povšimnou překryté metody needsStartNode()
prostřednictvím které je do algoritmu přidán krok pro výběr startovního uzlu.
•
Vlastní kroky jsou vytvořeny v metodách step3() - step7(). Každý krok je vyčleněn do
samostatné metody, aby bylo jednodušší provádění změn ve třídách, které dále dědí od třídy
BFS (bude ještě diskutováno).
•
V kódu je využíváno řady metod zděděných od třídy Algorithm (např. nextStep(), repaint()).
•
Metoda getExpandSelectedIndex() na řádku 65 je tu přítomna opět hlavně kvůli tomu, aby
šlo provádět méně změn ve třídách zděděných. Kdyby číslo dalšího kroku bylo v metodě
code() daného kroku napevno, v potomkovi by se musel přepsat celý krok, pokud bychom se
chtěli po provedení přemísťovat na jiný index.
4.18.10 Další příklady
Nyní ještě ve zkratce k několika dalším třídám, které mají sloužit k simulování:
•
DFS – Určeno pro simulování prohledávání do hloubky. Je zde překryta metoda step6()
zděděná od třídy BFS a metoda closeNode(Color closeColor) zděděná od třídy Algorithm.
Celá simulace tak vezme jen několik málo řádků kódu.
•
BFSWithDestination – Pro simulování prohledávání do šířky, kde máme zadaný cílový
uzel. Třída dědí od třídy BFS a slouží k demonstrování, jak si vynutit výběr cílového uzlu.
Třída si realizuje frontu otevřených uzlů sama.
•
BFSLocalized – Třída, na které je ukázáno, jak lze algoritmy případně lokalizovat.
U většiny algoritmů lokalizace není řešena, protože typický uživatel si spíš napíše simulaci
sám pro sebe nebo pro kolegy, a nějaká lokalizace ho nebude zajímat, protože je pracnější.
Základem lokalizace je překrytí metody loadLanguage, která má defaultně prázdné tělo.
•
DFSWithOrder – Jde o variantu procházení do hloubky, kde je u uzlů vypisováno,
v kterém pořadí byly procházeny. Algoritmus ukazuje, jak u uzlů vypisovat hodnoty, které
ale po ukončení provádění algoritmu budou nahrazeny původními hodnotami.
73
•
Dijskra – Pomocí této třídy se realizuje simulace Dijskrova algoritmu. Algoritmus si tu
prostřednictvím několika. needs metod vynucuje, aby uživatel musel zadat startovní uzel,
aby všechny hrany byly ohodnocené číslem, a aby tato čísla byla nezáporná.
•
PigIsomorfism – Jenom demonstruje, že graf může být nakreslený různými způsoby. Ve
zdrojovém kódu lze najít, jak si vytvořit nový graf se zadaným zobrazovačem a jak automaticky posunovat s uzly grafu. Na začátku simulace máme graf ve tvaru prasátka, a když
spustíme automatické krokování, tak se uzly tohoto grafu pohybují libovolnými směry.
4.18.11 Příznaky a staré hodnoty uzlů a hran
Kvůli simulování algoritmů byly rozhraní Edge a Node obohaceny o několik metod, které se hodí
pro provádění algoritmů. Za zmínku stojí hlavně následující:
•
public void setColorInAlgorithm(Color color) – Příklad metody, pomocí které si
uzel / hrana uloží starou hodnotu, kterou obsahoval, a nahradí si jí novou.
•
public void resetToAttributesBeforeAlgorithm() - Souvisí s předešlou metodou.
Uzel / hrana si díky ní obnoví svoje původní data.
•
public void setAlgorithmIndication(Object object) – Prostřednictvím této metody si lze
u hrany / uzlu uložit referenci na libovolný objekt. Hodí se k indikaci. Např. že je uzel ve
stavu UZAVŘEN nebo z jaké hrany se do uzlu v rámci prohledávání přišlo.
•
public void setAlgorithmIndicationField(Object[] object) – Obdoba předešlého.
Indikačních příznaků lze takto uložit více.
•
public Object getAlgorithmIndication() - Pro získání indikace.
•
public void clearAllAlgorithmIndications() - Slouží k tomu, aby se všechny indikace
nastavily na null.
V programu je dostupná i možnost vytvoření si kopie celého grafu.
4.19 Generátor grafů
4.19.1 Základní principy
V případě generátoru se opět nesetkáváme s o mnoho odlišnou funkčností, než u jiných částí
programu. Opět tu tedy využíváme možnosti třídy SystemInfoFunctions z balíčku functions. Pro
generátory platí:
74
•
Generovat grafy lze prostřednictvím instancí tříd, které implementují rozhraní Generator.
•
Při otevření okna pro nabídku generátorů dochází k vyhledání dostupných generátorů. Od
těch se vytvoří instance. Jde o jednoduché objekty, které při volání metody displayMe()
vytváří panel, ve kterém lze různě nastavovat možnosti pro generování.
•
Kliknutím na tlačítko Generovat, které je v okně dostupné, dochází k volání metody
generate() vybraného generátoru. Ta povětšinou jen volá stejnojmennou metodu u svého
panelu.
•
Toto řešení se např. od panelu s nabídkou algoritmů pro simulování liší v tom, že v roletce
s generátory není název třídy, ale už lokalizovaný název generátoru. Generátor si svůj obsah
(panel) vytváří až v době, kdy je vybrán v roletce. Důvod je ten, aby se zbytečně
nevytvářelo něco, co třeba uživatel vůbec nepoužije.
Obrázek 4.29: Třídy, které se podílí na realizaci generátoru
75
4.19.2 Dostupné generátory
V tuto chvíli jsou v rámci nastínění možností v programu dostupné celkem 4 generátory. Jde
konkrétně o tyto (názvy odpovídají názvům tříd, které je realizují):
•
BasicGenerator -Jde o převzatý generátor z BP.
•
BinaryTreeGenerator – Tento generátor umí generovat binární stromy. V panelu lze
nastavit, jestli chceme mít ohodnocené hrany či uzly, jestli mají být orientované hrany
a jestli má být strom úplný. Pokud ne, tak může nastat situace, že graf bude mít např. jen
jeden uzel.
•
PigGenerator – Příklad generátoru, kde nelze nastavovat vůbec nic. Generátor jen vytvoří
vytvoření pevně definovaného grafu vzhledově připomínajícího prasátko.
•
ShapeGenerator – Součástí tohoto generátoru jsou obrázky, na kterých je znázorněna
přesná podoba grafu, který bude při stisku tlačítka Generovat vygenerován.
76
5
Testování
Tato nedlouhá kapitola je určena testování. Pojem je to relativně obecný, proto bude zmíněno hned
několik skutečností, které lze pod pojem testování zahrnout.
5.1
HW nároky
Na HW nebyly kladeny žádné speciální nároky, ale samozřejmě je krásné, když výsledný SW chodí
i na strojích méně výkonných. V tomto směru jsem provedl testy jen takovým stylem, že jsem
program zkusil spustit na několika běžných přístrojích. Úspěšně jsem tak s programem pracoval
mimo jiné i na následujících počítačích:
•
notebook Acer TravelMate 2492NWLMi, Intel Celeron M, 1.6 Ghz, 1MB L2 Cache, 1GB
RAM, MS Windows 7
•
notebook HP Compac X6000, Pentium 4 CPU 3,20 GHZ, 1 GB RAM, MS Windows XP
•
netbook Asus Eee PC 900A, Intel Atom, 1.60 Ghz, 0.99GB RAM, MS Windows XP
5.2
Spouštění v různých OS a JDK
Program jsem zkoušel spustit v následujících operačních systémech (na všech funkčně):
•
Windows XP
•
Windows Vista
•
Windows 7 (64 bit)
•
Ubuntu 10.04 (Linux)
V uvedeném OS Windows 7 bylo zkoušeno program spouštět jak s pomocí JDK 5.0, tak
s pomocí JDK 6. Práce s programem proběhla úspěšně. Na ostatních OS bylo použito jen JDK 6.
5.3
Testování funkčnosti SystemInfoFunctions
Důležitou součástí programu je třída SystemInfoFunctions. Prostřednictvím jejích metod mimo jiné
dochází k vyhledání dostupných tříd, které implementují zadaný interface (dále označované jen jako
třídy). Z funkčního hlediska je důležité, aby takové chování bylo možné, i když program nebude
spouštěn z NetBeans jako původní NetBeans projekt. Konkrétně jsem zkoušel následující scénáře:
77
5.3.1 Vytváření nových projektů
1. Dostupnost tříd, když v NetBeans spustíme původní projekt. Funkční.
2. Dostupnost tříd, když původní vygenerovaný JAR balík (GraphEditor2) přesuneme na disku
někam jinam a tam spustíme. Funkční.
3. Vytvoření nového projektu B, kde si do knihoven přidáme i balík GraphEditor2.jar představující náš programu. Když zavoláme main metodu původního projektu, program funguje
stejně.
4. Když si přesuneme JAR balík projektu B na jiné místo na disku (musíme i s vygenerovanou
složkou lib), tak stále vše funguje.
5. Když si do projektu B přidáme novou třídu NewGenerator, která bude implementovat např.
rozhraní Generator, a opakujeme body 3 a 4, tak v nabídce generátorů se navíc objevuje
námi nově vytvořený generátor.
6. Když si vytvoříme projekt C, kterému do knihoven přidáme balík B.jar, tak spuštění
původního programu neuspěje. Důvod je ten, že balík B.jar má u sebe složku lib se
souborem GraphEditor2.jar. Tento soubor je nutné do knihoven přidat také. Teprve pak
spuštění programu uspěje.
7. Obsahem lib adresáře vytvořeného při souboru C.jar jsou i soubory B.jar a GraphEditor2.jar.
Úspěšně lze tedy pro C.jar provést obdobu kroku 4.
5.3.2 Spouštění z konzole / příkazové řádky
Spouštěním z disku v kapitole 5.2.1 se myslelo takové chování, kdy člověk proklikal myší na soubor GraphEditor2.jar a takto ho spustil. Ve Windows je takové chování možné. Co když ale někdo
bude chtít spustit soubor z příkazové řádky (případně z konzole v Linuxu)? I toto jsem radši
vyzkoušel, a výsledky byly zajímavé. Zdát se dokonce mohlo, že je ve třídě SystemInfoFunctions
něco špatně, ale nebylo tomu tak. Zde bych odkázal na [16] a kapitolu 3.6. Problémem byla
nevhodná cesta v CLASSPATH. Pokud program spouštíme z příkazové řádky pomocí programu
java.exe s přepínačem -jar, tak musíme jako argument zadat absolutní cestu k JAR souboru našeho
programu.
Příklad nevhodného spouštění v příkazové řádce ve Windows:
C:\java\jdk6\bin\java.exe -jar GraphEditor2.jar
Příklad správného spouštění v příkazové řádce ve Windows:
78
C:\java\jdk6\bin\java.exe -jar C:\GraphEditor2\dist\GraphEditor2.jar
Při správném spuštění programu z příkazové řádky, během kterého se do CLASSPATH
uložila absolutní cesta ke složce, ve které byl umístěn balík GraphEditor2.jar, proběhl test všech
funkcionalit úspěšně.
5.4
Testování funkčnosti ostatních částí programu
V této kapitole se podíváme na testování funkčnosti obecně. To probíhalo převážně hned při vyvíjení programu. Každá funkcionalita, kterou člověk napsala, byla hned otestována. Sledovalo se,
jestli dělá, co by dělat měla, a co by se stalo, kdyby na vstupu takové funkcionality byly nesprávné
hodnoty. Pro kritická místa, kde třeba bylo nutné, aby v nějaké proměnné nebylo null, byly
vytvořeny JUnit testy, které toto testovaly. Tyto testy byly pravidelně spouštěny. Občas člověk díky
nim odhalil chybu,kterou udělal z nepozornosti. O všech objevených chybách byl okamžitě udělán
záznam, aby nebylo opomenuto je opravit. K opravě chyb docházelo většinou hned.
Dále bych tu po vzoru BP zmínil několik ilustračních situací ke větším problémům, které
člověk testoval. Mohlo by na nich být vidět, na jaké věci si člověk musel dávat pozor. Tyto situace
budou probrány zběžně, protože částečně kryjí s obsahem BP.
1. Testování, jestli došlo k nabídnutí všech dostupných tříd, které implementují zadané
rozhraní A.
Člověk si vždycky zkusil vytvořit několik tříd, které implementovaly zadané rozhraní A,
rozmístil je po různých balíčcích, různě jim nastavoval, jestli jsou abstraktní nebo ne, a sledoval,
jestli roletka, která je má nabízet, obsahuje správné hodnoty. Tyto hodnoty byly vybírány, čímž byla
testována správná reakce na výběr konkrétní možnosti.
2. Testování jazykových výpisů.
Při vytváření objektů, které obsahovaly text, který měl být lokalizovaný, došlo vždycky
k vytvoření kódu, který by uměl načíst lokalizované zprávy z properties souboru, ale takový soubor
ještě neexistoval. Člověk si tak ověřil, jestli se ve všech výpisech objeví výchozí hodnoty. Když
bylo vše v pořádku, tak teprve pak byl postupně vytvářen obsah potřebného souboru. Během toho
bylo testováno, jestli jsou výchozí texty postupně zaměňovány texty lokalizovanými.
3. Testování změny Locale (jazyku)
K několika třídám nevznikly texty lokalizované jen pro Češtinu, ale i pro jiný jazyk. Na
těchto textech pak bylo vyzkoušeno, jestli funguje změna Locale, což odpovídá přepnutí výpisů do
jiného jazyka. Změnu Locale lze provést v souboru settings/GlobalData.xml.
79
4. Testování souborů s nastavením
U každé objektu, který umožňoval ukládání a načítání uživatelského nastavení, bylo
okamžitě vyzkoušeno, jestli toto funguje. Program byl pravidelně spouštěn bez uživatelského nastavení (stačilo vymazat složku settings). Docházelo díky tomu i ke sledování, jestli se všechny soubory s uživatelským nastavením opět vygenerují. Před dopsáním této práce byl program funkčně
spuštěn i z CD. Do souborů s nastavením byly dokonce ručně vkládány nesmyslné hodnoty a došlo
i k odstranění několika řádků, jestli s tím program nebude mít nějaké problémy.
5. Sledování výpisů logu
Log v průběhu vývoje realizoval objekt, který informace o všech vzniklých výjimkách
vypisoval na standardní výstup. Člověk si tak testoval log a měl zároveň na očích všechny výpisy
o všech výjimkách, které během práce s programem vznikly. Na případné chyby bylo možné hned
reagovat.
6. Vytváření příkladů
Ke všem funkcionalitám, které program nabízí, jsem se snažil vytvořit několik příkladů, aby
spíš byly odchyceny některé sporné či chybné bloky kódu. Sledoval jsem přitom, jak moc příjemně
se mi s nabízeným API pracuje a snažil jsem se uvažovat, jestli by nebyl možný nějaký refactoring,
díky kterému by se práce s vytvořeným kódem usnadnila.
7. Zaměňování objektů
Vytvářením možností pro Manage okno jsem si zároveň testoval, jestli je možné zaměnit na
konkrétních místech instanci jedné třídy instancí jiné třídy (implementující stejné rozhraní).
K odchycení několika problémů zde došlo.
8. Další testování
Dál by se v těchto místech dalo pokračovat popisováním dílčích testů souvisejících již
s jednotlivostmi. Vše by odpovídalo obecnému principu funkčního testování popsanému v úvodu.
Testování bylo postaveno především na zkoušení různým možností, které by mohly nastat. K tomu
navíc bylo využíváno nástroje zvaného Profiler. Jde o plug-in pro NetBeans IDE, ve kterém bylo
vidět, které metody vyžadují nejvíce paměti a času. Díky tomu došlo k náhradě několika sporných
kódů výkonnějšími alternativami.
80
5.5
Testy použitelnosti
Testování použitelnosti je posledním problémem, kterého bych se chtěl v rámci testování
dotknout. Pojato nebylo nijak velkolepě, ale proběhlo. Nebyly vytvořeny žádné dotazníky, nebyla
k dispozici žádná laboratoř, jen jsem se postupně sešel se 3 lidmi, kteří vědí, co je to teorie grafů
a ty jsem poprosil, aby si v programu zkusili nakreslit graf ve tvaru prasátka a pak si na něm nechali
automaticky simulovat procházení grafu do šířky. Všichni dotyční bez jakéhokoli radění různě
rychle uspěli. Já během toho vizuálně posbíral několik podnětů, které se projevily převážně na
vzhledu hlavního okna programu – změněny např. byly obrázky dvou ikonek v nástrojové liště.
Co se týče pokročilejších funkcionalit, tak o těch proběhla jen diskuse. Jak bylo uváděno,
program je spíš taková spustitelná knihovna pro práci s grafy, a aby bylo např. možné si vytvářet
vlastní algoritmy, tak se člověk musí s celým prostředím seznámit blíže.
81
6
Podobné programy
Nyní několika odstavci k programům, které by mohlo jít označit za podobné. Učiněných požadavků
na tuto práci, jak je vidno v kapitole 2.2, bylo dost. Proto bude asi slušné zmínit, že hlavními
takovými porovnávacími faktory jsou možnost vytváření grafů a možnost simulování grafových
algoritmů.
Další informací, jejíž uvedení pokládám za vhodné, je skutečnost, že na podobné programy
nejsem žádný odborník. S několika jsem se sice seznámil v době psaní BP, ale na všechny jsem
už úspěšně zapomněl. Nejde totiž o SW díla, která by člověk běžně používal. K jejich využití může
docházet např. jen v rámci semestrálního kurzu na nějaké VŠ.
O následujících programech, které budou uvedeny, proto platí, že jde jen o ukázky jiných
dostupných řešení. K těm jsem se dostal prostřednictvím vyhledávání ve vyhledávači Google.
Vycházel jsem přitom z názoru, že řada z lidí, kteří by podobný program sháněli, by postupovala
stejným způsobem. To je i účel vyhledávačů, aby vyhledávaly potřebné.
V souvislosti s tímto procesem, prostřednictvím kterého jsem se dostal k jiným řešením, pro
korektnost uveďme:
Škála programů, které jsem vyzkoušel, byla ovlivněna tím, jaké odkazy vyhledávač nabídl
na předních pozicích.
• U populárnějších / kvalitnějších programů je pravděpodobné, že o nich internetoví uživatelé
více píší a že na ně více odkazují. Tím se zvyšuje šance, že vyhledávač nabídne na předních
pozicích stránky, které s těmito programy souvisí.
• Běžný uživatel, který použije vyhledávač, prochází nabídnuté výsledky od první pozice
a příliš daleko v rámci vyhledaných výsledků se nedostane.
• Případné kvalitní programy, ke kterým existuje přístup přes špatně vyhledatelné stránky, mé
pozornosti nejspíše unikly.
A nyní už k několika programům, které lze považovat za typické zástupce.
•
6.1
Pokročilý editor grafů
Jde o editor, který jsem implementoval v rámci BP. Výstup programu a práce s ním se příliš
neliší. Inspiroval jsem se chováním běžných kreslících editorů. Vnitřní struktura je ale úplně jiná.
Více o BP v [2].
Kromě tohoto programu, který na ČVUT v rámci bakalářských a diplomových prací vznikl,
lze na https://dip.felk.cvut.cz/ najít i jiné práce. Ty se funkčně, co jsem si zběžně některé pročítal,
s programem vzniklým při této DP kryjí jen částečně (více či méně).
82
6.2
uDraw(Graph) 3.1
uDraw(Graph) [20] je zástupce programů, které umožňují kreslení grafů a editaci vytvořeného
obsahu, ale nelze zde simulovat grafové algoritmy. Moje uživatelské zkušenosti konkrétně
s uDraw(Graph) 3.1 jsou takové, že se mi neovládal dobře. Vykreslovaný obsah různě skákal po
obrazovce, občas se něco nevykreslilo, hrany se samy od sebe různě zalamovaly a zase narovnávaly
atd. Z mého pohledu zkrátka neintuitivní chování.
Jako plus bych zmínil přítomnost ukládání a načítání obsahu, funkcionality umožňující
činnosti jako přeorientování hran, a širokou paleta možností, jak obsah vizualizovat – např. uzel
může mít 8 různých tvarů, a ty lze ještě různě formátovat.
6.3
yEd Graph Editor 3.5
Program stejného ražení jako uDraw(Graph), ale od prvního pohledu mnohem propracovanější.
S programem se mi pracovalo příjemně a graf šlo vizualizovat mnoha způsoby. Podporovány jsou
všechny známé grafové formáty. Více v [21].
6.4
Applety
Na Internetu lze najít řadu stránek, kde jsou dostupné různé programy (povětšinou applety),
prostřednictvím kterých autoři nastiňují, jak který algoritmus funguje. Taková řešení jsou typická
tím, že je v nich graf zadán napevno a uživatel jeho strukturu nemůže nijak ovlivnit. Příklady:
•
•
6.5
http://www-b2.is.tokushima-u.ac.jp/~ikeda/suuri/main/index.shtml
http://cam.zcu.cz/~rkuzel/aplety/
Graph Magics 2.1
Program, kde lze grafy nejenom vytvářet, ale je tu dostupných i několik algoritmů, které lze nad
takovými grafy provést. Přidat si vlastní algoritmus nelze. Co se týká ovládání, tak to z mého
pohledu bylo rovněž jako v případě programu uDraw(Graph) neintuitivní. Člověk se musel
s programem poměrně dlouho seznamovat, aby se dostal k činnostem, na které ho program na
Internetu nalákal. Zdarma ke stažení je pouze 30 denní trial verze. Více viz. v [22].
6.6
Platforma NetBeans
Není to tak neznámý fakt, že NetBeans není jen vývojové prostředí (NetBeans IDE). NetBeans
je i vývojová platforma. Ta má dostupných již několik verzí. Cílem je usnadnit vývojářům tvorbu
okenních aplikací, kdy nemusí znovu programovat něco, co už je hotové – například splash screen.
V podstatě můžeme vzít NetBeans IDE a přidat do něj nějaký doplněk (IDE je postaveno na této
83
platformě). A jsou i jiné možnosti, jak platformu použít. Doplňkem by v takovém případě byl
simulátor grafových algoritmů.
Nad touto možností jsem velmi vážně uvažoval, zkoušel jsem si různé věci, které by šlo nad
platformou udělat, ale nakonec jsem se rozhodl pro vlastní řešení.
Co se týče platformy NetBeans, tak práce s ní byla z mého pohledu poměrně obtížná, člověk
si četl manuály, jak co udělat, když si takto přidal např. položku do menu, došlo k vytvoření
několika souborů, a pohled do obsáhlé dokumentace k platformě, která poskytuje veliké množství
tříd, které by člověk k ničemu nevyužil, byl až děsivý. Ve výsledku jsem si nedokázal představit, že
mi použití platformy ke zvládnutí všech 31 požadavků, které byly v kapitole 2 vyjmenovány, práci
nějak zjednoduší.
Závěrem bych ještě uvedl, že kromě NetBeans existují i jiné platformy. Např. Eclipse
Platform.
84
7
Závěr
A dostáváme se k závěru této práce. Je čas být o něco více subjektivním. A v prvé řadě musím přiznat, že jsem měl poněkud velké oči, když jsem začal na tomto projektu dělat. Člověk si vymýšlel
různé roztodivnosti, které ve výsledku nejsou až tak důležité, co by mohl program umět. Člověk si
kreslil různé diagramy, z jakých částí by se mohl program skládat a jak by tyto části mezi sebou
spolupracovaly. Následně třeba tyto diagramy byly v některých případech nahrazovány diagramy
novými, kde byl celý problém řešen o něco více obecně, a ve výsledku stres, že tohle všechno nelze
ani zdaleka stíhat.
V mých představách byl v počátcích vývoje program, který bude zvládat kde co. V tomto
programu mělo být možné grafy zobrazovat nepřebernou sadou způsobů, mělo být možné různě podrobnými způsoby simulovat chování všech aspoň trochu známých grafových algoritmů, měla být
dostupná spousta generátorů, pro generování speciálních typů grafů, a takto bych mohl pokračovat
dál.
Závěr? Zadání práce splněno, požadavky vyjmenované v části s analýzou také, ale osobní
představy nikoli. Program představil možnosti, jak všechny učiněné požadavky řešit a vytvořil pro
všechno několik ukázkových příkladů. Těch by ale dle mého názoru měla být spousta, aby se
využilo možností, které program nabízí.
Z toho důvodu se domnívám, že by budoucnost programu mohla být i taková, že by ho někdo
jiný např. v rámci své BP dále rozšířil.
7.1
Náměty na vylepšení
V případě dalšího vylepšování doporučuji se zaměřit hlavně na následující:
•
Přidání dalších tříd pro simulaci grafových algoritmů. Simulovat lze cokoli, co půjde
krokovat a vizualizovat – např. i činnost automatů.
•
Přidání dalších specializovaných generátorů. Pěkné by mohlo být vytvořit podporu předmětům vyučovaným na škole. V této souvislosti mě napadají generátory grafů diskutovaných
v předmětu Paralelní systémy a algoritmy, a vytištěné ve skriptech na Teoretickou informatiku.
•
Nabídnutí všech možných informací k právě vybranému grafu. Např. že graf je orientovaný,
acyklický, jeho průměr je 4 atd.
85
Možné jsou i další z mého pohledu celkem zajímavá vylepšení:
•
Nabízené algoritmy k simulaci by mohly býti tématizovatelné, aby se z nich lépe vybíralo.
Např. již nyní je v programu několik ukázkových algoritmů, které se různými způsoby
zabývají procházením do šířky. Ty by mohly být shluknuty pod jednu položku Procházení
do šířky.
•
V současné chvíli se výpisy toho, co simulace zrovna provádí, zapisují slovně. Co krok, to
jeden řádek v listu s výpisy. Zde by mohla vzniknout druhá možnost, která by se hodila pro
pseudokódy. Výpis toho, co se právě provádí, by v takovém řešení mohl být i na 2 řádcích,
mezi které by byly vloženy výpisy pro jiné kroky.
•
Při simulaci by se mohly používat zobrazovače obsahů využívaných front apod.
Tedy nabídka funkčností, pomocí kterých by se přehledně zobrazil obsah zadaného
pole / seznamu / proměnné.
•
Dostupná by mohla být historie změn, která by uvažovala skutečnost, že graf může být zobrazován různými způsoby.
•
Neznám možnosti Javy 3D, ale stálo by za prozkoumání, jak moc složité by bylo s její
pomocí vytvořit 3D zobrazovač grafu. Hodilo by se např. pro mřížky.
•
V panelu, ve kterém se simulují algoritmy, by mohlo existovat tlačítko pro zobrazení detailů
o simulovaném v grafu. V BP již bylo. Do DP jsem nezařazoval z časových důvodů, protože
jde jen víceméně o záležitost psaní textů, které o algoritmech pojednávají. Uživatel samozřejmě musí mít možnost vytvářet simulaci i bez takového textu.
•
Mohla by být prozkoumána možnost, že když v programu budeme mít simulace algoritmů
a k nim třeba i informační text popisovaný o bod výše, tak jestli by z toho nemohl být
generován nějaký výstup zobrazitelný i mimo okno programu. Např. HTML stránka
s appletem, který by pro simulaci algoritmu používal graf, který uživatel v době generování
výstupu používal.
•
Kdyby se to dalo využít, tak by mohla být prozkoumána i možnost, jestli by nemohl
program obsahovat nějaký objekt s funkcí serveru, který by poslouchal na zadaném portu
a přijímal případné požadavky od jiných programů, které by byly spuštěné jako klienti.
Mohlo by takto být komunikováno s programem na počítači připojeném k diaprojektoru.
86
7.2
Zhodnocení
Uvedl jsem své pocity a názory z vytvořeného programu, uvedl jsem i poměrně nemálo možností,
jak z programu učinit ještě komplexnější pomůcku, a teď bych uvedl už jen opravdu poslední
odstavec. V něm chci vyjádřit skutečnosti, že zadání pokládám za splněné a že práci na programu
pokládám za přínosnou, protože jsem se díky ní seznámil s řadou věcí a v určité míře se v nich
i procvičil. Doufám, že program bude mít svůj přínos i pro jiné a že ho třeba ještě časem někdo
vylepší.
87
8
Seznam použité literatury
[1] KOLÁŘ, Josef. Teoretická informatika. 2. vyd.. Praha : Česká informatická společnost, 2004
[2] ŠACH, Martin. Pokročilý editor grafů : bakalářská práce. Praha : ČVUT
Fakulta elektrotechnická, 2007. [online] [přístup 3. květen 2010]
Dostupné na : http://dip.felk.cvut.cz/browse/pdfcache/sachm2_2007bach.pdf
[3] Java (programovací jazyk) na Wikipedii [online] [přístup 9. květen 2010]
Dostupné na: http://cs.wikipedia.org/wiki/Java_(programovací_jazyk)
[4] Java 6 System Requirements [online] [přístup 11. květen 2010]]
Dostupné na: http://www.java.com/en/download/help/6000011000.xml
[5] Java Reflection Performance [online] [přístup 11. květen 2010] ]
Dostupné na: http://stackoverflow.com/questions/435553/java-reflection-performance
[6] Objektově orientované programování na Wikipedii [online] [přístup 9. květen 2010]
Dostupné na: http://cs.wikipedia.org/wiki/Objektově_orientované_programování
[7] SEMECKÝ, Jiří: Naučte se Javu – výjimky [online] [přístup 11. květen 2010]
Dostupné na: http://interval.cz/clanky/naucte-se-javu-vyjimky/
[8] Principy javovské grafiky a GUI [online] [přístup 11. květen 2010]
Dostupné na: http://www.kai.tul.cz/~kopetschke/java/PG2-JavaGUI1.pdf
[9] The JavaTM Tutorials: 2D Graphics [online] [přístup 9. květen 2010]
Dostupné na: http://java.sun.com/docs/books/tutorial/2d/index.html
[10] Graf (teorie grafů) na Wikipedii [online] [přístup 9. květen 2010]
Dostupné na: http://cs.wikipedia.org/wiki/Graf_(teorie_grafů)
[11] Brandes, U. - Eiglsperger, M. - Lerner, J. GraphML Primer [online] [přístup 9. květen 2010]
Dostupné na: http://graphml.graphdrawing.org/primer/graphml-primer.html
88
[12] Winter, A. - KullBach, B. - Riediger V. An Overview of the GXL Graph Exchange Language.
[online] [přístup 9. květen 2010]
Dostupné na: http://www.gupro.de/GXL
[13] XGMML Format [online] [přístup 9. květen 2010]
Dostupné na: http://gephi.org/users/supported-graph-formats/xgmml-format/
[14] Himsolt, Michael, GML: A portable Graph File Format [online] [přístup 9. květen 2010]
Dostupné na: http://www.infosun.fim.uni-passau.de/Graphlet/GML/
[15] The JavaTM Tutorials: The Reflection API [online] [přístup 9. květen 2010]
Dostupné na: http://java.sun.com/docs/books/tutorial/reflect/index.html
[16] Classpath (Java) [online] [přístup 11. květen 2010]
Dostupné na: http://en.wikipedia.org/wiki/Classpath_(Java)
[17] Document Object Model [online] [přístup 11. květen 2010]
Dostupné na: http://cs.wikipedia.org/wiki/Document_Object_Model
[18] Simple API for XML [online] [přístup 11. květen 2010]
Dostupné na: http://cs.wikipedia.org/wiki/Simple_API_for_XML
[19] The JavaTM Tutorials: Internationalization [online] [přístup 9. květen 2010]
Dostupné na: http://java.sun.com/docs/books/tutorial/i18n/index.html
[20] Universität Bremen: uDraw(Graph) – The powerful solution for graph visualization
[online] [přístup 11. květen 2010]
Dostupné na:
http://www.informatik.uni-bremen.de/uDrawGraph/en/uDrawGraph/uDrawGraph.html
[21] yEd Graph Editor [online] [přístup 11. květen 2010]
Dostupné na: http://www.yworks.com/en/products_yed_about.html
[22] GraphMagic [online] [přístup 11. květen 2010]
Dostupné na: http://www.graph-magics.com/
89
A
Seznam použitých zkratek
API – Applicaion Programming Interface
BP - Bakalářská Práce
DP – Diplomová Práce
DOM – Document Object Model
GML – Graph Modelling Language
GUI – Graphical User Interface
GXL – Graph Exchange Language
HTML – HyperText Markup Language
HW - Hardware
i18n - Internacionalization
JAR – Java Archive
JDK - Java Development Kit
JFC – Java Foundation Classes
JVM – Java Virtual Machine
OOP – Objektově Orientované Programování
OS – Operační Systém
SAX - Simple API for XML
SW - Software
XGMML – Extensible Graph Markup and Modeling Language
XML - Extensible Markup Language
90
B
Uživatelská příručka
Součástí programu je Nápověda, kde je k některým úkonům poskytnut pomocný text. V rámci
tohoto dodatku bude jen popsáno, jak program spustit:
Spouštění programu
Existuje několik možností, jak program spustit. Výpis následuje:
1. Přiložené CD obsahuje NetBeans projekt programu (z OS Windows 7). Ten si lze otevřít v
NetBeans IDE (http://netbeans.org).
2. Přiložené CD obsahuje JAR balík s názvem GraphEditor2.jar. Ten lze spouštěn několika
způsoby:
◦ Na některých OS postačí na tento soubor poklikat myší a zachová se jako běžný
spustitelný soubor.
◦ Obecně je nutné použít interpret jazyka Java – program java.exe. A to s parametrem -jar.
Jako další parametr se uvede absolutní cesta k souboru GraphEditor2. Vhodné je uvést i
absolutní cestu k programu java.exe, abychom měli jistotu, že ne nespouští např. jeho
starší verze z jiného JRE. Pro spuštění je potřebné aspoň JRE 5.0.
příklad z příkazové řádky ve Windows:
C:\java\jdk6\bin\java.exe -jar C:\GraphEditor2\dist\GraphEditor2.jar
(Nutno samozřejmě nahradit vlastní cestou k souborům. V Linuxu je spouštění
obdobné.)
91
C
Obsah CD
obsahCD.txt – dokument s tímto obsahem
readme.txt – dokument s pomocnými radami
dp.pdf – celý tento dokumentace
GraphEditor2.jar – JAR balí programu
/GraphEditor2 – Projekt z NetBeans
/B – Jiný projekt z NetBeans, který jako knihovnu využívá soubor GraphEditor2.jar. Byl diskutován
v kapitole 5.3.1.
/JRE -Instalační soubory k prostředí, přes které lze program spustit.
92

Podobné dokumenty

jarní katalog 2011

jarní katalog 2011 F.E.A.R. 3 pokračuje v odkazu zavedené značky jménem F.E.A.R. Jedná se o paranormální hororovou střílečku z pohledu první osoby, v níž dostávají prostor líté souboje zakotvené v realitě. F.E.A.R. 3...

Více

Modulární systém kurzů programovacích nástrojů Borland

Modulární systém kurzů programovacích nástrojů Borland mohou absolvovat učitelé středních průmyslových škol a gymnázií se zaměřením na informatiku a výpočetní techniku. Nezabývá se základy algoritmizace, ačkoliv jsou, zejména v několika počátečních kap...

Více

bakalářská práce - Unicorn College

bakalářská práce - Unicorn College vedením vedoucího bakalářské práce a s použitím odborné literatury a dalších informačních zdrojů, které jsou v práci citovány a jsou též uvedeny v seznamu literatury a použitých zdrojů. Jako autor ...

Více

PowerPoint 2010

PowerPoint 2010 Prezentační program má za úkol zobrazit dodatečné informace k přednášce. Dosahuje toho pomoc zobrazování textů, obrázků, animací a dalších. Poskytuje tak prezentujícímu velkou pomoc při přednášení....

Více

Program konference

Program konference Menu:hovězí s játrovou zavářkou a nudlemi, 150 g steak s panenské svíčkové, zeleninová obloha, brambory, nápoj. Obědy se vydávají oproti stravenkám, které obdržíte při registraci. Zájemce o bezmasé...

Více