Design Patterns - Een korte handleiding voor het decoratorpatroon.

Het Decorator-patroon is een structureel patroon waarmee u extra functies dynamisch aan een object kunt koppelen. Met andere woorden, de client heeft de vrijheid om een ​​object te maken en dit vervolgens uit te breiden door er een aantal "functies" aan toe te voegen. Een goede analogie om dit patroon te vereenvoudigen is: "Een geschenk inpakken, in een doos stoppen en de doos inpakken".

Het Decorator-patroon is geclassificeerd onder structurele ontwerppatronen die allemaal over Klasse en Objectsamenstelling gaan. Structurele patronen voor het maken van klassen gebruiken overerving om interfaces samen te stellen. Structurele objectpatronen definiëren manieren om objecten samen te stellen om nieuwe functionaliteit te verkrijgen. [door Design Patterns eenvoudig uitgelegd]

Een rugzak in het perfecte voorbeeld van een Decorator-patroon:

  • Tegenwoordig kunnen de rugzakken verschillende functies hebben. De functies kunnen variëren van zoiets eenvoudigs als een laptopslot, zijvakken of zelfs een powerbank voor opladen via USB.
  • Het hoofddoel van het Decorator-patroon is om de klant de benodigde functies toe te voegen, dynamisch, veilig en op de gemakkelijkst mogelijke manier. Het moet net zo eenvoudig en netjes zijn als de rugzak op de foto terugplaatsen.
  • Het onderstaande codefragment is met opzet geschreven omdat het zo mooi samenvalt met het citaat dat we hierboven hebben genoemd: "Een geschenk inpakken, in een doos doen en de doos inpakken". We stellen een rugzak samen waaraan we een USB-lader toevoegen waaraan we een laptopsleuf toevoegen waaraan we ...
int main // de klant
{
  IBackpack * bp = nieuwe LaptopSlot (nieuwe UsbCharge (...))));
  terugkeer 1;
}

Stap 1 - Zoekwoorden

Het definiëren van trefwoorden is het geheime recept in deze serie beknopte handleidingen. Deze methode heeft me geholpen de ontwerppatronen echt te begrijpen, ze hard te coderen en de verschillen tussen andere ontwerppatronen te begrijpen.

  • Flexibiliteit: we willen de klant de kracht en flexibiliteit geven om elke functie dynamisch toe te voegen aan een component / object, die als waardevol kan worden beschouwd.
  • Functionaliteit uitbreiden: de titel is misschien een beetje misleidend, dit patroon gaat niet alleen over het "decoreren" van een bepaald object, maar het gaat vooral over het uitbreiden van de functionaliteit.

Stap 2 - Diagrammen bij voorbeeld

Laten we het diagram van onder naar boven toelichten. Het doel is om een ​​rugzak te "monteren" en er verschillende functionaliteiten aan toe te voegen.

  • Betondecorateurs: We hebben drie voorbeelden van deze klassen die elk een extra functionaliteit moeten uitbreiden: (1) LaptopSlot, (2) USBCharge, (3) WaterBottle. Ze houden de implementatie van de functionaliteit en ze kunnen een rugzak dienovereenkomstig "samenstellen". Merk op dat ze een constructor hebben die een decorateur als parameter ontvangt.
  • Decorateur: deze klasse leidt de bovenstaande klassen af ​​en erft van de component die IBackpack is. De decorateur heeft ook een exemplaar van IBackpack. Nadat IBackpack is geïnstantieerd, wordt het gebruikt in de methode assemble ().
  • Betoncomponent: deze klasse is het belangrijkste stukje van de puzzel omdat het de sleutel is om alles met elkaar te verbinden. Het is het onderdeel (rugzak) dat zich in de eenvoudigste vorm bevindt die het zou kunnen krijgen. Een gewone rugzak bestaat bijvoorbeeld alleen uit de "schouderbanden en hoofdvak". De eenvoudige rugzak werkt als een start van een ketting. Het wordt doorgegeven aan de constructeur van de decorateur en zelf wordt doorgegeven aan de constructor van een betonnen decorateur (om specifieke functionaliteiten toe te voegen, d.w.z. een laptop-slot) die kunnen worden doorgegeven aan een andere constructor van een betonnen decorateur en zo verder.
  • Component: het is een abstracte weergave van het object dat we willen versieren. We kunnen er verschillende betonnen componenten van erven. In dit voorbeeld kunnen we de "PlainBackpack" scheiden als "OfficeBackpack", "CampingBackpack", "HikingBackpack" maar we kiezen ervoor om het simpel te houden.

Stap 23 - Codeer als voorbeeld

Ik zou willen voorstellen om de code klasse per klasse te kopiëren uit mijn git-repository “Andreas Poyias” of de onderstaande fragmenten (in de aangegeven volgorde) en deze in een van de beschikbare online C ++ editors te plakken zoals c ++ shell, jdoodle, onlineGDB en voer het uit om de output te observeren. Lees dan de opmerkingen of beschrijving hieronder. Neem de tijd om het grondig te lezen (dat betekent één minuut, niet minder en niet meer).

IBackpack:
Het volgende codefragment is een eenvoudige overerving, IBackpackis geërfd door PlainBackpackclass. Alle afgeleide klassen moeten assemble () implementeren, omdat het een pure virtuele functie = 0 ;. Een eenvoudige rugzak heeft alleen schouderbanden en het hoofdvak.

# include 
namespace std; gebruiken;

klasse IBackpack
{
openbaar:
  virtuele leegte assemble () = 0;
  virtual ~ IBackpack () {}
};

klasse PlainBackpack: openbare IBackpack
{
openbaar:
  virtual void assemble () {cout << "\ n ShoulderStraps and mainCompartment";}
};

BackpackDecorator:
Bovenstaand fragment is verantwoordelijk voor de constructie van een eenvoudige rugzak. Laten we het nu versieren met de BackpackDecorator. De decorateur neemt van deIBackpack over, wat betekent dat hij de methode theassemble () moet implementeren. Het bevat ook een IBackpackobject dat wordt gebruikt om de implementatie van de methode assemble () te delegeren, afhankelijk van het type m_decorator.

klasse BackpackDecorator: openbare IBackpack
{
openbaar:
  BackpackDecorator (IBackpack * decorator): m_Decorator (decorator) {}
  
  virtuele leegte monteren ()
  {
    m_Decorator-> monteren ();
  }
privaat:
  IBackpack * m_Decorator;
};

Betondecorateurs:
Het onderstaande fragment toont drie verschillende decorateurs die zijn afgeleid van de klasse BackpackDecorator die zelf vanIBackpack erft. In dit voorbeeld zijn ze allemaal identiek, afgezien van de implementatie vanassemble (). De eerste voegt een laptopSlot toe, de tweede voegt een UBCharge toe en de derde voegt een waterBottle toe.

klasse WithLaptopSlot: openbare BackpackDecorator
{
openbaar:
  WithLaptopSlot (IBackpack * dcrator): BackpackDecorator (dcrator) {}
  virtuele leegte monteren ()
  {
    BackpackDecorator :: monteren ();
    cout << "+ LaptopSlot";
  }
};
 
klasse WithUSBCharge: openbare BackpackDecorator
{
openbaar:
    WithUSBCharge (IBackpack * dcrator): BackpackDecorator (dcrator) {}
    virtuele leegte monteren ()
    {
        BackpackDecorator :: monteren ();
        cout << "+ USBCharge";
    }
};
 
klasse WithWaterBottle: openbare BackpackDecorator
{
openbaar:
    WithWaterBottle (IBackpack * dcrator): BackpackDecorator (dcrator) {}
    virtuele leegte monteren ()
    {
        BackpackDecorator :: monteren ();
        cout << "+ WaterBottle";
    }
};

Hoofd (klant):
De hoofdmethode werkt als de client (hetzelfde als de vorige handleidingen). We zijn eindelijk in staat om alle puzzelstukjes in elkaar te zetten. Maar voordat we dit doen, laten we onthouden wat er werd vermeld in de eerste paragraaf van de blog "Een cadeau inpakken, in een doos stoppen en de doos inpakken". Om dit te begrijpen moeten we de constructie van de rugzak (in het onderstaande fragment) in omgekeerde volgorde lezen:

  1. Maak een PlainBackpack.
  2. Geef het door aan de BackpackDecorator.
  3. Die wordt doorgegeven om te worden versierd met een laptop-slot.
  4. Op zijn beurt wordt het doorgegeven om te worden versierd met een USB-lading.
  5. Ten slotte is de "doos" "ingepakt" met een waterfles.

Het is ook belangrijk om de afdrukopdracht te observeren terwijl assemble () wordt aangeroepen. De volgorde is gerelateerd aan hoe de constructors zijn geïnitialiseerd. Het begint opnieuw bij de eenvoudige rugzak en is helemaal versierd om de waterfles te bereiken.

int main ()
{
  IBackpack * pBackpack =
   nieuwe WithWaterBottle (// 5
    nieuwe WithUSBCharge (// 4
     nieuwe WithLaptopSlot (// 3
      nieuwe BackpackDecorator (// 2
       nieuwe PlainBackpack ()))); // 1

  pBackpack-> monteren ();
  verwijder pBackpack;

  terugkeer 0;
}
// Uitgang
// ShoulderStraps en mainCompartment + LaptopSlot + USBCharge
// + Waterfles

De eenvoud die de klant wordt geboden, is een van de grootste voordelen.

  • De klant heeft de mogelijkheid om een ​​rugzak dynamisch samen te stellen met alle beschikbare functies.
  • Het is eenvoudig, gemakkelijk en veilig.
  • De client hoeft zich niet te vermengen met de code.
  • Het toevoegen van een extra "afgeleide" decorateur is eenvoudig en onafhankelijk van andere afgeleide decorateurs.

Vergeet niet mijn blogpost leuk te vinden / te klappen en mijn account te volgen. Dit is om mij de voldoening te geven dat ik sommige mede-ontwikkelaars heb geholpen en me aanspoort om te blijven schrijven. Als er een specifiek ontwerppatroon is waarover je meer wilt weten, laat het me dan weten in de reacties hieronder, zodat ik het je de komende weken kan aanbieden.

Andere snelgidsen over ontwerppatronen:

  1. Design Patterns - Een korte handleiding voor Abstract Factory.
  2. Design Patterns - Een korte handleiding voor Bridge Pattern.
  3. Design Patterns - Een korte handleiding voor Builder Pattern.
  4. Design Patterns - Een korte handleiding voor Decorator Pattern.
  5. Ontwerppatronen - Een korte handleiding voor gevelpatroon.
  6. Ontwerppatronen - Een korte handleiding voor het waarnemerspatroon.
  7. Design Patterns - Een korte handleiding voor Singleton Pattern.