Design Patterns - the Series: the Decorator
Het basisprincipe
Een van de basis design principes is: classes moeten openstaan voor uitbreidingen, maar gesloten zijn voor aanpassingen. Op het eerste gezicht twee tegenstrijdige begrippen, maar het Decorator pattern biedt de mogelijkheid om verantwoordelijkheden van objecten uit te breiden, ook al kunnen we de onderliggende code niet kunnen aanpassen. Dit is overigens geen vrijbrief om dit te pas en te onpas toe te passen binnen je applicatie; hierdoor zou je code onnodig complex en moeilijk te begrijpen maken.
Volg de discussie van Remi en Mark over het Decorator pattern …
Classes moeten openstaan voor uitbreidingen, maar gesloten zijn voor aanpassingen
Remi: Mark, ik heb een vraag aangaande een issue dat je vaker tegenkomt bij het bepalen van de architectuur van een applicatie. Jij bent hier waarschijnlijk ook wel eens tegenaan gelopen, dus ik ben benieuwd naar jouw mening.
Ik heb enkele boeken versleten over het fenomeen design patterns en ik onderschrijf de theorie, maar de gebruikte voorbeelden in die boeken komen vaak niet in de buurt van de design issues waar ik mee worstel in mijn projecten.
Mark: Inderdaad, de meeste voorbeelden gaan over tekstverwerkers en recepturen voor pizzarestaurants o.i.d. Ik herken je probleem heel goed!
Remi: Ik denk aan het volgende voorbeeld: in mijn huidige project heb ik te maken met een diversiteit aan personen. We hebben het over vaste medewerkers, tijdelijke medewerkers, stagiaires, en binnen de vaste medewerkers bestaat weer een diversiteit aan types zoals verkoper buitendienst, administratief personeel, enz.
Ik kan natuurlijk beginnen met een Persoon class die ik dan subclass voor ieder benodigd persoonstype, maar dat lijkt me niet verstandig vanuit onderhoudsoogpunt.
Mark: Nee, dat wordt nogal een explosie van classes en wie zegt dat de genoemde lijst uitputtend is?. Je zou kunnen overwegen om soort superclass te maken waarin je op basis van boolean switches aangeeft van welk ‘type class’ een specifieke instantie is, op basis waarvan je applicatie dan de juiste keuzes kan maken.
Remi: Hmm, ik zie problemen op het moment dat het design moet wijzigen, omdat er een type werknemer bijkomt waar nu nog geen rekening is gehouden, b.v. de consultant of de parttimer.
Mark: Daar heb je wel een punt. Je zult in dat geval waarschijnlijk methods moeten toevoegen en de werking van bestaande methods moeten aanpassen, waardoor er problemen kunnen ontstaan in subclasses. Eigenlijk zoek jij dus naar een manier om het design principe Classes should be open for extension, but closed for modification toe te passen.
Remi: Precies! Dat was juist waar ik aan dacht, maar dat is op het eerste gezicht met elkaar in tegenspraak.
Mark: Dat is waar, maar in de OO-wereld hebben we de mogelijkheid om verantwoordelijkheden van classes uit te breiden, ook al kunnen (of mogen) we de onderliggende code niet aanpassen. Een voorbeeld hiervan is het Observer pattern en zo zijn er nog meer.
Remi: Al bladerend door mijn grote boek kom ik het Decorator pattern tegen en die zou het volgende moeten bewerkstelligen: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
Mark: Dat zou wel eens kunnen zijn wat we zoeken! Als ik het goed lees, biedt de decorator mogelijkheden om een object meer verantwoordelijkheden te geven door compositie van een of meerdere objecten. Dit kan dus ook dynamisch, m.a.w. tijdens programma-executie i.p.v. van statisch tijdens de compilatie van de code.
Remi: Ok, laten we eens kijken hoe dit er uit zou kunnen zien voor het “probleem met de medewerkers”

Mark: Remi, ik zie hier toch nog wel een behoorlijke hoeveelheid subclasses ontstaan. Wilden we dat niet juist omzeilen?
Remi: Correct! Maar dit is cruciaal in het geheel. Decorators moeten namelijk van hetzelfde type zijn als de objecten die zij ‘decoreren’. Dus in dit geval gebruiken we inheritance om een type overeenkomst af te dwingen, waar normaliter inheritance wordt gebruikt om gedrag te erven.
Mark: Ok, daar kan ik me in vinden, maar waar brengen we dan de extra verantwoordelijkheden in het spel? O, wacht even, ik zie het: door een object aan te kleden (decorate) met een of meerdere decorator objects kunnen we naar hartelust het gedrag van een object beïnvloeden.
Remi: Juist! Als we dit zouden doen m.b.v. inheritance - hetgeen ook kan natuurlijk - ligt het gedrag vast tijdens het compileren, maar door te werken met decorators kunnen we runtime verantwoordelijkheden toevoegen aan of verwijderen uit decorated objects.
using System;
namespace Decorator
{
public abstract class Employee
{
public Employee()
public abstract string EmpRole();
public virtual string EmpAddress()
{return ""; // intentionally left empty}
}
public class Mark : Employee
{
public override string EmpRole()
{return "Author;";}
public string EmpAddress()
{return ""; // intentionally left empty}
}
public class Remi : Employee
{
public override string EmpRole()
{return "";}
public string EmpAddress()
{return ""; // intentionally left empty }
}
}
Listing 1: Employee baseclass and concrete classes Mark & Remi
using System;
namespace Decorator
{
public class EmployeeDecorator : Employee
{
public EmployeeDecorator()
{// Other methods intentionally left out }
public override string EmpRole()
{return ""; // Must override due to abstract nature}
}
public class EmployeeDecoratorA : EmployeeDecorator
{
private Employee _Employee ;
public EmployeeDecoratorA(Employee oEmp)
{this._Employee = oEmp;}
public override string EmpRole()
{return this._Employee.EmpRole() +_
"Editor;Publisher";}
}
public class EmployeeDecoratorB : EmployeeDecorator
{
private Employee _Employee ;
public EmployeeDecoratorB(Employee oEmp)
{this._Employee = oEmp;}
public override string EmpRole()
{return this._Employee.EmpRole() +_
"Reviewer;Technical Editor";}
}
}
Listing 2: Employee base Decorator and 2 concrete decorators A & B
Het gebruik maken van de decorators om de verantwoordelijkheden van Mark (Employee) en Remi (Employee) te beïnvloeden gaat als volgt:
// Mark with DecoratorA
Employee oEmp = new Mark();
oEmp = new EmployeeDecoratorA(oEmp);
MessageBox.Show(oEmp.EmpRole());
// Remi with DecoratorB
Employee oEmp1 = new Remi();
oEmp1= new EmployeeDecoratorB(oEmp1);
MessageBox.Show(oEmp1.EmpRole());
// Remi with Decorator A & Decorator B
Employee oEmp2 = new Remi();
oEmp2= new EmployeeDecoratorA(oEmp2);
oEmp2= new EmployeeDecoratorB(oEmp2);
MessageBox.Show(oEmp2.EmpRole());
Zoals je ziet, kun je meer dan 1 decorator gebruiken in combinatie met een concrete Employee class, in dit voorbeeld “hard gecodeerd” maar het is eenvoudig dynamisch te doen in je applicaties.
Mark: Ik ga hier eens mee aan de slag binnen mijn applicatie, want je zou dit principe ook kunnen toepassen op b.v. adressen. Denk aan postadres, bezoekadres, enz!
Remi: Ja, en zo zijn er nog wel meer real life voorbeelden te bedenken waar je met de decorator meer flexibiliteit hebt.
Door te werken met decorators kunnen we runtime verantwoordelijkheden toevoegen aan of verwijderen uit decorated objects
Conclusie
Het Decorator pattern biedt je veel flexibiliteit m.b.t. realisatie van ‘inheritance-achtige’ doelstellingen. Door gebruik te maken van decorators vereenvoudig je het onderhoud van je applicaties, omdat het wijzigen van gedrag of verantwoordelijkheden plaats vindt in één decorator. De kans op ‘code breaking changes’ is dan afwezig. Je kunt ook eenvoudiger inspringen op voorgestelde wijzigingen door nieuwe decorators te creëren die voorzien in evt. extra wensen zonder de onderliggende code te hoeven aanpassen.
Er dient echter wel als kanttekening geplaatst te worden, dat het gebruik van decorators het debuggen kan ‘verwarren’ door de verschillende gedragingen die een object kan aannemen.