Ugrás a tartalomhoz

Programozás technika

Kusper Gábor, Radványi Tibor

Kempelen Farkas Hallgatói Információs Központ

Programozási technológiák – Tervezési minták

Programozási technológiák – Tervezési minták

A tervezési minták gyakori programozói feladatokat oldanak meg. Gyakorlott programozók, miután már sokszor megoldottak egy-egy problémát, desztillálták a bevált megoldások lényegét. Így születtek a tervezési minták, ezek gyűjteményei. Ezek közül az első a GOF könyv volt. Ezt több is követte. Ezek közül a legjelentősebbek:

  1. Eric Freeman, Elisabeth Robson, Kathy Sierra, Bert Bates: Head First Design Patterns, O'Reilly Media, 2004.

  2. Cay S. Horstmann: Object-Oriented Design and Patterns, Wiley, 2006.

  3. Robert C. Martin: Agile Software Development, Principles, Patterns, and Practices, Prentice Hall, 2002.

Ebben a jegyzetben elsősorban a GOF könyvben ismertetett mintákat nézzük át. Más mintáknál megjegyezzük, melyik gyűjteményben jelent meg.

A minták alkalmazásával könnyen bővíthető, módosítható osztályszerkezetet kapunk, tehát rugalmas kódot. Az ár, amit ezért fizetünk a bonyolultabb, nehezebben átlátható kód és a nagyobb fejlesztési idő. Sokan azt mondják, hogy ez nem fizetődik ki. Törekedjünk a legegyszerűbb megoldásra (lásd extrém programozás) és ha kell, kódszépítéssel általánosítjuk a kódot. Így érjük el a rugalmas kódot.

A tervezési minták viszonylagos bonyolultsága abból adódik, hogy olyan osztályokat tartalmaznak, amiknek semmi köze valóságos objektumokhoz, habár azt tanultuk, hogy egy OOP osztály a valóság absztrakciója. Ugyanakkor ezekre a technikai osztályokra szükség van a rugalmassághoz. Ezek azok az osztályok, amiket józan paraszti ésszel nehéz kitalálni, de nem is kell, mert a legjobb megoldások tervezési minták formájában rendelkezésre állnak.

Architekturális minták

Az architektúra a program azon része, ami nem változik az idő során, vagy ha változik, akkor az nagyon nehezen kivitelezhető. Talán egy szívátültetéshez vagy agyműtéthez hasonlítható.

MVC – Model-View-Controller

Az MVC minta talán az első tervezési minta. A nevét a három fő komponensének nevéből kapta:

  1. Model (magyarul modell): Az adatokat kezelő, vagyis tulajdonképpen az üzleti logikát megvalósító réteg. Ez felel az adatok tárolásáért, visszaolvasásáért. Itt foglalnak helyet azok a függvények is, amik műveleteket végeznek az adatokon. Része az adatbázis is.

  1. View (magyarul nézet): A felhasználói felület megjelenítéséért, a felhasználó különféle nyűgjeinek a Vezérlő felé továbbításáért felelős réteg. Itt jelennek meg a felhasználó számára a vezérlőelemek, a felhasználónak szánt adatok megfelelő formában való megjelenítése is itt történik.

  2. Controller (magyarul vezérlő): Ez a réteg a vezérlőelemek eseményeinek alapján meghívja a modell megfelelő függvényeit, illetve ha a megjelenítésben érintett adatok változnak, akkor erről értesíti a Nézetet.

13. ábra MVC

Az alkalmazást három egységre bontjuk. Külön egység felelős a megjelenítésért, az adatok kezeléséért valamint a felhasználói cselekedetek megfelelő kezeléséért. Ez több okból is jó nekünk, legelőször is, ha lecseréljük valamelyik részt, akkor a többi még maradhat, nem kell hozzányúlni, több időnk marad (munkaidőben játszani:). Könnyebben módosíthatjuk az egyes részeket.

Az MVC egyik fő újítása az volt, hogy lehetővé tetette, hogy egy modellhez több nézet is tartozzon. Minden nézet ugyanannak a modellnek a belső állapotát jeleníti meg. Bármelyik nézeten lenyomnak egy gombot, az az esemény eljut a kontrollernek. A kontroller meghívja a modell megfelelő metódusát. Ha e miatt a modell belső állapota megváltozik, akkor a modell a megfigyelő tervezési mintának megfelelően értesíti a nézeteket, hogy változás történt, nekik is meg kell változni.

14. ábra Továbbfejlesztés

Az MVC mintának több továbbfejlesztése is létezik. Ezek közül a két legismertebb:

  1. MVP – Model View Presenter, magyarul Modell – Nézet – Megjelenítő: Ebben a változatban a modell nem a nézetet, hanem a megjelenítőt értesíti, ha változás történik. A megjelenítő lekéri az adatokat a modellből, feldolgozza, és megformázza a nézet számára.

  2. MVVM – Model View View-Model, magyarul Modell – Nézet – Nézetmodell: Ez az MVP továbbfejlesztése, ahol a nézetben a lehető legkevesebb logika van. A nézetmodell elvégez minden feladatot a nézet helyett, csak a megjelenítés marad a nézetre.

ASP.NET MVC Framework

Az ASP.NET MVC Framework az ASP.NET Web Forms alapú fejlesztésnek nyújt alternatívát MVC alapú web alkalmazások fejlesztéséhez. ASP.NET MVC Framework egy olyan könnyű és jól tesztelhető megjelenítő keretrendszer, amely (csakúgy, mint az ASP.NET Web Forms) integrálja a már meglévő ASP.NET lehetőségeit, mint például a master page-eket és a beépített felhasználó kezelést, azaz membership provider alapú azonosítást. Az MVC alapjait a System.Web.Mvc névtér definiálja, amely a System.Web névtér támogatott része.

Az MVC egy alapvető programtervezési minta, amely számos fejlesztőnek már ismerős lehet. Néhány web alkalmazás már régóta használja az MVC keretrendszer előnyeit, míg mások továbbra is az ASP.NET hagyományos Web Forms-os postback alapú rendszert használják. Egyesek pedig ötvözik a két rendszer előnyeit. Azt később tárgyaljuk, hogy az MVC fejlesztési mód mikor előnyös.

Az MVC framework három fő komponenst foglal magában:

  1. Modellek: A modell objektumok az alkalmazás azon részei, amelyek az adatokat "szállító" logikát implementálják. A modell objektumok gyakran fogadnak adatokat az adatbázisból és tárolják azokat magukban. Például egy Termék objektum lekérhet adatokat adatbázisból, dolgozhat vele, majd a módosított adatokat visszaírhatja a Termék táblába az SQL Szerveren.

Kisebb alkalmazások esetében a modellek inkább koncepcionálisak, mint fizikailag megvalósítottak. Például ha az alkalmazás kizárólag olvassa és megjeleníti az adatokat, akkor nincs konkrétan megvalósítva a modell réteg és a hozzá tartozó osztályszerkezet. Ebben az esetben a modell réteget csak az adattoló objektumok reprezentálják.

  1. Nézetek (Views): A nézetek a felhasználói felület (User Inteface - UI) megjelenítő komponensei. A UI általában azokból az adatokból készül, amelyek a modell rétegből jönnek. Ilyen lehet például egy szerkesztő nézete a Termék táblának, amely állhat szövegdobozokból, gombokból, lenyíló menükből stb., melyek a Termék objektum aktuális állapotát mutatják.

  2. Vezérlők (Controllers): A vezérlők azok a komponensek, melyek a felhasználói interakciót kezelik, dolgoznak a modell objektumokkal és kiválasztják a megfelelő nézetet a megjelenítéshez. Egy MVC alkalmazásban a nézet csak információt jelenít meg; a vezérlő kezeli és reagálja le a felhasználói interakciót. Például a vezérlő kezeli a query sztring értékeket, továbbítja a modell felé, melyek a megfelelő adatbázis lekérdezést állítják össze az átadott értékek alapján.

15. ábra

Az MVC minta olyan alkalmazások elkészítésében nyújt segítséget, melyek szétválasztják az alkalmazás különböző részeit (input logika, üzleti logika, megjelenítési logika), miközben egy laza csatolófelületet biztosít a szétválasztott részek között. A minta meghatározza azt is, hogy melyik logikai rétegnek hol kell elhelyezkednie az alkalmazásban. A megjelenítési vagy UI réteg a nézetekhez kötődik, az üzleti logika a vezérlőkhöz, az input logika pedig a modellekhez tartozik. Ez a szeparáció segít kezelni egy a komplexitást egy alkalmazás fejlesztésénél, mivel lehetővé teszi, hogy az implementáció során egy adott időben adott szemszögből vizsgáljuk a dolgokat. Például a megjelenítési réteg fejlesztésekor nem kell foglalkoznunk azzal, hogy az üzleti logikai rétegben milyen műveleteket kell végezni az adattal, hiszen a nézeteken keresztül csak megjelenítjük őket.

Ráadásul a komplexitás kezelésében az MVC minta könnyebbé teszi az alkalmazás tesztelését, mint egy Web Forms alapú fejlesztési modellben. Például Web Forms alapú web alkalmazásban egyetlen osztály felelhet a megjelenítésért és a felhasználói interakcióért is. Automata teszteket írni Web Forms alapú alkalmazásokhoz bonyolult lehet, mert egyedülálló oldal teszteléséhez példányosítani kell az oldal osztályát, az összes gyerekvezérlőt és további függő osztályokat is. Mivel az oldal futtatásához ennyi osztály példányosítására van szükség, nehéz olyan tesztet írni, amely az oldal egyes részeivel kizárólagosan foglalkozik. Kijelenthetjük tehát, hogy Web Forms alapú környezetbe sokkal nehezebb a tesztelést integrálni, mint egy MVC-t használó alkalmazásba. Továbbá Web Forms-os környezetben a teszteléshez szükségeltetik egy web szerver is. Mivel az MVC keretrendszer szétválasztja a komponenseket és ezek között interfészeket használ, könnyebb különálló komponensekhez teszteket gyártani az izoláció miatt.

A laza kötés az MVC alkalmazás három fő komponense között párhuzamos fejlesztést is lehetővé tesz. Ez azt jelenti, hogy egy fejlesztő dolgozhat a kinézeten, egy második a vezérlő logikán, egy harmadik pedig az üzleti logikára fókuszálhat egy időben.

Mikor készítsünk MVC alkalmazást

Körültekintően kell megválasztanunk, mikor használunk ASP.NET MVC keretrendszert a fejlesztéshez az ASP.NET Web Forms helyett, ugyanis az ASP.NET MVC nem helyettesíti a Web Forms modellt; használhatjuk mindkettőt egyszerre egy alkalmazáson belül is akár.

Mielőtt az MVC keretrendszer használata mellett döntünk a Web Forms modell helyett, mérlegeljük mindkettő előnyeit.

Az MVC alapú web alkalmazás előnyei

Az ASP.NET MVC keretrendszer a következő előnyöket nyújtja:

  1. Könnyebbé teszi komplex alkalmazások fejlesztését azzal, hogy három részre osztja az alkalmazást: modellre, nézetre és vezérlőre.

  2. Nem használ állapottárolást (view state) és szerveroldali form-okat sem. Ez ideálissá teszi az MVC keretrendszert azok számára, akik teljes hatalmat szeretnének az alkalmazás viselkedése felett.

  3. Egy fő vezérlőn mintán keresztül dolgozza fel a web alkalmazáshoz érkező kéréseket, innen továbbítja a megfelelő vezérlőknek tovább. (A fő vezérlőről az MSDN weboldalán lehet több információhoz hozzájutni a Front Controller szekció alatt)

  4. Segítséget nyújt a teszt-vezérelt fejlesztéshez (test-driven development - TDD)

  5. Jól működik olyan web alkalmazások esetében, amelyet fejlesztők csapata fejleszt és támogat, ahol a kinézet tervezőknek magas fokú ellenőrzésre van szüksége az alkalmazás viselkedése felett.

A Web Forms alapú alkalmazás előnyei

A Web Forms alapú keretrendszer előnyei:

  1. Támogatja az eseménykezelés modellt és megőrzi az állapotokat HTTP protokoll felett, mely előnyös az un. "line-of-business" web alkalmazás fejlesztésnél. A Web Forms alapú alkalmazás tucatnyi eseménykezelőt biztosít, amit több száz szerverkontrollból elérhetünk.

  1. Page Controller mintát használ melyek különálló tulajdonságokkal ruházzák fel az egyes oldalakat. További információ a Page Controller-ről az MSDN weboldalán található.

  2. Állapottárolást (view state) és szerveroldali form-okat használ, melyek megkönnyítik az állapotkezelési információk menedzselését.

  3. Jól működik kisebb fejlesztői csoportban, számos komponens felhasználható segítve a gyors alkalmazásfejlesztést.

  4. Összességében kevésbé összetett alkalmazás fejlesztés szempontjából, mert a komponensek (a Page osztály, vezérlők stb.) szorosan integráltak így általában kevesebb kódolást igényel, mint az MVC modell.

Az ASP.NET Framework tulajdonságai

Az ASP.NET MVC Framework a következő funkciókat nyújtja:

  1. Az alkalmazás feladatainak szeparálása (input logika, üzleti logika, megjelenítési logika), tesztelhetőség és teszt-vezérelt fejlesztés támogatása alapból. Az MVC összes mag eleme interfész alapú, mely lehetővé teszi az úgynevezett "mock" objektumokkal való tesztelést, amelyek olyan objektumok, amik imitálják az aktuális objektum viselkedését az alkalmazásban. Lehetővé teszi a unit-test alapú tesztelést anélkül, hogy a vezérlőket egy ASP.NET folyamaton keresztül futtatnunk kellene, így flexibilissé és gyorssá téve a unit-tesztelést. Bármelyik unit-test keretrendszert használhatjuk, amelyik kompatibilis a .NET keretrendszerrel.

  1. Egy kiterjeszthető és bővíthető keretrendszer. Az ASP.NET MVC keretrendszer komponensei úgy lettek lefejlesztve, hogy azok könnyen testre szabhatóak, ill. lecserélhetőek legyenek.

  1. Az URL-mapping (útvonal feltérképezés) komponens lehetővé teszi olyan alkalmazások fejlesztését, amelyek érthető és kereshető URL-ekkel rendelkeznek. Az URL-ek nem tartalmaznak fájlnév kiterjesztéseket, kereső (SEO) és felhasználóbarát.

  2. Támogatja a meglévő ASP.NET oldalak (.aspx fájlok), felhasználói vezérlők (.ascx fájlok) és master page-ek (.master fájlok) használatát. Használhatjuk a meglévő ASP.NET lehetőségeit, mint a beágyazható (azaz nested) master page-ek használatát, valamint az ASP.NET jelölőnyelvén belüli szerveroldali kód (pl. C#) használatát a <%= %> kifejezés segítségével.

  1. Meglévő ASP.NET funkciók támogatása. Az ASP.NET MVC lehetőséget ad a beépített lehetőségek használatára, mint a form autentikáció, Windows autentikáció, felhasználó kezelés (membership és roles), session kezelés, stb.

Több rétegű architektúra

Réteg alatt a program olyan jól elszeparált részét értjük, amely akár külön számítógépen futhat. A rétegek jól definiált interfészeken keresztül kommunikálnak, mindig csak a felettük és alattuk lévő réteggel kommunikálhatnak. A rétegek annyira lazán csatoltak a többihez, hogy egy réteg a többi réteg számára észrevétlenül lecserélhető, feltéve, hogy ugyanazt az interfészt használja, mint az elődje.

A több rétegű architektúra akárhány rétegből állhat. Minél több a rétegek száma, annál rugalmasabb a rendszer, de ezzel szembeható, hogy annál nehezebb a karbantartása. A legismertebb több rétegű architektúra a 3 rétegű (angolul 3-tier). Itt a három réteg:

  1. Felhasználói felület

  2. Üzleti logika

  3. Adatbázis

A felhasználói felület gyakran grafikus, így csak a GUI (graphical user interface) rövidítéssel hivatkozunk rá. Az üzleti logikát (angolul: busyness logic) angol neve után gyakran BL-nek rövidítjük. Az adatbázis (angolul: database) réteget gyakran perzisztencia rétegnek hívjuk és általában DB-nek rövidítjük az angol neve után.

Hiba azt gondolni, hogy a három rétegi architektúra csak az MVC minta másik neve. Az első esetén a felhasználói felület nem kommunikálhat az adatbázis réteggel, tehát ez egy lineáris rendszer a kommunikáció útját tekintve. Ezzel szemben az MVC háromszög alakú, hiszen a modell közvetlenül értesíti a nézeteket, ha megváltozik.

A 3 rétegű architektúra általában három számítógépet használ:

  1. kliens

  2. alkalmazás szerver

  3. adatbázis szerver

A kliens lehet vastag vagy vékony kliens. A vékony kliens csak egy böngésző, illetve a benne futó weboldal. A vastag kliens egy általában C# vagy Java nyelven megírt önálló alkalmazás. Mindkettőnek van előnye és hátránya:

Vékony kliens

Vastag kliens

Szegényes felhasználó élmény.

Gazdag felhasználói élmény.

Nem kell frissíteni. Nem kell a frissítéseket eljuttatni a felhasználóhoz.

Hibajavítás, új verzió kiadása csak frissítéssel lehetséges.

Kicsi hardver igény.

Magas hardver igény.

A kliens számítógép erőforrásai csak részben állnak rendelkezésére.

A kliens számítógép erőforrásaihoz hozzáférhet, pl. állományt írhat, olvashat.

Fő hátránya a szegényes felhasználó élmény, de ez AJAX technológiával gazdagabbá tehető.

Fő hátránya a nehézkes frissítés, de ez történhet automatikusan is, ha van internet kapcsolat.

Látható, hogy igazán a két technológia előnyei és hátrányai kezdenek kiegyenlítődni egymással szemben.

16. ábra Három rétegű szoftver architektúra

Az alkalmazás szerveren (hardver értelemben) JavaEE platform esetén alkalmazás szerver (szoftver értelemben) fut. Ez megkönnyíti az alkalmazás fejlesztését, mert néhány szerver funkciót, pl. a terhelés elosztást (load balancing) megold helyettünk az alkalmazás szerver.

Létrehozási tervezési minták

A létrehozási minták feladata, hogy megszüntessék a sok new kulcsszóval ránk szakadó függőségeket. Ha úgy írjuk meg a programunkat, hogy mindenhová a new Kutya() hívást írjuk, amikor Kutya példányra van szükségünk, akkor nehéz lesz ezt lecserélni egy későbbi new SzuperKutya() hívásra. Jobban járunk, ha a „gyártást” a létrehozási mintákra hagyjuk és például így készítjük a kutyáinkat: kutyaGyár.createKutya(). Ilyenkor, ha változnak a követelmények, akkor csak egy helyen kell változtatni a létrehozás módját. Ott, ahol létrehozzuk a kutyaGyár példányt.

Egyke – Singleton

Gyakori feladat, hogy egy osztályt úgy kell megírnunk, hogy csak egy példány lehet belőle. Ez nem okoz gondot, ha jól ismerjük az objektum orientált programozás alapelveit. Tudjuk, hogy az osztályból példányt a konstruktorával készíthetünk. Ha van publikus konstruktor az osztályban, akkor akárhány példány készíthető belőle. Tehát publikus konstruktora nem lehet az egykének. De ha nincs konstruktor, akkor nincs példány, amin keresztül hívhatnánk a metódusait. A megoldás az osztály szintű metódusok. Ezeket akkor is lehet hívni, ha nincs példány. Az egykének van egy osztály szintű szerezPéldány (angolul: getInstance) metódusa, ami mindenkinek ugyanazt a példányt adja vissza. Természetesen ezt a példányt is létre kell hozni, de a privát konstruktort a szerezPéldány hívhatja, hiszen ő is az egyke osztály része.

Forráskód

using System;

namespace Singleton

{

    public class Singleton

    {

        // statikus mező az egyetlen példány számára

        private static Singleton uniqueInstance=null;

        // privát konstruktor, hogy ne lehessen 'new' kulcsszóval példányosítani

        private Singleton() { }

        // biztosítja számunkra a példányosítást és egyben visszaadja a példányt

        // mindenkinek ugyanazt

        public static Singleton getInstance()

        {

            if (uniqueInstance==null) // megvizsgálja, hogy létezik-e már egy példány

            {  

                uniqueInstance = new Singleton(); // ha nem, akkor létrehozza azt

            }

            // visszaadja a példányt

            return uniqueInstance;

         }

    }

    class Program

    {

        static void Main(string[] args)

        {

            //a konstruktor private, nem lehet new kulcsszóval példányosítani

            Singleton s1 = Singleton.getInstance();

            Singleton s2 = Singleton.getInstance();

            // Teszt: ugyanaz a példány-e a kettő?

            if (s1 == s2)

            {

                Console.WriteLine("Ugyanaz! Tehát csak egy példány van.");

            }

            Console.ReadKey();

        }

    }

Szálbiztos megoldás

using System;

namespace SingletonThreadSafe

{

    public sealed class Singleton

    {

        // A statikus konstruktor akkor fut le, amikor az osztályt példányosítjuk,

        // vagy statikus tagra hivatkozunk ÉS egy Application Domain alatt

        // (értsd: adott program futásakor) maximum egyszer futhat le.

        private static readonly Singleton instance = new Singleton();

        // privát konstruktor külső 'new' példányosítás ellen

        private Singleton() { }

        // statikus konstruktor

        // Azon osztályok, melyek nem rendelkeznek statikus

        // konstruktorral beforefieldinit attribútumot

        // kapnak az IL kódban. A statikus tagok inicializációja

        // a program kezdetén azonnal megtörténik.

        // Az olyan osztályok, amelyeknek van statikus konstruktora

        // ezt nem kapják meg,

        // ezért a statikus tagok akkor példányosulnak,

        // amikor először hivatkozunk az osztályra,

        // vagyis jelen esetben amikor elkérjük a példányt.

        static Singleton() { }

        public static Singleton Instance { get{ return instance; } }

    }

    class Program

    {

        static void Main(string[] args)

        {

            Singleton s1 = Singleton.Instance;

            Singleton s2 = Singleton.Instance;

            if (s1 == s2) { Console.WriteLine("OK"); }

            Console.ReadKey();

        }

    }

}

UML-ábra

17. ábra

Prototípus – Prototype

A prototípus tervezési minta fő technikája a klónozás. A klónozás feladata, hogy az eredeti objektummal megegyező objektumot hozzon létre. Erre az egyszerű értékadás nem alkalmas, mert azok csak az objektum referenciáját másolják, így a két referencia ugyanoda mutat. A klónozásnak két fajtája van:

  1. sekély klónozás (angolul: shallow copy),

  2. mély klónozás (angolul: deep copy).

A különbség, hogy sekély esetben az osztály referenciáit ugyanúgy másoljuk, mint az elemi típusait. Mély klónozásnál az osztály referenciái által mutatott objektumokat is klónozzuk. Nézzük ezt meg egy konkrét példán:

    class Ember

    {

        private String név;

        private Ember[] barátok;

        public Ember DeepCopy()

        {

            Ember clone = new Ember();

            clone.név = név;

            clone.barátok = (Ember[])barátok.Clone();

            return clone;

        }

        public Ember ShallowCopy()

        {

            Ember clone = new Ember();

            clone.név = név;

            clone.barátok = barátok;

            return clone;

        }

        public Ember ShallowCopy2()

        {

            return (Ember)MemberwiseClone();

        }

    }

A sekély klónozást a C# nyelv a MemberwiseClone() metódussal segíti, ami az Object osztály része, így minden osztály örökli. Ezért tudtunk a sekély klónozásra két verziót adni a fenti példában.

Példa

A prototípus mintát egy példán keresztül mutatjuk be: Hurrá a magyar gépkocsigyártás újra feléledt, legalábbis a példánk kedvéért. Megjött az utasítás a tehergépkocsi gyártására, kis csapatunk összedugja a fejét és úgy dönt, mer nagyban gondolkodni. Amennyiben sikeres lesz a teherautó üzletág, akkor megvehetjük a méltán híres Porsche és Aston Martin márkákat a profitból. Ezért, gondolva a jövőre első körben egy általános gépkocsi osztályt hoznak létre, mely azokat a tulajdonságokat tartalmazza, amik minden négy vagy több kerekű gépesített járműre jellemzőek. Ebből az osztályból öröklődik a nagy és erős tehergépkocsi, melynek csak pár speciális tulajdonságát kell beállítanunk. Majd ha a zsebünk tele lesz a teherautó export-import bevételeiből, és végre megvettük a fent említett márkákat, könnyű dolgunk lesz az implementáció során, hiszen egy új osztályban beállítjuk a sportkocsi végsebességét, a tankméretet kisebbre vesszük és indulhat a sorozatgyártás a gyáron keresztül, és a határ a csillagos ég vagy a Forma 1. A gyártósor egy prototípust vár. Mindegy, hogy milyen Gépkocsit kap, mindent tud gyártani, mert csak klónozza a prototípust. A klónozáson túl csak festeni tud. Szóval a gyár buta, de hatékony.

A lenti forráskódban figyeljük meg, hogy sekély klónozást használunk. Ezt kétféleképen is megírtuk. Ha a MemberwiseClone() segítségével oldjuk meg, akkor elegendő az ősbe megírni a Clone() metódust. Egyébként minden alosztályban meg kell írni. Ezt a megoldást a lenti megoldásban megjegyzések formájában látjuk.

Forráskód

using System;

namespace ConsoleApplication63

{

    public abstract class Gépkocsi : ICloneable

    {

        private string _Tipus;

        public string Tipus

        {

            get { return _Tipus; }

            set { _Tipus = value; }

        }

        private int _UtasokSzama;

        public int UtasokSzama

        {

            get { return _UtasokSzama; }

            set { _UtasokSzama = value; }

        }

        private double _TankMeret;

        public double TankMeret

        {

            get { return _TankMeret; }

            set { _TankMeret = value; }

        }

        private string _Szin;

        public string Szin

        {

            get { return _Szin; }

            set { _Szin = value; }

        }

        public Gépkocsi(string tipus, int utasokszama, double tankmeret)

        {

            this.Tipus = tipus;

            this._UtasokSzama = utasokszama;

            this._TankMeret = tankmeret;

        }

        public object Clone() { return this.MemberwiseClone(); }

        /*

        public virtual object Clone()

        {

            Gépkocsi uj = new Gépkocsi(Tipus, UtasokSzama, TankMeret);

            uj.Szin = Szin;

            return uj;

        }*/

        public override string ToString()

        {

            return Tipus + " " + UtasokSzama + " " + TankMeret + " " + Szin;

        }

    }

    public class Versenyautó : Gépkocsi

    {

        private int _Vegsebesseg;

        public int Vegsebesseg

        {

            get { return _Vegsebesseg; }

            set { _Vegsebesseg = value; }

        }

        public Versenyautó(string t, int u, double tm, int vegsebesseg) :

            base(t, u, tm) { this.Vegsebesseg = vegsebesseg; }

        /*

        public override object Clone()

        {

            Versenyautó uj =

                new Versenyautó(Tipus, UtasokSzama, TankMeret, Vegsebesseg);

            uj.Szin = Szin;

            return uj;

        }*/

        public override string ToString()

        {

            return base.ToString() + " " + Vegsebesseg;

        }

    }

    public class Teherautó : Gépkocsi

    {

        private double _Teherbiras;

        public double Teherbiras

        {

            get { return _Teherbiras; }

            set { _Teherbiras = value; }

        }

        public Teherautó(string t, int u, double tm, double teherbiras)

            : base(t, u, tm) { this.Teherbiras = teherbiras; }

        /*

        public override object Clone()

        {

            Teherautó uj =

                new Teherautó(Tipus, UtasokSzama, TankMeret, Teherbiras);

            uj.Szin = Szin;

            return uj;

        }*/

        public override string ToString()

        {

            return base.ToString() + " " + Teherbiras;

        }

    }

    public class Gyar

    {

        public Gépkocsi[] sorozatgyartas(Gépkocsi g, string sz, int db)

        {

            Gépkocsi[] temp = new Gépkocsi[db];

            for (int i = 0; i < db; i++)

            {

                temp[i] = (Gépkocsi)g.Clone();

                temp[i].Szin = sz;

            }

            return temp;

        }

    }

    class Program

    {

        static void Main(string[] args)

        {

            //a versenyautó és a teherautó prototípus létrehozása

            Gépkocsi prototipus1 = new Versenyautó("Aston Martin", 4, 180, 220);

            Gépkocsi prototipus2 = new Teherautó("Csepel", 3, 200, 1000);

            Gyar gyartosor = new Gyar();

            // legyárt 10 piros versenyautót

            Gépkocsi[] vk = gyartosor.sorozatgyartas(prototipus1, "Piros", 10);

            foreach (Versenyautó v in vk) { Console.WriteLine(v); }

            // legyárt 20 szürke teherautót

            Gépkocsi[] tk = gyartosor.sorozatgyartas(prototipus2, "Szürke", 20);

            foreach (Teherautó t in tk) { Console.WriteLine(t); }

            Console.ReadLine();

        }

    }

}

UML-ábra

18. ábra

Gyártófüggvény – Factory Method

Ezzel a mintával lehet szépen kiváltani a programunkban lévő rengeteg hasonló new utasítást. A minta leírja, hogyan készítsünk gyártófüggvényt. Ezt magyarul gyakran készít, angolul a creat szóval kezdjük. A gyártófüggvény a nevében magadott terméket adja vissza, tehát a készítKutya (createDog) egy kutyát, a készítMacska (createCat) egy macskát. Ez azért jobb, minta a new Kutya() vagy a new Macska() konstruktor hívás, mert itt az elkészítés algoritmusát egységbe tudjuk zárni. Ez azért előnyös, mert ha a gyártás folyamata változik, akkor azt csak egy helyen kell módosítani. Általában a gyártás folyamata ritkán változik, inkább az a kérdés mit kell gyártani, azaz ez gyakran változik, ezért ezt az OCP elvnek megfelelően a gyermek osztály dönti el.

Tehát az ősben lévő gyártómetódus leírja a gyártás algoritmusát, a gyermek osztály eldönti, hogy mit kell pontosan gyártani. Ezt úgy érjük el, hogy az algoritmus 3 féle lépést tartalmazhat:

  1. A gyártás közös lépései: Ezek az ősben konkrét metódusok, általában nem virtuálisak, illetve Java nyelven final metódusok.

  2. A gyártás kötelező változó lépései. Ezek az ősben absztrakt metódusok, amiket a gyermek felülír, hogy eldöntse, hogy mit kell gyártani. A gyermek osztályok itt hívják meg a termék konstruktorát.

  3. A gyártás opcionális lépései: Ezek az ősben hook metódusok, azaz a metódusnak van törzse, de az üres. Ezeket az OCP elv megszegése nélkül lehet felülírni az opcionális lépések kifejtéséhez.

Jó példa a gyártó metódusra az Office csomag alkalmazásiban lévő Új menüpont. Ez minden alkalmazásban létrehoz egy új dokumentumot és megnyitja. A megnyitás közös, de a létrehozás más és más. A szövegszerkesztő esetén egy üres szöveged dokumentumot, táblázatkezelő esetén egy üres táblázatot kell létrehozni.

Érdekes megfigyelni, hogy az absztrakt ős és a gyermek osztályai IOC (inversion of control) viszonyban állnak. Azaz nem a gyermek hívja az ős metódusait, hanem az ős a gyermekét. Ezt úgy érjük el, hogy a gyártófüggvény absztrakt, illetve virtuális metódusokat hív. Amikor a gyermek osztály példányán keresztül hívjuk majd a gyártófüggvényt, akkor a késői kötés miatt ezen metódusok helyett az őket felülíró gyermek béli metódusok fognak lefutni.

Forráskód

using System;

namespace Factory_Method

{

     abstract class MinositesGyar

     {

        public Minosites createMinosites()

        {

            // itt a gyártás előtt lehet ezt-azt csinálni, pl. logolni

            return Minosites();

        }

        public abstract Minosites Minosit();

    }

    class KonkretMinositesGyar1 : MinositesGyar

    {

        public override Minosites Minosit() { return new A_Minosites(); }

    }

    class KonkretMinositesGyar2 : MinositesGyar

    {

        public override Minosites Minosit() { return new B_Minosites(); }

    }

    interface Minosites { void Minosit(); }

    class A_Minosites : Minosites

    {

        public void Minosit() { Console.WriteLine("A-mínősítésben részesül!"); }

    }

    class B_Minosites : Minosites

    {

        public void Minosit() { Console.WriteLine("B-minősítésben részesül!"); }

    }

    class Program

    {

        static void Main(string[] args)

        {

            MinositesGyar[] minosito = new MinositesGyar[2];

            minosito[0] = new KonkretMinositesGyar1();

            minosito[1] = new KonkretMinositesGyar2();

            foreach (MinositesGyar m in minosito)

            {

                Minosites min = m.Minosit();

                min.Minosit();

            }

            Console.ReadLine();

        }

    }

}

UML-ábra

19. ábra

Gyakorló feladat

Készítsünk forráskódot az alábbi leírás szerint. A megoldáshoz használjuk a gyártófüggvény tervezési mintát!

Feladat

Ma reggel az egyik programozónk, aki egyébként teljesen normális, azzal állt elő, hogy gazoljuk ki a „new” szócskát a kódjainkból. Először azt gondoltuk, hogy nem itta meg a reggeli kávéját és majd rendbe jön, de azután újra gondoltunk a dolgokat. Mit is jelent a „new”? Azt, hogy hozzákötöttük magunkat egy konkrét osztályhoz amivel nincs is semmi baj amíg nem gondolunk a: „programozz interfészre” illetve a „nyílt a változásra, de zárt a módosításokra” varázsszavakra. Kapóra jött az új ötlet kipróbálásához a nemrég érkezett megrendelés, melyben egy zsíros kenyér franchise hálózatot kellett kidolgozni. A csomagolásnak országos szinten egyformának kell lennie, de a kenyeret vidékenként más-más vastagságura vágják, még Pest és Buda között is különbség van, és akkor még nem szóltunk a különböző típusokról, kacsazsír, libazsír, stb. Két absztrakt osztályt hoztunk létre, egyet a boltnak (factory), egyet a terméknek (interface). Esetünkben ez a Zsíros_Deszka. A boltban kidolgoztuk a csomagol függvényt az egyen csomagolásért, de az elkészítés függvényét absztrakt típusúra vettük. Egyszóval rábíztuk a konkrét boltra, milyen vastagra vágja a kenyeret, mennyi zsírt ken rá. A másik osztályt a termékért hoztuk létre. Mert mi kell a zsíros deszkához? Kenyér, zsír, só, hagyma, de csak a kenyérből legalább nyolc félét tudunk egy ültő helyünkben felsorolni. Így a Nyíregyházi bolt (factory) tud gyártani nyíregyházi stílusú zsíros kenyeret. Szóval, ha zsíros kenyeret kell gyártanunk Kecskeméten a kódunk kb. így fog kinézni: Zsíros_Deszka zsíros_kenyér = KecskemétiBolt.készítZsíros_Deszka();.

Szerkezeti tervezési minták

A szerkezeti minták azt mutatják meg, hogy hogyan használjuk a gyakorlatban az objektum összetételt, hogy az igényeinknek megfelelő objektum szerkezetek létrejöhessenek futási időben.

Ismétlésképp leírjuk, hogy az objektum összetételnek, vagy más néven a HAS-A kapcsolatnak három típusa van:

  1. aggregáció: amikor az összetételben szereplő objektum nem kizárólagos tulajdona az őt tartalmazó objektumnak,

  2. kompozíció: amikor kizárólagos tulajdona,

  3. átlátszó csomagolás (wrapping): amikor a tulajdonos átlátszó.

Illesztő – Adapter

Az illesztő (angolul: adapter) tervezési minta arra szolgál, hogy egy meglévő osztály felületét hozzá igazítsuk saját elvárásainkhoz. Leggyakoribb példa, hogy egy régebben megírt osztályt akarunk újrahasznosítani úgy, hogy beillesztjük egy osztály hierarchiába. Mivel ehhez hozzá kell igazítani az ős által előírt felülethez, ezért illesztő mintát kell használnunk.

A régi osztályt ilyen estben gyakran illesztendőnek (adaptee) hívjuk. Az illesztő és az illesztendő között általában kompozíció van, azaz az illesztő kizárólagosan birtokolja az illesztendőt. Ezt gyakran úgy is mondjuk, hogy az illesztő becsomagolja az illesztendőt. Ennek megfelelő az illesztő minta másik angol neve a Wrapper. Ugyanakkor ez a becsomagolás nem átlátszó, hiszen az illesztő nem nyújtja az illesztendő felületét.

Példa

Az alábbi példában az Ember osztály hierarchiába illesztjük bele a Robot osztályt a Robot2Ember osztály segítségével. Tehet a Robot az illesztendő (adaptee) a Robot2Ember az illesztő (adapter). Úgy is mondhatnánk, hogy a robotunkat emberként szeretnénk használni. A főprogramban ehhez az R2D2 nevű robotunkat becsomagoljuk egy Robot2Ember példányba.

Mivel az illesztő átkonvertálja az egyik felületet egy másikká, ezért gyakran Régi2Új nevet adunk az osztálynak. Példánkban Robot2Ember. Itt a 2 az angol „Two” szóra utal, amit ugyanúgy kell kiejteni, mint az angol „To” szavat. Ez egy gyakori elnevezési konvenció a konverziót végző metódusokra, osztályokra.

Forráskód

using System;

abstract class Ember

{

    public abstract string getNév();

    public abstract int getIQ();

}

class Tanár : Ember

{

    string név;

    int IQ;

    public override string getNév() { return név; }

    public override int getIQ() { return IQ; }

}

class Robot

{

    string ID;

    int memory; //memoria MB-ban megadva

    public Robot(string ID, int memory)

    {

        this.ID = ID;

        this.memory = memory;

    }

    public string getID() { return ID; }

    public int getMemory() { return memory; }

}

class Robot2Ember : Ember

{

    Robot robi;

    public Robot2Ember(Robot robi) { this.robi = robi; }

    public override string getNév()

    {

        return robi.getID();

    }

    public override int getIQ()

    {

        return robi.getMemory() / 1024; // 1GB memória = 1 IQ

    }

}

class Program

{

    static void Main(string[] args)

    {

        Robot R2D2 = new Robot("R2D2", 80000);

        Ember R2D2wrapper = new Robot2Ember(R2D2);

        Console.WriteLine("Neve: {0}", R2D2wrapper.getNév());

        Console.WriteLine("IQ-ja: {0}", R2D2wrapper.getIQ());

        Console.ReadLine();

    }

}

Díszítő – Decorator

A díszítő minta az átlátszó csomagolás klasszikus példája. Klasszikus példája a karácsonyfa. Attól, hogy a karácsonyfára felteszek egy gömböt, az még karácsonyfa marad, azaz a díszítés átlátszó. Ezt úgy érjük el, hogy az objektum összetételben szereplő mindkét osztály ugyanazon őstől származik, azaz ugyanolyan típusúak. Ez azért hasznos, mert a díszítő elemek gyakran változnak, könnyen elképzelhető, hogy új díszt kell felvenni. Ha díszítő egy külön típus lenne, akkor a karácsonyfa feldolgozó algoritmusok esetleg bonyolultak lehetnek.

A díszítő mintánál egy absztrakt ősből indulunk ki. Ennek kétfajta gyermeke van, alap osztályok, amiket díszíteni lehet és díszítő osztályok. A karácsonyfa példa esetén az alap osztályok a különböző fenyőfák. A díszítő osztályokat általában egy absztrakt díszítő osztály alá szervezzük, de ez nem kötelező.

A díszítés során az ős minden metódusát implementálni kell, úgy hogy, a becsomagolt példány metódusát meghívjuk, illetve ahol ez szükséges, ott hozzáadjuk a plusz funkcionalitást. Kétféle díszítésről beszélhetünk:

  1. Amikor a meglévő metódusok felelősségkörét bővítjük. Ilyen a karácsonyfás példa.

  2. Amikor új metódusokat is hozzáadunk a meglévőkhöz. Ilyen a Java adatfolyam (angolul: stream) kezelése, illetve a lenti kölcsönözhető jármű példa.

Mindkét esetben a példányosítás tipikusan így történik:

ŐsOsztály példány = new DíszítőN(…new Díszítő1( new AlapOsztály())…);

Mivel a csomagolás átlátszó, ezért akárhányszor becsomagolhatjuk a példányunkat, akár egy díszítővel kétszer is. Ez rendkívül dinamikus, könnyen bővíthető szerkezetet eredményez, amit öröklődéssel csak nagyon sok osztállyal lehetne megvalósítani.

Érdekes megfigyelni a minta UML ábráján, hogy a díszítő osztályból visszafelé mutat egy aggregáció az ős osztályra. Ez az adatbázis kezelés Alkalmazott – Főnök reláció megoldásához hasonlít, amikor az Alkalmazott tábla önmagával áll egy-több kapcsolatban, ahol a külső kulcs a főnök alkalmazott_ID értékét tartalmazza.

Példa

A díszítő mintát a következő példával mutatjuk be. Képzeljük el, hogy egy versenypályán üzemeltetünk egy autókölcsönzőt. Az autókölcsönzőben természetesen több típusú autót, többnyire versenyautót adunk kölcsönzésre. A lényeg, hogy előfordulhat az, hogy újabb autókkal bővítjük az állományt. Felkészülve erre, először egy alap autó osztályt hoznak létre, amelyben a bérelhető autók információi szerepelnek, mint gyártó neve, a modell neve, a bérlés időtartama körökben számolva és a bérlés díja. A kölcsönzőben időnként akciókkal kedveskednek az ügyfeleknek, valamint változó, hogy egy bizonyos autó kölcsönözhető-e, vagy sem. Ezen extrák hozzáadását, a díszítő minta implementálásával tették lehetővé. Az alap autó osztályból származik az alap dekorátor osztály, mely elvégzi a becsomagolást. A konkrét díszítő osztályoknak már csak a funkciók kibővítésével kell foglalkozniuk. Amint egy autót feldíszítünk, mint kölcsönözhető, az már csak a bérlőjét várja, aki kiviszi a pályára. Az akciókat is díszítő osztályokkal valósíthatjuk meg. Látható, ha új autókkal bővül a parkunk, vagy újabb akciós ajánlatokat szeretnénk bevezetni, azt könnyedén megtehetjük, új konkrét autó és konkrét díszítő osztályok hozzáadásával.

Forráskód

using System;

namespace DecoratorDesignPattern

{

    public abstract class VehicleBase // alap osztály, adott funkcionalitásokkal

    {

        public abstract string Make { get; }

        public abstract string Model { get; }

        public abstract double HirePrice { get; }

        public abstract int HireLaps { get; }

    }

    public class Ferrari360 : VehicleBase // egy konkrét autó

    {

        public override string Make { get { return "Ferrari"; } }

        public override string Model { get { return "360"; } }

        public override double HirePrice { get { return 100; } }

        public override int HireLaps { get { return 10; } }

    }

    public abstract class VehicleDecoratorBase : VehicleBase // a dekorátor osztály

    {

        private VehicleBase _vehicle; // HAS-A kapcsolat, ezt csomagoljuk be

        public VehicleDecoratorBase(VehicleBase v) { _vehicle = v; }

        public override string Make { get { return _vehicle.Make; } }

        public override string Model { get { return _vehicle.Model; } }

        public override double HirePrice { get { return _vehicle.HirePrice; } }

        public override int HireLaps { get {return _vehicle.HireLaps; } }

    }

    public class SpecialOffer : VehicleDecoratorBase // konkrét dekorátor osztály

    {

        public SpecialOffer(VehicleBase v) : base(v) { }

        public int Discount { get; set; }

        public int ExtraLaps { get; set; }

        public override double HirePrice

        {

            get

            {

                double price = base.HirePrice;

                int percentage = 100 - Discount;

                return Math.Round((price * percentage) / 100, 2);

            }

        }

        public override int HireLaps { get { return (base.HireLaps + ExtraLaps); } }

    }

    public class Hireable : VehicleDecoratorBase

    {

        public Hireable(VehicleBase v) : base(v) { }

        public void Hire(string name)

        {

            Console.WriteLine("{0} {1} típust kölcsönzött {2} {3}$-ért {4} körre.\r\n", Make, Model, name, HirePrice, HireLaps);

        }

    }

    class Program

    {

        static void Main(string[] args)

        {

            Ferrari360 car = new Ferrari360();

            Console.WriteLine("Alap Ferrari360:\r\n");

            Console.WriteLine("Alap ár: {0}, alap tesztkörök száma: {1}\r\n\r\n", car.HirePrice, car.HireLaps);

            SpecialOffer offer = new SpecialOffer(car);

            offer.Discount = 25;

            offer.ExtraLaps = 2;

            Console.WriteLine("Speciális ajánlat:\r\n");

            Console.WriteLine("Különleges ajánlat ára: {0}, {1}$-ért\r\n\r\n", offer.HirePrice, offer.HireLaps);

            Hireable hire = new Hireable(car);

            hire.Hire("Bill");

            Hireable hire2 = new Hireable(offer);

            hire2.Hire("Jack");

            Console.ReadLine();

        }

    }

}

UML-ábra

20. ábra

Gyakorló feladat

Az alábbi leírás szerint készítsünk forráskódot. A megoldáshoz használjuk a díszítő tervezési mintát!

Feladat

Nekem senki ne mondja, hogy egy programozó élete unalmas, azon kívül, hogy szabadidejében ugyanazokat a dolgokat teheti, mint más rendes ember. Még a munkájában is kaphat érdekes megbízatásokat. A minap kaptunk is: egy bringa boltot kellett csinálnunk. Mi már láttuk is a szemünk előtt a sok csillogó-villogó bringát fel alá gurulni a szalonban. Okosan vagy inkább objektumorientáltan-t kéne mondanom, készítettünk egy absztrakt osztályt Bringa_Alap néven, majd ebből az osztályból származtattuk a Bringa21seb, Bringa_Csengővel, Bringa_Női, stb. osztályokat, ezek az osztályok tudták a konkrét példány árát. Telt múlt az idő és a vásárlók igényeit követve már ilyen osztályneveket használtunk: BringaCsengovel21sebAluvazSarvedoveldecsakElolAkcios. Szép ugye? Éreztük rögtön, hogy ez így nem lesz jó, arról nem is beszélve, hogy osztályaink számának növekedése hasonlított egy demográfiai robbanásra. Hosszas tanácskozás után kénytelenek voltunk belátni, hogy majdnem az egész kódot ki kell dobni, bár az első, absztrakt Bringa_Alap osztályt megtartottuk. Ebben írtunk egy getLeírás és egy Ár nevű absztrakt függvényt a hozzájuk tartozó mezőkkel. Ebből öröklődött a konkrét Bringa, de még kellettek az alkatrészek, csengő, váltó, sárvédő, stb. Így létrehoztunk egy újabb absztrakt osztályt, amit Bringa_Díszítőnek neveztünk el és ez is a Bringa_Alap gyermek. A Díszítőből származnak a konkrét elemek, amelyek csak a saját áraikat ismerik, de az Ár függvényük és a konstruktoruk úgy van megírva, hogy az őket hívó elem árát is hozzáadják az árhoz. Tulajdonképpen veszünk egy bringát, majd „körbecsomagoljuk” (ezért nevezik ezt a mintát wrapper-nek is) egy sárvédővel, majd ezt egy csengővel, és így tovább. Amikor minden igényt kielégítettünk meghívjuk a legutolsó elem Ár függvényét, mely a saját árával meghívja a következő elem ugyanezen függvényét, és a végén visszakapjuk az összeállítás teljes árát.

Feladat

Készítsen egy kávé ital programot, amely szemlélteti a díszítő működését! A feladat szempontjából csak az ár és a kávé összetevői számítsanak (pl. cukor, tejszín, tej, hab, esetleg rum, öntet)! A program vegye figyelembe az árak alakulását is. A feladat az, hogy a kezdetben üres, keserű, fekete kávénkat díszítsük fel.

Helyettes – Proxy

A helyettes (angolul: proxy) tervezési minta egy nagyon egyszerű kompozícióra ad példát, ami ráadásul átlátszó becsomagolás. Egy valamilyen szempontból érdekes (drága, távoli, biztonsági szempontból érzékeny, …) példányt birtokol a helyettese. Ez az érdekes objektum nem érhető el kívülről, csak a helyettesén keresztül érhetők el a szolgáltatásai. Ugyanakkor a külvilág azt hiszi, hogy az érdekes objektumot közvetlenül éri el, mert a helyettes átlátszó módon csomagolja be az érdekes objektumot. Az átlátszóság miatt a helyettesnek és az érdekes objektumnak közös őse van.

Sokféle helyettes létezik aszerint, hogy milyen szempontból érdekes a helyettesített objektum, pl.:

  1. Virtuális proxy: Nagy erőforrás igényű objektumok (pl. kép) helyettesítése a példányosítás (vagy más drága művelet) elhalasztásával, amíg ez lehetséges. A szövegszerkesztők ezt használják a képek betöltésére. Ha csak gyorsan átlapozom a dokumentumot, akkor a kép nem töltődik be (elhalasztódik a betöltés), csak helye látszik.

  2. Távoli proxy: Távoli objektumok lokális megjelenítése átlátszó módon. A kliens nem is érzékeli, hogy a tényleges objektum egy másik gépen van, amíg van hálózati kapcsolat. Ezt alkalmazza a távoli metódus hívás (remote method invocation – RMI).

  3. Védelmi proxy: A hozzáférést szabályozza különböző jogok esetén.

  4. Okos referencia: Az egyszerű referenciát helyettesíti olyan esetekben, amikor az objektum elérésekor további műveletek szükségesek.

  5. Gyorsító tár (cache): Ha van olyan számítás (ide sorolva a letöltéseket is), ami drága, akkor a számítás eredményét érdemes letárolni egy gyorsító tárban, ami szintén egyfajta proxy.

Forráskód – Példa 1.

using System;

namespace helyettes___proxy

{

    class MainApp

    {

        static void Main()

        {

            // Készítünk egy helyettest és kérünk egy szolgáltatást.

            Proxy proxy = new Proxy();

            proxy.Kérés();

            Console.ReadKey();

        }

    }

    // Közös interfész a Tárgy és a Proxi számára, ezáltal tud a minta működni.

    abstract class Tárgy { public abstract void Kérés(); }

    // valódi munka "tárgy"amit tenni akarunk

    // a valódi objektum, amit a proxy elrejt

    class ValódiTárgy : Tárgy

    {

        public override void Kérés()

        {

            Console.WriteLine("Meghívom a ValódiTárgy.Kérés-et()");

        }

    }

    // The 'Proxy' osztály

    // Tartalmaz egy referenciát a tényleges objektumra, hogy el tudja azt érni.

    // Szabályozza a hozzáférést a tényleges objektumhoz, feladata lehet a tényleges

    // objektum létrehozása és törlése is.

    class Proxy : Tárgy

    {

        private ValódiTárgy _ValódiTárgy;

        public override void Kérés()

        {

            if (_ValódiTárgy == null) { _ValódiTárgy = new ValódiTárgy(); }

            _ValódiTárgy.Kérés();

        }

    }

}

UML-ábra

21. ábra

Forráskód – Példa 1.

using System;

using System.Collections.Generic;

abstract class Faktoriális

{

    public abstract long fakt(int n); //n faktoriálist számol

}

class FaktoriálisCache : Faktoriális

{

    class RekurzívFaktoriális : Faktoriális  // beágyazott osztály

    {

        public override long fakt(int n)

        {

            if (n == 0) return 1;

            return n * fakt(n - 1);

        }

    }

    Dictionary<int, long> t = new Dictionary<int, long>();

    RekurzívFaktoriális f = new RekurzívFaktoriális();

    public override long fakt(int n)

    {

        if (t.ContainsKey(n)) return t[n];

        long value = f.fakt(n);

        t.Add(n, value);

        return value;

    }

}

class Program

{

    static void Main(string[] args)

    {

        Faktoriális f = new FaktoriálisCache();

        Console.WriteLine(f.fakt(20));

        Console.WriteLine(f.fakt(10));

        Console.WriteLine(f.fakt(20));

        Console.ReadLine();

    }

}

Gyakorló feladat

Az alábbi leírás szerint készítsünk forráskódot. A megoldáshoz használjuk a helyettes tervezési mintát!

Feladat

A fenti forráskódot úgy írjuk át, hogy minden részeredmény bekerüljünk az átmeneti tárba (cache). A rekurzió során is figyeljük, hogy a kívánt részeredmény megvan-e az átmeneti tárban. Csináljuk ide futás idő összehasonlítást az egyes megoldások között.

Feladat

Ki ne ismerné azokat a helyes kis automatákat, amik némi pénz bedobása után jópofa dolgokat adnak egy műanyag golyóban. A hálózat üzemeltetője jelentkezett cégünknél, hogy szeretné az interneten keresztül felügyelni a gépek állapotát, mint például tudni, hogy mennyi golyó van még benne. A megvalósítási megbeszélésen egyik kollegánk felhozta, hogy Ő volt egy csapatépítő tréningen ahol az esti tábortűznél egy nagyszakállú, bölcs és kellőképpen öreg programozó mesélt nekik a proxyról és hogy az pont valami ilyesmire való. Szakkönyvek, internet és valóban az öregnek igaza volt. Innen már könnyű út vezetett a megvalósításig. Természetesen az absztrakt osztályok kidolgozásával kezdtük, először is a közös felületet kellett megírni, amin a helyettes és a mi kis automatánk megtalálja a közös nyelvet. Tehát ebbe az osztályba került a MennyiGolyo() és a MennyiPenz() abstract függvények. A szakirodalomból azt is megtudtuk, hogy a visszatérési értékeknek seriaziable-nek kell lennie a hálózati forgalom miatt. Ezután a Proxy-t implementáltuk, feladata, hogy a kliens kérését (a főnők utasítását) eljutassa az automatának. Ami ténylegesen átmegy az a meghívott függvény neve és az esetleges argumentumai. Az igazi kemény munkát ezután az automata (ValódiTárgy) végzi, hiszen csak ő tudja hány golyó rejt még meglepetést az arra sétálóknak. Meghívja a MennyiGolyo() függvényt, majd a kapott eredmény visszajutatja a Proxy-nak, mely büszkén mutatja azt fel a kliensnek. Persze ebben az esetben nem szabad megfeledkezni a kivételek kezeléséről, mert ami a hálózaton indul az nem biztos, hogy oda is ér.

Viselkedési tervezési minták

A viselkedési tervezési minták középpontjában az algoritmusok és az objektumokhoz rendelt felelősségi körök állnak. E minták az osztályok és objektumok rendszerén túl a közöttük folyó kommunikációt és a felelősségi köröket is leírják. A viselkedési minták öröklés helyett összetételt alkalmaznak.

Állapot – State

Célja: Lehetővé teszi egy objektum viselkedésének megváltozását, amikor megváltozik az állapota. Példa: TCPConnection osztály egy hálózati kapcsolatot reprezentál; Három állapota lehet: Listening, Established, Closed; a kéréseket az állapotától függően kezeli.

 Használjuk, ha

  1. az objektum viselkedése függ az állapotától, és a viselkedését az aktuális állapotnak megfelelően futás közben meg kell változtatnia, illetve

  2. a műveleteknek nagy feltételes ágai vannak, melyek az objektum állapotától függenek.

Előnyök:

  1. Egységbe zárja az állapotfüggő viselkedést, így könnyű új állapotok bevezetése.

  2. Áttekinthetőbb kód (nincs nagy switch-case szerkezet).

  3. A State objektumokat meg lehet osztani.

Hátrányok: Nő az osztályok száma (csak indokolt esetben használjuk).

Példa

Az Állapotgép tervezési mintát a következő példán keresztül mutatjuk be: Feladatunk, hogy elkészítsünk egy rendkívül egyszerű audio lejátszót. A lejátszónknak a következőképpen kell működnie. Ha a lejátszó készenléti állapotban van, akkor a lejátszás gomb hatástalan, az audio forrás gombbal pedig megkezdődik az mp3 fájl lejátszása. Mp3 lejátszás közben a lejátszás gomb leállítja a lejátszást, az audio forrás gomb pedig rádióhallgatást tesz lehetővé. Ha az mp3 lejátszás szünetel, akkor a lejátszás gomb hatására folytatódik a lejátszás, az audio forrás gomb pedig ebben az esetben is rádióhallgatást tesz lehetővé. Rádióhallgatás közben a lejátszás gomb adót vált, az audio forrás gomb pedig készenléti üzemmódot eredményez. A leírt összetett működés eléréséhez az állapotgépet valósítjuk meg. Létrehozzuk az audio lejátszó osztályunkat, amelynek van egy belső állapota, valamint egy lejátszás és egy audio forrás metódusa. Létrehozunk egy alap állapot osztályt is, melyből a később szükséges állapotaink származni fognak, és amelyeke később a lejátszónk állapotai lehetnek. Az, hogy a lejátszónk az egyes állapotokban, hogyan reagál a lejátszás és audio forrás lenyomására, az egyes állapotoktól függ, ezért ezek az egyes állapotokban vannak definiálva, csak úgy, mint az állapotátmenetek is. Módszerünk előnye, hogy könnyedén bővíthetjük a lejátszónkat újabb állapotokkal, és ezáltal újabb funkciókkal bővülhet.

Forráskód

using System;

namespace ÁllapotGép

{

    /// <summary>

    /// State/Állapotgép viselkedési minta

    /// média lejátszó

    /// két gomb

    /// 4 állapot

    /// a két gomb viselkedése más és más lesz a 4 belső állapottól függően

    /// lesz egy: Állapot

    /// Play gomb

    /// Audió forrás gomb

    /// Állapotváltozások:

    /// Állapotok: készenlét, mp3 lejátszás, mp3 megállítás, rádió hallgatás

    /// Lejátszás: stop-paused, start-play, next station

    /// Audió forr: mp3 play, rádió play, rádió play, készenlét

    /// </summary>

    public class AudioPlayer

    {

        private AudioPlayerState _state; // ebben tároljuk a belső állapotot

        public AudioPlayer(AudioPlayerState state) { _state = state; }

        public AudioPlayerState SetState

        {

            get { return _state; }

            set { _state = value; }

        }

        public void PressPlay() { _state.PressPlay(this); }

        public void PressAudioSource() { _state.PressAudioSource(this); }

    }

    public abstract class AudioPlayerState // állapot reprezentálása

    {

        // a két gomblenyomása

        public abstract void PressPlay(AudioPlayer player);

        public abstract void PressAudioSource(AudioPlayer player);

    }

    public class StandbyState : AudioPlayerState // készenléti állapot

    {

        public StandbyState() { Console.WriteLine("StandBy"); }

        public override void PressPlay(AudioPlayer player)

        {

            Console.WriteLine("Play pressed: no effect");

        }

        public override void PressAudioSource(AudioPlayer player)

        {

            player.SetState = new MP3PlayingState();

        }

    }

    public class MP3PlayingState : AudioPlayerState // mp3 hallgatás állapot

    {

        public MP3PlayingState() { Console.WriteLine("Playing MP3"); }

        public override void PressPlay(AudioPlayer player)

        {

            player.SetState = new MP3PausedState();

        }

        public override void PressAudioSource(AudioPlayer player)

        {

            player.SetState = new RadioState();

        }

    }

    public class MP3PausedState : AudioPlayerState // a megállított mp3 állapot

    {

        public MP3PausedState() { Console.WriteLine("Paused MP3"); }

        public override void PressPlay(AudioPlayer player)

        {

            player.SetState = new MP3PlayingState();

        }

        public override void PressAudioSource(AudioPlayer player)

        {

            player.SetState = new RadioState();

        }

    }

    public class RadioState : AudioPlayerState // a rádió állapot

    {

        public RadioState() { Console.WriteLine("Playing Radio"); }

        public override void PressPlay(AudioPlayer player)

        {

            Console.WriteLine("Switch to next Station");

        }

        public override void PressAudioSource(AudioPlayer player)

        {

            player.SetState = new StandbyState();

        }

    }

    class Program

    {

        static void Main(string[] args)

        {

            AudioPlayer player = new AudioPlayer(new StandbyState());

            player.PressPlay();

            player.PressAudioSource();

            player.PressPlay();

            player.PressPlay();

            player.PressAudioSource();

            player.PressPlay();

            player.PressAudioSource();

            Console.ReadLine();

        }

    }

}

UML-ábra

22. ábra

Gyakorló feladat

Az alábbi leírás szerint készítsünk forráskódot. A megoldáshoz használjuk az állapot tervezési mintát!

Feladat

Egy napon az egyik munkatársunk kiment a kávéautomatához egy frissítő italért, pár perc múlva vörös fejjel és kezében az automata programjával jött vissza. Kérdésünkre elmondta, hogy nem először jár úgy, hogy a gép elnyeli az aprót, de kávét nem add, úgyhogy gondolta, a programmal lesz a baj. Elkezdtük tanulmányozni a szoftvert, mely tele volt csúnya és néhol egymásba ágyazott IF feltételekkel (ezek váltották a belső állapotot, ha bedobtuk a pénzt vagy elfogyott a kávépor, stb.). Rögtön gondoltuk, hogy erre van jobb módszer. Először hívtunk egy grafikust, aki lerajzolt minket, majd egy állapotdiagramban a gép lehetséges belső állapotait (pl. nincs apró, apró bedobva stb.). Ebből rögtön láttuk, hogy egyes állapotokban a gépnek meg kell, hogy változzon a viselkedése (ha nincs apró és megnyomom a kávé gombot nem adhat kávét, míg ha van apró, akkor illik legalább valami sötét löttyöt adni). Így már tudtunk csinálni egy interfészt az állapotoknak. Ebből az osztályból dolgoztuk ki a konkrét állapotokat külön osztályokba. Alapesetben a gép „a nincs apró” állapotban (osztályban) van, de ha dobunk be aprót lecseréli az állapotát (osztályát) „apró bedobva” típusúra. Mióta megírtuk a programot nekünk már apró sem kell a kávéhoz.

Megfigyelő – Observer

Lehetővé teszi, hogy egy objektum megváltozása esetén értesíteni tudjon tetszőleges más objektumokat anélkül, hogy bármit is tudna róluk. Részei:

  1. Alany: Tárolja a beregisztrált megfigyelőket, interfészt kínál a megfigyelők be- és kiregisztrálására valamint értesítésére.

  2. Megfigyelő: Interfészt definiál azon objektumok számára, amelyek értesülni szeretnének az alanyban bekövetkezett változásról. Erre a frissít (update) metódus szolgál.

Két fajta megfigyelő megvalósítást ismerünk:

  1. „Pull-os” megfigyelő: Ebben az esetben a megfigyelő lehúzza a változásokat az alanytól.

  2. „Push-os” megfigyelő: Ebben az esetben az alany odanyomja a változásokat a megfigyelőnek.

A kettő között ott van a különbség, hogy a frissít metódus milyen paramétert kap. Ha az alany átadja önmagát (egy frissít(this) hívás segítségével) a megfigyelőnek, akkor ezen a referencián keresztül a megfigyelő képes lekérdezni a változásokat. Azaz ez a „pull-os” megoldás.

Ha a frissít metódusnak az alany azokat a mezőit adja át, amik megváltoztak és amiket a megfigyelő figyel, akkor „push-os” megoldásról beszélünk. A következő példában épp egy ilyen megvalósítást láthatunk.

Forráskód

using System;

using System.Collections.Generic;

namespace Observer_Pattern_Weather_Station

{

    public interface ISubject

    {

        // observer regisztrálásra

        void registerObserver(IObserver o);

        // observer törlésre

        void removeObserver(IObserver o);

        // meghívódik, hogy értesítse az megfigyelőket

        // amikor a Subject állapota megváltozik

        void notifyObservers();

    }

    public interface IObserver

    {

        // értékék amiket megkapnak az observerek a Subjecttől, push-os megoldás

        void update(float temp, float humidity, float pressure);

    }

    public interface IDisplayElement

    {

        // meghívjuk amikor meg szeretnénk jeleníteni a display elementet

        void display();

    }

    // implementáljuk a Subject interfészt

    public class WeatherData : ISubject

    {

        // hozzáadunk egy listát amiben observereket tárolunk

        private List<IObserver> observers;

        private float temperature;

        private float humidity;

        private float pressure;

        public WeatherData()

        {

            // létrehozzuk az observereket tároló listát

            observers = new List<IObserver>();

        }

        public void registerObserver(IObserver o)

        {

            // amikor egy observer regisztrál, egyszerűen hozzáadjuk a listához

            observers.Add(o);

        }

        public void removeObserver(IObserver o)

        {

            // amikor egy observer le akar regisztrálni egyszerűen töröljük a listából

            int i = observers.IndexOf(o);

            if (i >= 0)

            {

                observers.Remove(o);

            }

        }

        // itt szólunk az observereknek az állapotról

        // mivel mind observerek, van update() metódusuk, így tudjuk őket értesíteni

        public void notifyObservers()

        {

            for (int i = 0; i < observers.Count; i++)

            {

                IObserver observer = (IObserver)observers.ElementAt(i);

                observer.update(temperature, humidity, pressure); // ez push-os

                // observer.update(this); // ez pull-os

            }

        }

        // amikor a Weather Station-től megkapjuk a frissített értékeket,

        //értesítjük az observereket

        public void measurementsChanged()

        {

            notifyObservers();

        }

        // értékek beállítása hogy tesztelhessük a display elementeket

        public void setMeasurements(float temperature, float humidity, float pressure)

        {

            this.temperature = temperature;

            this.humidity = humidity;

            this.pressure = pressure;

            measurementsChanged();

        }

        // egyéb metódusok

    }

    // a display implementálja az Observert,

    //így fogadhat változásokat a WeatherData objektumtól

    // továbbá implementálja a DisplayElement-et, mivel

    //minden display element-nek implementálnia kell ezt az interfészt

    public class CurrentConditionsDisplay : IObserver, IDisplayElement

    {

        private float temperature;

        private float humidity;

        private ISubject weatherData;

        // a konstruktor megkapja a weatherData objektumot

        // (a Subject) és arra használjuk, hogy

        // a display-t observerként regisztráljuk

        public CurrentConditionsDisplay(ISubject weatherData)

        {

            this.weatherData = weatherData;

            weatherData.registerObserver(this);

        }

        // amikor az update() meghívódik, mentjük a temperature-t és a humidity-t

        // majd meghívjuk a display()-t

        public void update(float temperature, float humidity, float pressure)

        {

            this.temperature = temperature;

            this.humidity = humidity;

            display();

        }

        // Megjelenítjük a legújabb eredményeket

        public void display()

        {

            Console.WriteLine("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");

        }

    }

    public class WeatherStation

    {

        static void Main(string[] args)

        {

            // létrehozzuk a weatherData objektumot

            WeatherData weatherData = new WeatherData();

            // létrehozzuk a displayt és odaajuk neki a weatherData-t

            CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

            // új időjárási mérésértékek szimulálása

            weatherData.setMeasurements(80, 65, 30.4f);

            weatherData.setMeasurements(82, 70, 29.2f);

            weatherData.setMeasurements(78, 90, 29.2f);

            Console.ReadKey();

        }

    }

}

UML-ábra

23. ábra

Gyakorló feladat

Az alábbi leírás szerint készítsünk forráskódot. A megoldáshoz használjuk a megfigyelő tervezési mintát!

Feladat

A fenti példakódot alakítsuk át „pull-os” megfigyelővé.

Feladat

Cégünk azt a megtisztelő feladatot kapta, hogy kalózhajót kellett programozni, ami több heti kódírás után egész szépen úszott a vízen, egy picike probléma volt csak vele. Mégpedig, hogy a főárboc tetején lévő kosárban ülő őrszemnek nem volt rádiója, hiszen még nem találták fel, ezért minden alkalommal, ha valaki alatta ment el az felkiabált, hogy látja-e már a gazdag zsákmányt, vagy az ellent. Szegény emberünknek úgy kiszáradt a torka, hogy egy hordó rumot kellett délig meginnia. A probléma orvoslására és a rumkészlet megmentésére azt találtuk ki, hogy aki szeretne értesülni a hírekről (observer) az köt egy kötelet a csuklójához, majd a másik végét feldobja (registerObserver()) az őrszemnek (subject). Amikor az őrszem említésre méltót lát, akkor megrángatja a kötelek végét (notifyObservers()), és az összegyűlt „megfigyelőknek” lekiabálja a hírt (update()). Aki nem kíváncsi tovább az eseményekre, az egész egyszerűen lehúzza a kötelének a végét a kosárból (removeObserver()).

Sablonfüggvény – Template Method

A sablonfüggvény egy olyan tervezési minta, amit akkor használunk, ha van egy általános receptünk, ami alapján több hasonló dolog is gyártható. Klasszikus példa a tea és a kávé, amit részletesen is ismertetünk. A sablonfüggvény mintát gyakran hasonlítják össze a stratégia mintával az alábbi mondattal:

  1. Stratégia: Ugyanazt csináljuk, de másképp;

  2. Sablonfüggvény: Ugyanúgy csináljuk, de mást.

A receptben háromféle lépés lehet:

  1. kötelező és közös: bármit készítünk a recepttel ez a lépés mindig ugyanaz,

  2. kötelező, de nem közös: bármit készítünk a recepttel ez a lépés szükséges, de minden esetben mást és mást kell konkrétan csinálni,

  3. opcionális: ez a lépés nem minden esetben szükséges.

Ezeket programozás technikailag így valósíthatjuk meg:

  1. A kötelező és közös lépések olyan metódusok, amelyek már az ősben konkrétak és azokat általában nem is szabad felülírni. Ilyen a forró ital főzésénél a vízforraló metódus.

  2. A kötelező, de nem közös lépések az ősben absztrakt metódusok, amit a gyermek osztályok fejtenek ki. Ilyen a forró ital főzésénél az édesítés.

  3. Az opcionális lépések az ősben hook metódusok, azaz van törzsük, de az üres.

Mivel a gyermek osztálynak implementálnia kell minden absztrakt metódust, ezért az ilyenek kötelezőek. Igaz, hogy akár az implementáció üres is lehet. Mivel a hook metódusoknak van implementációjuk, de üres az üres, ezért nem muszáj őket felülírni, de lehet az OCP elv megszegése nélkül. Ezért ezek az opcionális lépések. A hook metódusokat C# nyelven virtuálisnak kell deklarálni.

Maga a recept a sablonfüggvény. Gyakran csak ez az egy metódus publikus, minden más metódus, azaz a recept lépései privát vagy védett metódusok (a szerint, hogy a gyermeknek felül írhatja-e vagy sem). Erre azért van szükség, hogy az egyes lépéseket ne lehessen össze-vissza sorrendben hívni, csak a recept által rögzített sorrendben.

Elméletben a sablonfüggvény egy algoritmus, amelyben a lépések nem változnak, de a lépések tartalma igen. Ha esetleg mégis bejön egy új lépés, azt érdemes hook metódusnak felvenni.

Érdekes megfigyelni, hogy az absztrakt ős és a gyermek osztályai IOC (inversion of control) viszonyban állnak hasonlóan, mint a gyártófüggvény esetén. Ugyanúgy itt is nem a gyermek hívja az ős metódusait, hanem az ős a gyermekét. Ezt úgy érjük el, hogy a sablonfüggvény absztrakt, illetve virtuális metódusokat hív. Amikor a gyermek osztály példányán keresztül hívjuk majd a sablonfüggvényt, akkor a késői kötés miatt ezen metódusok helyett az őket felülíró gyermek béli metódusok fognak lefutni.

Példa

A sablonfüggvényt egy példával szemléltetjük: Nincs is jobb a visszatérő ügyfélnél, és már nekünk is van ilyen. A gyártófüggvény kapcsán megismert zsíros kenyér bolt visszatért. Olyan jól ment az üzlet, hogy kávét és teát is elkezdtek árusítani. Két kolléga el is kezdte a munkát egyikőjük a teát, míg a másik a kávét kapta feladatául. Estig nyugalom is volt az irodában, amikor is a teás kolléga meggyanúsította a kávést, hogy tőle lopta a kódot. Megnéztük és tényleg nagyon hasonló dolgokat írtak például: vízforraló függvény vagy a kitölt függvény. Ezek a dolgok mindkét ital esetében ugyanúgy történnek, nosza tegyük őket egy absztrakt osztályba. Ennek Ital lett a neve. Az elkészítési lépéseket (függvényeket: vízforraló, kitölt), betettük egy elkészít nevű függvénybe, nehogy valaki előbb tudja kitölteni az italt, mint a vizet felforralni. Majd jött a következő ötlet a hozzávalók hozzáadását is ide tettük, sőt csináltunk egy főz függvényt is, hogy teljes legyen a csapat. Mindkét előző függvény absztrakt, mert ezt majd a konkrét osztály kidolgozza, hiszen a kávéba tej és cukor kell, míg a teába citrom. Most már csak a két konkrét Tea és Kávé osztályt kellett kidolgozni, ahol a főz és az édesít (ami néha ugyan savanyít) függvények implementálása volt a feladat. Hazafelé menet még hallottuk a két kollegánk veszekedését: akkor is tőlem loptad. Másnap jött az ötlet, hogy a teába rumot is lehet tenni. Ez egyelőre opcionális lehetőség és nem is élünk vele, mert még a főnök nem egyezett bele. Talán majd télen.

Forráskód

using System;

namespace Ital_Készítő

{

    public abstract class Ital

    {

        public void elkészít()

        {// Ez a függvény nem kapta meg a virtual jelzőt a sorrend betartása miatt.

            vízforraló();// kötelező és közös lépés

            főz(); // inversion of control, mert a gyermek főz metódusa fog futni

            édesít(); // kötelező és nem közös lépés

            rum();    // opcionális

            kitölt();

        }

        private void vízforraló() // kötelező közös lépés

        {// Ennek szintén nem kell felűlírhatónak lennie

            Console.WriteLine("Vízforralás 98..99..100c");

        }

        protected abstract void főz(); // Ki kell dolgoznia a konkrét osztálynak.

        protected abstract void édesít(); // Ezek kötelező nem közös lépések.

        protected virtual void rum(){} // Ez egy hook, vagyis egy opcionális lépés.

        private void kitölt() // kötelező közös lépés

        {

            Console.WriteLine("Egy szép porceláncsészébe öntöm az italt\n");

        }

    }

    public class Tea : Ital

    {

        public override void főz()

        {

            // ezt ki kell dolgozni, hiszen másképp főzök teát, mint kávét

            Console.WriteLine("Belógatom és tunkolom a tea filtert");

        }

        public override void édesít()

        {

            Console.WriteLine("Kis cukor, és egy kis citrom ízlés szerint");

        }

    }

    public class Kávé : Ital

    {

        public override void főz()

        {

            // ezt ki kell dolgozni, hiszen másképp főzök teát, mint kávét

            Console.WriteLine("Leforrázom a kávét egy jó török kávé kedvéért.");

        }

        public override void édesít()

        {

            Console.WriteLine("Kis cukor, és egy kis tej ízlés szerint");

        }

    }

    class Program

    {

        static void Main(string[] args)

        {

            Ital tea = new Tea();

            Ital kávé = new Kávé();

            tea.elkészít(); // a késői kötés miatt a Tea főz és édesít metódusa fut

            kávé.elkészít();// a késői kötés miatt a Kávé főz és édesít metódusa fut

            Console.ReadKey();

        }

    }

}

Gyakorló feladat

Az alábbi leírás szerint készítsünk forráskódot. A megoldáshoz használjuk a sablonfüggvény tervezési mintát!

Feladat

Egészítse ki az alábbi kódrészletet! Írja meg a hiányzó metódusokat és a főprogramot! A feladat megoldásához döntse el, hogy melyik lépés

  1. közös és kötelező,

  2. kötelező, de nem közös,

  3. opcionális.

A kódrészlet:

    abstract class BuktaSűtés

    {

        public void recept()

        {

            tésztaGyúrás();

            töltelékBele();

            beSűtőbe();

            porcukorTetejére();

        }

        private void tésztaGyúrás() // ezt megadtuk segítségnek

        {

            Console.WriteLine("Meggyúrom a tésztát.");

        }

        // írja mega hiányzó metódusokat

    }

    class TurósBuktaSűtés : BuktaSűtés

    {

        // írja mega hiányzó metódusokat

    }

    class LekvárosBuktaSűtés : BuktaSűtés

    {

        // írja mega hiányzó metódusokat

    }

Stratégia – Strategy

Célja: Algoritmus családokat definiálunk, az algoritmusokat különválasztjuk, és csereszabatossá tesszük. Stratégia minta segítségével az algoritmusok közül választhatunk függetlenül a klienstől.

Alkalmazási terület:

  1. Olyan osztályaink vannak, amelyek csak viselkedésükben különböznek. A stratégia minta segítségével az osztályunkat a sok viselkedés egyikével ruházhatjuk fel.

  2. Egy algoritmus különböző változataira van szükségünk. A stratégia mintát akkor használhatjuk, ha ezeket az algoritmusokat osztályhierarchiában implementáltuk

  3. Az algoritmus olyan adatot használ, amiről a kliensnek nem kell tudnia. A stratégia minta segít elkerülni a komplex, algoritmus specifikus adatok feltárását.

  4. Egy osztály sokféle viselkedést definiál, amelyek a műveleteiben többször megjelennek feltételként.

Forráskód

using System;

namespace Strategy

{

    public abstract class KaveStrategia

    {

        public abstract void KaveFozes();

    }

    public class GyengeKave : KaveStrategia

    {

        public override void KaveFozes()

        {

            Console.WriteLine("Gyenge kávét főztél. Ha csak kicsit vagy fáradt.");

        }

    }

    public class NormalKave : KaveStrategia

    {

        public override void KaveFozes()

        {

            Console.WriteLine("Normál kávét főztél. Egy átlagos napra.");

        }

    }

    public class ErosKave : KaveStrategia

    {

        public override void KaveFozes()

        {

            Console.WriteLine("Egy erős kávé. A hosszú és fárasztó napokra.");

        }

    }

    public class KaveGep

    {

        private KaveStrategia _kaveStrategia;

        public void KaveValasztas(KaveStrategia k)

        {

            this._kaveStrategia = k;

        }

        public void KaveFozes()

        {

            this._kaveStrategia.KaveFozes();

        }

    }

    class Program

    {

        static void Main(string[] args)

        {

            KaveGep automata = new KaveGep();

            automata.KaveValasztas(new GyengeKave());

            automata.KaveFozes();

            automata.KaveValasztas(new ErosKave());

            automata.KaveFozes();

            automata.KaveValasztas(new NormalKave());

            automata.KaveFozes();

            Console.ReadLine();

        }

    }

}

UML-ábra

24. ábra

Gyakorló feladat

Készítsük el az alábbi leírásnak megfelelő forráskódot. A feladat megoldásához használjuk a stratégia tervezési mintát!

Feladat

Micsoda megtiszteltetés! A Magyar Forma-1 istálló megkért minket, hogy készítsünk nekik programot, hogy milyen időben milyen vezetési stílust válasszanak a pilóták. Tettük is a dolgunkat a feladat komolyságához méltón. Elkészült a kód és mi az esti tévézés helyett kivetítettük, hogy gyönyörködjünk a jól végzett munkánk eredményében. A kreációnk tele volt switch és if-else-if elágazásokkal, amik az időjáráshoz képest más és más vezetési stílusú függvényt hívtak meg. Halk elégedett mormogás a sötét teremben, amikor valaki felkiáltott: én azt olvastam, hogy ha egy kódban sok a feltételes utasítás, akkor nézd meg, hátha rá tudsz húzni egy stratégiát. Sajnos a főnők is ott volt, úgyhogy nem volt mit tenni. A már megszokott absztrakt osztállyal kezdtük. Ennek neve Vezetési_Stratégia lett. Ebbe van egy absztrakt függvénye, a Vezet, ez írja le a technikát. Ebből születnek a konkrét osztályok, mint például a Csúszós_Út_Stratégia vagy a Napos_Idő_Stratégia. Ezek az osztályok kidolgozzák a legjobb stílust a Vezet függvényükben. Már csak egy osztály hiányzott, a Pilóta, melynek van egy Stílusválasztás függvénye, ami paraméterként egy Vezetési_Stratégia példányt vár. Pilótánk másik függvénye a Versenyez. Ez csak a beállított stratégia Vezet függvényét hívja meg. A verseny napján létrehozunk egy Pilóta példányt az útviszonyoknak megfelelő Vezetési_Stratégiával, meghívjuk a Versenyez függvényt és vasárnap délután elégedetten nézzük, ahogy megnyeri a versenyt.