A következő címkéjű bejegyzések mutatása: Tervezési minták. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: Tervezési minták. Összes bejegyzés megjelenítése

2011. február 5., szombat

Prototípus (Prototype)

Megint a Factory-ból kell kiindulni ám ezúttal prototípusokat lehet gyártani. A cél, hogy előre inicializált típusokat lehessen menet közben készíteni úgy, hogy annak részletkérdéseivel már ne kelljen foglalkozni. A típusokat klónozással kell előállítani, szükség esetén mély másolással. (Amikor a referencia tagokat újra kell kreálni, hogy két objektum ne használja őket közösen). Sokszor előfordul az, hogy bizonyos osztályt prototípusként használunk fel. Ilyenkor általában az "új" típust származtatjuk a régiből és semmit sem változtatunk rajta, csak a konstruktorban inicializáljuk:
class MyObject
{
int size;

public MyObject(int size)
{
this.size = size;
}
}

class MyOtherObject : MyObject
{
public MyOtherObject()
: base(22)
{ }
}
Ezzel tulajdonképpen semmit sem csináltunk, nem fejlesztettük tovább az objektumot, csak létrehoztunk egy prototípus osztályt. Ez főleg akkor válik nehézkessé, amikor ebből kellene 30-40 féle. Többek között az ilyen esetekre jó a Prototípus használata.

Alkalmazhatóság

Akkor használjuk a Prototype mintát, ha rendszernek függetlennek kell lennie a termék létrehozási, összeállítási és megjelenítési módjától; és
  • ha a példányosítandó osztályok futásidőben határozódnak meg, például dinamikus betöltés esetén, vagy
  • el szeretnénk kerülni a termékek osztályhierarchiájával párhuzamos osztályhierarchiájú gyárak építését, vagy
  • amikor az osztályok példányainak csak néhány különböző állapotkombinációja lehet. Kényelmesebb a megfelelő számú prototípust elhelyezni és klónozni, mint példányosítani a megfelelő állapottal.
Struktúra


Résztvevők
  1. Protype: deklarál egy interface-t önmaga klónozásához.
  2. ConcretePrototype: implementál egy műveletet önmaga klónozásához.
  3. Client: új objektumot hoz létre, megkérve a prototípust önmaga klónozására.

Együttműködési információk

A kliens megkéri a a prototípust, hogy klónozza önmagát.

Következmények

A Prototype minta hasonló következményekkel jár mit az Abstract Factory és Builder: elrejti a termék osztályokat a kliens elől, csökkentve a ezzel azon nevek számát, amiket a kliens ismer. Emellett, ezek a minták lehetővé teszik, hogy a kliens módosítás nélkül dolgozzon az alkalmazás-specifikus osztályokkal.

A Prototype minta további előnyei:
  1. Termékek hozzáadása és eltávolítása futásidőben. Új konkrét termékosztály beépítése a rendszerbe, egyszerűen a prototípuspéldány bejegyeztetése a klienssel. Ez valamelyest hatékonyabb, mint a többi létrehozási minta, mert a kliens futásidőben építhet be és távolíthat el prototípusokat.
  2. Új objektumok meghatározása változó értékek mellett. A nagymértékben dinamikus rendszerek lehetővé teszik a új viselkedés definiálását az objektum összetételén keresztül – például az objektum változóinak értéket adva – és nem új osztályokat meghatározva. Ténylegesen a meglévő osztályok példányosításával, és ezek a prototípus kliens-objektumaiként bejegyeztetve adjuk meg. A kliens az új viselkedésmódot a felelősség prototípusra való átruházásával adhatja meg. Ez a tervezési mód lehetőséget ad új „osztályok” létrehozására programozás nélkül. Valójában a prototípus klónozása hasonló egy osztály példányosításához. A Prototype minta nagymértékben csökkenti a rendszer számára szükséges osztályok számát.
  3. Új objektum megadása változó struktúra mellett. Sok alkalmazás részekből és alrészekből építi fel az objektumokat. Kényelmi szempontok miatt, az ilyen alkalmazások lehetővé teszik az összetett, felhasználó által definiált struktúrák példányosítását.
  4. Kevesebb alosztály. A Prototype minta lehetőséget ad a prototípus klónozására, ahelyett, hogy egy gyártó metódust kérne meg új objektum létrehozására. Ezért nincs szükség Creator osztály hierarchiára.
  5. Alkalmazás dinamikus beállítása osztályokkal. Egyes futásidejű környezetek lehetővé teszik az osztályok dinamikus betöltését alkalmazásokba.
A Prototype minta legfőbb felelőssége, hogy mindegyik Prototype alosztálynak implementálnia kell a Clone műveletet, ami bonyolult lehet. Például nehéz, ha az adott osztály már létezik. Clone implementálása akkor is nehéz lehet, ha olyan objektumokat tartalmaznak, amelyek nem támogatják a másolást vagy körkörös hivatkozást tartalmaznak.

Implementációs rész

A Prototype implementációjánál tartsuk szem előtt a következőket:
  1. Prototype menedzser használata. Amikor egy rendszerben a prototípusok száma nem rögzített (tehát dinamikusan létrehozhatóak és megsemmisíthetőek), nyilván kell tartani a rendelkezésre álló prototípusokat. A kliensek maguk nem kezelik a prototípusokat, csak mentik és kiolvassák a nyilvántartóból. Egy kliens elkéri a prototípust a nyilvántartóból, mielőtt klónozná azt. Ezt nyilvántartót hívjuk Prototype menedzsernek. A prototype menedzser egy asszociatív tároló, ami egy adott kulcshoz tartozó prototípust ad vissza. Vannak műveletei a kulcshoz tartozó prototípus bejegyzésére és a bejegyzés érvénytelenítésére. A kliensek megváltoztathatják a nyilvántartót és tallózhatják azt futásidőben. Ez lehetővé teszi a klienseknek, hogy a rendszer leltárát kódírás nélkül felvegyék.
  2. A Clone művelet implementálása. A Prototype minta legnehezebb része a Clone művelet helyes implementálása. Ez kiváltképp bonyolult amikor az objektumstruktúra körkörös hivatkozásokat tartalmaz. Ha a rendszerben lévő objektumok rendelkeznek Save és Load műveletekkel, akkor ezek használhatóak a Clone alapértelmezett implementálásához, egyszerűen csak menteni kell az objektumot majd azonnal visszatölteni. A Save művelet menti az objektumot egy puffermemóriába, a Load egy másolatot hoz létre, rekonstruálva a objektumot a pufferből.
  3. Klónok inicializálása. Amíg néhány kliens tökéletesen elégedett a klónnal, ahogy van, mások az általuk választott értékkel akarják inicializálni néhány vagy az összes belső állapotát. Általában ezeket az értékeket nem lehet átadni a Clone műveletben, mert számuk prototípus-osztályonként változik. Néhány prototípusnak szüksége lehet több inicializáló paraméterre; másoknak egyre sincs szükségük. A Clone műveletben történő paraméterátadás eleve kizárná egy egységes klónozó interfész használatát. Lehetséges, hogy a prototípus osztályok már definiálnak műveleteket az állapot kulcsrészeinek (újra) beállítására. Ha ez van, akkor a kliensek ezeket a műveleteket használhatják azonnal klónozás után. Ha nincs, akkor lehet nekünk kell az Initialize műveletet bevezetni, amely argumentumként kapja meg az inicializáló paramétereket és ennek megfelelően állítja be a klón belső állapotát.
Minta kód
// Prototype pattern
using System;
using System.Collections.Generic;

namespace Prototype
{
///
/// MainApp startup class for Real-World
/// Prototype Design Pattern.
///

class MainApp
{
///
/// Entry point into console application.
///

static void Main()
{
ColorManager colormanager = new ColorManager();

// Initialize with standard colors
colormanager["red"] = new Color(255, 0, 0);
colormanager["green"] = new Color(0, 255, 0);
colormanager["blue"] = new Color(0, 0, 255);

// User adds personalized colors
colormanager["angry"] = new Color(255, 54, 0);
colormanager["peace"] = new Color(128, 211, 128);
colormanager["flame"] = new Color(211, 34, 20);

// User clones selected colors
Color color1 = colormanager["red"].Clone() as Color;
Color color2 = colormanager["peace"].Clone() as Color;
Color color3 = colormanager["flame"].Clone() as Color;

// Wait for user
Console.ReadKey();
}
}

///
/// The 'Prototype' abstract class
///

abstract class ColorPrototype
{
public abstract ColorPrototype Clone();
}

///
/// The 'ConcretePrototype' class
///

class Color : ColorPrototype
{
private int _red;
private int _green;
private int _blue;

// Constructor
public Color(int red, int green, int blue)
{
this._red = red;
this._green = green;
this._blue = blue;
}

// Create a shallow copy
public override ColorPrototype Clone()
{
Console.WriteLine(
"Cloning color RGB: {0,3},{1,3},{2,3}", _red, _green, _blue);

return this.MemberwiseClone() as ColorPrototype;
}
}

///
/// Prototype manager
///

class ColorManager
{
private Dictionary _colors =
new Dictionary();

// Indexer
public ColorPrototype this[string key]
{
get { return _colors[key]; }
set { _colors.Add(key, value); }
}
}
}
Ismert felhasználás
  • Eszköztáraknál
  • Alapvető plug-in mechanizmusoknál
  • EJB/COM Szervereknél
Kapcsolódó minták

A Prototype és az Abstract Factory bizonyos tekintetben konkurens minták. Azonban használhatóak együtt is. Az Abstract Factory tárolhat egy olyan prototípus halmazt, amelyet klónozni akarunk, és a termék objektumokat adjuk vissza.

Felhasznált anyagok

2011. január 27., csütörtök

Építő (Builder)

Egy újabb olyan minta, ami a Factory-ból következik, ám attól picit eltérő a felhasználási módja. Az Építő (Builder) célja az, hogy a megfelelő elemeket menet közben építse fel különböző ábrázolási módszerekkel. Egy összetett objektum szerkezetének függetlenítése az ábrázolásától, így ugyanazzal az építési folyamattal különböző ábrázolásokat hozhatunk létre.

Motiváció

Egy RTF (Rich Text Format) dokumentumcsere formátum olvasónak képesnek kell lennie az RTF számos egyéb szövegformátumba való konvertálásra. Az olvasó átalakíthatja sima ASCII szöveggé vagy egy interaktívan szerkeszthető szövegeszközzé. A probléma mégis az, hogy a lehetséges konverziók száma végtelen. Így egyszerűen, az olvasó változtatása nélkül kell az új átalakítást megadni.
Egy megoldás, ha az RTFReader osztályt egy más szövegmegjelenítésre konvertáló TextConverter objektummal állítjuk be. Amikor az RTFReader elemzi az RTF dokumentumot, a TextConvertert használja az átalakítás végrehajtására. Amikor az RTFReader felismer egy RTF tokent (sima szöveget vagy RTF vezérlő szót), kiad egy kérelmet a TextConverter-nek, hogy alakítsa át az adott tokent. A TextConverter objektumok felelnek az adatkonverzióért és a token egy bizonyos megjelenítési formátumáért.
A TextConverter alosztályai specializálódhatnak különböző konverziókra és formátumokra. Például a az ASCIIConverter elutasíthat minden konverziós felkérést, kivéve a sima szövegeseket. A TeXConverter megvalósíthatja azokat a műveleteket, amelyek a TeX formátumban való, a szöveg minden stílusinformációját tartalmazó megjelenítésre vonatkozó kérelmekkel kapcsolatosak. A TextWidgetConverter olyan felhasználói felületet hozhat létre, amivel a felhasználó láthatja és szerkesztheti a szöveget.
Mindegyik konvertáló osztály-típus tartalmazza az összetett objektumok létrehozásához és összeállításához szükséges mechanizmusokat, és ezeket egy absztrakt interfész mögé rejti. Az átalakító független az olvasótól, amely az RTF dokumentum elemzéséért felelős.
A Builder minta magába foglalja mindezen kapcsolatokat. A mintában minden konverter osztályt builder-nek, és minden olvasót director-nak hívnak. A példát alkalmazva, a Builder minta elkülöníti a szöveges formátumot értelmező algoritmust (ez az, ami az RTF dokumentumokat elemezi) az átalakított formátum létrehozásától és megjelenítésétől. Lehetővé teszi számunkra, hogy az RTFReader elemző algoritmusát újra felhasználjuk különböző szövegmegjelenítés létrehozására RTF dokumentumokból – csak más TextConverter alosztályt kell beállítani az RTFReader számára.

Alkalmazhatóság

Akkor alkalmazzuk a Builder mintát, ha
  • Az összetett objektum létrehozási algoritmusának függetlennek kell lennie az objektumot felépítő részektől és azok összeállítási módjától.
  • A létrehozási folyamatnak lehetővé kell tennie a létrehozott objektum különböző megjelenítését.
Struktúra


Résztvevők

  • Builder (TextConverter) meghatározza a Product objektum létrehozására szolgáló absztrakt interfészt
  • ConcreteBuilder (ASCIIConverter, TeXConverter, TextWidgetConverter) a Builder interfészt megvalósításánál felépíti és összeállítja a termék részeit. Definiálja és nyomon követi az általa készített megjelenítési módokat. Interfészt biztosít a termék beolvasásához (pl.: GetASCIIText, GetTextWidget)
  • Director (RTFReader) a Builder interfész használatával létrehoz egy objektumot.
  • Product (ASCIIText, TeXText, TextWidget) a felépítendő összetett objektumot képviseli. ConcreteBuilder felépíti a termék belső ábrázolását és definiálja az összeállítási folyamatokat. Tartalmazza az alkotó elemeket defináló osztályokat, beleértve a részek összeállítására szolgáló interfészeket.

Együttműködési információk

  • A kliens létrehozza a Director objektumot és beállítja a kívánt Builder objektummal.
  • A Director értesíti a builder-t, ha egy objektum részét fel kell építeni.
  • A Builder kezeli a dircetor-tól érkező kérvényeket és a részeket ad a product-hoz.
  • A kliens elkéri a product-ot a builder-től.


A fenti diagram a Builder és a Director együttműködését szemlélteti a klienssel.

Következmények

A Builder minta föbb következményei:
  1. Lehetővé teszi a termék belső ábrázolásának a megváltoztatását. A Builder objektum a director számára egy absztrakt interfészt biztosít a termék felépítéséhez. Az interfész lehetővé teszi a builder-nek, hogy elrejtse a termék ábrázolását és belső felépítését. Elrejti a termék összeállításának módját is. Mivel a termék felépítése egy absztrakt interfészen keresztül történik, elég a termék belső ábrázolását megváltoztatni egy új builder típus definiálásához.
  2. Elszigeteli a felépítési és az összeállítási kódot. A Builder minta a modularitást az összetett objektum felépítésének és megjelenítésének az egységbe zárásával javítja. A kliensnek nem kell tudnia semmit a termék belső szerkezetét definiáló osztályokról; ilyen osztályok nem jelennek meg a Builder interfészében.
  3. Az építési folyamat finomabb vezérlését teszi lehetővé. Azoktól a létrehozási mintáktól eltérően, amelyek egy lépésben építik fel a terméket, a Builder minta lépésről lépésre építi fel a terméket a director felügyelete mellett. Csak akkor veszi át a director a terméket a builder-től, ha az elkészült vele. Emiatt a Builder interfész jobban tükrözi a termék felépítésének folyamatát, mint a többi létrehozási minta. Finomabb vezérlést tesz lehetővé az építési folyamatokon és ennek következtében a végső termék belső szerkezetén.

Implementációs rész

Tipikusan van egy absztrakt Builder osztály, ami minden egyes komponens működését definiálja, amelyek létrehozására a director megkéri. Alapértelmezésben a műveletek nem tesznek semmit. A ConcreteBuilder osztály felülbírálja azon komponensek műveleteit, amelyeknek létrehozásában érdekelt.
  1. Összeállítási és építési interfész. A Builder-ek termékeiket lépésről lépésre készítik el. Emiatt a Builder osztály interfésznek elég általánosnak kell lennie, hogy lehetővé tegye mindenfajta konkrét builder-nek a termékek létrehozását. A fő tervezési probléma az építési- és összeállítási folyamatok modellje. Azok a modellek megfelelőek, amelyek a létrehozási kérelmek eredményeit egyszerűen hozzácsatolják a termékhez.
  2. Miért nem absztrakt osztályokat használunk a termékeknél? Általában a konkrét builder-ek termékei annyira eltérnek a reprezentációjukban, hogy csak keveset nyerünk azzal, ha egy általános szülőosztályt adunk a különböző termékekhez. Mivel rendszerint a kliens a megfelelő konkrét builder-rel állítja be a director-t, ezért tudja, hogy a Builder melyik konkrét alosztálya van használatban, és ennek megfelelően kezeli a termékeket.
  3. Üres metódusok alapértelmezésként a Builderben. Üres metódusokat definiálunk, hogy a kliensek csak azokat a műveleteket bírálják felül, amelyekben érdekeltek.

Minta kód

A kódban szereplő osztályok:
  • Builder: VehicleBuilder
  • ConcreteBuilder: MotorCycleBuilder, CarBuilder, ScooterBuilder
  • Director: Shop
  • Product: Vehicle
Megvalósítás:
// Builder pattern -- Real World example

using System;
using System.Collections.Generic;

namespace Builder
{
///
/// Builder Design Pattern.
///

public class MainApp
{
///
/// Entry point into console application.
///

public static void Main()
{
VehicleBuilder builder;

// Create shop with vehicle builders
Shop shop = new Shop();

// Construct and display vehicles
builder = new ScooterBuilder();
shop.Construct(builder);
builder.Vehicle.Show();

builder = new CarBuilder();
shop.Construct(builder);
builder.Vehicle.Show();

builder = new MotorCycleBuilder();
shop.Construct(builder);
builder.Vehicle.Show();

// Wait for user
Console.ReadKey();
}
}

///
/// The 'Director' class
///

class Shop
{
// Builder uses a complex series of steps
public void Construct(VehicleBuilder vehicleBuilder)
{
vehicleBuilder.BuildFrame();
vehicleBuilder.BuildEngine();
vehicleBuilder.BuildWheels();
vehicleBuilder.BuildDoors();
}
}

///
/// The 'Builder' abstract class
///

abstract class VehicleBuilder
{
protected Vehicle vehicle;

// Gets vehicle instance
public Vehicle Vehicle
{
get { return vehicle; }
}

// Abstract build methods
public abstract void BuildFrame();
public abstract void BuildEngine();
public abstract void BuildWheels();
public abstract void BuildDoors();
}

///
/// The 'ConcreteBuilder1' class
///

class MotorCycleBuilder : VehicleBuilder
{
public MotorCycleBuilder()
{
vehicle = new Vehicle("MotorCycle");
}

public override void BuildFrame()
{
vehicle["frame"] = "MotorCycle Frame";
}

public override void BuildEngine()
{
vehicle["engine"] = "500 cc";
}

public override void BuildWheels()
{
vehicle["wheels"] = "2";
}

public override void BuildDoors()
{
vehicle["doors"] = "0";
}
}

///
/// The 'ConcreteBuilder2' class
///

class CarBuilder : VehicleBuilder
{
public CarBuilder()
{
vehicle = new Vehicle("Car");
}

public override void BuildFrame()
{
vehicle["frame"] = "Car Frame";
}

public override void BuildEngine()
{
vehicle["engine"] = "2500 cc";
}

public override void BuildWheels()
{
vehicle["wheels"] = "4";
}

public override void BuildDoors()
{
vehicle["doors"] = "4";
}
}

///
/// The 'ConcreteBuilder3' class
///

class ScooterBuilder : VehicleBuilder
{
public ScooterBuilder()
{
vehicle = new Vehicle("Scooter");
}

public override void BuildFrame()
{
vehicle["frame"] = "Scooter Frame";
}

public override void BuildEngine()
{
vehicle["engine"] = "50 cc";
}

public override void BuildWheels()
{
vehicle["wheels"] = "2";
}

public override void BuildDoors()
{
vehicle["doors"] = "0";
}
}

///
/// The 'Product' class
///

class Vehicle
{
private string _vehicleType;
private Dictionary _parts = new Dictionary();

// Constructor
public Vehicle(string vehicleType)
{
this._vehicleType = vehicleType;
}

// Indexer
public string this[string key]
{
get { return _parts[key]; }
set { _parts[key] = value; }
}

public void Show()
{
Console.WriteLine("\n---------------------------");
Console.WriteLine("Vehicle Type: {0}", _vehicleType);
Console.WriteLine(" Frame : {0}", _parts["frame"]);
Console.WriteLine(" Engine : {0}", _parts["engine"]);
Console.WriteLine(" #Wheels: {0}", _parts["wheels"]);
Console.WriteLine(" #Doors : {0}", _parts["doors"]);
}
}
}
Eredmény:


Ismert felhasználás

Különféle formátumok konvertálásához, fordítókban elemzőknél, normalizált adatbázisok „építéséhez”.

Kapcsolódó minták

Az Abstract Factory abban hasonlít a Builder-re, hogy az is összetett objektumokat építhet fel. Az elsődleges különbség, hogy a Builder minta az összetett objektumok lépésről lépésre való létrehozását helyezi középpontba. Az Abstract Factory esetében a termékobjektum-családok a hangsúlyosabbak (akár egyszerű, akár összetett). A Builder minta utolsó lépésben visszaadja a terméket, azonban az Abstract Factory minta, ami azt illeti azonnal.

Felhasznált anyagok

2011. január 14., péntek

Gyártó függvény(Factory Method)

Az Elvont Gyár mintához szorosan kapcsolódik a Gyártó Függvény minta, amivel magát az Elvont Gyárat lehet létrehozni, de implementálhatóak Protoype használatával is. Tehát az előzőekben bemutatottakban felül itt egy rövid kód erről a mintáról:
class Creator where T : new()
{
T Create()
{
return Activator.CreateInstance();
}
}
Ennek lényege a megfelelő objektumok létrehozás anélkül, hogy annak részleteivel foglalkozni kellene egy magasabb absztrakciós szinten.

DXCoder

Elvont Gyár (Abstract Factory)

A Gyár (Factory) célja, hogy objektumokat gyártson úgy, hogy a gyártás részleteivel a hívónak nem kell foglalkoznia. Az Elvont gyár (Abstract Factory) pedig egy felületet biztosít a gyárak számára a konkrét osztályok megadása nélkül, biztosítva így a továbbfejlesztési lehetőséget.

Alkalmazhatóság

Akkor használjuk az Abstract Factory mintát, ha
  • A rendszernek függetlennek kell lenni attól, hogyan hozza létre, állítja össze és jeleníti meg a termékeit.
  • A rendszernek be kell állítania egyet a termékcsalád közül.
  • Kapcsolódó termékobjektumok egy családját együttes használatra tervezték, és ezt a megszorítást ki kell kényszeríteni.
  • Gondoskodni kell a termékek osztálykönyvtáráról, és csak az interfészüket fedhetjük fel, de a megvalósításukat nem.
Struktúra


Résztvevők
  • AbstractFactory: az absztrakt termékobjektumokat létrehozó interfészt deklarálja.
  • ConcreteFactory: a konkrét termékobjektumok létrehozására szolgáló műveleteket implementálja.
  • AbstractProduct: a termékobjektumok egy típusának interfészt deklarálja.
  • ConcreteProduct: definiálja a létrehozandó termékobjektumot a megfelelő konkrét factory (gyár) mellett, implementálja az AbstractProduct interfészt.
  • Client: csak az AbstractFactory és az AbstractProduct osztályok által deklarált interfészeket használja.
Együttműködési információk

Normál esetben a ConcreteFactory osztály egyetlen példányát hozza létre futásidőben. Ez a konkrét factory egyéni implementációval rendelkező termékobjektumokat készít. Különböző termékobjektumok létrehozásához a kliensnek különböző konkrét factory-kat kell használnia. Az AbstractFactory a termékobjektumok létrehozását a ConcreteFactory alosztályra bízza.

Következmények

Az Abstract Factory mintának a következő előnyei és hátrányai vannak:
  1. Elszigeteli a konkrét osztályokat. Az Abstract Factory minta segít az alkalmazások által létrehozott objektumosztályok irányítását. Mivel a factory egységbe zárja a termékobjektum létrehozásának felelősségét és folyamatát, így elszigeteli a klienst az implementációs osztályoktól. A kliensek a példányokat saját absztrakt interfészükön át kezelik. A termékosztályok nevei elszigeteltek a konkrét factory implementációjában; azok nem jelennek meg a kliens kódjában.
  2. Megkönnyíti a termékcsaládok cseréjét. A konkrét factory osztálya egy alkalmazásban egyszer jelenik meg – ahol példányosítottuk. Ez megkönnyíti az alkalmazás által használt konkrét factory megváltoztatását. A különböző termék használatához csak a konkrét factory-t kell megváltoztatni. Mivel az absztrakt factory komplett termékcsaládokat készít, egyszere megváltozik az egész termékcsalád.
  3. Támogatja a termékek közti konzisztenciát. Amikor egy családon belül a termékobjektumokat együttműködésre tervezték, akkor fontos, hogy az alkalmazás egy időben csak egy családhoz tartozó objektumokat használjon. Az AbstractFactory ezt könnyen érvényesíti.
  4. Az új termékfajták támogatása nehézkes. Az absztrakt factory kibővítése újfajta termékek létrehozására nem könnyű. Azért mert az AbstractFactory interfész rögzíti a létrehozható termékek halmazát. Az újfajta termékek támogatásához ki kell bővíteni a factory interfészt, amihez meg kell változtatni az AbstractFactory osztályt és annak minden alosztályát.
Implementációs rész
  1. Factory-k mint Singleton-ok. Egy alkalmazásnak tipikusan csak egyetlen egy ConcreteFactroy példányra van szüksége termékcsaládonként. Így azokat általában legjobb Singleton segítségével implementálni.
  2. A termékek létrehozása. Az AbstractFactory csak a termékek létrehozására szolgálóinterfészt deklarálja. A ConcreteProduct alosztály végzi valójában a létrehozást. A legáltalánosabb megoldás, ha minden termékhez deklarálunk egy factory metódust. Amíg az implementáció egyszerű, addig minden egyes termékcsaládhoz szükség van egy konkrét factory alosztályra, még akkor is ha a termékcsaládok csak alig térnek el.
  3. Ha sok termékcsaládra van szükség, a konkrét factory implementálható a Prototype minta felhasználásával. A konkrét factory a családba tartozó minden termékhez külön tartozó prototípus példány segítségével inicializálható, és a prototípus klónozásával hozható létre új termék. A prototípus alapú megközelítés kiküszöböli, hogy minden egyes új termékcsaládnak új konkrét factory-ra lenne szüksége.
  4. ővíthető factory-k definiálása. Az AbstractFactory általában különböző műveleteket definiál az általa létrehozható minden termékfajtához. A termékek típusa a műveletek szignatúrájába van kódolva. Egy új terméktípus hozzáadása estén szükség van az AbstractFactory interfész és az összes tőle függő osztály megváltoztatására.
  5. Egy rugalmasabb, de kevésbé biztonságos kivitelezés, ha az objektumokat létrehozó műveleteket paraméterezzük. Ez a paraméter határozza meg a létrehozandó objektum típusát. Ez lehet egy osztályazonosító, egy egész szám, egy sztring, vagy bármi ami azonosíthatja a termék típusát. Ezzel a megközelítéssel, az AbstractFactory-nak csak egy egyszerű „Make” műveletre van szüksége, ami a létrehozandó objektum típusát jelző paraméterrel rendelkezik. Ezt a technikát használjuk a fent megbeszélt Prototype- és osztály-alapú absztrakt factory-kban.
Példakód

Az elvont gyár minta segítségével most létrehozunk egy pályaszerkesztőt DXCoder példája alapján. Alaposztályok a megjelenítéshez:
class Sprite 
{
public Sprite(int size, string textureName) { }
}

static class SpriteFactory
{
public static Sprite CreateBall()
{
return new Sprite(20, "ball.png");
}

public static Sprite CreateWall()
{
return new Sprite(100, "wall.jpg");
}
}

class Game
{
Sprite[,] map = new Sprite[10, 10];

public void CreateLevel()
{
map[0, 0] = SpriteFactory.CreateWall();
map[1, 0] = SpriteFactory.CreateWall();
map[1, 1] = SpriteFactory.CreateBall();
}
}
Mint látható a Game osztályban már nem kell foglalkozni az egyes elemek részleteivel. A CreateLevel helyére el lehet képzelni egy olyan ciklus, ami egy file-ból tölti be a pályát. A másik trükk, hogy felhasználtuk az Egyke mintát is egyúttal.
Mi van akkor, ha a pályaszerkesztővel több pályát is szeretnénk készíteni egységes jelöléssel? A wall az mindig W, a ball az pedig mindig B. Többféle világ is van, ahol a ball az lehet forest, vagy robo is. Nem szeretnénk átírni se az editort, se a pálya betöltőt, úgyhogy a gyárak körül kell változtatni. A következő példa már az Elvont gyárat mutatja be:
  • AbstractFactory: IAbstractFactory
  • ConcreteFactory: RobotSpriteFactory, ForestSpriteFactory
  • Client: Game (CreateLevel)
Forrás:
using System;

namespace ConsoleApp
{
static class SingletonManager where T : new()
{
static T instance = Activator.CreateInstance();

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

class Sprite
{
public Sprite(int size, string textureName) { }
}

interface IAbstractFactory
{
Sprite CreateBall();
Sprite CreateWall();
}

class ForestSpriteFactory : IAbstractFactory
{
public Sprite CreateBall()
{
return new Sprite(20, "ball.png");
}

public Sprite CreateWall()
{
return new Sprite(100, "wall.jpg");
}
}

class RobotSpriteFactory : IAbstractFactory
{
public Sprite CreateBall()
{
return new Sprite(26, "roboBall.png");
}

public Sprite CreateWall()
{
return new Sprite(120, "roboWall.jpg");
}
}

class Game
{
Sprite[,] map = new Sprite[10, 10];

public void CreateLevel(IAbstractFactory factory)
{
map[0, 0] = factory.CreateWall();
map[1, 0] = factory.CreateWall();
map[1, 1] = factory.CreateBall();
}
}

static class Program
{
static void Main()
{
var forestGame = new Game();
forestGame.CreateLevel(SingletonManager.Instance);

var robotGame = new Game();
robotGame.CreateLevel(SingletonManager.Instance);
}
}
}
Látható, hogy a CreateLevel lényegében semmit nem változott. Ha több 100 elemet kell betölteni, akkor se történik változás, mindig a világnak megfelelő objektumok fognak betöltődni. Itt még pluszban az Egyke minta egyik C# változatát is felhasználtuk, hogy ne kelljen állandóan fölösleges objektumokat létrehozni.

Kapcsolódó minták

Az Abstact Factory osztályokat gyakran factory metódusokkal (gyártó függvényekkel) valósítják meg, de implementálhatóak Protoype használatával is.

Felhasznált anyagok

2010. július 2., péntek

Az Egyke (Singleton) tervezési minta

Egy nagyon jó kis cikket találtam a tervezési mintákkal kapcsolatban Nyisztor Károly tollából, amit C#-ban meg is valósítottam. A bejegyzés és a forrás itt olvasható:

Az Egyke (Singleton) tervezési minta

Az egyik legalapvetőbb és valószínűleg leggyakrabban használt minta az egyke minta. A neve is mutatja legfőbb jellemvonását: az adott osztályból egyetlen példány jön létre az alkalmazás futása során. Erre akkor van szükség, ha az adott objektumból felesleges, sőt zavaró lenne több példányt létrehozni; jó példa erre a hibanaplózó vagy teszem azt az XML-feldolgozó osztály.

A cél tehát egy olyan módszer bevezetése, amely garantálja, hogy mindössze egy objektumot lehet létrehozni az osztályból. Az első, szembeötlő nehézség az, hogy az osztály konstruktorát bárki meg tudja hívni - ezt kell megakadályozzuk. A megoldás a konstruktor elrejtése - tegyük priváttá, és ezzel megakadályozzuk a közvetlen példányosítást. Azonban valahogy biztosítanunk kell az osztály példányosítását, hiszen különben mindössze egy használhatatlan osztályt kaptunk.
Ezt a célt szolgálja az Instance() tagfüggvény, amely az egyetlen osztálypéldány létrehozásáért felelős. Az osztály egyetlen példánya az első Instance() hívás során jön létre, az ezt követő hívások pedig ezt a példányt adják vissza. Egy Singleton osztály (avagy Egyke) létrehozásának menete:
  • rejtsük el a ctor-okat, a másoló ctor-t és az "operator =" -t (ezáltal lehetetlenné tesszük a közvetlen példányosítást)
  • deklaráljuk az adattaghoz történő hozzáférését biztosító, static public metódust (általában Instance() névre hallgat)
  • deklaráljuk az osztály példánymutatóját statikus privát adattagként (ez az adott osztályra egyetlen példányának címe)
  • gondoskodjunk arról, hogy az első hozzáféréskor létrehozzuk az egyetlen osztálypéldányt, minden azt követő híváskor pedig a már létező objektumpoinetrt adjuk vissza
Kérdés

Miért statikus az Instance() metódus? Hamarosan jön a kézenfekvő magyarázat, de előbb vizsgáljuk meg a kódot:
   using System;

public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();

private Singleton() { }

public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
Az előző kérdésre - azaz, hogy miért static az Instance() tagfüggvény? - a fenti kódrészletben található a válasz: az osztálynak eredetileg nincs érvényes példánya, és érvényes példánymutató nélkül pedig nem tudunk meghívni nem-statikus tagfüggvényeket. A statikus függvények kivételt képeznek, hiszen az osztálypéldány megléte nem előfeltétel. (A statikus függvények korlátja, hogy kizárólag statikus adattagokon dolgozhatnak.)
Az egyik felmerülő gond a fenti Singleton megvalósítással a konkurens hozzáférés. Ha több szálból (nagyjából) egyszerre hívják az Instance() függvényt, előfordulhat, hogy az if(Instance == null) ellenőrzést egyszerre hajtják végre, majd több példány is létrejön, hiszen a feltétel egyik szálnál sem teljesült. Erre természetesen vannak megfelelő megoldások és nyelvi elemek, de a többszálúság rejtelmeibe egyelőre nem szeretnék jobban belemenni...

Eddig tartott Nyisztor Károly remek bejegyzése. Természetesen a többszálúsággal kapcsolatos problémát a fenti kód már orvosolja. Ezzel kapcsolatban további információkat az alábbi linken találhatunk: msdn
További jó kódolást mindenkinek!

2010. július 1., csütörtök

Tervezési minták

"Objektumközpontú programot tervezni nehéz, újrahasznosíthatót még nehezebb. Meg kell találni a megfelelő osztályokat és az osztályok közötti kapcsolatokat. A tervnek igazodnia kell a megoldandó problémához, de eléggé általánosnak kell lennie ahhoz, hogy később könnyen módosítható legyen. A tapasztalt tervezők jó terveket készítenek, a kezdők viszont elvesznek a lehetőségek dzsungelében. Éppen ezért jó lenne, ha a tapasztalt tervezők-fejlesztők valamilyen módon át tudnák adni a kezdőknek a jól működő megoldásokat. Ha egy problémára ismerünk egy megoldási mintát, ami már bevált, akkor azt legközelebb, hasonló problémánál is alkalmazhatjuk. Ezt felismerve, a GoF (Gang of Four – „Négyek bandája”: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) összegyűjtötte a tapasztalt objektum-orientált tervezők mintáit (23 darabot) és egységes leírást, magyarázatot készítettek hozzájuk. Így egy katalógust kaptunk, amelyben programtervezési minták találhatók. Az ő definíciójuk szerint a minta: „Egymással együttműködő objektumok és osztályok leírása, amely testreszabott formában valamilyen általános tervezési problémát old meg egy bizonyos összefüggésben.” A tervezési minta azonosítja a részt vevő osztályokat és objektumokat, szerepüket és kapcsolataikat."
Szabolcsi Judit: Szoftvertechnológia, 2010

A fenti bevezető szépen összefoglalta mindazt, amiről én is írni akartam. A magyar nyelvű C#-os könyvek alig-alig foglalkoznak a tervezési mintákkal, csak nyelvi elemek és a .NET környezet bemutatására szorítkoznak. Pedig már egy kezdő programozó is viszonylag hamar beleütközhet olyan problémákba, ahol nagy szüksége lenne egy tapasztalt fejlesztő tanácsaira. Ebben segítnek a tervezési minták, hiszen azok kiállták a gyakorlat próbáját. Ezért érdemes megismernünk őket akkor is, ha nem vagyunk éles fejlesztők, hiszen sok időt nyerhetünk velük…