A decorator minta több esetben is nagyon hasznos tud lenni. Az alap elgondolás mögötte az, hogy az öröklést aggregációra vagy kompozícióra cseréljük.
A .NET esetén találkoztunk már a decorator mintával, mégpedig fájlkezelés során a különböző Stream osztályoknál. A Stream bizonyos leszármazottjai (Pl. tömörítő és titkosító) decoratorként működnek: Ugyan azt a Stream ősosztályt implementálják, de létrehozásukhoz kell egy Stream, amibe a tényleges írás fog történni.
Eltérés a megvalósított műveletek viselkedésében és működésében van csupán. Ez a megközelítés nagyobb flexibilitást ad a programozó kezébe, mint ha simán csak örökléssel lenne megvalósítva a működés.
A dekorátort szokás még „Wrapper”-nek nevezni, ami talán jobban ki is fejezi a minta mögött álló gondolatot: van egy osztályunk, amit becsomagolunk egy másikba úgy, hogy az eredeti objektum interfészét vagy ősosztályát implementáljuk olyan módon, hogy az a becsomagolt osztály felé irányítsa a kommunikációt.
Mivel a wrapper osztály ugyan azt a felületet valósítja meg, mint a becsomagolt osztály, ezért a felhasználó szempontjából ezek az objektumok azonosak.
Nézzünk egy példát, amiben szövegeket kell írnunk lemezre egyszerű szövegként és base64 kódolt szövegként:
internal interface IDataSource
{
void WriteData(string data);
string ReadData();
}
internal class FileDataSoruce : IDataSource
{
private readonly string _fileName;
public FileDataSoruce()
{
_fileName = Path.Combine(AppContext.BaseDirectory, "data.txt");
}
public string ReadData()
{
return File.ReadAllText(_fileName);
}
public void WriteData(string data)
{
File.WriteAllText(_fileName, data);
}
}
internal class DataSourceDecorator : IDataSource
{
private readonly IDataSource _decorated;
public DataSourceDecorator(IDataSource decorated)
{
_decorated = decorated;
}
public virtual string ReadData()
{
return _decorated.ReadData();
}
public virtual void WriteData(string data)
{
_decorated.WriteData(data);
}
}
internal class Base64DataSourceDecorator : DataSourceDecorator
{
public Base64DataSourceDecorator(IDataSource decorated) : base(decorated)
{
}
public override string ReadData()
{
byte[] bytes = Convert.FromBase64String(base.ReadData());
return Encoding.UTF8.GetString(bytes);
}
public override void WriteData(string data)
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
base.WriteData(Convert.ToBase64String(bytes));
}
}
A közös interfészt az IDataSource definiálja. Ennek egy implementációja a FileDataSoruce, ami egy fájlból biztosítja az adatokat. A DataSourceDecorator osztály a dekorátorok őse. Jelen példában el is hagyható lenne, de ha több decorator osztályunk lesz, akkor érdemes bevezetni egy közös őst, hogy ne legyen sok duplikált kód. Ez az osztály a dekorálandó osztály tartalmazását és a közös interfész egy alap, közvetítő implementációját valósítja meg. A Base64DataSourceDecorator a tényleges decorator implementációnk. Ez a konstruktor paraméterben kapott IDataSource implementáció felé már base64 kódolással végzi az adat továbbítást, illetve olvasás során elvégzi a dekódolást.
Előnyök
- Kibővíthetjük vele egy objektum viselkedését anélkül, hogy új alosztályt hoznánk létre
- Lehetővé teszi, hogy a funkcionalitást futás közben kombináljuk
- Többféle viselkedést is elérhetünk, ha egy objektumot több dekorátoron keresztül vezetünk
- Követi a a Single Responsibility elvet
Hátrányok
- Egy specifikus wrapper eltávolítása a wrapper stack-ből nehéz
- Nehézséget okozhat úgy implementálni, hogy egy decorator viselkedése ne függjön a becsomagolásban szereplő sorrendjétől
- A szükséges wrapper létrehozó kódja elég csúnyán nézhet ki