Gyár programtervezési minta
Az objektumorientált programozásban a gyár egy olyan objektum, amely más objektumokat hoz létre.[1] Formálisan, a gyár függvény vagy metódus, ami paramétertől függően különböző prototípusú vagy osztályú objektumokat ad vissza. Tágabb értelemben véve a gyártó metódus egy szubrutin, ami új objektumot ad vissza. A gyár az objektumorientált programozás egyik alapfogalma, és több tervminta alapját képezi. A gyár és a gyártó programtervezési minták létrehozási minták.
Motiváció
[szerkesztés]Az osztály alapú programozásban a gyár egy osztály konstruktorának absztrakciója, míg a prototípus alapú programozásban a gyár egy prototípus objektum absztrakciója. Egy konstruktor konkrét abban, hogy egyetlen osztály példányait hozza létre; ezzel szemben egy gyár különböző osztályú objektumokat (termékek) készít, és nem feltétlenül hív konstruktort, hanem kezelhet objektumkészletet is. Egy prototípus objektum konkrét abban, hogy klónozás céljára hoz létre objektumokat, míg egy gyár többféle prototípust képes klónozni.
A gyárt többféleképpen lehet hívni, többnyire metódushívással (gyártó metódus), néha pedig függvényhívással, ha a gyár függvény objektum. Egyes nyelvekben a gyár a konstruktor általánosítása, ami azt jelenti, hogy a konstruktorok is gyártó függvények, és egy gyártó függvény úgy hívható, mint egy konstruktor. Más nyelvek megkülönböztetik a kettőt, és a konstruktor hívása különbözik a metódushívástól (new kulcsszó). Ezekben a nyelvekben a gyártó függvény a konstruktor absztrakciója, de nem általánosítása, mivel maguk a konstruktorok nem gyártó függvények.
Fogalmi rendszerezés
[szerkesztés]A fogalomalkotás megkülönböztetheti magát a gyár fogalmát (absztrakt gyár programtervezési minta) és a gyártó metódust. Gammáék ( Erich Gamma, Richard Helm, Ralph Johnson és John Vlissides) könyvükben (Design Patterns: Elements of Reusable Object-Oriented Software) így rendszerezik a mintákat.[2][3] Vannak más források, amelyek nem különítik el a kettőt.[4] Megint mások a gyártó fogalmát olyan esetekre használják, amelyek összetettebbek, és több gyárat tartalmaznak; ebben a megfogalmazásban a gyárra magára egyszerű gyárként hivatkoznak.[4] Egyes nyelvek, mint például a Python is használják a factory szót.[5] Általánosabban szubrutin vagy metódus is lehet gyártó.[6] Mivel egyes nyelvekben a gyárakat egy metódus hívásával hívják, a gyár általános fogalma összetéveszthető a gyártó metódussal.
Használata
[szerkesztés]Az objektumorientáltság többféle lehetőséget is nyújt a polimorfizmusra. Ha altípusos polimorfizmust használunk, akkor egy bázis osztályú objektum értékként kaphat leszármazott osztályú objektumot értékül. Ha ez felüldefiniál egy metódust, akkor a híváskor a leszármazott osztály metódusa hívódik meg. Konstruktorokra viszont ez nem működik, mivel a konstruktor nem öröklődik, hiszen a konstruktor nem használja, hanem létrehozza az objektumot.
A konstruktorok hívását és a prototípusok klónozását gyártókra bízva lehetővé válik, hogy a kliens szemszögéből a polimorfizmus kiterjedjen az objektumok létrehozására is. Eszerint a kliensnek ne kell ismernie a pontos típust, tehát az összecsomagolható a gyárral, és a klienstől függetlenül kezelhető, legfeljebb át kell írni egy helyen a paramétereket, vagy a beállítási lehetőségeket megváltoztatni.
Amely nyelvekben a gyártó függvény általánosítja a konstruktort, gyártó függvény hívható bárhol, ahol konstruktor is. A kliens csak meghív egy metódust, amiből kap egy adott osztályú objektumot. Nem kell azzal foglalkoznia, hogy pontosan milyen osztályú.
Például Pythonban a collections.defaultdict osztály konstruktora egy defaultdict típusú objektumot hoz létre, aminek alapértelmezett értékeit egy gyár objektum adja.[7] A gyár objektumot paraméterként megkapja egy konstruktor, és maga is lehet konstruktor, vagy bármi, ami konstruktorként működik: egy hívható objektum, ami visszaad egy értéket. Például a list konstruktor a listákra:
# collections.defaultdict([default_factory[, ...]])
d = defaultdict(list)
Gyárak használhatók akkor is, ha az objektumok létrehozása bonyolultabb folyamat, mint egy konstruktorhívás. Magában foglalhat bonyolult allokációt vagy inicializációt. A szükséges folyamatok közé tartozik a termék pontos típusának meghatározása, életciklusának kezelése, és a konstrukción túl menő inicializálása, vagy a destrukció előkészítése. A gyár dönthet úgy, hogy dinamikusan választja ki a termék osztályát, visszaadja az objektumkészletnek, bonyolult beállításokat végez róla, vagy más, bonyolult műveleteket végez. Ez az ő magánügye. Általánosabb értelemben a szingleton is gyár, csak speciális, mivel létrehoz egy (és csak egy) objektumot.
Példák
[szerkesztés]A gyár legegyszerűbb példája az egyszerű gyártó függvény, ami meghívja a konstruktort és visszaadja az értéket. Pythonban az f
függvény, ami az A
osztályt példányosítja, így implementálható:
def f():
return A()
Egyszerű gyártó függvény a szingleton minta számára:
def f():
if f.obj is None:
f.obj = A()
return f.obj
f.obj = None
Ez először hívva egy új objektumot ad; a további hívásokra ugyanezt az objektumot adja.
Szintaxis
[szerkesztés]Gyárak hívhatók többféleképpen is, többnyire gyártó metódussal. Ha a gyár hívható objektum, akkor függvényként hívják. Vannak nyelvek, például Python, Perl, Ruby, Object Pascal és F#, amelyekben a konstruktorok és a gyárak hívásának szintaxisa megegyezik, ami lehetővé teszi, hogy a konstruktorokat transzparensen lehessen helyettesíteni gyárakkal. Más nyelvekben a könnyű cserélhetőség interfészek közbeiktatásával oldható meg.
Szemantika
[szerkesztés]Azokban a nyelvekben, amelyek az objektumokat dinamikusan allokálják, mint Javában és Pythonban, a gyárak szemantikusan ekvivalensek a konstruktorokkal. Más nyelvek, mint a C++ megengedik objektumok statikus allokálását, a gyárak szemantikusan elkülönülnek a statikusan allokált objektumok konstruktoraitól, mivel ez utóbbiaknak tárigénye fordítás időben ismert, és a gyárak termékeinek tárigényét futás időben kell számítani. Ha egy konstruktor paraméterként átadható egy függvénynek, akkor a konstruktorhívás és a termék allokációja dinamikusan, fordítás időben kell, hogy végbemenjen. Ennek a szintaxisa ugyanaz, vagy hasonlít egy gyár hívására.
Tervminták
[szerkesztés]Gyárakat többféle programtervezési minta használ, speciálisan a létrehozási minták. Egyes speciális eseteket sok különböző nyelven is megvalósítottak. Például Gammáék egyes mintái, mint a gyártó metódus, az építő vagy az egyke ennek a fogalomnak a különböző megvalósításai. Az absztrakt gyártó több gyár együttműködését tartalmazza.
Egyes tervmintákban a gyár objektumnak minden legyártandó objektumra van metódusa. Ezek fogadhatnak paramétereket, amelyek meghatározhatják, hogy milyen objektumot gyártson.
Felhasználása
[szerkesztés]A gyárak gyakori összetevői a keretrendszereknek és az eszközkészleteknek, ahol a könyvtári kód olyan objektumok létrehozását igényli, amelyeknek típusát a keretrendszert használó alkalmazások származtatják le. A tesztvezérelt fejlesztésben is használják, hogy teszt alá vonják az osztályokat.[8]
A gyárak meghatározzák a termék pontos típusát, és aktuálisan legyártják a terméket. Ha a gyár csak egy absztrakt interfészt ad, akkor a kliens nem fog tudni a konkrét típusról, és nem is lesz szüksége rá. Az absztrakt gyár azonban ismeri a pontos típust. Ez részletesen kifejtve a következőt jelenti:
- A kliens kód a pontos típus ismerete nélkül működik. Ez azt jelenti, hogy nem is kell include-olnia, használnia, vagy importálnia a konkrét osztályokat. Elég az absztrakt interfészt ismernie, annak a műveleteit hívogathatja.
- Az új konkrét típus hozzáadása lényegében elkerülheti a klienst. Azt az osztályt, vagy dokumentumot is kis mértékben kell módosítani, ami a beállításokhoz szükséges, illetve a lehetséges típusokhoz hozzáadni az új konkrét típust, ami üzenetként átmegy a kliensen. A legtöbb módosítást a gyáron kell végezni, ott kell megadni, hogy a paraméter melyik értékéhez tartozik az új konkrét típus, ott kell meghívni a konstruktort, és ott kell végső beállításokat végezni az újfajta terméken. A gyár nélkül mindenütt módosítani kellene a kódot, ahol a konstruktort hívnánk.
Felhasználhatósága
[szerkesztés]Gyárak használhatók, ha:
- Az objektum létrehozása lehetetlenné tenné az újrafelhasználást a kód másolása nélkül
- Az objektum létrehozása olyan információt vagy forrásokat igényelne, amit nem adnánk az osztályt felhasználó kódnak
- Az objektum életciklusának kezelését központosítani kell, hogy az alkalmazás zavartalanul működhessen.
Párhuzamos osztályhierarchiák gyakran azt követelik, hogy az egyik hierarchia tagjai a másik hierarchia megfelelő tagjait hozzák létre. Azaz a gyárak hierarchiájának meg kell egyeznie a termékekével.
A gyártó metódusok felhasználhatók a tesztvezérelt fejlesztésben: Tegyük fel, hogy a Foo osztály létrehoz egy Dangerous (Veszélyes) osztályú objektumot, amit nem lehet közvetlenül teszt alá vonni.[8] Ennek az lehet az oka, hogy adatbázissal kommunikál, ami nem biztos, hogy mindig rendelkezésre áll. Ekkor a tesztelő létrehoz egy gyártó metódust a Foo osztályban, ami egy Dangerous objektumot ad vissza, és a konstruktort ennek közvetítésével hívja meg. Ezután származtatja a Foo osztályból a TestFoo osztályt, ahol felüldefiniálja a createDangerous metódust, és egy másik, a Dangerous osztállyal azonos felületű objektumot ad vissza a FakeDangerous osztályból, ez egy mock objektum. Az egységtesztek a TestFoo osztályt tesztelik anélkül, hogy a valódi Dangerous objektummal kellene bajlódniuk.
Előnyei és változatai
[szerkesztés]Beszélő nevek
[szerkesztés]A legtöbb programozási nyelvben a konstruktornak kötelezően az osztály nevét kell viselnie. Ez többértelműséghez vezethet. Ezek a megkötések nem vonatkoznak a gyártó metódusokra, lehet beszélő nevük. Például, ha két valós szám, mint paraméter megadásával hívható a komplex szám konstruktora, akkor csak a paraméterek neve adhat arról információt, hogy Descartes- vagy polárkoordinátákról van-e szó. Gyártó metódusokkal azonban ez egyértelműbb lehet. Ezt az alábbi példa mutatja be C#-ban:
public class Complex
{
public double real;
public double imaginary;
public static Complex FromCartesian(double real, double imaginary)
{
return new Complex(real, imaginary);
}
public static Complex FromPolar(double modulus, double angle)
{
return new Complex(modulus * Math.Cos(angle), modulus * Math.Sin(angle));
}
private Complex(double real, double imaginary)
{
this.real = real;
this.imaginary = imaginary;
}
}
Complex product = Complex.FromPolar(1, Math.PI);
Ekkor a konstruktort gyakran priváttá teszik, hogy a klienseket rákényszerítsék a gyártó metódusokra.
Egységbe zárás
[szerkesztés]A gyártó metódusok magukba zárják a termék objektumok létrehozását. Ez hasznos lehet, ha az objektum létrehozása bonyolult, például függhet konfigurációs fájloktól vagy felhasználói bemenettől.
Például a képnézegető különféle képeket nyit meg, és minden típushoz más olvasó osztályt kell használnia. Valahányszor megnyit egy képet, a fájl elején található információ alapján kell kiválasztania a megfelelő olvasó osztályt. Ezt a logikát egy gyár tartalmazhatja, amire gyakran egyszerű gyárként hivatkoznak.
Alábbi példák Java és PHP nyelven:
Java
[szerkesztés]public class ImageReaderFactory {
public static ImageReader createImageReader(ImageInputStreamProcessor iisp) {
if (iisp.isGIF()) {
return new GifReader(iisp.getInputStream());
}
else if (iisp.isJPEG()) {
return new JpegReader(iisp.getInputStream());
}
else {
throw new IllegalArgumentException("Unknown image type.");
}
}
}
PHP
[szerkesztés]class Factory
{
public static function build($type)
{
$class = 'Format' . $type;
if (!class_exists($class)) {
throw new Exception('Missing format class.');
}
return new $class;
}
}
interface FormatInterface {}
class FormatString implements FormatInterface {}
class FormatNumber implements FormatInterface {}
try {
$string = Factory::build('String');
} catch (Exception $e) {
echo $e->getMessage();
}
try {
$number = Factory::build('Number');
} catch (Exception $e) {
echo $e->getMessage();
}
Korlátjai
[szerkesztés]A gyártó metódussal szemben három akadály merülhet fel. Az első a kód refaktorálásához, a másik kettő pedig az öröklődéshez kapcsolódik.
- Az első lehetséges probléma lehet a kliens osztályok törése. Ha például a Complex egy szokásos módon működő osztály, akkor a kliensek tele lehetnek a következővel:
Complex c = new Complex(-1, 0);
- Miután azonban beüzemeljük a gyárat, megváltoztatjuk az osztályt. A konstruktor priváttá tétele miatt az addig forduló kód akár számos hibát is jelezhet. Ha a konstruktor nem privát, akkor ugyan fordul a kód, de megkérdőjeleződhet a tervminta értelme.
- Egy másik korlát, hogy a termékből nem lehet származtatni, ha a konstruktor privát. Ekkor ugyanis az alosztályok nem tudják hívni. Ezen lehet segíteni protected konstruktorral.
- A harmadik korlát ebben az esetben is fennáll, ugyanis az alosztályoknak újra kell implementálniuk az öröklött gyártó metódusokat egy anomália miatt. Különben a metódus visszatérési értéke egy ősosztály típusú objektum lesz, vagyis a visszaadott objektum elfelejti, hogy ő valójában egy utód osztály példánya. Ezt reflexióval ki lehet védeni.
Mindhárom lehetséges probléma megoldható azzal, hogy a nyelv a gyárakat elsőosztályú tagokként kezeli.[9]
Fordítás
[szerkesztés]Ez a szócikk részben vagy egészben a Factory (object-oriented programming) című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.
Jegyzetek
[szerkesztés]- ↑ Gamma, Erich. Design Patterns. Addison-Wesley, 18–19. o. (1994). ISBN 9780321700698
- ↑ "Factory Pattern", OODesign.com
- ↑ Factory Pattern, WikiWikiWeb
- ↑ a b Chapter 4. The Factory Pattern: Baking with OO Goodness Archiválva 2017. március 11-i dátummal a Wayback Machine-ben: The Simple Factory defined Archiválva 2014. február 19-i dátummal a Wayback Machine-ben
- ↑ "30.8 Classes Are Objects: Generic Object Factories", Learning Python, by Mark Lutz, 4th edition, O'Reilly Media, Inc., ISBN 978-0-596-15806-4
- ↑ Factory Method, WikiWikiWeb
- ↑ defaultdict objects
- ↑ a b Feathers, Michael (October 2004), Working Effectively with Legacy Code, Upper Saddle River, NJ: Prentice Hall Professional Technical Reference, ISBN 978-0-13-117705-5
- ↑ Agerbo, Ellen (1998). „How to preserve the benefits of design patterns”. Conference on Object Oriented Programming Systems Languages and Applications, Vancouver, British Columbia, Canada, 134–143. o, Kiadó: ACM.