ABSTRAKT
Návrhové vzory sú softvérové riešenia, ktoré je možné použiť v rôznych aplikáciách. Používateľ môže identifikovať vzor v diagrame UML (Unified Modeling Language). Návrhové vzory sú opakujúce sa riešenia problémov s návrhom softvéru, ktoré nájdete znova a znova v reálnom vývoji aplikácií. Vzory sú zamerané na návrh a interakciu tried a objektov, ako aj na poskytovanie komunikačnej platformy týkajúcej sa elegantných, opakovane použiteľných riešení pre bežne vyskytujúce sa programové problémy. V tomto článku sa sústreďujeme na kombinovanie viacerých návrhových vzorov a ukazujeme, ako ich môžeme využiť pri programovaní aplikácií.
Kľučové slová: Návrhové vzory, Kombinácia návrhových vzorov, Jazyk UML, UML diagramy
ÚVOD
Navrhovanie objektovo-orientovaného programového vybavenia môže byť obtiažne. Je potrebné nájsť vhodné triedy, definovať rozhrania tried a dedičnosť, stanoviť vzťahy medzi triedami. Chceme, aby sa dal navrhnutý softvér viackrát použiť a aby sme nemuseli každý problém riešiť od začiatku. Ak nájdeme vhodné riešenie, chceli by sme ho používať neustále. V množstve objektovo – orientovaných systémov nájdeme znovuopakujúce sa vzory tried. Takéto vzory vytvárajú návrhy, ktoré sú flexibilné, elegantné a znovupoužiteľné.
Účelom knihy Gamma a kol. [1] bolo poskytnúť návrhové vzory zachytávajúce skúsenosti, ktoré programátori môžu účinne používať. Autori zdokumentovali najdôležitejšie návrhové vzory a vytvorili z nich katalóg. Každý vzor popisuje problém, ktorý sa neustále vyskytuje. Návrhové vzory sú popisy komunikujúcich objektov a tried, ktoré sú upravené k riešeniu všeobecného návrhového problému. Vzory nepopisujú konkrétny tvar návrhu alebo implementáciu. Vzor je šablóna, ktorú možno použiť v množstve rôznych situácií. Vzor obsahuje abstraktný popis problému a spôsob, ako ho usporiadanie prvkov (tried a objektov) vyrieši. Každý vzor môžeme reprezentovať graficky pomocou jazyka UML. Jazyk UML pritom nie je viazaný na konkrétny programovací jazyk. Avšak, voľba programovacieho jazyka je dôležitá, lebo ovplyvňuje pohľad na vec. Pôvodne sa v knihe Gamma a kol. použil jazyk C++ a Smalltalk. V súčastnosti sa ukázalo veľmi vhodné používať jazyk C# (C Sharp) [2, 3].
V tomto článku sa budeme venovať problematike kombinovania návrhových vzorov. Takýto prístup zvyšuje kvalitu programov a môže zabezpečiť flexibilitu, efektívnosť a znovupoužiteľnosť programov.
NÁVRHOVÉ VZORY
Ako sme už uviedli, dôležitou súčasťou každého popisu vzoru je UML diagram, ktorým môžeme zobraziť triedy a vzťahy medzi triedami. Jazyk UML je všeobecne uznávaný spôsob popisu softvéru v schématickej podobe [4]. Základným prvkom pre triedu je diagram, ktorý obsahuje názov triedy, jej atribúty a metódy triedy. Dôležitá je aj abstraktná trieda vo vzore, ktorá obsahuje abstraktné metódy, abstraktná metóda nemá príkazy. Pre abstraktnú triedu použijeme symbol {A}.
Medzi triedami sa vytvárajú často vzťahy. Asociácia je taký vzťah medzi triedami A a B, keď si triedy vzájomne volajú svoje metódy. Asociácia môže byť aj jednostranná, ide o prípad, keď trieda A volá metódy triedy B, ale nie naopak (Tabuľka 1). Toto zobrazujeme orientovanou šípkou. Agregácia reprezentuje taký vzťah, ktorý znázorňujeme prázdnym kosoštvorcom – trieda A vlastní B a B môže prežiť A, v programátorskom zmysle . Ak zrušíme objekt triedy A, tak objekt B nebude zrušený. Objekt B potom nevytvárame vo vnútri triedy A. Silnejší vzťah je kompozícia, ktorú znázorňujeme plným kosoštvorcom. Ide o prípad, keď A vlastní B a B závisí od A. Dom sa skladá z miestností a keď zrušíme dom, tak zrušíme všetky miestnosti. Dedičnosť medzi triedami A a B zobrazujeme orientovanou šípkou, čo znamená, že B dedí od A. I keď UML diagram nie je presnou kópiou programu, možno ho tak nakresliť a stanoviť také pravidlá, že sa z neho dá vytvoriť štruktúra programu (kód metód obyčajne neuvádzame).
Tabuľka 1: Grafické zobrazenie základných vzťahov medzi triedami.
Návrhové vzory klasifikujeme podľa určitých kritérií. Prvé kritérium je účel, určujúci, čo vzor robí. Vzory môžu mať účel vytvárajúci, štrukturálny alebo týkajúci sa chovania. Vytvárajúce vzory sa zaoberajú procesom objektovej tvorby, štrukturálne vzory sa zaoberajú skladbou tried a vzory chovania charakterizujú spôsoby, podľa ktorých triedy konajú a rozdeľujú si povinnosti. Druhé kritérium určuje, čoho sa vzor týka, tried alebo objektov. Triedne vzory sa zaoberajú vzťahmi medzi triedami a ich podtriedami. Tieto vzťahy sa zavádzajú prostredníctvom dedičnosti a sú staticky zafixované pri kompilácii. Objektové vzory sa zaoberajú objektovými vzťahmi a dajú sa meniť počas behu programu.
Existuje rozdiel medzi triedou a objektom. Implementácia objektu je definovaná triedou. Trieda špecifikuje dáta a definuje operácie, ktoré objekt môže vykonávať. Objekty sa vytvárajú vytvorením inštancie triedy. O objekte hovoríme, že je inštancia triedy. Proces vytvorenia inštancie triedy pridelí pamätový priestor pre dáta objektu a s týmito dátami spájame operácie. Hlavný zmysel abstraktnej triedy je definovať rozhranie pre svoje podtriedy. Abstraktná trieda odkladá všetky svoje implementácie na operácie definované v podtriedach. Dôležité je pochopiť, že programujeme oproti rozhraniu a nie oproti triede. Uvedieme kód pre vzťah medzi triedou a objektom:
class MojaTrieda {
public void MojaMetoda() {
//Prikazy v metóde
}
static void Main() {
MojaTrieda t1 = new MojaTrieda();
MojaTrieda t2 = new MojaTrieda();
}
}
Obrázok 1: Trieda a jej objekty.
t1 a t2 reprezentujú objekty, ktoré sú vytvorené pre triedu s názvom MojaTrieda. V podstate objekty sú premenné dátového typu MojaTrieda.
KOMBINÁCIA NÁVRHOVÝCH VZOROV V APLIKÁCIÁCH
V súčasnosti vzrastá použitie návrhových vzorov v rôznych aplikáciách, ako sú hry [5] alebo mobilné aplikácie [6] . V týchto aplikáciách sa často kombinuje viac rôznych vzorov. Uvedieme si príklad kombinácie dvoch návrhových vzorov a to Zreťazenie zodpovednosti (Chain of Responsibility) a Stratégie (Strategy). Našim cieľom je tiež lepšie pochopiť použitie vzorov v reálnych situáciách.
Vzor Zreťazenie zodpovednosti pracuje so zoznamom objektov, ktoré majú určité obmedzenia na typy požiadaviek, ktoré sú schopné zpracovať. Ak objekt nemôže danú požiadavku obslúžiť, prepošle ju ďalšiemu objektu v reťazci. S takýmto vzorom sa stretávame aj v bežnom živote. Predstavme si človeka pristujúceho k banke alebo k nejakej inej inštitúcii so žiadosťou. V prípade banky, prvá úroveň bankových úradníkov spravuje najjednoduchšie žiadosti. Žiadosti vyžadujúce viac skúmania sú poslané k nadriadenému a v najzložitejšom prípade žiadosť môže skončiť aj u vedúceho banky. Úradník, jeho vedúci a riaditeľ sú typickým príkladom zreťazenia zodpovednosti. UML diagram pre takýto prípad je na obrázku:
Obrázok 2: UML diagram vzoru Zreťazenia zodpovednosti.
Zjednodušený kód, kde neuvažujeme triedu Handler3, bude
using System;
class Client {
public Client(Handler h, int request) {
h.Request(request);
}
}
abstract class Handler {
protected Handler successor;
public void SetSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void Request(int request);
}
class Handler1 : Handler {
public override void Request(int request)
{
if (request > 0 && request < 5000)
{
Console.WriteLine(“{0} zpracovaná žiadosť {1}”, this.GetType().Name, request);
}
else if (successor != null)
{
successor.Request(request);
}
}
}
class Handler2 : Handler {
public override void Request(int request)
{
if (request >= 5000 && request < 20000)
{
Console.WriteLine(“{0} zpracovaná žiadosť {1}”, this.GetType().Name, request);
}
else if (successor != null)
{
successor.Request(request);
}
}
}
class MainApp {
static void Main() {
Handler h1 = new Handler1();
Handler h2 = new Handler2();
h1.SetSuccessor(h2);
Client c;
int[] requests = { 2000, 5000, 14000, 22000, 18000, 3000, 27000, 20000 };
foreach (int request in requests) {
if (request >0 && request < 5000)
c = new Client(h1, request);
else if (request >= 5000 && request < 15000)
c = new Client(h2, request);
}
Console.ReadKey();
}
}
Client je trieda, ktorá volá metódu Request. Handler je rozhranie, je to abstraktná trieda, ktorá obsahuje abstraktnú metódu Request. Handler1(Uradník) Handler2 (Vedúci) a Handler3 (Riaditeľ) sú konkrétne triedy obsluhy, successor je odkaz na ďalší objekt triedy. Pracovníci banky posudzujú výšku výberu, ktorú si chce klient vybrať z banky, napríklad číslo 5000 znamená hodnotu výberu v Eurách.
Môžeme teraz pridať do diagramu vzor Stratégia. Je to z dôvodu, že každý pracovník ma iný postup ako spracuje žiadosť, ktorú obdrží od klienta. Môžu existovať rôzne algoritmy (stratégie), ktoré sa používajú na daný problém. Ak sú všetky algoritmy v jednej triede vznikne neusporiadaný kód s množstvom podmienok. Je rozumnejšie konkrétne algoritmy rozmiestniť do viacerých tried. To reprezentuje vzor Stratégia. UML diagram, ktorý bude kombinovať dva vzory Zretazenie zodpovednosti a Stratégiu je na obrázku:
Obrázok 3: Kombinácia dvoch návrhových vzorov.
Kód zodpovedajúci tomuto diagramu je možné zostrojiť, ak uvážime pravidlá uvedené v článku [7]:
using System;
class Prg {
abstract class Handler {
public abstract void Request(int request);
}
class Handler1 : Handler {
public override void Request(int request)
{
Console.WriteLine(“{0} Spracovaná žiadosť {1}”, this.GetType().Name, request);
Context context;
context = new Context(new StrategyA());
context.ContextAlgorithm();
}
}
class Handler2 : Handler {
public override void Request(int request) {
Console.WriteLine(“{0} Spracovaná žiadosť {1}”, this.GetType().Name, request);
Context context;
context = new Context(new StrategyB());
context.ContextAlgorithm();
}
}
class Client {
public Client(Handler h, int request)
{
h.Request(request);
}
}
abstract class Strategy {
public abstract void Algorithm();
}
class StrategyA : Strategy {
public override void Algorithm() {
Console.WriteLine(“Zavolaná metóda StrategyA.Algorithm()\n”);
}
}
class StrategyB : Strategy {
public override void Algorithm()
{
Console.WriteLine(“Zavolaná metóda StrategyB.Algorithm()\n”);
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) //Konštruktor
{
this.strategy = strategy;
}
public void ContextAlgorithm() {
strategy.Algorithm();
}
}
static void Main() {
Handler h1 = new Handler1();
Handler h2 = new Handler2();
Client c;
int[] requests = { 5000, 14000, 18000, 3000 };
foreach (int request in requests)
{
if (request > 0 && request < 5000)
c = new Client(h1, request);
else if (request >= 5000 && request < 20000)
c = new Client(h2, request);
}
Console.ReadKey();
}
}
}
Tento kód je možné prepísať s využitím technológie ASP.NET a publikovať ho na web serveri [8, 9].
DISKUSIA A ZÁVER
Návrhové vzory sa ukázali ako veľmi užitočné v rámci objektovo-orientovaného programovania a pomohli dosiahnuť dobrý návrh aplikácií prostredníctvom opätovnej použiteľnosti komponentov. Dobrá pracovná znalosť návrhových vzorov pomáha vývojárom navrhnúť robustnejšie a flexibilnejšie riešenia.
V softvérovom inžinierstve je dizajnový vzor všeobecným opakovane použiteľným riešením bežne sa vyskytujúceho problému v rámci návrhu softvéru. Návrhový vzor nie je hotový dizajn, ktorý sa dá priamo premeniť na kód. Ide o opis alebo šablónu, ako riešiť problém, ktorý možno použiť v mnohých rôznych situáciách. Vzory formalizujú osvedčené postupy, ktoré môže efektívne programátor implementovať v aplikáciách. V posledných rokoch vzrastá záujem o použitie návrhových vzorov napríklad v mobilných aplikáciách.
ZDROJE:
[1] Gamma, E., Helm, R., Johnson, R., and Vlissides, J. Design Patterns:
Elements of Reusable Object-Oriented Software, Addison-Wesley, 1995.
[2] Bishop J. , C# 3.0 Design Patterns, O’Reilly Media, Inc.,2008.
[3] Lasater Ch. G., Design Patterns, Wordware Publishing, Inc., 2007.
[4] Schmuller J., Teach Yourself UML in 24 Hours, third edition, Sams
Publishing, 2004.
[5] Ampatzoglou A., Chatzigeorgiou A., Evaluation of Object-Oriented Design
Patterns in Game Development, Information and Software Technology 49,
2007, p. 445-454.
[6] Ali M. M., Elsharkawi A. F., Elsaid M.G., Zaki M., Design patterns for
Multimedia Mobile Applications, Journal of Computer Science and Software
Application, Vol.1, No. 2, December 2014, p.16-34.
[7] Malcher V., Exact Implementation of Design Patterns in C# Language, IJAIT,
Vol.4, No.6, December 2014.
[8] Millet S., Professional ASP.NET Design Patterns, Wiley Publishing, Inc.,
2010.
[9] Dhamayanti N., and Thangeval P., Structural Design Patterns and ASP.NET
Framework 2.0, Journal of Object Technology, Vol.5, No.8, November-
December 2006.
AUTOR:
Doc. RNDr. Viliam Malcher, CSc
Univerzita Komenského v Bratislave
Fakulta managementu
Odbojárov 10, P. O. Box 95
820 05 Bratislava
viliam.malcher@fm.uniba.sk
RECENZENTI:
prof. RNDr. Michal Greguš, PhD.
Univerzita Komenského, Fakulta managementu
Ing. Jaroslav Vojtechovský, PhD.
Univerzita Komenského, Fakulta managementu
VYDANIE:
Digital Science Magazine, Číslo 1, Ročník VI.