Objektově orientované programování II OBOP2

Transkript

Objektově orientované programování II OBOP2
Objektově orientované programování II
OBOP2
Ing. František Huňka, CSc.
1
1 Streamy – toky dat
Třída Stream (datový proud) představuje indexovanou kolekci prvků (většinou znaků) s
indikátorem pozice. Pohyb indikátoru pozice se nedá provádět přímo (jak je tomu např. u
repetice), ale prostřednictvím aplikovaných metod. Operace se streamy dovolují prvkům,
která jsou na aktuální pozici indikátoru, aby byly zpřístupněny, nebo nahrazeny za současné
změny indikátora pozice. Při práci připomíná stream sekvenční soubor.
Streamy se používají pro práci s textovými řetězci, k realizaci vstupně výstupních operací a
pro práci se soubory.
Streamy (datové toky) jsou zevšeobecněné interní a externí textové objekty. Interní textový
objekt (text) je sekvence (repetice) znaků (char). Externí textový objekt je soubor (file)
odpovídající tradičnímu textovému souboru. Třídy stream, text a file jsou organizovány
v následující hierarchické struktuře:
stream: (# . . . #)
text: stream (# . . . #)
file: stream (# . . . #)
Stream je abstraktní nadtřída, která poskytuje všeobecné metody pro manipulaci s datovým
tokem. Při práci se streamem se zpřístupňují, dodávají další znaky na místo indikátora pozice.
Bílé znaky jsou netisknutelné znaky, patří mezi ně oddělovače, mezery, znak ukončení řádky,
ukončení stránky, vertikální tabulátor atd. Základní metody třídy stream jsou následující:
aStream.lenght
aStream.position
aStream.eos
aStream.reset
aStrean.peek->c
aStream.get->c
vrací délku streamu aStream
vrací pozici indikátoru pozice ve aStream
vrací true, je-li konec streamu, jinak false
nastaví indikátor pozice v aStreamu na 0
vrací následující znak v aStream, neposunuje indikátor pozice
vrací (čte) následující znak v aStream, posune indikátor pozice
o1
aStream.getNonBlank->c vrací první "nebílý" znak z aStream
aStream.getint->i
vrací celé číslo, přeskočí bílé znaky
aStream.getAtom->r[]
vrací další atom, to je sekvence nebílých znaků ukončena
následujícím bílým znakem
aStream.getline->r[]
čte znaky z aStream,až narazí na znak nový řádek (newline);
vrací referenci na načtený text
aStream.asInt->i
konvertuje aStream na celé číslo
c->aSteram.put
načte znak do aStream a posune indikátor pozice
aStream.newline
zapíše do aStream znak nový řádek
i->aStream.putint
zapíše do aStream celé číslo
r[]->aStream.puttext
zapíše text, který je na vstupu do aStream
r[]->aStream.putline
podobná metoda jako předchozí s tím, že se navíc přidá nový
řádek
aStream.scan(# do #)
prochází postupně přes všechny znaky aStream a aplikuje na něj
příkazy, které dále specializují metodu scan
aStream.scanAtom(# do #) prochází postupně všechny sekvence znaků oddělené bílým
znakem a aplikuje na něj příkazy dále specializující metodu
scanAtom
k->aStream.setPos
nastaví indikátor pozice v aStream na hodnotu k
2
Text stream
Text stream jako podtřída třídy Stream dědí všechny metody popsané v této třídě a navíc
deklaruje další metody. V následující tabulce jsou uvedeny nejvíce používané metody.
aText.pos
aText.empty
aText.clear
r[]->aText.append
r[]->aText.preppend
(i,j)->aText.sub->r[]
(i,j)->aText.delete
r[]->aText.less
r[]->aText.greater
aText.makeLC
aText.makeUC
r[]->aText.findAll(# do#)
vrací aktuální indikátor pozice v aText
vrací true, je-li aText prázdný
nastavuje indikátor pozice a délku aText na 0
přidá text na který odlazuje r[] na konec aText
přidá text na který odkazuje r[] na začátek aText
r bude reference na text od pozice i do pozice j v aText
maže znaky v aText od pozice i, po pozici j
testuje, zda je text na který odkazuje r "menší" než text v aText
testuje, zda je text na který odkazuje r "větší" než text v aText
převede všechny znaky v aText na malá písmena (lower case)
převede všechny znaky v aText na velká písmena (upper case)
provede příkazy, které dále specializují metodu findAll pro každý
výskyt textu na který odkazuje r v aText
Příklad1.1 ukazuje program na použití základních metod třídy Stream při práci se textem.
Příklad1.1
ORIGIN '~beta/basiclib/betaenv';
-- program: Descriptor -(# r,r1,r2: ^text; t,t1,t2: @text
do
'Nejaky text ulozime do staticke promenne t'->t;
t.reset;
'Ukazatel: '->puttext;
t.pos->putint;
newline;
1->t.pos;
loop
(# while:: (# do not (t.eos)->value #)
do t.getAtom->r[]; r[]->screen.puttext; newline
#);
newline;
t.clear;
'Novy nazev'->t.append;
t[]->puttext;
t.reset;
newline;
3->t.pos;
'FFF'->t.puttext;
t.pos->putint;
t.get->put;
t.peek->put;
newline;
'GGG'->t.append;
t[]->puttext;
'\nPozice ukazatele: '->puttext;
3
t.pos->putint; newline;
'xyxy'->t.prepend;
t[]->puttext;
'\nPozice ukazatele: '->puttext;
t.pos->putint
#)
Příklad1.2 je program, který načte zadaný textový řetězec a vypíše jednotlivá slova tohoto
řetězce a frekvenci výskytu těchto slov v zadaném textu. K ukládání a načítání slov se
používá dynamický zásobník. Každé nově načtené slovo je nejdříve testováno, zda se již
nevyskytuje v zásobníku. Pokud se vyskytuje, zvýší se čítač výskytu daného slova. Jinak se
slovo zapíše na konec zásobníku. V dynamickém zásobníku jsou jednotlivá slova ukládána do
objektů třídy slozka.
Příklad1.2
ORIGIN '~beta/basiclib/betaenv';
-- program: Descriptor -(#
CSeznam:
(#
Link: (# succ: ^Link; elm: ^slozka #);
head: ^link;
init: (# do none ->head[] #);
scan:
(# e: ^slozka; p: ^link
do
head[]->p[];
N1:
(if (p[] <> none ) then
p.elm[]->e[]; INNER scan; p.succ[]->p[]; restart N1
if)
#);
vypis:
(#
do '\n\tVypis seznamu: '->putline; scan (# do e.tisk #)
#);
insert:
(# e: ^text; r: ^link
enter e[]
do
&Link[]->R[];
&slozka[]->R.elm[];
e[]->R.elm.slovo[];
R.elm.pocet+1->R.elm.pocet;
head[]->R.succ[];
R[]->head[]
#);
find:
(# slo: ^text; r,rp: ^link
enter slo[]
do
head[]->R[];
none ->Rp[];
(if r[] = none then
slo[]->insert
else
lp:
(#
do
4
(if slo[]->r.elm.rovno then
r.elm.incr; leave lp
else
r[]->rp[];
r.succ[]->r[];
(if r[] <> none then restart lp if)
if);
slo[]->insert
#)
if)
#)
#);
slozka:
(#
slovo: ^text;
pocet: @integer;
rovno:
(# tx: ^text; b: @boolean
enter tx[]
do tx[]->slovo.equal->b
exit b
#);
incr: (# do pocet+1->pocet #);
tisk:
(#
do
'\nslovo: '->puttext;
slovo[]->puttext;
'\t pocet vyskytu: '->puttext;
pocet->putint
#)
#);
Seznam: @CSeznam;
Records: @text;
t1: ^text;
do
Seznam.init;
'to je to je a a pekny den a den pekny, novy to to je'->Records;
Records.reset;
Records[]->putline;
N1: cycle
(#
do
Records.getAtom->t1[];
t1[]->Seznam.find;
(if Records.eos then leave N1 if)
#);
Seznam.vypis
#)
Příklad 1.3 je program, který čte postupně znaky z textového souboru data1. Po každých 10-ti
načtených znacích tiskne znak tečka. Program obsahuje základní metody pro práci se soubory,
kterými jsou: přiřazení jména souboru (cesty) do atributu name, otevření souboru - metoda
openRead a uzavření souboru - metoda close. V programu musíme přidat knihovnu pro práci
se soubory, viz deklarace INCLUDE.
5
Příklad1.3
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/basiclib/file';
-- program: Descriptor -(# inFile: @file;
Ch: @char;
nc: @integer;
do
'data1'->inFile.name ; inFile.openRead; (* OPENING *)
'Reading: '->puttext;
inFile.name->putline;
Loop:
(if not inFile.eos then
inFile.get->Ch;
nc + 1->nc;
(if nc mod 10 = 0 then '.'->put if);
restart Loop
if);
newline;
nc->putint; ' characters in file'->putline;
inFile.close;
#)
Příklad1.4 je program, který rovněž počítá znaky textového souboru. Procedury
NoofArguments a ARGUMENTS zabezpečí načtení jména textového souboru. Název
textového souboru se zadá spolu s názvem programu. Je-li název programu např. Count1 a
název datového souboru např. datax, pak při spuštění zadáme:
count1 datax
Příklad1.4
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/basiclib/file';
-- program: Descriptor -(# inFile: @file;
Ch: @char;
nc: @integer;
do
(if NoofArguments = 2 then
2->ARGUMENTS->inFile.name ;
inFile.openRead; (* OPENING *)
'Reading: '->puttext;
inFile.name->putline;
Loop:
(if not inFile.eos then
inFile.get->Ch;
nc + 1->nc;
(if nc mod 10 = 0 then '.'->put if);
restart Loop
if);
newline;
nc->putint; ' characters in file'->putline;
inFile.close;
else
'Missing Arguments'->putline;
if)
#)
6
Příklad1.5 je program, který podle pokynu uživatele počítá buď počet znaků v zadaném
textovém souboru, nebo počet řádek tohoto souboru.
Příklad1.5
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/basiclib/file';
-- program: Descriptor -(# inFile: @file;
Ch: @char;
nc: @integer;
answer: ^text;
lines, chars: @Boolean;
do
(if NoofArguments
// 2 then
2->Arguments->inFile.name ;
inFile.openRead; (* OPENING *)
'Count what in \''->Puttext;
inFile.name->PutText;
'\' (lines/chars)? '->PutText;
GetLine->answer[]; (* read from keybord - what the user types *)
(if true
//('lines'->answer.equal) then true->lines;
//('chars'->answer.equal) then true->chars;
else
'Unknown input'->PutLine;
Stop; (* end execution *)
if);
Loop:
(if inFile.eos//false then
inFile.Get->Ch;
(if true
//lines then (if Ch//ascii.newline then nc + 1->nc if);
//chars then nc + 1->nc;
if);
restart Loop
if);
NewLine;
nc->PutInt;
(if true
//lines then ' lines '->PutText;
//chars then ' characters '->PutText;
if);
'in file \''->Puttext;
inFile.name->PutText;
'\'\n\n'->PutText;
inFile.close;
else
'Missing Argument'->putline;
if)
#)
Třídy Stream, Text a File.
7
Pomocí streamů se realizují vstupně / výstupní operace. Abstraktní třída Stream poskytuje
základní atributy a metody pro své podtřídy text a file. Indikátor pozice označuje aktuální
pozici ve streamu, na které se provádějí požadované operace.
S využitím uvedených příkladů zjistěte, v čem se liší práce s texty od práce s datovými
soubory? Jak se liší metody peek, get a put?
Prakticky si ověřte při práci s textem metody append prepend a puttext a jejich účinek na
indikátor pozice.
Upravte příklad1.2 tak, aby vstupem mohl být textový soubor.
V příkladě1.2 musíte:
• přidat knihovnu pro práci se soubory;
• deklarovat Records: @ file;
• načíst jméno souboru a přiřadit do atributu name; otevřít soubor;
• na konci soubor uzavřít.
Jak vidíte práce s texty a se soubory je velmi podobná. Při práci se soubory musíte soubor
vždy v úvodu otevřít a na konci zavřít. Vlastní operace mají díky polymorfismu stejná jména
(liší se příjemce zprávy, instance třídy text, nebo instance třídy file).
8
2 Části objektů a referenční atributy
Klasifikace a kompozice jsou fundamentální prostředky pro chápání reálného světa.
Klasifikace je prostředek pomocí kterého vytváříme a rozlišujeme mezi odlišnými třídami
jevů a konceptů. Kompozice je prostředek pomocí kterého chápeme jevy a koncepty jako
složky jiných jevů a konceptů.
Části objektů - part objects
Jako příklad si uvedeme popis dřevěného panáčka (StickMan). Třída StickMan je složena z
mnoha dalších objektů. Tyto objekty jako např. theHead, theBody atd. jsou části celku
(objektu třídy StickMan). Následující kód popisuje uvedenou deklaraci:
StickMan:
(#
theHead: @Head;
theBody: @Body;
LeftArm,RightArm: @Arm;
LeftLeg,RightLeg: @Leg;
move: (# (* ... *) #);
draw: (# (* ... *) #);
clear: (# (* ... *) #);
(* ... *)
#);
Head: (# (* ... *) #);
Body: (# (* ... *) #);
Arm: (# theHand: @Hand; (* ... *) #);
Leg: (# theFoot: @Foot; (* ... *) #);
Hand: (# wave: (# #); (* ... *) #);
Foot: (# bigToe: @Toe; (* ... *) #);
Toe: (# wriggle: (# #); (* ... *) #)
Vytvořením instance Joe můžeme s panáčkem manipulovat následujícím způsobem:
Joe: @StickMan
Joe.move;
Joe.wave;
Joe.LeftLeg.theFoot.bigToe.wringle;
Operace move může být provedena vyvoláním odpovídajících operací jednotlivých částí:
move:
(# pos: @point
enter pos
do
pos->theHead.move;
pos->theBody.move;
pos->LeftArm.move;
pos->RightArm.move;
pos->LeftLeg.move;
pos->RightArm.move;
V některých situacích může být výhodnější "šířit" operace částí na celý objekt. Operace
wriggle (pokrčení prstu) může být šířena definováním operace LeftBigToeWriggle na třídě
StickMan:
9
LeftBigToeWriggle: (# do LeftLeg.theFoot.BigToe.wriggle #)
Nezávislé a závislé části
Části jsou nezávislé na celkovém objektu ve smyslu, že jsou instancemi vzorů definovaných
bez znalostí toho, že budou nějakými částmi objektu (celku). V některých situacích by bylo
výhodné, aby definice části měla znalost o celém složeném objektu (celku). Toto řešení
existuje umístěním definice vzoru dovnitř celého objektu. Tímto způsobem je pak možné se
odkazovat na atributy celkového objektu z jeho částí.
Na příkladě ukážeme, jak je možné využít části objektu definovaného pomocí virtuálních
procedur.
Address:
(#
Street: @text;
StreetNo: @integer;
Town,Country: @text;
printLabel:<
(# do INNER ; (* print Street, StreetNo, Town, Country *) ;
#)
V takovéto definici adresy není rozhodnuté, zda je to adresa osoby (Person), nebo společnosti
(Company). Z toho důvodu je operace printLabel deklarovaná jako virtuální. Na následujícím
příkladě je uvedeno, jak může být třída Address použita k definování adres osob a společností:
Person:
(#
name: @text;
adr: @Address (# printLabel::< (#
#);
Company:
(#
name,director: @text;
adr: @Address
(# printLabel::< (#
do (* print name *)
do (* print name and director *)
#) #);
#) #);
#)
Atribut adr části objektu Person je definován jako singulární instance třídy Address. Virtuální
procedura printLabel odkazuje na jméno (name) konkrétní Osoby.
Atribut adr části objektu Company je definován jako singulární objekt třídy Address.
Virtuální procedura printLabel odkazuje v tomto případě na jméno a ředitele společnosti.
Odkazy na části objektů
Na části objektů mohou být odkazy. Mějme následující objekty:
P: ^Person; C: ^Customer; A1, A2: ^Address;
Nyní je možné přiřadit referenci na adresovou část objektů P a C k objektům A1 a A2
následovně:
P.adr[]->A1[]; C.adr[]->A2[];
10
To může být vhodné při zpracování objektů stejných částí. Mějme:
Areg: @Register(# content::< Address #);
Areg je registr obsahující seznam adres objektů. Do tohoto seznamu je možné zařadit
libovolný objekt, který má část Address.
P.adr[]->Areg.insert;
C.adr[]->Areg.insert;
Části objektů versus podvzory
Jistě jste si všimli, že je možné zacházet s objekty třídy Person a objekty třídy Company jako
s objektem třídy Address, což může být technicky možné. Můžeme deklarovat třídu Person a
třídu Company jako podtřídy třídy Address:
Person: Address
(# name: @text; printLabel::< (# do (* print name *) #); #);
Company: Address
(#
name,director: @text;
printLabel::< (# do (* print name and directory *) #);
#)
Ačkoli uvedené řešení bude pracovat stejným způsobem, je zde hlavní rozdíl s ohledem na
modelování. Když užíváme části objektů uvažujeme, že třída Address je aspekt třídy Person a
Company. Když použijeme podtřídy, uvažujeme, že třída Person a Company jsou
klasifikovány jako adresovatelný objekt. Obě řešení jsou správná v konkrétním případě závisí
na skutečné situaci, kde mají být objekty použity.
Referenční atributy
V této části se budeme zabývat použitím atributů jako dynamických referencí pro modelování
referenční kompozice. Tato kompozice se používá k popisu složených objektů, jejichž části
nejsou fyzicky přítomny v celkovém objektu jako to bylo v předešlé kapitole. Princip
referenční kompozici si ukážeme na příkladu tříd Muž a Žena. Mohou existovat celkem čtyři
možnosti. První spočívá v tom, že pouze ve třídě Muž je reference na třídu Žena. Druhá
možnost je opačná, tedy pouze ze třídy Žena je reference na třídu Muž. Třetí případ spočívá
ve vzájemné referenci. Tedy v obou třídách je reference vždy na tu druhou třídu. A konečně
čtvrtá možnost je, že existuje nezávislá třída, např. registr svateb, kde je reference jak na třídu
Žena, tak na třídu Muž. Vše si nyní ukážeme na názorných příkladech.
Příklad2.1 ukazuje referenci ze třídy Muz na třídu Zena.
Příklad2.1
(#
Muz:
(#
jmeno: @text;
rZ: ^Zena;
11
print:
(#
do
'\nMuz: jmeno: '->puttext;
jmeno[]->puttext;
'\nReference zena jmeno: '->puttext;
rZ.jmeno[]->puttext
#)
enter (jmeno,rZ[])
#);
Zena:
(#
jmeno: @text;
print: (# do '\nZena jmeno: '->puttext; jmeno[]->puttext #)
enter jmeno
exit THIS(Zena)[]
#);
M1,M2: @Muz;
Z1,Z2: @Zena
do
'Jana'->Z1;
'Dana'->Z2;
('Jan',Z2)->M2;
('Karel',Z1)->M1;
M1.print;
M2.print;
Z1.print;
Z2.print;
(if Z1[] = Z1 then
'\Ano jsou si rovny'->puttext
else
'\nNe neni shoda'->puttext
if)
#)
Příklad2.2 zobrazuje skutečnost, kdy ze třídy Dvojice je reference jak na třídu Muz, tak na
třídu Zena. Objekty třídy dvojice jsou uloženy do registru.
Příklad2.2
(#
Muz:
(#
jmeno: @text;
tisk: (# do 'Muz jmeno: '->puttext; jmeno[]->puttext #)
enter jmeno
#);
Zena:
(#
jmeno: @text;
tisk: (# do 'Zena jmeno: '->puttext; jmeno[]->puttext #)
enter jmeno
#);
Dvojice:
(#
rM: ^Muz;
rZ: ^Zena;
datum: @text;
tisk:
(#
do
12
'\nTisk dvojice: '->puttext;
rM.tisk;
rZ.tisk;
' datum: '->puttext;
datum[]->puttext;
newline
#)
enter (rM[],rZ[],datum)
#);
Registr:
(#
dv: [10] ^Dvojice;
top: @integer;
insert:
(# p: ^Dvojice
enter p[]
do (if top+1 <= dv.range then top+1->top; p[]->dv[top][] if)
#);
tisk:
(# p: ^Dvojice
do (for i: top repeat dv[i][]->p[]; p.tisk for)
#)
#);
m: ^Muz;
z: ^Zena;
d: ^Dvojice;
r: @registr
do
&Muz[]->M[];
&Zena[]->Z[];
&Dvojice[]->D[];
'Karel'->M;
'Miroslava'->Z;
(M[],Z[],'12.10.1946')->d;
d[]->R.insert;
&Muz[]->M[];
&Zena[]->Z[];
&Dvojice[]->D[];
'Adam'->M;
'Eva'->Z;
(M[],Z[],'14.12.1920')->d;
d[]->r.insert;
r.tisk
#)
Celek, části objektů, referenční atributy.
Celek může být popsán jako kompozice z částí objektů. Na části objektů mohou odkazovat
referenční atributy a mohou z nich vytvořit např. seznam. Při modelování aplikační domény
můžeme využít také klasifikaci.
13
Jak označujeme atribut adr ve výrazu:
adr: @Address (# printLabel::< (#
do (* print name *)
#) #)
Jak byste upravili deklaraci třídy Address, v kapitole "Nezávislé a závislé části", kdyby jste
chtěli mít možnost u vybraných objektů této třídy ještě doplnit atribut telefonní_číslo?
Pomocí celku a části objektů deklarujte třídu auto, které má volant, karoserii, motor a 4 kola.
( # auto:
volant: @ CVolant;
motor: @ CMotor;
karoserie: @ CKaroserie;
kola: [4] @CKolo;
#};
CVolant: (# .... #);
CKaroserie: (# ... #);
CMotor: (# ... #);
CKolo: (# ... #);
Části objektů a referenční atributy mají velký význam při modelování aplikační domény.
Části objektů mohou být do jisté míry nezávislé na celku.
14
3 Vzorové proměnné a perzistence
Vezměme nyní příkaz &P. Jak jsme se již dozvěděli, tímto příkazem se vytvoří a spustí nová
instance vzoru pojmenovaného P. Pokud je P deklarováno jako definice vzoru
P:(# ... #)
pak je jednoznačně určeno, jaký vzor se pod názvem P skrývá. Pokud je P deklarováno jako
definice virtuálního vzoru, například
T: (# P:< R #)
pak může P znamenat různé vzory (omezené však na podvzory vzoru R) podle toho, jak je P
rozšiřováno v podvzorech vzoru T.
Navíc, v jazyce BETA, je k dispozici mechanismus zvaný vzorová proměnná (pattern
variable) pro vytváření proměnných, které mohou odkazovat na různé vzory, jež jim mohou
být přiřazovány za běhu programu.
Deklarací
S: ##P
definujeme vzorovou proměnnou S kvalifikovanou vzorem P. Vzorové proměnné mohou být
přiřazovány libovolné vzory (nikoli instance), které jsou podvzory vzoru P (nebo sám P).
Máme-li podvzory
P1: P(# ... #);
P2: P(# ... #)
pak
P1## -> S##
přiřadí vzor P1 do vzorové proměnné S. Příkaz &S potom vytvoří a spustí instanci vzoru P1,
ale provedeme-li
P2## -> S##
pak &S vytvoří a spustí instanci vzoru P2.
Získání struktury objektu
V jazyce BETA je pro libovolný objekt možné získat odkaz na jeho strukturu, tj. na strukturu
podle níž byl vytvořen. Pokud se nejedná o Singulární objekt, získáme takto odkaz na vzor,
jehož je objekt instancí. Odkaz na strukturu získáme
R##
kde R je odkaz na objekt.
Mějme
R1: @P;
R2: @P(# ... #);
R3: ^P
pak R1## je vzor P (odkaz na vzor P lze jinak získat zápisem P##), R2## je struktura
P(# ... #) a R3## je odkaz na příslušnou strukturu, podle toho na jaký objekt R3 právě
odkazuje.
15
Relační operátory
Pro vzorové proměnné jsou definovány relační operace. Například
S## = R3## - levá i pravá strana jsou stejný vzor
S## < R3## - levá strana je podvzor pravé strany
S## <= R3## - levá strana je podvzor pravé strany nebo jsou ob ě stejný vzor
Jednoduchý příklad použití ukazuje jak lze snadno zjistit, zdali je nějaký virtuální vzor
v aktuální instanci rozšířen, či nikoli:
T: (# V:< P;
P:(# do ... INNER; ...
(if V##=P## then
(* ve vzoru (podvzoru T) jehož instancí je
aktuální objekt není virtuální vzor V dále specializován *)
#)
#)
if)
Možnosti vzorových proměnných
Prostřednictvím vzorových proměnných lze se vzory provádět tytéž operace jako s hodnotami
jiných typů, lze je přiřazovat proměnným, předávat jako parametry procedurám či vracet jako
hodnoty funkcí.
Pomocí vzorových proměnných je možno měnit chování objektů za běhu programu.
Použijeme-li vzorovou proměnnou jako atribut nějakého vzoru, lze dosáhnout různé funkce
tohoto atributu v různých instancích vzoru a dokonce tento atribut zcela předefinovat.
Například:
T: (# S:##P; ... #);
Xl,X2: @T;
do P1## -> X1.S##;
P2## -> X2.S##
má každá instance vzoru T atribut S jinak specializován (může se jednat i o plné
předefinování, neboť P může být objekt). Za běhu programu lze dále libovolně měnit chování
objektů, například takto:
P2## -> X1.S##
Příklad3.1 je program, který popisuje využití vzorových proměnných. F1 až F5 jsou
deklarované funkce, které se pomocí vzorové proměnné předávají do třídy vypocet resp. třídy
vypocet1.
Příklad3.1
ORIGIN '~beta/basiclib/v1.6/betaenv';
INCLUDE '~beta/basiclib/v1.6/math';
-- program: Descriptor -(#
Zn1: (# x,y: @integer enter x do INNER exit y #);
Zn2: Zn1 (# z: @integer enter z do INNER #);
F1: Zn1 (# do x->log->y #);
F2: Zn1 (# do (x,2)->pow->y #);
16
F3: Zn1 (# do x->abs->sqrt->y #);
F4: Zn2 (# do (x*x+z*z)->sqrt->y #);
F5: Zn2 (# do (x*x-z*z)->abs->sqrt->y #);
vypocet:
(# p: ##Zn1; a: @integer enter (p##,a) do a->p->putint; newline #);
vypocet1:
(# p: ##Zn2; a,b: @integer
enter (p##,a,b)
do (a,b)->p->putint; newline
#)
do
newline;
newline;
(F1##,21)->vypocet;
(F2##,16)->vypocet;
(F3##,25)->vypocet;
(F4##,4,3)->vypocet1;
(F5##,9,12)->vypocet1
#)
Perzistentní objekty
Perzistence je rovněž jedna z důležitých vlastností požadovaných pro objektově orientované
databázové systémy. Perzistence by měla splňovat následující požadavky:
•
•
•
Ortogonální k typům, to znamená že jakákoli data jakéhokoli typu mohou být vytvořena
jako perzistentní v libovolném čase.
Transparentní, to znamená, že manipulace s perzistentními a transientními objekty
probíhá stejným způsobem.
Nezávislá, to znamená, že objektový manažer OODBMS by měl automaticky poskytovat
operace explicitního čtení a zápisu objektů z disku a na disk, což je nezbytné pro podporu
integrity dat.
Pokud perzistence splňuje výše uvedené podmínky, hovoříme o ortogonální perzistenci.
Implicitně platí, že je-li objekt vytvořen jako perzistentní, všechny objekty, které mohou být
dosaženy přes reference z tohoto objektu, jsou také uloženy jako perzistentní. Množina
objektů, které mohou být dosaženy z objektu tímto způsobem, se nazývá tranzitivní uzávěra
(transitive closure) objektu.
Persistentní objekty jsou uloženy v persistentní paměti, což je kolekce perzistentních objektů.
V dané perzistentní paměti může být uložen jeden, nebo více perzistentních objektů.
Persistentní paměť má své jméno.
Perzistentní programovací jazyky, které můžeme označit za první krok k objektově
orientovaným databázím, dovolují aplikačnímu programátorovi vyjádřit perzistenci ve
výrazech konceptů programovacího jazyka.
Další krok ve vývoji databázové technologie již zavádí objektově orientované databáze, kde
byly myšlenky perzistentních programovacích jazyků rozšířeny o tradiční databázové
koncepty a funkcionalitu.
Perzistentní programovací jazyky splňují pouze základní (podstatné) funkce objektově
orientovaných databází programovacích jazyků. Perzistentní programovací jazyky nejsou
určeny ani k tomu, aby zahrnovaly všechny aspekty funkcionality databází, ani k tomu, aby
17
umožňovaly přístup k perzistentním datům z několika různých programovacích jazyků.
Hlavním záměrem perzistentních programovacích jazyků je umožnit sdílení datových
objektů.
Perzistentní objekt může být sdílen mezi dvěma nebo více běhy aplikací. Ačkoli tradiční
objektově orientované systémy jsou jednouživatelské systémy, charakteristiky objektově
orientovaných programovacích jazyků vytvářejí vše potřebné pro použití takových jazyků v
oblastech, kde je sdílení přirozené.
Vlastnost perzistence spočívá v možnosti přidělit objektům modelu libovolnou dobu života
vyžadovanou aplikací.
Perzistence v BETĚ
Programovací jazyk BETA je příkladem perzistentního programovacího jazyka. Z hlediska
objektově orientovaných databázových architektur patří tyto jazyky do třídy objektově
orientovaných databázových programovacích jazyků, které jsou rozšířeny o základní
databázové funkce.
Hlavním cílem perzistentních programovacích jazyků je umožnit sdílení objektů dat.
Perzistentní objekt může být sdílený mezi dvěma a více programovými běhy, nebo různými
programy.
Na jakýkoli běžící program v BETĚ se můžeme dívat jako na graf objektů. Uzly v grafu jsou
objekty a spojnice mezi uzly jsou reference mezi objekty. Perzistentní systém v BETĚ pracuje
s granularitou uzlů v grafu a dovoluje manipulovat grafem objektů, který je mnohem větší,
než dostupné množství rezidentní paměti. Perzistentní systém automaticky zpřístupňuje
objekty (uzly) z perzistentní paměti, když jsou potřebné a přemisťuje je zpět do diskové
paměti, pokud je paměť počítače zaplněná.
Při práci s perzistentními objekty musíme určit textové jméno dané perzistentní paměti. Dále
určíme perzistentní kořen, což je objekt, který může být zpřístupněn (dosažen při přístupu).
Jedna perzistentní paměť může obsahovat více než jeden perzistentní kořen.
Použitím perzistentní paměti můžeme zvýšit rozsah zpracovaných dat (počet objektů), protože
většina z dat zůstane v druhotné paměti, zatímco v paměti počítače je obsažena jen malá část
potřebných dat.
V zásadě jakýkoli objekt v Betě může být persistentní. Perzistence v Mjolner BETA System
je založena na modelu dostupnosti (reachability model), který znamená, že standardní chování
při ukládání objektů do druhotné paměti je uložit všechno co je dostupné z daného objektu.
Není nutné, aby perzistentní objekty, které na sebe vzájemně odkazují, byly uloženy v jedné
perzistentní paměti. Je možná i varianta viz obrázek, kde perzistentní objekty jsou uloženy ve
dvou perzistentních pamětech.
Ve stávající verzi je to název adresáře obsahující soubory vytvořené persistentní pamětí.
Samotná persistentní paměť je objekt v BETĚ s řadou atributů.
V perzistentní paměti se můžeme přímo odkazovat pouze objekt, který je označený jako
perzistentní kořen (persistent root). K označení objektu jako perzistentní kořen se používá
metoda put. Persistentní kořen musí mít logické jméno ve formě textového řetězce. V době
18
checkpointu jsou všechny objekty dosažitelné z persistentního kořenu uloženy v persistentní
paměti.
Pro perzistentní objekty můžeme s výhodou použít již dříve zmiňovanou knihovnu Container
Library.
Základní operace s perzistentními pamětmi:
•
create, openRead, openWrite se používají pro vytvoření, a otevření perzistentní paměti,
aby byl zpřístupněn jejich obsah. Při otevření perzistentní paměti pouze pro čtení, není
možné měnit obsah persistentní paměti, i když objekty načtení do operační paměti mohou
být aktualizovány (ale pouze jen v operační paměti).
•
put označí objekt, který se stane perzistentním kořenem. Nemá žádný vliv na obsah
perzistentní paměti, ale registruje objekt, který bude v budoucnu uložen do druhotné
paměti během operace checkpopint.
•
get zpřístupní objekt z perzistentní paměti. Zadaná proměnná bude odkazovat na
zpřístupněný objekt. V této metodě musí být ještě dodán parametr, vzorová proměnná,
podle které je pak daný objekt vytvořen.
•
checkpoint uloží stav perzistentních objektů do druhotné paměti. Všechny objekty, které
mají odkazy na daný perzistentní kořen, jsou uloženy také. Metoda nemá žádný vliv na
perzistentní paměti otevřené pomocí metody openRead.
•
close uzavírá uvedenou perzistentní paměť. Uvedená metoda nejdříve implicitně vyvolá
metodu checkpoint.
Příklad3.2 ukazuje použití perzistentních objektů pro uložení jednoduchých objektů. Vše se
ukládá do jedné perzistentní paměti theStore. Uvedený program pouze ukládá datové objekty
do perzistentní paměti.
Příklad3.2
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/persistentstore/persistentstore'
'~beta/basiclib/numberio';
-- program: Descriptor -(#
ch: ^charObject;
r: ^realObject;
i: ^integerObject;
theStore: @persistentstore
do
&charObject[]->ch[];
&realObject[]->r[];
&integerObject[]->i[];
28->i;
36.56->r;
'f'->ch;
theStore.init;
19
'testCisel'->theStore.create;
(ch[],'znakRoot')->theStore.put;
(r[],'rCisloRoot')->theStore.put;
(i[],'iCisloRoot')->theStore.put;
theStore.close
#)
Příklad3.3 navazuje na předešlý příklad a pouze čte (znovu obnovuje) uložené objekty
z perzistentní paměti.
Příklad3.3
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/persistentstore/persistentstore'
'~beta/basiclib/numberio';
-- program: Descriptor -(# a: ^charObject; b: ^realObject; c: ^integerObject; ps: @persistentstore
do
ps.init;
'testCisel'
->ps.openWrite
(#
notFound::< (# do '\nChyba persistent store '->puttext; stop #)
#);
('znakRoot',charObject##)
->ps.get
(#
notFound::<
(# do '\nZnakRoot chyba '->puttext; ps.close; stop #)
#)->a[];
('rCisloRoot',realObject##)
->ps.get
(#
notFound::<
(# do '\nrCisloRoot chyba '->puttext; ps.close; stop #)
#)->b[];
('iCisloRoot',integerObject##)
->ps.get
(#
notFound::<
(# do '\niCisloRoot chyba '->puttext; ps.close; stop #)
#)->c[];
'\nUlozena data: '->puttext;
'\nznak: '->puttext;
a->put;
' rCislo: '->puttext;
b->putreal;
' iCislo: '->puttext;
c->putint;
ps.close
#)
Příklad3.4 je program, který jak zapisuje, tak i čte objekty s perzistentní paměti. Ke své
činnosti využívá odpovídající virtuální metodu notFound, ve které se ošetří výjimky.
Příklad3.4
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/persistentstore/persistentstore'
'~beta/basiclib/numberio';
-- program: Descriptor -(#
a: ^charObject;
20
b: ^realObject;
c: ^integerObject;
ps: @persistentstore;
zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #)
do
ps.init;
'testCisel'
->ps.openWrite
(# notFound::< (# do 'testCisel'->ps.create; true->continue #)
#);
('znakRoot',charObject##)
->ps.get
(#
notFound::<
(#
do
&charObject[]->a[];
'$'->a;
(a[],'znakRoot')->ps.put;
a[]->theObject[];
true->continue
#)
#)->a[];
('rCisloRoot',realObject##)
->ps.get
(#
notFound::<
(#
do
&realObject[]->b[];
0->b;
(b[],'rCisloRoot')->ps.put;
b[]->theObject[];
true->continue
#)
#)->b[];
('iCisloRoot',integerObject##)
->ps.get
(#
notFound::<
(#
do
&integerObject[]->c[];
0->c;
(c[],'iCisloRoot')->ps.put;
c[]->theObject[];
true->continue
#)
#)->c[];
'\nUlozena data: '->puttext;
'\nznak: '->puttext;
a->put;
' rCislo: '->puttext;
b->putreal;
' iCislo: '->puttext;
c->putint;
'\nZadavani novych dat a, n: '->puttext;
(if getNonBlank
// 'a' then
'\nZnak: '->puttext;
zn;
21
get->a;
zn;
'\nrCislo: '->puttext;
getreal->b;
zn;
'\niCislo: '->puttext;
getint->c;
zn
if);
ps.close
#)
Vzorové proměnné a jejich samostatné použití a použití v perzistentních objektech.
Vzorové proměnné umožňují získat strukturu daného objektu. Pomocí nich můžeme
rozhodnout, zda dva objekty jsou instancí stejné třídy. Další možnosti vzorových proměnných
je předávat tyto proměnné jako parametr do volané procedury a tím měnit její funkčnost.
Perzistentní objekty jsou objekty, které přežívají aplikaci, která je vytvořila.
Jaké jsou možnosti vzorových proměnných?
Jak jsou využity vzorové proměnné při zpřístupnění perzistentních objektů z perzistentní
paměti?
Jaké jsou základní požadavky na perzistenci?
22
•
•
•
Ortogonální k typům, to znamená že jakákoli data jakéhokoli typu mohou být vytvořena
jako perzistentní v libovolném čase.
Transparentní, to znamená, že manipulace s perzistentními a transientními objekty
probíhá stejným způsobem.
Nezávislá, to znamená, že objektový manažer OODBMS by měl automaticky poskytovat
operace explicitního čtení a zápisu objektů z disku a na disk, což je nezbytné pro podporu
integrity dat.
Vzorové proměnné rozšiřují možnosti tradičních proměnných. Jsou také využity při operaci
get v perzistentních objektech. Objektově orientované jazyky mají nějaké prostředky jak se
dostat ke struktuře třídy daného objektu.
23
4 Zpracování výjimek
Velká část návrhu programového vybavení zahrnuje zpracování chybových situací, nebo
vzácných situací, které s velkou pravděpodobností nenastanou. Mějme například program
Register, uvedený v první části tohoto textu. Register může obsahovat až 100 celých čísel.
Chceme-li vložit další celé číslo pomocí metody insert, nastane problém tzv. přetečení
(overflow). V tomto místě by měl existovat nějaký kód programu, který by vyřešit uvedenou
situaci. Pro malé programy je běžné vytisknout chybovou zprávu a ukončit běh programu.
Uživatel pak musí zvětšit rozsah repetice (100), přeložit program a znova ho spustit.
Pro většinu složitějších programů je nemožné ukončit svůj běh s chybovou zprávou.
Uvažujme situaci, kdy uvedený Register je částí textového editoru. Chybová zpráva která
říká, že nějaký vnitřní register přetekl, není ve všeobecnosti pro uživatele takového textového
editoru moc smysluplná. Navíc uživatel nebude schopen zvýšit rozsahy těchto tabulek. Jiným
příkladem může být systém pro rezervaci letenek Takový systém běží dlouhodobě a není
přijatelné, aby přetečení nějaké tabulky jednoduše vyústilo v chybovou zprávu a následné
ukončení programu.
Program využívající třídu Register může samozřejmě vést v patrnosti, kolik prvků je uloženo
v objektu Register a následně provést nějaké speciální operace, pokud má být vloženo více
než 100 prvků. To může samozřejmě zahrnovat to, že program je velmi komplikovaný,
protože obsahuje navíc příkazy, které testují tyto vzácné situace. Ideální by bylo oddělit kód
pro zpracování vyjímečných situací od kódu pro zpracování normálních situací.
Existuje mnoho typů chyb nebo vyjímečných situací, které by program měl umět zpracovat.
Třída exception (výjimka) je třída výpočetních stavů, které vyžadují speciální výpočty. Není
možné dát přesnou definici kdy je výpočetní stav klasifikován jako stav výjimky; toto
rozhodnutí je na programátorovi. Vyjímečné situace jsou takové situace, které zahrnují hlavní
výpočetní chyby. Následuje seznam příkladů situací, které jsou považovány za vyjímečné:
Přetečení tabulky (overflow of tables). Příklad popisující tuto výjimku je Register. Mnoho
programových systémů s pevně deklarovanými tabulkami může přetéct.
Nedefinované výsledky (undefined results). Procedura nebo funkce nemusí mít dobře
definované výsledky pro všechny možné vstupní parametry. Jedním z příkladů může být
dělení nulou, dalším inverze singulární matice.
Nesprávné použití uživatelem (incorrect usage by the user). Uživatel, který používá program
nebo aplikaci nesprávným způsobem, dostává nedefinované výsledky. Nejčastější chybou
uživatele je zadání špatné vstupní hodnoty. V jistých situacích se toto může vyřešit jako
výjimka, ale v mnoha případech je kompletní vstup podroben kontrole.
Nekonzistentní data (inconsistent data). Programy často komunikují prostřednictvím
souborů. Jeden program zapisuje data do souboru a druhý je čte. Datový soubor může být
nekonzistentní z řady důvodů: v prvním programu mohou být chyby, nějaký jiný program
může manipulovat s datovým souborem, chybně může být použit jiný soubor jako vstup pro
druhý program, atd.
Chyby operačního systému (Operating system errors). Uvažujme textový editor. Když je
dokument ukládán na disk, může nastat chyba nedostatek místa na disku. Uživatel se může
24
pokusit otevřít dokument (soubor), který nemůže být interpretován jako soubor pro textový
editor. Uživatel se může pokusit modifikovat dokument vlastněný jiným uživatelem. Pokud
textový editor nezabezpečí zpracování těchto situací, operační systém ukončí textový editor s
chybovou zprávou, která nebude pro uživatele smysluplná.
Chyby definované jazykem (language defined errors). Chyby za běhu programu (run time
errors) jako chyby indexu a pokusy použít referenci na NONE se mohou objevit i v dobře
testovaných programech. Protože se takové situace stávají z důvodů chyb v programech, je ve
všeobecnosti těžké znovu pokračovat v programu po takové chybě. Avšak program by měl
alespoň ukončit svoji činnost s chybovou zprávou.
Numerické chyby. Počítače podporují pouze čísla s daným rozsahem číslic. To znamená, že
výsledek jakékoli aritmetické operace může vyústit v číslo, které nemůže být reprezentováno
hardwarem počítače. Tento druh aritmetického přetečení je příkladem výjimky.
Program musí být schopen si poradit s výjimkami. V BETĚ je zpracování výjimek řešeno
prostřednictvím virtuálních vzorů. Mechanismus zpracování výjimek by měl poskytovat
následující podporu:
•
Smysluplné chybové zprávy, které stručně a zřetelně oznámí o co se jedná.
•
Znovuobnovení běhu programu. Program by měl mít možnost v některých situacích znova
obnovit svůj běh po zpracování výjimky. V jiných případech kdy znovu obnovení není
možné, by měl ukončit svoji činnost či se restartovat.
•
Oddělení řízení od zpracování výjimek. Kód pro zpracování výjimek by měl být oddělený
od kódu zpracovávající normální běh programu, aby nerušil srozumitelnost programu.
Další užitečnou vlastností programu by měla být explicitní indikace možných výjimek.
Jednoduché výjimky
Uvažujme příklad Register, uvedený v předchozích částech. V tomto příkladu existují dvě
výjimky. Přetečení (overflow) je inicializováno když nestačí zadaná kapacita Registru, a
notFound je inicializováno když nastane pokus rušit položku, která není v Registru. Obě tyto
výjimky reprezentují dvě různé třídy výjimek:
•
•
výjimka přetečení je fatální ve smyslu, že není pravděpodobně smysluplné pokračovat v
běhu programu;
výjimka notFound je pouze chyba, která by neměla ukončit program.
Obě uvedené výjimky (overflow a notFound) jsou podtřídami třídy Exception, která popisuje
všeobecné vlastnosti výjimky.
Register:
(#
Table: [100] @integer;
25
Top: @integer;
Init: (# do 0->Top #);
Has: (* Test if Key in Table[1: Top] *)
(# Key: @integer; Result: @boolean;
enter Key
do (* ... *)
exit Result
#);
Insert: (* Insert New in Table *)
(# New: @integer
enter New
do
(if not (New->&Has) then (* New is not in Table *)
Top+1->Top;
(if Top <= Table.Range then
New->Table[Top]
else
Overflow (* An Overflow exception is raised *)
if)
if)
#);
Remove: (* Remove Key from Table *)
(# Key: @integer
enter key
do
Search:
(#
do
(for inx: Top repeat
(if Table[inx] = Key then (* remove key *) leave Search if)
for);
key->NotFound;
(* A NotFound exception is raised *)
#)
#);
Overflow:< Exception
(# do 'Register overflow'->msg.Append; INNER #);
NotFound:< Exception
(# key: @integer
enter key
do key->msg.putInt; ' is not in register'->msg.Append; INNER
#);
#)
Uvažujme aplikaci Registrations, která je podtřídou třídy Register. Výjimka Overflow je
zpracována a zastiňuje původní chybovou zprávu, aplikace je ukončena. Výjimka NotFound
není fatální, ale zpráva popisující událost je zobrazena uživateli. Spuštění objektu třídy
Exception bude standardně ukončovat běh programu. Pokud je ale přiřazena logická hodnota
true do vnitřní proměnné continue, program bude pokračovat v běhu. (Implicitní hodnota
continue je false).
26
Registrations: @Register
(#
Overflow::<
(#
do
'Too many registration numbers.'->msg.append;
'Program terminates.'->msg.append
#);
NotFound::<
(#
do
'Attempt to delete: '->PutText;
key->screen.putInt;
'which is not in the register'->PutText;
true->Continue
#)
#)
#)
Znovu pokračování v aplikaci
Ovladač sdružený s přetečením v Registrations poskytuje novou chybovou zprávu kdykoli je
běh programu ukončen. Ovladač by ale mohl provést nastavení true do proměnné continue a
rozšířit stávající rozsah tabulky. V jazyce Beta je možnost rozšířit velikost repetice. Tvar
rozšíření je následující:
25->Table.extend
Ovladač pro přetečení by pak mohl mít tvar:
Register:
(#
Overflow:< Exception
(# do true->Continue; INNER ;
(Table.range div 4)->Table.extend #);
Insert:
(# New: @integer
enter New
do
(if not (New->Has) then (* New is not in Table *)
Top+1->Top;
(if Top > Table.Range then Overflow if);
New->Table[Top]
if)
#);
(* ... *)
#)
S řešením výjimek se setkáme při práci se soubory, stejně jako i při práci s perzistentními
pamětmi.
27
Příklad4.1 je program, který demonstruje využití výjimek při práci se soubory.
Příklad4.1
(# inFile: @file
(# noSuchFileError:: (* continue execution *)
(# do true->continue; false->OK #)#);
outFile: @file
(# noSpaceError:: (* extend exception; put message to msg *)
(# do 'It is time to delete garbage!'->msg.putline #)#);
OK: @boolean;
do 'in.bet' -> inFile.name;
true -> OK;
openFile: (* labeled block *)
(# do inFile.openRead;
(if not OK then 'File does not exist!' -> screen.putline;
'Type input file name: ' -> screen.puttext;
inFile.readFileName;
true -> OK;
restart openFile (* restart labeled block *) if)#);
'out.bet' -> outFile.name;
outFile.openWrite;
readFile:
(# do (if not inFile.eos then
false -> inFile.gettext -> outFile.puttext;
outFile.newline;
restart readFile
else leave readFile
if)#);
inFile.close;
outFile.close; #)
V tomto modulu jsme probrali základní dělení výjimek, třídu Exception, která slouží pro
ošetření výjimek a základní příklady použití této třídy. Další jsou uvedeny v předchozím
modulu u perzistentních pamětí.
Třída Exception (výjimka) umožňuje oddělit standardní běh programu od ošetření chybových
stavů. Řešení využívá mechanismus virtuálních procedur. Při ošetření vyjímečných stavů
rozlišujeme, zda se jedná o chybu způsobující ukončení programu (fatální chyba), nebo je
možné po určitých úpravách pokračovat v běhu programu.
28
Podle čeho program rozhoduje, zda se jedná o chybu fatální, či zda bude pokračovat běh
programu?
Jaké další výjimky (kromě noSuchFileError a noSpaceError) byste našli ve třídě File? Co
musíme provést, aby došlo k ošetření konkrétní výjimky v našem programu?
Jak jsou deklarovány a jak pracují výjimky v příkladě3.4 v předchozí kapitole? Jaké další
výjimky jsou deklarovány ve třídě PersistentStore?
V metodě assertOpen je to výjimka notFound, v metodě assertInit je to výjimka notInitialized
a další výjimky jsou uvedeny v metodě abstractOpenOperation.
Ošetření výjimek je řešeno v každém moderním programovacím jazyce. Virtuální
mechanismus jak je deklarovaný v BETĚ pomáhá elegantně vyřešit tento problém.
29
5 Deterministická alternace – korutiny
V počítačových modelech reálného světa musíme být schopni reprezentovat akce vykonávané
v aplikační doméně, kterou modelujeme. Příkladem takovýchto akcí může být vkládání a
výběr peněz z banky, či rezervace letenek nebo místenek. Doposud jsme tyto akce modelovali
tak, že jejich časové uspořádání (ordering in time) bylo přesně dané. Například v bankovním
účtu jsme prováděli:
500->ucet.vlozeni;
350->ucet.vyber;
Pro modelování aplikační domény reálného světa je důležité mít možnosti a prostředky pro
popis akcí, které jsou časově řazené, stejně tak jako popis akcí, které jsou zcela nezávislé na
čase (u nichž se nedá předpokládat uspořádání v čase). Příkladem může být vkládání peněz na
účet, stejně jako rezervace letenek (více klientů žádá v daný okamžik o rezervaci na stejný
dopravní spoj).
V počítačových modelech musíme být schopni modelovat několik akcí, které probíhají
současně (concurrently). Někdy může být sekvence akcí synchronizovaná. Například v
případě rezervace stejného místa. Na tyto činnosti se můžeme dívat jako na složené systémy,
skládající se z několika souběžných sekvencí činností. Na druhé straně tyto aktivity mohou
být charakterizovány vykonáváním několika sekvencí činností, ale nanejvýš jednu v daném
okamžiku. Příkladem může být kuchař připravující jídla. To zahrnuje několik souběžných
aktivit, mezi kterými se kuchař pohybuje podle toho, jak vyžadují jeho pozornost. Když se
kuchař posune na další aktivitu, jeho stávající aktivita je suspendována. Později se k ní může
zase vrátit právě do bodu, kdy byla suspendována a obnovit tuto činnost. Tato forma
sekvenčního střídání (posunu) se nazývá alternací (alternation). Procesor řídící několik
zařízení může být také popsán pomocí alternace.
Alternace by neměla být zaměňována s pravou souběžností (concurrency), kde řada úkolů je
prováděna skutečně ve stejném čase. Deterministická alternace je situace, kdy objekt
rozhoduje sám, jak alternovat mezi různými úlohami. Nedeterministická alternace je
situace, kde externí události způsobí, že objekt se posune k další úloze.
Zpracování sekvenčních akcí se objevuje v mnoha způsobech v programovacích jazycích.
Nejjednodušší mechanismus je sekvenční vykonávání, kde procedury jsou vykonávány
sekvenčně a dynamická struktura aktivních procedur je organizována jako zásobník.
Modelování souběžnosti (konkurence) a alternace běhu (vykonávání) programu je
organizováno jako několik sekvenčních procesů. Tento způsob vykonávání se nazývá
multisekvenční vykonávání.
Jedním z příkladů multi sekvenčního vykonávání je sekvence korutin. Korutina je objekt,
který má vlastní zásobník aktivací procedur.
Korutina. Název korutiny (angl. coroutine) je zkrácením pojmu kooperující rutiny =
spolupracující procedury. Jejich název vznikl v době, kdy rozlišení pojmů program a proces
nebylo ještě aktuální. Korutiny jsou takové procedury, které si navzájem předávají procesor
v monoprocesorovém prostředí. Každá z korutin je ve skutečnosti procesem. Je vytvořena
specifikací příslušné procedury a paměťové oblasti. Po vytvoření je korutina proveditelná,
zůstává však ve stavu připravenosti (tj. neaktivní), dokud ji jiná korutina nevyvolá. Hlavní
program je také korutinou.
30
Korutiny alternují mezi stavy:
•
•
běžící (tj. aktivní), v tomto stavu může být vždy jen jedna korutina a dokud nevyvolá
sama jinou, zůstává stále ve stavu běžící;
připravený, do něj přechází volající korutina. Připravených korutin může být současně
několik.
Korutiny se liší od procedur v tom, že:
•
volání nemohou být rekurzivní;
•
neimplikují návrat do místa volání. Výpočet může skončit, aniž by došlo k návratu do
místa volání korutiny;
•
nevyžadují podřízenost volané vůči volající. Volaná korutina pokračuje ve své činnosti
z toho místa, kde předala při svém posledním provádění řízení jiné korutině;
•
jejich činnost není obecně ukončena průchodem koncem programového textu, jako je
tomu u procedur. Obvykle mají podobu nekonečného cyklu, obsahujícího jedno či
několik volání korutin;
•
výpočet končí, dosáhne-li některá korutina konec těla svého programového popisu.
Ukončí se tím současně i všechny ostatní korutiny. Korutina, která dospěla na konec
svého programu vlastně ani nemá komu předat řízení.
Pro rozlišení pojmů program a proces použijeme analogie z divadla. Scénáře jednotlivých rolí
jsou obdobou programů, vystoupení herců odpovídají procesům.
Vykonávání programu potom sestává z vykonávání řady korutin a procesor bude alternovat
mezi vykonáváním těchto korutin. Korutina může dočasně suspendovat (přerušit) své
vykonávání a jiná korutina může být vykonávána. Suspendovaná korutina může být později
znova obnovena v místě a za podmínek kdy byla suspendována. Sekvenční střídání mezi
korutinami je deterministické a explicitní, protože programátor specifikuje kde bude korutina
suspendována a která korutina převezme řízení.
V řadě situací se provádění programu musí vypořádat s vícenásobnou sekvencí akcí, které
běží souběžně. Korutiny nejsou vhodné pro podporu sekvence souběžných akcí. V případě
korutin, má každá korutina výlučný přístup ke společným datům a není zde žádná potřeba
synchronizace. Avšak explicitní řízení sekvenčního střídání mezi velkým počtem
symetrických korutin vyžaduje přísnou disciplínu programátora.
V Betě jsou sekvence akcí sdruženy s objekty. Objekty mohou vykonávat své činnosti jako
části vykonávání jiných objektů. Tyto objekty se nazývají items a byly probírány především v
první části učebního textu.
Objekty mohou také vykonávat své akce souběžně s jinými objekty, nebo mohou vykonávat
svoje akce alternativně s jinými objekty. Takové objekty se nazývají komponentami.
Alternování mezi dvěmi či více sekvencemi akcí může být deterministické, nebo
nedeterministické.
V Betě je deterministická alternace podpořena komponentními objekty používanými jako
korutiny. Korutiny umožňují alternovat mezi zásobníky vykonávání. Objekty jsou stavové
stroje ve smyslu, že výsledek vzdáleného volání procedury může záviset na stavu proměnných
objektu. Pro objekty, které jsou korutiny stav může zahrnovat bod spuštění. Ve všeobecnosti
31
stav spuštění zahrnuje zásobník aktivací procedur aktuálně volaných. Možnost ukládání stavy
provádění činí korutiny užitečné pro řadu aplikací:
•
•
•
•
modelování objektů, které alternují mezi řadou sekvenčních úloh (procesů);
vytváření iluze souběžnosti (concurrency);
využití pro backtracking a pattern matching;
využití jako generátorů sekvence hodnot. Nová hodnota je vytvořena pro každé vyvolání
korutiny. Nová hodnota závisí na sekvenci předchozích generovaných hodnot.
Zásobníky provádění (Execution stacks)
Mějme následující objekt:
(# R1:@ |
(# A: (#
B: (#
C: (#
do ...;
#)
R2:@ |
(# X: (#
Y: (#
Z: (#
do ...;
#)
do M0: R1;
#)
do ...; B; ...; C: ... #);
do ...; L2: suspend; ...; #);
do ...; L3: ...; #);
L1: A; ...
do
do
do
X;
...; Y; ... #);
...; K1: Z;... #);
...; K2: suspend; ...; #);
... K3:
R2; R1; R2;
Vykonávání objektu R1 je organizováno v termínech zásobníku aktivních objektů. V místě
návěští L1 zásobník obsahuje pouze objekt R1. V místě návěští L2 zásobník se skládá z R1, A
a B. V místě návěští L3 B ukončilo činnost a C má být voláno. Každý objekt v zásobníku má
strukturální atribut, který se nazývá návratové spojení (return link). Toto návratové spojení je
dynamická reference na volající objekt a místo programu ve volaném objektu, kde bylo volání
provedeno.
K modelování multi sekvenčních provádění je nezbytné organizovat provádění programu v
pojmech několika zásobníků. Uvažujme následující příklad:
Symbol | popisuje, že objekty R1 a R2 mohou být prováděny alternativně. Příkaz suspend
popisuje, že provádění R1 a R2 je v místě příkazu suspendováno (dočasně přerušeno).
Jazykové konstrukce pro základní řazení korutin
Rozlišujeme tři základní operace:
•
•
•
attach - připojení objektu do zásobníku;
suspend - dočasné přerušení vykonávání objektu;
resume - objekt znovu dostal řízení (vykonávání).
Deklarace:
R1: @ | P;
R2: @ | P(# ... #)
32
Objekt typu komponenta je instancí od třídy P, objekt R2 je singulární komponenta. R1 a R2
jsou statické komponenty, které budou stále odkazovat na uvedené komponenty a obě
komponenty budou mít svůj vlastní zásobník aktivních objektů. Deklarace:
S : @ | ^ P
je dynamická komponenta. Reference S může odkazovat na komponenty instancí vzoru P. S
může být přiřazena odkazovat na R1 příkazem:
R1[] -> S[]
a instance komponenty se vytvoří příkazem:
&|P[]->S[]
Aktivní zásobník. Provádění programu se skládá z řady zásobníků komponent. Skládá se z
jednoho aktivního zásobníku a řady suspendovaných zásobníků. Aktivní objekt může
vykonávat činnosti attach a suspend.
Připojení (attach). Činnost attach(R) kde R není člen aktivního zásobníku zahrnuje, že
zásobník R je připojen k aktivnímu zásobníku. Technicky se to stane vyvoláním objektu R
příkazem:
R
Suspendování. Činnost suspend(R), kde R je členem aktivního zásobníku zahrnuje, že
zásobník komponenty R se odstraní z aktivního zásobníku. Technicky se to stane příkazem:
suspend
který musí provést komponenta R.
Ukončení. Pokud daný operační zásobník ukončí provádění příkazů své výkonné části (do
část) nastane ukončení komponenty. Následující připojení ukončené komponenty povede k
operaci abort což znamená, že se ukončí celý program.
Vlastní program v BETĚ je singulární popisovač objektu. Tento singulární objekt je typu
komponenta což znamená, že to je základ pro zásobník provádění.
Příklad5.1 je program demonstrující použití deterministické alternace na řízení světelné
křižovatky.
Program5.1
(#
TrafficLight:
(# state: @Color
do Cycle (# do red->state; SUSPEND; green->state; SUSPEND #)
33
#);
North,South: @|TrafficLight;
(* Declaration of two component instances of TrafficLight *)
Controller: @| (* Declaration of a singular component *)
(#
do
North;
(* attachment of North *)
(* North.state=red *)
South;
South;
(* two attachments of South *)
(* South.state=green *)
Cycle
(#
do (* wait some time *) South; North; (* switch the states *)
#)
#)
do Controller (* attachment of Controller *)
Generátory
Komponenta může mít vstupní a výstupní část (enter a exit part). Před připojením
komponenty mohou být přiřazeny hodnoty do vstupní části komponenty. Když je komponenta
suspendována, nebo ukončena, hodnoty mohou být hodnoty přiřazeny z výstupní části. Je-li R
komponenta, která má vstupní a výstupní část, potom připojení R s přenosem parametrů bude
mít tvar:
X->R->Y
V následujícím příkladu5.2 komponenta Factorial počítá N!. Volání E->Factorial->F vrací E!
v F. Následující volání Factorial->F vrací (E+1)!. Hodnoty faktorialu vypočítané v předešlých
krocích jsou uloženy v tabulce. To znamená, že každý faktoriál je vypočítán pouze jednou.
Příklad5.2
(#
Factorial: @| (* a singular component *)
(# T: [100] @Integer; N,Top: @Integer;
enter N
do
1->Top->T[1];
Cycle
(#
do
(if Top < N then (* Compute and save (Top+1)!...N! *)
(Top+1,N)
->ForTo
(#
do (* T[inx-1]=(inx-1)! *)
T[inx-1]*i->T[inx] (* T[inx]=inx! *)
#);
N->Top
34
if);
N+1->N;
(* suspend and exit T[N-1]: *)
SUSPEND;
(* When execution is resumed after SUSPEND, *)
(* a new value may have been assigned *)
(* to N through enter *)
#)
exit T[N-1]
#);
F: @Integer
do
4->Factorial->F;
(* F=4! *)
(* This execution of Factorial will result in
computation of 1!, 2!, 3! and 4! *)
Factorial->F;
(* F=5! *)
(* Here 5! was computed *)
3->Factorial->F;
(* F=3! *)
(* No new factorials were computed by this call *)
#)
Komponenty a rekurzivní vzory procedur
V následujícím příkladu5.3 je ukázána síla kombinování komponent s vykonáváním
rekurzivních vzorů procedur. Je to klasický příklad využití korutin. Program ukazuje setřídění
tří dříve setříděných registrů. První část tvoří knihovna a v další části je uveden program
využívající knihovnu a realizující uvedené zadání.
Příklad5.3
ORIGIN '~beta/basiclib/betaenv';
-- lib: Attributes -Register:
(#
Content:< Object;
Table: [0] ^Content;
Top: @integer;
Init: (# n: @integer enter n do 0->Top; n->Table.new #);
Has: Find
(# Result: @boolean; NotFound:: (# do False->Result #)
do True->Result;
exit Result
#);
Find:
(# subject: ^Content; index: @integer; NotFound:< Object
enter Subject[]
do
1->index;
Search:
35
(if (index <= Top) then
(if Subject[] = Table[index][] then
INNER Find; leave Search
else
1+index->index; restart search
if)
else
&NotFound
if)
#);
Insert:
(# New: ^Content
enter New[]
do
top+1->top;
(if (Top <= Table.Range) then
New[]->Table[Top][];
else
' Owerflow'->putline
if)
#);
Get:
(# i: @integer; rx: ^Content
enter i
do Table[i][]->rx[]
exit rx[]
#);
scan:
(# Current: ^Content
do (for i: Top repeat Table[i][]->Current[]; INNER scan for)
#);
getNext: @|
(# prvek: ^content
do scan (# do current[]->prvek[]; SUSPEND #); none ->prvek[]
exit prvek[]
#);
capacity: (# exit Table.range #)
#);
zn: (#
do (if keyboard.peek = ascii.newline then keyboard.get if) #)
Příklad5.3 hlavní program
ORIGIN '~beta/basiclib/betaenv';
INCLUDE './gencomplib';
-- program: Descriptor -(#
RegR,RegA,RegB,RegC: @Register (# content:: integerObject #);
p1,p2,p3: ^integerObject
do
4->RegA.init;
2->RegB.init;
3->RegC.init;
RegA.capacity+RegB.capacity+RegC.capacity->RegR.init;
(for i: RegA.capacity repeat
&integerObject[]->p1[]; i->p1; p1[]->RegA.insert
for);
(for i: RegB.capacity repeat
&integerObject[]->p1[]; i-5->p1; p1[]->RegB.insert
for);
(for i: RegC.capacity repeat
&integerObject[]->p1[]; i+4->p1; p1[]->RegC.insert
for);
36
RegA.getNext->p1[];
RegB.getNext->p2[];
RegC.getNext->p3[];
Nav:
(#
do
(if ((p1[] <> none ) and (p2[] <> none ) and (p3[] <> none )) then
(if p1 < p2 then
(if p1 < p3 then
p1[]->RegR.insert; RegA.getNext->p1[]; restart Nav
else
p3[]->RegR.insert; RegC.getNext->p3[]; restart Nav
if)
else
(if p2 < p3 then
p2[]->RegR.insert; RegB.getNext->p2[]; restart Nav
else
p3[]->RegR.insert; RegC.getNext->p3[]; restart Nav
if)
if)
if);
(if (p1[] = none ) and (p2[] <> none ) and (p3[] <> none ) then
(if p2 < p3 then
p2[]->RegR.insert; RegB.getNext->p2[]; restart Nav
else
p3[]->RegR.insert; RegC.getNext->p3[]; restart Nav
if)
if);
(if (p2[] = none ) and (p1[] <> none ) and (p3[] <> none ) then
(if p1 < p3 then
p1[]->RegR.insert; RegA.getNext->p1[]; restart Nav
else
p3[]->RegR.insert; RegC.getNext->p3[]; restart Nav
if)
if);
(if (p3[] = none ) and (p1[] <> none ) and (p2[] <> none ) then
(if p1 < p2 then
p1[]->RegR.insert; RegA.getNext->p1[]; restart Nav
else
p2[]->RegR.insert; RegB.getNext->p2[]; restart Nav
if)
if);
(if (p1[] = none ) and (p2[] = none ) and (p3[] <> none ) then
p3[]->RegR.insert; RegC.getNext->p3[]; restart Nav
if);
(if (p1[] = none ) and (p3[] = none ) and (p2[] <> none ) then
p2[]->RegR.insert; RegB.getNext->p2[]; restart Nav
if);
(if (p2[] = none ) and (p3[] = none ) and (p1[] <> none ) then
p1[]->RegR.insert; RegA.getNext->p1[]; restart Nav
if);
#);
RegR.scan
(# do current->putint; ' '->put #)
#)
37
Korutina, komponenta, deterministická alternace, proces.
Pro modelování aplikační domény reálného světa potřebujeme prostředky pro popis akcí,
které jsou časově nezávislé. Deterministická alternace je střídání komponent, kdy o další
komponentě stejně jako o době předání řízení rozhoduje aktivní komponenta.
Co je to korutina, jaké musí splňovat požadavky? Jaký je rozdíl mezi programem a procesem.
Představte si, že jste kuchař a připravujete jídlo. Co představuje program a co jsou jednotlivé
procesy?
Jak byste upravili příéklad5.3, pokud by na vstupy byly pouze dva setříděné seznamy?
Předpokládáme, že v hlavním programu budeme mít pouze RegA a RegB a promenné p1 a
p2.
Nav: (# do
(if (p1[]<> none) and (p2[][<>none) then
(if p1<p2 then
p1[]->RegR.insert; RegA.getNext->p1[]; restart Nav
else p2[]->RegR.insert; RegB.getNext->p2[]; restart Nav
if)
else
(if p1[]=none then p2[]->RegR.insert; RegB.getNext->P2[];
restart Nav;
else P1[]->RegR.insert; RegA.getNext->P1[]; restart Nav
if)
if) #);
38
Deterministická alternace je významný mechanismus při modelování aplikační domény
reálného světa. Je vhodná ale pouze pro modelování procesů, které nevyžadují skutečnou
souběžnost.
39
6 Souběžné zpracování – concurrency
Komponenty (components) mohou být také spouštěny souběžně, to znamená, že dvě nebo
více komponent mohou vykonávat své akce ve stejném čase. V této kapitole budou zavedeny
základní mechanismy pro odstartování souběžného (konkurentního) vykonávání komponent
(concurrent execution).
Souběžné (konkurentní) komponenty mohou na sebe vzájemně působit různými způsoby:
•
•
•
mohou mít přístup ke společným objektům;
mohou spouštět metodu (proceduru) společných objektů;
mohou komunikovat přímo zpřístupněním svých atributů.
Souběžné spouštění objektů vyžaduje mechanismus pro synchronizaci přístupu k společným
objektům. Stejně tak přímá komunikace mezi objekty vyžaduje synchronizaci. Základní
mechanismus pro synchronizaci se v BETĚ nazývá semafor (semaphore). Semafory jsou však
užitečné pro synchronizaci velmi jednoduchých problémů. Proto existují mechanismy na
vyšším stupni abstrakce pro synchronizaci složitějších problémů. Tyto mechanismy zahrnují
monitory, ty garantují exkluzivní (výlučný) přístup k objektu a tak zvané rendezvous
mechanismus pro zpracování přímé komunikace mezi objekty. Všechny abstraktní
mechanismy souběžnosti (konkurence) mohou být definovány prostřednictvím semaforů.
Souběžné spuštění komponent.
Souběžné spouštění komponent může být popsáno prostřednictvím příkazu fork:
S[]->fork;
kde S je reference na komponentu. Význam uvedeného příkazu je, že vykonávání S se bude
konat souběžně s komponentou vykonávající příkaz S[]->fork. Běh S bude pokračovat, dokud
S nevykoná suspend, nebo neukončí svoji výkonnou část (do část). Pokud S pozastaví svůj
běh prostřednictvím explicitního příkazu suspend, komponenta může být znova spuštěna
novým příkazem S[]->fork.
Následující příklad zahrnuje bankovní účet osoby (Joe) a dvě komponenty. Jedna odpovídá
bankovnímu úředníkovi (bankAgent), který ukládá peníze na Joeův účet a další představuje
samotného Joa. Joe vybírá peníze ze svého účtu.
(#
Account: (# (* ... *) #);
JoesAccount: @Account;
bankAgent: @|
(#
do cycle (# do ; 500->JoesAccount.deposit; ... #)
#);
Joe: @|
(# myPocket: @integer
do
cycle
(#
do ... ; 100->JoesAccount.Withdraw->myPocket;
#)
#)
40
do
...
bankAgent[]->fork;
(* start konkurentního provádění komponenty bankAgent *)
Joe[]->fork;
(* start konkurentního provádění komponenty Joe *)
#)
Jednoduchá synchronizace
Výše popsaný příklad funguje dobře až do chvíle, kdy Joe a bankovní úředník nemají přístup
k účtu ve stejný čas. Předpokládejme, že Joe má na účtu 800,- Kč. Bankovní úředník přidává
500,- a Joe vybírá 100,-. Pokud je operace přidávání přerušena výběrem, skončí to tak, že po
výběru se do atributu stav uloží 1300,- a po výběru se uloží 700,-. Správný výsledek by měl
být 1200,-.
To je klasický příklad dvou souběžných objektů, které zpřístupňují sdílený objekt, obecně
sdílený zdroj. Ve všeobecnosti není možné předpovídat cokoli o pořadí běhu akcí, které
provádí souběžné (konkurentní) objekty. Skutečný scénář závisí na základním hardware, který
je použitý pro implementaci procesoru BETA.
Semafory - Semaphores
Semafor si můžeme představit jako semafor na sdíleném úseku železniční trati, který umožní
pouze jedné vlakové soupravě v daném čase vjet do daného sdíleného úseku. Další vlaková
souprava je do sdíleného úseku vpuštěna teprve až předešlá souprava tento úsek opustí.
BETA má předdefinovanou třídu (vzor) Semaphore. Instance od této třídy má dvě operace P a
V. Operace P zabezpečí výlučné přidělení zdroje požadovanému objektu. Operace V provede
uvolnění sdíleného zdroje a tím umožní jeho výlučné zpřístupnění dalšímu objektu. Při
operaci P se zjistí, není-li požadovaný sdílený zdroj obsazen jiným objektem. Pokud tomu tak
je, uloží se žádající objekt do fronty čekající na uvolnění sdíleného zdroje. Ve frontě jsou
dočasně uloženy všechny objekty žádající výlučné zpřístupnění daného objektu, které nebyly
uspokojeny. Dojde-li k uvolnění sdíleného zdroje, je tento přidělen prvnímu objektu ve frontě.
Uvažujme následující příklad, popisující dvě komponenty A a B:
(# S: @semaphore;
A: @ | (# do imp1; S.P; imp2; S.v; imp3 #);
B: @ | (# do imp4; S.P; imp5; S.v; imp6 #);
do S.V; A[]->fork; B[]->fork
#)
Z příkladu je vidět, že pouze příkazy imp2 a imp5 vyžadují semafor pro výlučný přístup ke
sdílenému objektu. Příkaz S.V je inicializace semaforu.
Ve skutečnosti nejsou stavy semaforu dány zelenou a červenou barvou, ale jsou vyjádřeny
binárně. Semafor má atribut s celočíselnou hodnotou, který se využívá pro jeho řízení.
•
Operace P odečítá jedničku od tohoto atributu, tak dlouho, dokud je kladná a komponenta
není blokovaná (uložena ve frontě). Pokud má celočíselný atribut zápornou hodnotu, je
volající komponenta zdržena ve frontě. (zpožděna)
41
•
•
Operace V zvyšuje celočíselný atribut o jedničku. Je-li hodnota tohoto atributu menší než
jedna, je z fronty reaktivována další komponenta.
Je-li semafor inicializován, je inicializován na kladnou hodnotu n reprezentující počet
souběžných komponent, které mohou pracovat "souběžně". Na začátku má semafor řídící
proměnnou nastavenu na nulu. Tedy bez inicializace na kladnou hodnotu, budou všechny
komponenty čekat ve frontě, až dojde k přetečení fronty a deadlock. Náš program
souběžných komponent zhavaruje.
Třída semafor může být deklarována následovně:
Semaphore:
(# P: (#
do (if (cnt-1->cnt)<0 then
{zpoždění volající komponenty ve frontě Q }
#);
V: (#
do (if (cnt+1->cnt)<1 then
{reaktivace čekající komponenty z fronty Q }
Count:
{vrací počet procesů čekajících u tohoto semaforu }
(# V: @integer
do (if cnt<0 then -cnt->V if)
exit V #)
cnt: @integer
Q: @Queue
#);
Stav semaforu je reprezentován celočíselnou proměnnou cnt a frontou Q pro sledování
čekajících procesů (komponent). Protože cnt a Q jsou přístupny pouze prostřednictvím P a V
operací, pouze jedna z těchto operací se může provádět v daném čase. Provádění P a V musí
být nedělitelné. Semafor je předdefinovaný vzor (třída), který má vlastnost nedělitelnosti pro
operace P a V.
Semafor můžeme použít pro výlučný přístup k bankovnímu účtu, tak jak je to ukázáno
v následujícím příkladě:
Account:
(# mutex: @Semaphore; (* semaphore řízený přístup *)
balance: @integer;
Deposit:
(# amount,bal: @integer
enter amount
do mutex.P;
balance+amount->balance->bal;
mutex.V
exit bal
#);
Withdraw:
(# amount,bal: @integer
enter amount
42
do mutex.P;
balance-amount->balance->bal;
mutex.V
exit bal
#);
Init:< (# do INNER; mutex.V; (* Inicializace semaforu *) #)
#)
Vykonání operací Deposit a Withdraw již nebude možné provádět současně. Je samozřejmě
možné dostat se na atribut balance přímo, ale to je porušení „pravidel hry“.
Semafor je jednoduchý a primitivní mechanismus pro synchronizaci. Na předchozím příkladě
je relativně snadné ukázat, že synchronizace pracuje správně. V systému s několika
konkurentními objekty a několika sdílenými objekty může být obtížné popsat synchronizaci
prostřednictvím semaforů. Samozřejmě program s velkým počtem semaforů je obtížné číst a
provádět v něm další změny. Proto se zavádí řada dalších abstraktních vzorů (tříd) pro
zpracování komplikovanějších forem synchronizace.
Monitory
Monitor usnadňuje synchronizaci. Jeho struktura je vyjádřena v následující deklaraci:
Monitor:
(# mutex: @ Semaphore;
Entry: (# do mutex.P; INNER; mutex.V #);
Init: (# do mutex.V; Inner #)
#)
Objekt Monitor má atribut Semaphore a lokální metody Entry, pro definování operací.
Předchozí příklad by s využitím vzoru Monitor vypadal následovně:
Account: Monitor
(# balance: @integer;
Deposit: Entry
(# amount,bal: @integer
enter amount
do balance+amount->balance->bal
exit bal
#);
Withdraw: Entry
(# amount,bal: @integer
enter amount
do balance-amount->balance->bal
exit bal
#);
#)
V uvedeném příkladě je třída Account podtřídou Monitor a metody Deposit a Withdraw jsou
„podmetodami“ metody Entry.
43
Monitor s podmínkami
Při praktickém řešení se může stát, že komponenta vykonávající operaci Entry nemůže
pokračovat, protože nejsou splněny nějaké podmínky. Uvažujme příklad ohraničeného
bufferu znaků. Takový buffer může být implementován jako monitor se dvěma operacemi Put
a Get. Operace Put nemůže být prováděna, když je buffer plný, operace Get nemůže být
prováděna, když je buffer prázdný. Celá situace je schematicky vyjádřena v následujícím
příkladě:
(# buffer: @Monitor
(# R: [100] @char; in,out: @integer;
full: (# exit in=out #);
empty: (# exit (in = (out mod R.range)+1) #);
Put: Entry
(# ch: @char
enter ch
do wait(# do (not full)->cond #);
ch->R[in]; (in mod R.range)+1->in;
#);
get: Entry
(# ch: @char
do wait(# do (not empty)->cond #);
R[(out mod R.range)+1->out]->ch;
exit ch
#);
init::< (# do 1->in; R.range->out #)
#);
prod: @| (# do cycle(# do (* ... *); ch->buffer.put; (* ... *) #) #);
cons: @| (# do cycle(# do (* ... *); buffer.get->ch; (* ... *) #) #)
do buffer.init;
prod[]->fork; cons[]->fork
#)
Operace wait je deklarována následovně:
wait (# do <some condition> -> cond #)
Příklad6.1 je program, který ukazuje použití vzoru Monitor pro čtení a zápis do bufferu.
Příklad6.1
SystemEnv
(# buffer: @Monitor
(# R: [20] @char; in,out: @integer;
full,empty: @Condition;
put: Entry
(# ch: @char
enter ch
44
do (if in = out then full.wait if);
ch->R[in]; (in mod R.range)+1 ->in;
empty.signal;
#);
get: Entry
(# ch: @char
do (if in = (out mod R.range)+1 then empty.wait if);
R[(out mod R.range)+1->out]->ch;
full.signal;
exit ch
#);
init::< (# do 1->in; R.range->out #)
#);
prod: @| System(# do cycle(# do keyboard.get->buffer.put #) #);
cons: @| System(# do cycle(# do buffer.get->screen.put #) #);
do buffer.init;
conc(# do prod[]->start; cons[]->start #)
#)
Význam operace wait znamená, že volání komponenty je opožděno, až je podmínka
nastavena na true.
Přímá komunikace mezi komponentami
V předchozí části jsme popsali mechanismus pro komunikaci konkurentních komponent
prostřednictvím sdílených objektů. V mnoha případech se jeví mnohem přirozenější, aby
konkurentní komponenty komunikovaly přímo, místo sdílených objektů. Uvažujme
následující příklad:
(# S: @ | (# P: (# ... #) do ... ; R.Q; ... #);
R: @ | (# Q: (# ... #) do ...; S.P; ... #)
do S[]->fork; R[]->fork
#)
V uvedeném příkladě komponenty S a R vyvolávají operace jedna ve druhé. Komponenta
S může požadovat vykonání metody komponenty R. Komponenta R musí akceptovat (metoda
accept), že tento požadavek může být splněn.
Synchronizační komunikace je popsána v abstraktním vzoru System, který definuje atribut
Port pro řízení komunikace. Atribut Port má uvnitř definovanou metodu Entry a také metodu
Accept pro signalizaci, že operace sdružená s portem může být spuštěna. System má
následující strukturu:
System:
(# Port:
(# mutex, m: @ semaphore;
Entry: (# do m.P; INNER; mutex.V #);
accept: (# do m.V; mutex.P #)
#)
do ... INNER; ...
#)
V uvedeném popisu jsou použity dvě instance třídy semaphore. Metoda Entry popisuje
požadavek na spuštění operace z těla jiného objektu. Operace m.P představuje žádost a ta je
splněna, až metoda accept provede operace m.V a mutex.P. Operace m.P a mutex.P provedou
45
suspendování objektu, který je použije. Operace m.V a mutex.V provedou uvolnění
odpovídajících objektů.
Příkladě6.2 ukazuje sdružení více jak jedné operace s portem. Master systém vysílá sekvenci
hodnot do dvou Slave systémů a každý z těchto Slave systémů počítá sumu hodnot, kterou
obdržel. Kladné hodnoty se ukládají od Slave1 a záporné do Slave2.
Příklad6.2
(# Slave: System
(# receive: @Port;
Clear: receive.entry(# do 0->sum #);
Add: receive.entry
(# V: @integer enter V do sum+V->sum #);
Result: receive.entry(# S: @integer do sum->S exit S #);
sum: @integer;
do 0->Sum;
Cycle(# do receive.accept #);
#);
Slave1: @| Slave;
Slave2: @| Slave;
Master: @| System
(# Pos,Neg: @integer; V: [100] @integer;
do (* Read values to V *)
Slave1.Clear; Slave2.Clear;
(for inx: V.Range repeat
(if True
//V[inx] > 0 then V[inx]->Slave1.Add
//V[inx] < 0 then V[inx]->Slave2.Add
if) for);
Slave1.Result->Pos;
Slave2.Result->Neg;
#)
do Master[]->fork; Slave1[][->fork; Slave2[]->fork
#)
Konkurentní - souběžné zpracování, semafory, monitory a rendesvous mechanismy.
Souběžní zpracování představuje další rozšíření deterministické alternace z předchozí
kapitoly. Třída Semaphore je nejjednodušší synchronizační mechanismus pro přístup ke
sdíleným objektům. Vyšší stupeň abstrakce představuje třída Monitor. Pro vzájemné
zpřístupnění atributů komponent se využívá třída System s metodami Entry (požaduje přístup)
a Accept (umožní přístup).
46
Co se stane, neprovedeme-li operaci init u objektů třídy Semaphore, nebo Monitor?
Detailně porovnejte třídu Semaphore a Monitor.
Upravte Příklad6.2 tak, aby rozlišoval kladná sudá čísla, kladná lichá čísla, nulu, záporná sudá
čísla a záporná lichá čísla.
Konkurentní zpracování komponent nepředstavuje ve skutečnosti paralelní zpracování,
protože v daném časovém okamžiku má procesor přidělena pouze jedna komponenta. O
přidělení či odejmutí procesoru ale rozhodují vnější události.
47
7 Objektově orientované modelování – modularizace
Při objektově orientovaném modelování je referenční systém (systém reálného světa)
modelován modelem systému. Reálnou nebo imaginární část světa, kterou chceme modelovat,
budeme nazývat referenční systém. Programovou realizaci, která vytváří fyzikální model,
budeme nazývat model systému. Obrázek ilustruje proces programování jako proces
modelování mezi referenčním systémem a modelem systému.
modelování
koncepty
specifikující
problém
abstrakce
jevy
referenční systém
realizované
koncepty
(třídy)
abstrakce
objekty
(instance)
model systému
Programování jako proces modelování
Proces programování (implementace) zahrnuje identifikaci odpovídajících konceptů a jevů
v referenčním systému a reprezentaci těchto konceptů a jevů v modelu systému. Tento proces
se skládá ze tří podprocesů:
Abstrakce v referenčním systému, abstrakce v modelu systému a modelování.
Abstrakce v referenčním systému je proces, kde vnímáme strukturu znalostí o jevech a
konceptech v referenčním systému se zvláštním zřetelem na doménu řešeného problému.
Abstrakce v modelu systému je proces, kde vytváříme struktury, které by měly podporovat
model, který plánujeme vytvořit v počítači. Říkáme, že vytváříme realizované koncepty
(třídy) v modelu systému.
48
Konečně modelování je proces, kde spojujeme koncepty specifikující problém v referenčním
systému s realizovanými koncepty (třídami) modelu systému. Objekty a jejich vlastnosti
v modelu systému reprezentují jevy a jejich vlastnosti v referenčním systému.
Proces programování zahrnuje identifikaci konceptů a jevů referenčního systému a jejich
následnou reprezentaci v pojmech objektů a tříd modelu systému.
Existují dvě základní výhody objektově orientovaného přístupu. První výhodou je
znoupoužitelnost kódu a tím vytváření rychlých prototypů a druhou je objektově orientované
modelování. Znovupoužitelnost se zaměřuje na co největší násobnou využitelnost kódu, i
když to ve svém důsledku vede mnohdy ke snížení přehlednosti výsledné aplikace.
Modelování si klade za cíl popsat danou aplikační doménu co nejvěrněji s využitím
odpovídajících mechanismů nutných pro objektově orientované modelování.
Základní mechanismy určené pro objektově orientované modelování:
•
•
•
•
•
•
mechanismus virtuálních procedur (umožňuje další specifikaci metod, než pouhé jejich
zastínění - overriding);
mechanismus virtuálních vzorů tříd umožňující deklarovat abstraktní třídy obsahující další
prvky (třídy typu container);
singulární objekt a jeho využití pro lokálně deklarované atributy;
prostředky pro kompozici zahrnující kompozici celek-část, referenční kompozici a
lokalizaci;
deklarace třídy ve třídě (vnořené třídy);
mechanismy umožňující modelování korutin a procesů.
Nejdříve bychom si uvedli příklad7.1 muslimské rodiny, kde rodinu tvoří jeden muž, několik
žen a dětí. Třída rodina se skládá ze tříd reprezentujících muže, ženy a děti a metody print,
která tiskne jména všech členů rodiny. Pro deklaraci jsou použity statické instance tříd,
protože předpokládáme, že třída rodina je pevně tvořena těmito objekty. Pokud bychom chtěli
popsat častější "výměnu" jednotlivých objektů rodiny, bylo by vhodnější, deklarovat tyto
objekty jako dynamické.
Příklad7.1
ORIGIN '~beta/basiclib/betaenv';
-- program: Descriptor -(#
rodina:
(#
CMuz: @Muz;
CZeny: @Zeny;
CDeti: @Deti;
print: (# do CMuz.print; CZeny.print; CDeti.print #)
#);
Muz:
(#
jmeno: @text;
print: (# do '\nMuz: jmeno: '->puttext; jmeno[]->puttext #)
enter jmeno
#);
zeny:
49
(#
z: [6] ^Zena;
top: @integer;
insert:
(# z1: ^zena
enter z1[]
do (if top+1 <= z.range then top+1->top; z1[]->z[top][] if)
#);
print:
(#
do
(if top = 0 then
'\nNejsou zadne zeny '->puttext
else
(for i: top repeat z[i].print for)
if)
#)
#);
Zena:
(#
jmeno: @text;
print: (# do '\nZena jmeno: '->puttext; jmeno[]->puttext #)
enter jmeno
#);
deti:
(#
d: [8] ^dite;
top: @integer;
insert:
(# dt: ^dite
enter dt[]
do (if top+1 <= d.range then top+1->top; dt[]->d[top][] if)
#);
print:
(#
do
(if top = 0 then
'\nNejsou zadne deti '->puttext
else
(for i: top repeat d[i].print for)
if)
#);
#);
dite:
(#
jmeno: @text;
print: (# do '\nDite jmeno: '->puttext; jmeno[]->puttext #)
enter jmeno
#);
R: @rodina;
Z1,Z2: ^Zena;
d1,d2,d3,d4: ^dite
do
'Karel'->R.CMuz.jmeno;
&zena[]->Z1[];
&zena[]->Z2[];
'Jana'->Z1;
'Dana'->Z2;
Z1[]->R.CZeny.insert;
Z2[]->R.CZeny.insert;
50
&dite[]->d1[];
'Zdenicka'->d1;
&dite[]->d2[];
'Jarecek'->d2;
&dite[]->d3[];
'Mirecek'->d3;
&dite[]->d4[];
'Adelka'->d4;
d1[]->R.CDeti.insert;
d2[]->R.CDeti.insert;
d3[]->R.CDeti.insert;
d4[]->R.CDeti.insert;
R.print
#)
Další příklad7.2 je knihovna abstraktní třídy CRegister a podtříd CVector a CMatrix.
Struktura těchto tříd je uvedena v diagramu tříd.
CRegister
CMatrix
1
*
CVector
Abstraktní třída CRegister deklaruje základní atributy a metody, které buď používány přímo,
nebo jsou dále rozšiřovány pomocí virtuálních procedur. Prvek třídy CRegister je dále
specializován pomocí virtuálních vzorů tříd. U třídy CVector je specializován na reálné číslo a
u třídy CMatrix je specializován na objekt třídy CVector. Pro ukládání všech hodnot je v
abstraktní třídě CRegister deklarovaná pouze jedna repetice Table. Skutečná velikost je pak
nastavena pomocí metody init.
První výpis je knihovna, která je využitelná v dalších programech. Její částečné využití je
vidět v programu matrix.
51
Knihovna matrixlib:
Příklad7.2
ORIGIN '~beta/basiclib/betaenv';
INCLUDE '~beta/basiclib/math'
'~beta/basiclib/numberio';
-- lib: Attributes -CRegister:
(* Abstraktní generická třída použita pro podtřídy
* jako vektor, matice...
*)
(#
content:< Object;
rc: ^Content;
Table: [0] ^Content;
Top,incr: @integer;
increment: (# do incr->Table.extend #);
init:<
(# n1: @integer
enter n1
do 0->Top; 5->incr; n1->Table.new; INNER Init
#);
size: (# exit table.range #);
rsize: (# exit top #);
push:
(# New: ^Content
enter New[]
do top+1->top; (if top > size then increment if);
New[]->Table[top][]
#);
pop: peek (# do top-1->top #);
peek:
(# subject: ^Content
do (if top >= 1 then Table[top][]->subject[]; INNER peek if)
exit subject[]
#);
first:
(# subject: ^Content
do (if top >= 1 then Table[1][]->subject[]; if); INNER first
exit subject[]
#);
firstRemove: first
(#
do top-1->top; (for i: top repeat Table[i+1][]->Table[i][] for)
#);
getR: (# i: @integer enter i exit Table[i][] #);
putR:
(# i: @integer; rx: ^Content enter (rx[],i) do rx[]->Table[i][] #);
rescan:
(# current: ^Content; index: @integer
do
top->index;
search:
(if (index >= 1) then
Table[index][]->current[];
INNER rescan;
index-1->index;
52
restart search
if)
#);
scan:
(# current: ^Content; index: @integer; b,q: @boolean
do
1->index;
false->q;
true->b;
search:
(if (index <= top) and (Table[index][] <> none ) then
Table[index][]->current[];
INNER scan;
(if q then leave search if);
index+1->index;
restart search
if)
#);
clear: (# do 0->top #);
delete: (# do scan (# do none ->current[] #); 0->top #);
remove:
(# res: ^Content
enter res[]
do
Search:
(for inx: top repeat
(if Table[inx][] = res[] then
(for i: top-inx repeat
Table[inx+i][]->Table[inx+i-1][] for);
top-1->top;
leave search
if)
for)
#)
#);
CVector: CRegister (* Deklarace vektoru *)
(#
content::< integerObject (* specializace content *) ;
init::<
(# do (for i: n1 repeat &Content[]->Table[i][] for); n1->top #);
getV: (# i: @integer enter i exit Table[i] #);
putV: (# i: @integer; e: @real enter (e,i) do e->Table[i] #);
print:
(#
do
(for i: size repeat
table[i]->putint; ','->put; (if i mod 9 = 0 then newline if)
for)
#);
clear: (# do scan (# do (0,index)->putV #) #)
#);
CMatrix: CRegister (* Třída matice a její metody *)
(#
Content::< CVector (* další specializace content *) ;
N,M: @integer;
init::<
(# M1: @integer
enter M1
do
N1->N;
M1->M;
53
(for i: N1 repeat &CVector[]->Table[i][]; M1->Table[i].init for)
#);
noObj: (* počet řádků *) (# exit N #);
noAtr: (* počet sloupců *) (# exit M #);
getV: (* get value *)
(# i,j: @integer enter (i,j) exit (j->Table[i].getV) #);
putV: (* put value *)
(# i,j: @integer; e: @real
enter (e,i,j)
do (e,j)->Table[i].putV
#);
print:
(# do (for j: Table.range repeat Table[j].print; newline for) #);
transpose: (* metoda pro vytvoření transponované matice *)
(# MT: ^CMatrix
do
&CMatrix[]->MT[];
(M,N)->MT.init;
(for i: Table.range repeat
(for j: Table[i].Table.range repeat
Table[i].Table[j]->MT.table[j].Table[i]
for)
for)
exit MT[]
#);
multiply: (* nová matice je vytvořena vynásobením dvou matic *)
(# M1,MR: ^CMatrix; sum: @real; k: @integer
enter M1[]
do
&CMatrix[]->MR[];
(if M < N then (M,M)->MR.init else (N,N)->MR.init if);
(for i: M1.noObj repeat
1->k;
Lbl:
(#
do
(for j: M1.noAtr repeat
((i,j)->M1.getV)*((j,k)->getV)+sum->sum
for);
sum / (noObj)->sum;
(sum,i,k)->MR.putV;
0->sum;
k+1->k;
(if k <= noAtr then restart Lbl if)
#)
for)
exit MR[]
#);
gravity: (* počítá těžiště jako vektor této matice *)
(# Tx: ^CVector
do
&CVector[]->Tx[];
noAtr->Tx.init;
(for i: noObj repeat
(for j: noAtr repeat
((((i,j)->getV)+(j->Tx.getV)),j)->Tx.putV
for)
for);
(for j: noAtr repeat (((j->Tx.getV) / noObj),j)->Tx.putV for)
exit Tx[]
#)
54
#);
zn: (#
do (if keyboard.peek = ascii.newline then keyboard.get if) #)
Program matrix využívající knihovnu matrixlib:
Příklad7.2
ORIGIN '~beta/basiclib/betaenv';
INCLUDE './matrixlib';
-- program: Descriptor -(# M1,M2: @CMatrix; V1,V2: ^CVector; r,s: @integer
do
'\npocet radku: '->puttext;
getint->r;
zn;
'\nPocet sloupcu: '->puttext;
getint->s;
zn;
(r,s)->M1.init;
(for i: r repeat
(for j: s repeat
' prvek('->puttext;
i->putint;
','->put;
j->putint;
'): '->puttext;
getint->M1.Table[i].table[j];
zn
for)
for);
'\nTisk matice\n'->puttext;
M1.print;
M1.gravity->V1[];
'\nTeziste tisk\n'->puttext;
V1.print
#)
Příklad7.3 popisuje prodejní automat, u kterého je možné si "zakoupit" colu nebo juice.
Program sleduje celkový počet col a juice, finanční stav automatu, umožňuje doplnit nápoje.
Třída Machine má následující metody: total - pro výpis aktuálního stavu, refill - pro doplnění
nápojů, set - pro nastavení ceny nápojů. Uvnitř této třídu jsou třídy cola a juice, obě podtřídy
water.
Příklad7.3
ORIGIN '~beta/basiclib/betaenv';
-- program: Descriptor -(#
Machine:
(#
cash: @integer;
tprice:
(# a: @integer enter a do a->putint; '
total:
(#
do
'\nCurrent totals: '->puttext;
cola.tamount;
juice.tamount;
cash->tprice
#);
refill:
dollars. '->puttext #);
55
(# c,j,d: @integer
do
'\nHow many colas to add ? '->puttext;
getint->c;
zn;
cola.amount+c->cola.amount;
'\nHow many juices to add ? '->puttext;
getint->j;
zn;
juice.amount+j->juice.amount;
'\nHow many dollars should you start with ? '->puttext;
getint->d;
zn;
cash+d->cash
#);
set:
(# c,j: @integer
do
'\nPrice setting: '->puttext;
'\nCola: ? '->puttext;
getint->c;
'Juice: ? '->puttext;
getint->j;
c->cola.price;
j->juice.price
#);
cola: @water
(#
tamount: (# do amount->putint; ' colas, '->puttext #);
drink::<
(#
do '\nCola empty'->t1; '\nA cola costs '->t2; INNER drink
#)
#);
juice: @water
(#
tamount: (# do amount->putint; ' juices, '->puttext #);
drink::<
(#
do '\nJuice empty'->t1; '\nA juice costs '->t2; INNER drink
#)
#);
water:
(#
amount: @integer;
price: @integer;
drink:<
(# d,p: @integer; t1,t2: @text
do
INNER drink;
(if amount = 0 then
t1[]->puttext
else
t2[]->puttext;
price->putint;
' dollars. '->puttext;
' How much money are you inserting ? '->puttext;
getint->d;
(if d >= price then
(if cash >= d-price then
d-price->p;
56
cash+price->cash;
amount-1->amount;
'\nThanks. '->puttext;
(if p > 0 then
'Your change is '->puttext; p->tprice
if)
else
'\nThere is not enough change to return'
->puttext
if)
else
'\nInsert more money '->puttext
if)
if)
#)
#)
#);
zn: (# do (if keyboard.peek = ascii.newline then keyboard.get if) #);
M: @Machine
do
'\nStart: \n'->puttext;
M.set;
N1: cycle
(#
do
'\nFunkce: (T) total, (R) refill, (C) cola, (J) juice, (Q) quit '
->puttext;
(if getNonBlank
// 't' then
M.total
// 'r' then
M.refill
// 'c' then
M.cola.drink
// 'j' then
M.juice.drink
// 'q' then
leave N1
if)
#);
'\nKonec programu: '->puttext
#)
Objektově orientované modelování, znovupoužitelnost.
Referenční systém představuje aplikační doménu reálného světa, model systému je jeho
počítačovým ekvivalentem. Jevy korespondují s objekty a koncepty se třídami.
57
Jaké jsou nezbytné prostředky pro objektově orientované modelování?
Kdy je vhodné použít kompozici celek-část a kdy referenční kompozici?
Co by se změnilo v příkladě7.1, pokud bychom chtěli sledovat děti každé ženy zvlášť?
Třídu rodina upravíme následovně:
(# rodina: (# CMuz: @Muz; CZeny: @ Zeny;
print: (# do CMuz.print; CZeny.print #) #);
Ve třídě Zena přibude atribut CDeti následovně:
Zena: (# jmeno: @text; CDeti: @Deti ...
V hlavním programu musíme ještě upravit vkládání "dětí", protože ty patří již ke konkrétní
ženě.
Především možnosti kompozice rozlišují jednotlivé objektově orientované jazyky. Jazyk
BETA poskytuje kompozici celek-část, referenční kompozici a lokalizaci.
58
8 Grafické uživatelské rozhraní
Každá současná aplikace by měla nabízet grafické uživatelské rozhraní. Ve všeobecnosti
objektově orientovaný přístup vytváří grafické uživatelské rozhraní s využitím tzv. Model View - Controlleru. Podstata tohoto přístupu je v tom, že se celá aplikace rozloží na část
model, ve které jsou vyjádřeny pouze objekty zabezpečující výpočetní operace dané aplikace
(mimo vstupně výstupních operací). Další dvě části reprezentují grafické rozhraní. Část View
reprezentuje okna, pohledy, grafické výstupy. Část Controller zabezpečuje vstup ať již z
klávesnice, nebo z myši v grafickém rozhraní.
Některé systémy rozeznávají pouze dvě části a to model a view-controller, tedy část výstupů a
vstupů pro grafické rozhraní je společná.
Výhody rozdělení aplikace na uvedené části jsou následující:
•
•
•
lepší využitelnost vytvořeného kódu;
snadnější modifikovatelnost;
vazba modelu na grafickém rozhraní je "volnější".
Objektově orientované systémy nabízejí základní třídy právě pro grafické uživatelské
rozhraní. U jazyka BETA je to knihovna GUIenv, která poskytuje základní třídy pro tvorbu
grafického rozhraní.
Základní třídy této knihovny mají následující strukturu:
interfaceObject: (# ... #);
menubar: interfaceObject (# ... #);
menu: interfaceObject (# ... #);
window: interfaceObject (# ... #);
windowItem: window (# ... #);
Celý postup si objasníme na jednoduchém příkladu celočíselného čítače. Po spuštění aplikace
se objeví okno, viz. obrázek s hodnotou čítače nula. K ovládání čítače jsou vytvořena tři
tlačítka "increment" pro zvýšení hodnoty čítače o jedničku, "decrement" pro snížení hodnoty
čítače o jedničku a "clear" pro vynulování čítače.
59
Část model pro jednoduchý čítač je poměrně krátká. Obsahuje deklaraci třídy citac s
atributem pocet a metodami increment a decrement. Vidíme, že tato část neobsahuje žádné
vstupně / výstupní operace. Je vytvořena jako knihovna s názvem counterMod. Výpis je
uveden v příkladu8.1.
Příklad8.1
ORIGIN '~beta/basiclib/betaenv';
-- lib: Attributes -citac:
(#
pocet: @integer;
clear: (# do 0->pocet #);
increment: (# do 1+pocet->pocet #);
decrement: (# do pocet-1->pocet #)
#)
V části view-controller je vytvořena podtřída mywin třídy window. Ve třídě mywin jsou dále
specializovány všechny požadované další činnosti. Je zde deklarováno textové okno pro výpis
hodnot čítače, název tohoto okna (Čítač) a tři tlačítka pro ovládání čítače. Protože celá
aplikace je řízená událostmi, uvnitř popisu tlačítek je virtuální procedura eventHandler, která
reaguje na událost uvolnění tlačítka myši (onMouseUp) vyvoláním odpovídající metody,
která příslušnou událost ošetří (např. onDecrement). Procedura eventHandler na úrovni okna
(window) reaguje na uzavření celého okna, tedy celé aplikace ukončením (příkazem
terminate). Konkrétní výpis je uveden v příkladu8.2.
Příklad8.2
ORIGIN '~beta/guienv/guienv';
INCLUDE '~beta/guienv/controls'
'./counterMod'
'~beta/guienv/utils/auxcontrols';
-- guienvLib: Attributes -mywin: window
(#
open::<
(#
do
(300,200)->size;
'Okno_citace'->title;
incrementbutton.open;
citactext.open;
edittext1.open;
decrementbutton.open;
clearbutton.open;
onClear;
INNER open
#);
eventHandler::< (# onAboutToClose::<
incrementbutton: @pushbutton
(#
do terminate #) #);
60
(#
open::<
(#
do 'Increment'->label; (60,20)->size; (30,135)->position; INNER
#);
eventHandler::< (# onMouseUp::< (# do onIncrement #) #)
#);
citactext: @statictext
(#
open::<
(#
do 'Čítač:'->label; (60,16)->size; (20,25)->position; INNER
#)
#);
edittext1: @edittext
(#
t1: ^text;
open::<
(#
do
borderStyles.simple->border.style;
(90,22)->size;
(60,25)->position;
&text[]->t1[];
INNER
#);
input:
(# width,height,p,d: @integer; tm: @text
enter p
do
'
'->tm;
t1.clear;
p->t1.putint;
t1.length->d;
size->(width,height);
width / 3->width;
(1,width-d-4)->tm.sub->t1.prepend;
t1[]->contents {zobrazení aktuálního stavu čítače }
#)
#);
decrementbutton: @pushbutton
(#
open::<
(#
do 'Decrement'->label; (60,20)->size; (110,135)->position; INNER
#);
eventHandler::< (# onMouseUp::< (# do onDecrement #) #)
#);
clearbutton: @pushbutton
(#
open::<
(#
do 'Clear'->label; (60,20)->size; (195,135)->position; INNER
#);
eventHandler::< (# onMouseUp::< (# do onClear #) #)
#);
onClear:< (# do INNER onClear #);
onDecrement:< (# do INNER onDecrement #);
onIncrement:< (# do INNER onIncrement #)
#)
61
Hlavní program, který obě části spojuje je uložen v souboru counterApl. Aby mohl využívat
deklarace grafického rozhraní, musí mít v deklaraci ORIGIN uveden soubor grafického
rozhraní (counterGui). Navíc musí mít uveden prefix GUIenv, aby měl přístupné všechny
deklarované proměnné v grafickém rozhraní.
Protože využívá i model, má v deklaraci INCLUDE uvedeno jméno souboru, kde je model
uložen (counterMod).
Vlastní program (příklad8.3) deklaruje instanci ctc třídy citac a instanci mw třídy mywin.
Příklad8.3
ORIGIN './counterGui';
INCLUDE './counterMod';
-- program: Descriptor -GUIenv
(#
ctc: @citac;
mw: @mywin
(#
onIncrement::<
(# do ctc.increment; ctc.pocet->edittext1.input #);
onDecrement::<
(# do ctc.decrement; ctc.pocet->edittext1.input #);
onClear::< (# do ctc.clear; ctc.pocet->edittext1.input #)
#)
do mw.open
#)
Grafické zobrazení vazeb mezi jednotlivými částmi aplikace je uvedeno na následujícím
obrázku:
CounterApl
CounterGui
CounterMod
Grafické uživatelské rozhraní, model – view- controller.
62
Při vytváření grafického uživatelského rozhraní rozdělíme aplikaci na část model (výpočetní
část) a view – controller (výstupy, vstupy). Dále využíváme třídy grafické knihovny.
Jaké jsou výhody využívání tzv. model – view – controlleru pro aplikace s grafickým
uživatelským rozhraním?
Dala by se realizovat objektově orientovaná aplikace s grafickým uživatelským rozhraním bez
použití model – view – controlleru?
V čem je odlišný přístup v BETĚ od obecného objektově orientovaného přístupu pro aplikace
s grafickým uživatelským rozhraním?
V jazyce BETA tvoří view-controller jednu část a druhou část tvoří model. Obě tyto části jsou
„svázány“ hlavním programem.
Madsen O., Moller-Pedersen, Nygaard K.: Object oriented programming in the BETA
language. Addison Weslay 1993. web: www.mjolner.dk
Doplňující materiály na CD – MIA 94-24
Polák J.: Objektově orientované programováni, skripta ČVUT Praha 1992
Polák J., Merunka V.: Objektově orientované pojmy. série článků v Softwarových novinách
1993
Ježek K., Matějovic P., Racek S.: Paralelní architektury a programy. Skriptum ZČU Plzeň,
1997
63
1. Podle příkladu1.2 vytvořte program, který vypíše frekvence výskytu jednotlivých znaků
v zadaném textu. Vypisuje pouze znaky vyskytující se v textu (tedy nikoli znaky s výskytem
nula.
2. Na základě příkladu2.2 vytvořte třídy Muz a Zena. Každá z těchto tříd má atributy jmeno,
referenční atribut na svůj protějšek (žena na muže a opačně) a metodu tisk, která tiskne
atribut jméno a jméno partnera (partnerky) prostřednictvím referenčního odkazu. Vytvořte
instance 2 mužů a 2 žen a přes referenční atributy je „propojte“.
3. Podle příkladu7.1 vytvořte třídu Rodina, která sestává ze třídy Muz, třídy Zena a seznamu
(registru) dětí (modelujete klasickou rodinu). Namodelujte skutečnost, že každé dítě může mít
jiného otce a jinou matku (v případě pěstounských rodičů).
Pomůcka: třída Dítě bude mít referenční atribut na otce a na matku, které mohou být totožné
se instancí třídy Muž a Žena popsané ve třídě Rodina, nebo ne. Vytvořte rodinu se třemi
dětmi.
Aplikovat grafické uživatelské rozhraní vyžaduje trochu zkušeností. V dokumentaci k jazyku
BETA je uvedena celá řada jednoduchých příkladů. Při praktické aplikaci se dá s velkou
výhodou využít ikona GUI, která umožňuje vytvářet grafické uživatelské rozhraní
interaktivně.
64

Podobné dokumenty

Kognitivní stimulace pacientů trpících Alzheimerovou chorobou

Kognitivní stimulace pacientů trpících Alzheimerovou chorobou mírnou formou demence, tak zdravých lidí. K řešení tohoto úkolu je třeba seznámit se s problémem deklinace kognitivních funkcí, existujícím řešením toho problému a způsobem programování aplikací pr...

Více

souboru - Gymnázium Český Brod

souboru - Gymnázium Český Brod PZ nad Vltavou Gymnázium Příbram Příbram Gymnázium Příbram Příbram Gymnázium MB Mladá Boleslav Opengate PV Babice 1.ZŠ Říčany Gymnázium Kolín Opengate Babice Gymnázium V.B.T. Slaný

Více

end loop

end loop Mechanismus rendezvous : -vedl k vytváření dodatečných úkolů pro obsluhu sdílených dat Typ protected má specifikační část (obsahuje přístupový protokol k objektu) a tělo (obsahuje implementační det...

Více

5.3 Rozhodovací pravidla

5.3 Rozhodovací pravidla jsme hledali podmínky, za kterých nějaký objekt patří do třídy Class. Každý takový vztah můžeme považovat za (objevenou) znalost. Tyto znalosti pak můžeme pak dále zkoumat a interpretovat. Problém ...

Více

ZDE - Katedra informatiky

ZDE - Katedra informatiky Kurs je zaměřený na získání základních teoretických vědomostí v oblasti návrhu databázových systémů se zaměřením na relační systémy. Při návrhu modelů relačních databází jsou kombinovány tři způsob...

Více

Katalogizace podle RDA ve formátu MARC 21

Katalogizace podle RDA ve formátu MARC 21 Hlavní záhlaví - jméno akce jméno akce nebo jurisdikce jako vstupní prvek místo konání akce datum konání akce

Více

12. Výjimky

12. Výjimky Vznikla výjimka NullPointerException a to v metodě hashCode() ve třídě Osoba (konkrétně na řádku 91 zdrojového kódu Osoba.java). Tato metoda byla volána z metody hash() ve třídě HashMap, která byla...

Více

MySQLi (objektově)

MySQLi (objektově) MYSQLI_ASSOC: vrátí řádek jako asociativní pole, kde je klíčem název sloupce a hodnotou obsah sloupce. MYSQLI_NUM: vrátí řádek jako číselné indexované pole, pořadí je určeno pořadím názvů sloupců s...

Více