Objektově orientované programování II OBOP2
Transkript
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
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...
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ý
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...
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 ...
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...
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
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...
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...