Design Patterns | |
Szerző |
|
Eredeti cím | Design Patterns |
Nyelv | angol |
Műfaj |
|
Kiadás | |
Kiadó | Addison-Wesley |
Kiadás dátuma | 1994 |
Sablon • Wikidata • Segítség |
A Tervezési minták: az újrahasználható objektumorientált szoftver elemei (eredeti címén angolul Design Patterns: Elements of Reusable Object-Oriented Software) egy 1994-es szoftverfejlesztési könyv, amelyben programtervezési minták vannak részletesen leírva. A könyvet Erich Gamma, Richard Helm, Ralph Johnson, és John Vlissides írta, a hozzátartozó előszót pedig Grady Booch. A szerzőket szokás Gang of Four (GoF), azaz Négyek bandája néven együtt említeni.
A könyv két részből áll: az első két fejezet az objektumorientált programozás által nyújtott lehetőségekről és a felmerülő buktatókról szól, a maradék fejezetekben pedig 23 klasszikus tervezési mintát mutatnak be. A könyv C++ és Smalltalk nyelven tartalmaz példákat.
A könyvnek nagy jelentősége van a szoftverfejlesztés területén, és fontos forrásként tekinthetünk rá az objektumorientált tervezés elméleti és gyakorlati alkalmazásának vonatkozásában is. Több mint 500 000 példányt adtak már el angol és 13 másik nyelven.
A könyv egy birds of feather (BoF) beszélgetésben merült fel először a OOPSLA (Object-Oriented Programming, Systems, Languages & Applications) konferencián a 90-es években "Towards an Architecture Handbook" címmel Bruce Anderson vezetésével. Itt találkozott Erich Gamma és Richard Helm, akik felismerték, hogy mindkettőjüket érdeklik a tervezési minták. Később csatlakozott hozzájuk Ralph Johnson és John Vlissides[1] is. A könyv hivatalos kiadásának dátuma 1994. október 21., de a szerzői joga 1995-ös, ennélfogva gyakran 1995-ös kiadásként utalnak rá, habár a tényleges publikálása 1994-ben volt. A nyilvánosság számára a könyv az 1994-es októberi OOPSLA konferencián lett elérhető Portlandben. 2005-ben az ACM SIGPLAN a Programming Languages Achievement Award díjat adta az íróknak, a „programozási gyakorlaton és a programozási nyelvek tervezésére”[2] tett nagy hatása miatt. 2012 márciusában a könyvnek már 40. nyomtatásánál tartottak.
Az első fejezete egy beszélgetés az objektumorientált tervezési technikákról az írók tapasztalatai alapján, olyanokról amik használatáról azt mondják, hogy egy jó objektumorientált szoftverdesign-t eredményez. Például:
Az írók szerint az interfészre programozás előnye az implementációra való programozással szemben a következők:
Az interfész használata dinamikus kötéseket és többalakúságot eredményez, amik az objektumorientáltság alapjai.
A szerzők az öröklődésre fehér dobozos újrafelhasználásként utalnak, ahol a fehérdobozosság a láthatóságra utal, mert a szülő osztályok felépítése gyakran látható a leszármazott osztályok számára. Ezzel ellentétben az objektum összetételre feketedobozos újrafelhasználásként utalnak, mert belső információknak nem kell láthatóaknak lenniük, akkor sem, ha használjuk az objektumokat.
Az írók beszélnek az öröklődés és az egységbe zárás közötti feszültségről, és azt állítják, hogy tapasztalatuk szerint a tervezők túl sokat használják az öröklést. (Gang of Four 1995:20). Ennek veszélyeit ilyen módon fogalmazzák meg:
Figyelmeztetnek, hogy egy alosztály implementálása annyira függővé válhat a szülőosztály implementációjától, hogy bármilyen változtatás a szülő implementációjában azt eredményez, hogy muszáj az alosztályt is módosítani. Ezenkívül, elmondják, hogy ezek elkerülésére egy lehetőség, hogy csak absztrakt osztályokból van öröklődés, ugyanakkor kiemelik, hogy ennél a megoldásnál minimális kódújrafelhasználás van.
Az öröklődés használata főleg akkor javasolt, ha már létező komponensekhez új funkciót adunk, nagy részben a régi kód újrafelhasználásával és relatíve kevés új kód írásával.
A szerzők a 'delegálás'-ra az objektum összetétel egy extrém formájaként tekintenek, ami mindig használható az öröklődés helyett. A delegálás 2 objektumból áll: A 'küldő' átadja saját magát a 'delegáló'-nak, hogy az rá tudjon hivatkozni. Így a link a 2 rész között csak a futási időben jön létre, nem a fordítási időben.
A szerzők szintén beszélnek az úgynevezett paraméteres típusokról. Ezek megengedik bármilyen típus definiálását, anélkül, hogy specifikálni kellene a többi típust, amit használ. A nem specifikált típusok a 'paraméterek' a használat szempontjából.
A szerzők elismerik, hogy a delegálás és a paraméterezés is nagyon erőteljes módszerek, de figyelmeztetnek is:
A szerzők továbbá különbséget tesznek az 'Aggregáció' (ahol az objektumnak 'van egy' vagy 'része egy' másik objektumnak, tehát a mindkét objektumnak ugyanakkora az élettartalma) és az egyszerű 'ismeretség' között (ahol az objektum csupán 'tud' a másik objektum létezéséről). Ez utóbbit szokták 'asszociációnak' vagy 'használói' kapcsolatnak is nevezni. Az ismeretség esetén az objektumok kérhetnek egyes műveleteket egymáshoz, de nem felelősek egymásért. Az ismeretség egy lazább kapcsolat, mint az aggregáció, és egy sokkal lazább összekötést igényel az objektumok között, ami gyakran fontos lehet a lehető legjobb karbantarthatóság szempontjából.
A szerzők a 'toolkit', azaz eszközkészlet fogalmát használják, míg mások manapság talán inkább a 'class library', azaz osztály könyvtár kifejezést használják, mint például C++-ban vagy Javában. Az ő értelmezésükben az eszköztár az objektumorientált megfelelője a szubrutinkönyvtáraknak, ahol a szoftverkeretrendszer együttműködő osztályok egysége, amik újrahasználható design-t biztosítanak a szoftver egy specifikus osztálya számára. A szerzők elmondják, hogy egy alkalmazás fejlesztése nehéz, egy eszköztáré még nehezebb, a legnehezebb pedig a keretrendszer fejlesztése.
A második fejezet egy lépésről lépésre levezetett esettanulmánya egy WYSIWYG dokumentumszerkesztőnek, a Lexinek.
A fejezet hét problémán megy végig, amiket sorban meg kell vizsgálni a helyes design-hoz Lexi esetén, beleértve minden megkötést is amiket követni kell. Minden probléma mélységeiben meg van vizsgálva és megoldási javaslatokat is adnak. Minden megoldás részletesen be van mutatva, beleértve a pszeudokódokat, valamint Objektummodellezési technikákat, ahol szükséges.
Végül pedig, minden megoldáshoz hozzátársít egy vagy több tervezési mintát. Bemutatják, hogy a megoldás milyen formában implementálja az adott tervezési mintát.
A hét probléma (beleértve a megkötéseket is) és a megoldások (beleértve a tervezési mintára/mintákra való hivatkozásokat) a következők:
A dokumentum "alapvető grafikai elemek elrendezése", mint a karakterek, sorok stb., amik "egybefoglalják a dokumentum információ tartalmát". A dokumentum szerkezete ezeknek az elemeknek az összessége és minden elem visszavezethető más résszerkezetekre.
Problémák és megszorítások
Megoldás és tervezési minta
A rekurzív kompozíció, elemek egy hierarchikus szerkezete, ami "fokozatosan egyre komplexebb elemeket épít az egyszerűbbekből". Minden csomópont a szerkezetben tud a szülő és a gyerek elemeiről. Ha egy művelet elvégzése az egész szerkezeten történik, akkor minden csomópont meghívja a műveletet a saját gyerekein. (rekurzívan).
Ez az összetétel tervezési mintának egy implementálása, ami csomópontoknak az összessége. A csomópont egy absztrakt alap osztály, és a tőle származhatnak levelek (egy esetén), vagy újabb csomópontokból álló kollekció(amik csomópontjai megint csak tartalmazhatnak leveleket, vagy újabb csomópont-kollekciókat). Amikor egy műveletet végrehajtanak egy szülőn, akkor a művelet rekurzív módon tovább adódik a hierarchián lefelé haladva.
A formázás nem a szerkezetet jelenti. A formázás egy módszer egy adott rész felépítésére a dokumentum fizikai struktúrája szempontjából. Ide tartozik a szöveg sorokba törése, a kötőjelek használata, a margó szélesség beállítása stb.
Problémák és megszorítások
Megoldás és tervezési minta
Egy Compositor osztály egységbe fogja zárni az algoritmust ami az elrendezést szolgálja. A Compositor egy alosztálya az objektumnak a dokumentum szerkezetében. Egy Compositornak van egy társított példánya a Composition objektumból. Amikor a Compositor futattja a Compose()
-t, akkor ez végig iterál minden ehhez társított elemen, és újra rendezi a szerkezetet beillesztve a megfelelő sorokat és oszlopokat.
A Compositor maga egy absztrakt osztály, ami lehetővé teszik az őt származtató osztályoknak a különböző formázó algoritmusok használatát (mint például a szélesebb margók, dubla-spacek stb.)
A stratégia programtervezési minta használható erre a célra. A stratégia egy módszer több algoritmus egységbe zárására a körülmények változására alapozva. Ebben az esetben a formázásnak különbözőnek kell lennie, attól függően, hogy szöveg, grafika, egyszerű elem stb. van-e formázva.
Annak a felhasználói felületnek (user interface) a változtatása, amin keresztül a felhasználók kapcsolatba léphetnek a dokumentummal.
Problémák és megszorítások
Megoldás és tervezési minta
Az „átlátszó elkerítés” (transparent enclosure) segíti javítani az összetételekhez adott viselkedéseket. Az ilyen elemek, mint a szegélyek és a görgetősávok, speciális alosztályai egy-egy különálló elemnek. Mivel ezek a kiegészítések részei a struktúrának, így a megfelelő Operation()
meg lesz hívva amikor a struktúra Operation()
-ja meg lesz hívva. Ez azt jelenti, hogy a kliensek nincs szüksége semmilyen speciális ismeretre vagy felületre a struktúrához ahhoz, hogy használja ezeket a kiegészítéseket.
Ezt egy Díszítő tervezési minta, amely lehetővé teszi adott objektumokhoz más viselkedések hozzáadását, anélkül, hogy magát az objektumot módosítaná.
A "Look-And-Feel" kifejezés a platform-specifikus UI szabványokra utal. Ezek a szabványok "meghatározzák az iránymutatásokat arra vonatkozóan, hogy egy alkalmazás, hogyan jelenik meg és hogyan viselkedik a felhasználóval"
Problémák és megszorítások
Megoldás és tervezési minta
Mivel az objektum létrehozása a konkrét objektumok esetén nem történhet futási időben, így az objektum létrehozásának folyamatát el kell vonatkoztatni. Ez egy absztrakt guiFactory-val tehető meg, ami átveszi a UI elemek létrehozásának feladatkörét. Az abstract guiFactory-nak vannak konkrét implementációi, mint a MotifFactory, ami létrehozza a konkrét elemeit az ennek megfelelő típusnak (MotifScrollBar). Ilyen módon a programnak elég a ScrollBar-t kérnie, és futási időben ez megkapja a megfelelő konkrét elemet.
Ez egy absztrakt gyár tervezési minta. Az átlagos gyár konkrét objektumot hoz létre egy típushoz. Az absztrakt gyár is konkrét objektumot hoz létre, de változó típusokhoz, a gyár konkrét implementációjától függően. Ez lehetővé teszi, hogy ne csak egyetlen konkrét objektumra fókuszáljunk, hanem egész konkrét objektum "családokra", "ezzel különbözik a többi létrehozási tervezési mintától, amik csak egyfajta termékobjektumot tartalmazhatnak"
Ahogyan a "look-and-feel" is különbözik platformonként, úgy az ablakok kezelésének művelete is. Minden platform máshogy jeleníti meg, kezeli az input és output formokat és rétegzi az ablakokat.
Problémák és megszorítások
Megoldás és tervezési minta
Lehetséges, hogy kifejlesszük a "saját, absztrakt és konkrét termékosztályainkat", mivel "minden ablakrendszer ugyanazt a dolgot csinálja általánosságban". Minden ablakrendszer műveleteket biztosít primitív alakzatok kirajzolására, átméretezésre, az ablak tartalmának frissítésére.
Egy absztrakt alap Window
osztályt származtatni lehet különböző típusú már létező ablakokból, mint például applikációk vagy dialogok. Ezek az osztályok tartalmazni fognak olyan műveleteket, amik kapcsolatban vannak az ablakokkal, mint az újraformázás, a grafikus frissítés stb. Minden ablak tartalmaz elemeket, amiknek a Draw()
funkciója meg van hívva a Window
saját rajzoláshoz kapcsolódó funkcióiban.
Hogy elkerüljük a platform-specifikus Window alosztály létrehozásának szükségességét minden lehetséges platformra, egy interfészt fogunk használni. A Window
osztály implementálni fog egy Window
absztrakt implementáció osztályt (WindowImp
). Ebből az osztályból aztán több platformspecifikus implementáció lesz származtatva, mindegyik platform specifikus műveletekkel fog rendelkezni. Ennélfogva csak egy Window
osztálykészletre van szükség minden platform esetén (minden lehetséges elérhető típus és platform Descartes-szorzata helyett). Így tehát egy új ablak típus hozzáadása nem igényel majd módosítást a platform implementációjában és vice versa.
Ez egy híd programtervezési minta. A Window
és a WindowImp
különböznek, de kapcsolódnak egymáshoz. A Window
foglalkozik a program felől az ablakokkal, míg a WindowImp
a platform felől teszi ezt. Bármelyik megváltoztatható anélkül, hogy a másikat is változtatni kellene. A Híd tervezési minta megengedi ennek a két "különböző osztály hierarchiából osztálynak, hogy együtt dolgozzanak, még ha külön-külön fejlődnek is".
Minden tevékenység amit a felhasználó a dokumentum kapcsán tehet, a szöveg beviteltől, a formázáson át a mentésig.
Problémák és megszorítások
Megoldás és tervezési minta
Minden menü elem, ahelyett, hogy paraméterlistával lenne szemléltetve, Command objektumokkal van megvalósítva.
A Command egy absztrakt objektum, aminek csak egyetlen absztrakt metódusa van az Execute()
. A származtatott objektumok kiterjesztik az Execute()
metódust a megfelelő módon. Ezek az objektumok widgetekkel vagy gombokkal egyszerűen használhatóak, akárcsak a menü elemeként.
A visszavonás és a mégis visszaállítás műveletének támogatására a Command
-hoz tartozik egy Unexecute()
és Reversible()
is. A származtatott osztályokban az Unexecute()
olyan parancsot tartalmaz, ami visszavonja a műveletet, míg a Reversible()
olyan boolean értéket ad vissza, ami megmondja, hogy a parancs visszavonható-e. Egyes parancsok nem visszavonhatóak, például a Save (mentés).
Minden végrehajtott Command
egy listában van tárolva, ahol egy metódus egy "most" jelzőt tesz közvetlenül a legutóbb végrehajtott parancs mögé. A visszavonás kérésekor a Command.Unexecute()
hívódik meg közvetlenül a "most" előtt, és aztán a "most" jelzőt egy paranccsal hátrébb teszi. Ezzel szemben a mégis visszaállítás kérésekor a Command.Execute()
hívódik meg a "most" jelző után, és aztán a jelzőt eggyel előrébb mozgatja.
Ez a Command
megközelítés, a parancs programtervezési minta implementációja. Ez egységbe zárja az objektumokra vonatkozó kéréseket, és az egyszerű interfészeket használja, hogy hozzá férjen ezekhez a kérésekhez. Így a kliens kezelni tud különböző kéréseket, parancsokat amik az applikációban elszórtan szerepelnek.
Ez a dokumentumszerkesztő azon képessége, amivel szöveges szempontból vizsgálni tudja a dokumentum tartalmát. Bár sokféle vizsgálat elvégezhető, a fókuszban a helyesírás-ellenőrzés és az elválasztás (kötőjelezés) van.
Problémák és megszorítások
Megoldás és tervezési minta
A szám alapú indexelés eltávolítása az alapvető elemek esetén lehetővé teszi, hogy egy másfajta iterációs interfészt implementáljunk. Ez plusz metódusokat igényel a bejárásra és az objektum visszakeresésére. Ezek a metódusok egy abstract Iterator
interfészben vannak elhelyezve. Ezután minden elem származtatni fogja az Iterator
-t,attól függően, hogy az elem hogyan tárolja a listáját (ArrayIterator
, LinkListIterator
stb.)
A bejárás és a visszakeresés függvényei az absztrakt Iterator interfészben vannak. A jövőbeli Iteratorok az alapján vannak származtatva, hogy milyen típusú listán kell majd végig iterálniuk, mint például tömbök (arrays) vagy láncolt listák (linked lists). Így függetlenül attól, hogy milyen módon történt az indexelés az elem használatának implementációjakor, a megfelelő Iteratort fogja használni.
Ez az Iterátor programtervezési minta implementációja. Ez lehetővé teszi a kliensnek, hogy bejárjon egy objektumkollekciót, anélkül, hogy direkt módon hozzá kellene férnie, vagy azon kellene aggódnia hogy milyen fajta listaszerkezetet használ a kollekció.
Most, hogy a bejárást már kezeltük, már lehetséges, hogy megvizsgáljuk az elem szerkezetét. Hogy minden elem szerkezetébe beépítsünk egyesével valamilyen vizsgálatot, az nem járható út, hiszen akkor minden elemben külön kellene a kód, pedig sok kód nagyon hasonló lenne a hasonló elemeknél.
Ehelyett, egy generikus CheckMe()
metódus van beépítve minden elem absztrakt osztályába. Minden Iteratornak adva van egy hivatkozás egy specifikus algoritmusra (pl.: helyesírás ellenőrzés, nyelvtani helyesség stb.). Amikor egy Iterator végig iterál a kollekción, meghívja minden elem CheckMe
metódusát, továbbadva a megfelelő algoritmust. A CheckMe
metódus aztán továbbad egy hivatkozást az elemre a megfelelő vizsgálatot végző algoritmusnak.
Így, hogy helysírás ellenőrzést végezzünk, egy elejétől a végéig iterálónak kell hivatkozásként átadni a SpellCheck
objektumot. Ekkor az iterátor minden egyes elemhez hozzáférve elvégzi annak CheckMe()
metódusát a SpellCheck
paraméterrel. Minden CheckMe
metódus meghívja a SpellCheck
-et, továbbadva a hivatkozást a megfelelő elemnek.
Ennél a módszernél, bármelyik algoritmus használható a bejárás metódusával, kemény kódolva az egyiket a másikkal. Például, a Find (Keresés) használható mint "find next" (következő keresése) vagy mint "find previous" (előző keresése), attól függően hogy a "forward" (előre) vagy "backward" (visszafelé) iterátor volt használva.
Tehát összegezve, maguk az algoritmusok felelhetnek a különböző elemek kezeléséért. Például, egy SpellCheck
algoritmus figyelmen kívül hagyna egy Graphic
elemet (grafikus elem), ahelyett, hogy minden ilyen Graphic
-ból származó elembe bele kellene programoznunk, hogy ne küldjék magukat tovább a SpellCheck
-nek.
A könyvben két tervezési alapelv jelenik meg, a GOF1 és a GOF2.
A Létrehozási minták objektumok létrehozását valósítják meg, miközben javítják abban a rugalmasságot, hogy ki, hol, hogyan és mikor hozzon létre objektumokat.
Ezek az osztályok és objektumok elrendezésével kapcsolatosak, Öröklődést használnak, hogy összeállítsák az interfészeket és, hogy meghatározzanak különböző módokat az objektumok összetételére, hogy hogy adhatunk hozzájuk új funkcionalitást.
A legtöbb ilyen tervezési minta az objektumok közötti kommunikációval kapcsolatos.
A szoftverfejlesztési tervezési minták koncepciójával szemben és a Design Patterns könyv kapcsán is fogalmaztak meg kritikákat. Az elsődleges kritika ami a Design Patternst érte, az volt, hogy a mintái egyszerűen csak a C++ hiányzó feature-eire adnak valamiféle megoldást az elegáns absztrakt feature-ök hosszú, koncentrált mintákra cserélésével. A kritikusok szerint lényegében arról van szó, hogy "emberi fordítóprogram"-ok lesznek vagy „néhány makro kézileg generált kibővítései” ezek. Peter Norvig rámutat, hogy 16 minta a 23-ból a könyveben egyszerűsödött vagy teljesen megszűnt Lisp-ben vagy Dylan-ben. Ehhez kapcsolódó megfigyeléseket tett Hannemann és Gregor Kiczales, akik többet mintát is implementáltak a 23-ból aspektusorientált programozási nyelven, az AspectJ használatával és megmutatták hogy a kód szintű függőségeket sikerült eltávolítani 17 minta esetén a 23-ból. Valamint, hogy az aspektusorientált programozás használatával egyszerűsíteni tudták a tervezési minták implementációját.[3]
Ezen kívül humoros kritikák is születtek, mint például az OOPSLA-ban 1999. november 3-án,[4][5] és egy paródia is, Jim Coplientől ilyen címmel: "Kansas City Air Conditioner".
Egy interjúban az InformIttal 2009-ben Erich Gamma elmondta, hogy a könyv szerzőinek 2005-ben volt egy beszélgetése arról, hogy változtatnának ma a könyvön. Arra jutottak, hogy valószínűleg újra kategorizálnának néhány mintákat, valamint hozzáadnának még néhány további mintát. Gamma eltávolította volna az Egyke (Singleton) mintát, de ebben nem jutottak közös nevezőre a szerzők.[6]