[C#] Decorator Pattern toepassen

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • SiXel
  • Registratie: Augustus 2004
  • Laatst online: 19-01 11:45
Hallo,

Ik ben momenteel bezig met het toepassen van het decorator pattern in C#. De theorie van dit pattern is mij helder, alleen heb ik wat moeite met de implementatie.

Scenario:
Stel: ik heb een koffiezaak, ik kan objecten aanmaken van bijvoorbeeld: Koffie, Espresso, Cappuccino. Daarnaast kan ik aan deze objecten extra toevoegingen dynamisch toevoegen, bijvoorbeeld, melk, suiker, room. In code ziet het er ongeveer zo uit:
code:
1
2
3
drank = new Espresso(); // basis drank  
drank = new Melk(drank);
drank = new Suiker(drank);


Deze code werkt prima. Echter het probleem wat ik heb zit bij toevoegingen. Ik heb namelijk een lijst met toevoegingen voorbeeld:
code:
1
new Melk(), new Suiker()
nu wil ik een methode schrijven die het decorator pattern automatisch toevoegd en de drank doet returnen.
Het probleem dat ik heb is met de constructor. Heeft iemand enig idee hoe ik dit kan oplossen?
Alvast dank!

Acties:
  • 0 Henk 'm!

  • L01
  • Registratie: December 2003
  • Laatst online: 16-09 11:38

L01

Wat is je probleem met de constructor precies?

Hi, I'm a signature virus. Put me in your signature to help me spread.


Acties:
  • 0 Henk 'm!

  • Mischa_NL
  • Registratie: Mei 2004
  • Laatst online: 01-02-2023
Ik moet zeggen dat ik dit geen mooie code vindt... Waarom niet een baseclass met drankjes met daarin voids zoals: drank.AddMilk, en drank.AddSuiker? In mijn optiek is dit veel helderder.
Wat er nu logisch gebeurt is het volgende:

Je maakt een espresso en noemt hem drank.
Daarna maak je melk en je stopt er espresso in.
Imho niet erg logisch...

Voordeel hiervan is ook dat je derived classes andere extra's kunnen hebben. je hebt bv pseudo:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
baseclass Drinks
{
    // je baseclass met daarin de eigenschappen van ALLE drankjes, bv alcohol, temperatuur, en extra         
    // dingen die bij elk drankje kunnen, enzovoort
    drinks()
    {
    }
}

Coffee : Drinks
{
    int AmountOfSugar
    int AmountOfMilk

    void AddMilk(int mililiter)
    {
        AmountOfSugar = mililliter;
    }
    void AddSugar(int aantal_klontjes)
    {
        AmountOfMilk = aantal_klontjes
    }
}


Maar wellicht is je voorbeeld de koffiezaak niet zo handig gekozen. in dat geval -> disregard mijn post

[ Voor 51% gewijzigd door Mischa_NL op 01-04-2010 22:02 ]


Acties:
  • 0 Henk 'm!

  • Jorick
  • Registratie: November 2001
  • Laatst online: 22:30
Bekijk dit hoofdstuk eens, hier staat het in uitgelegd: http://oreilly.com/catalog/hfdesignpat/chapter/ch03.pdf. In het boek gebruiken ze overigens Java, maar dit is m.i. eenvoudig om te zetten naar C#.

[ Voor 28% gewijzigd door Jorick op 01-04-2010 22:02 ]


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Ik vermoed dat het design pattern 'decorator' daar een mooi voorbeeld van overdesign is, en dat je beter KISS kan toepassen.

Bij Starbucks stel je niet op die manier je drankje samen, en het probleem van verschillende groottes van glazen lossen ze ook niet op. Bij java IO heb ik me altijd al afgevraagd waarom sommige simpele dingen 2 extra constructors nodig hebben (voor wat system.in.readLine() had kunnen zijn bijvoorbeeld, wat nu ook met System.console().readLine() kan).

Welk praktisch probleem probeer je hier eigenlijk op te lossen? :p Zie je het bestelproces van een rondje koffie al voor je? Oh, je wilt 5 koffie. Wat wil je er precies in dan, per kopje? :+

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • TheNameless
  • Registratie: September 2001
  • Laatst online: 07-02 21:38

TheNameless

Jazzballet is vet!

Kan aan mij liggen maar ik zie niet echt een decorator pattern hierin :?

Wat ik begrijp van het decorator pattern is het volgende:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface IRepository{
  void Save();
}

public class Repository : IRepository {
  public void Save(){
    //dingen hier
  }
}

public class LoggingRepository : IRepository {
  private readonly IRepository inner;

  public LoggingRepository(IRepository inner){
    this.inner = inner;
  }

  public void Save(){
    Log.Debug('Save called');
    inner.Save();
  }
}


Is duidelijk wat voor voordelen het biedt, zeker als je kan werken met Dependency Injection.

Edit:
Toch een beetje er over heen gekeken zie ik :X

Ik denk dat je er wat moeite mee hebt omdat je voorbeeld niet helemaal klopt.
Je suggereert nu in je voorbeeld dat de klasses Espresso, Melk en Suiker beide eenzelfde basis klasse hebben.

Welke is dat dan?
Espresso en Melk zijn beide een drank, maar Suiker niet :)

[ Voor 20% gewijzigd door TheNameless op 01-04-2010 22:53 ]

Ducati: making mechanics out of riders since 1946


Acties:
  • 0 Henk 'm!

  • L01
  • Registratie: December 2003
  • Laatst online: 16-09 11:38

L01

Ja precies, kan je niet beter een baseclass/interface/abstractclass maken voor je drankjes en een baseclass/interface/abstractclassvoor je toevoegingen:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Suiker : IToevoeging
{
       //hier wat mooie properties
}

public class Koffie : IDrank
{
        private Boolean stirred;
        private Boolean shaken;

        private List<IToevoeging> toevoegingen = new List<IToevoeging>();

       public void addToevoeging(IToevoeging toevoeging)
        {
                //hier wat code 
               toevoegingen.add(toevoeging);
        }

       public void Roer()
       {
             if(!stirred && shaken)
             {
                   Barman.Shake(this);
              }

       }
}

page_load() // jaa asp.net ftw 
{
       IToevoeging suiker = new Suiker();
       IDrank koffie = new Koffie();
       koffie.addToevoeging(suiker);
       koffie.Roer();
}

Hi, I'm a signature virus. Put me in your signature to help me spread.


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Waarom zou je wel roeren dan, en niet de melk toevoegen? ;)
Andere mogelijkheid, nog wat quick-and-dirty:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public abstract class Beverage : List<BeverageProperty> {
    public override string ToString()
    {
        return GetType().Name + " (" + String.Join(", ",
            this.Select(a => a.ToString()).ToArray()) + ")";
    }
}
public abstract class BeverageProperty
{
    public override string ToString()
    {
        return GetType().Name;
    }
}
public class Sugar : BeverageProperty { }
public class Milk : BeverageProperty { }
public class Shaken : BeverageProperty { }
public class Coffee : Beverage { }
class Program
{
    static void Main(string[] args)
    {
        var coffee = new Coffee() { new Sugar(), new Shaken() };
        Console.WriteLine(coffee);
    }
}

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • jmzeeman
  • Registratie: April 2007
  • Laatst online: 12-09 16:17
@Pedorus en @L01:
Ben het er mee eens dat dat voor dit voorbeeld misschien mooier is maar dat van jullie zijn geen decorator patterns. En gezien het voorbeeld denk ik dat de TS juist probeert wat te leren of een (slechte) analogie voor zijn probleem heeft gekozen. Vaak wordt decorator incombinatie met een factory pattern gebruikt het volgende is volgens mij al een stuk mooier:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class Drank
{ 
    internal Drank();
}

public class Koffie : Drank
{
    internal Koffie();
}

public abstract class DrankToevoeging : Drank
{
    Drank innerDrank;
    internal DrankToevoeging(Drank drank)
    {
        innerDrank = drank;
    }
}

public class DrankMetSuiker : DrankToevoeging
{
    internal DrankMetSuiker(Drank drank)
        :base(drank)
    {
    }
}

public class Barista
{
    public Drank MaakKoffie()
    {
        return new Koffie();
    }

    public Drank VoegSuikerToe(Drank drank)
    {
        return new DrankMetSuiker(drank);
    }    
}

@TS:
Het me niet helemaal duidelijk wat je probleem is maar het enige wat ik me kan voorstellen wat er mis gaat is dat je of niet de goede base class hebt of dat je moeite hebt met het aanroepen vam je base class constructor. voor een oplossing voor beide zie hierboven.

[ Voor 0% gewijzigd door jmzeeman op 02-04-2010 10:23 . Reden: typos ]


Acties:
  • 0 Henk 'm!

  • HMS
  • Registratie: Januari 2004
  • Laatst online: 21-08 23:06

HMS

In C# kan je natuurlijk ook extension methods gebruiken ;-)

http://andrewtroelsen.blo...rn-extension-methods.html

Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
jmzeeman schreef op vrijdag 02 april 2010 @ 10:22:
@Pedorus en @L01:
Ben het er mee eens dat dat voor dit voorbeeld misschien mooier is maar dat van jullie zijn geen decorator patterns.
Toch vraag ik me af bij dit pattern in welke volgorde je de toevoegingen dan toevoegt, en hoe je de connectie naar bijvoorbeeld een database gaat maken. Als je bijvoorbeeld later de bestelling wil veranderen, dan zit je met een Matroesjka-pop waarbij het erg lastig is om de middelste te verwijderen.. Als je het in een DB wil stoppen en terughalen is het ook niet echt handig. Ik weet eigenlijk wel zeker dat NYP, die zoiets heeft, niet met dit pattern werkt... :p
En gezien het voorbeeld denk ik dat de TS juist probeert wat te leren of een (slechte) analogie voor zijn probleem heeft gekozen.
Ik vermoed ook dat dit huiswerk is ofzo, en dat de docent een slecht voorbeeld heeft gekozen, vandaar dat ik liever niet te veel voor ga zeggen. ;)
Leuk bedacht, maar het punt van het decorator pattern is nu juist dat je ook functionaliteit toe kan voegen aan reeds bestaande methodes, zodat bijvoorbeeld niet die bestaande methodes vergeven worden van de if-statements. In sommige gevallen is het probleem een sealed class waar je geen invloed op hebt. Met decorator kun je dan bijvoorbeeld zaken als logging en security alsnog toevoegen. Dit is vooral handig als een eenvoudige afgeleide klasse niet genoeg is, bijvoorbeeld ivm sealed of als je een combinatie van aspecten wil kunnen aanzetten. Overigens is AOP een andere mogelijkheid hiervoor.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • SiXel
  • Registratie: Augustus 2004
  • Laatst online: 19-01 11:45
Oké iedereen alvast bedankt voor jullie reacties.

Even voor de duidelijkheid: Ik ben bezig met het studeren van Design Patterns in C#. Hierbij maak ik gebruik van het het boek: Head First Design Patterns, hoofdstuk 3 gebruikt met het voorbeeld dat ik in mijn starterspot heb laten zien.

Echter vind ik dit een vrij een onlogische manier om het decorator pattern te gebruiken. Ik probeer mijn probleem is duidelijker uit te leggen:

Stel ik heb een methode die de koffie met eventueel extra toevoegingen maakt en daarna mijn drankje retourneerd.

Voorbeeld:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Drankje bestaat uit een basis Koffie en eventuele extra toevoegingen van een klant 

        Koffie basisKoffie = new Espresso();

        List<Extra> toevoegingen = new List<Extra>() {
        toevoegingen.Add(new Melk());
        toevoegingen.Add(new Suiker());
        toevoegingen.Add(new Room());

        }

        public Drank MaakEenNieuwGerecht(Koffie basisKoffie, List<ExtraToevoeging> extra)
        {
            // Kies een basisdrank
            Drank drankje = basisKoffie();

            // Eventuele extra toevoegingen 
            foreach (ExtraToevoeging x in extra)
            {
                // Stel je voor dat ik 100 soorten dranktoevoegingen heb..
                if (x.TypeOf(Melk)) { drankje = new Melk(drankje); }
                if (x.TypeOf(Suiker)) { drankje = new Suiker(drankje); }
                if (x.TypeOf(Room)) { drankje = new Room(drankje); }
            }

            return drankje;
        }


Als ik het boek moet geloven zou het er dan zo ongeveer moeten uitzien. Maargoed, stel je voor dat ik 100 extra toevoegingen heb dan lijkt mij dit een zeer efficiënte manier..

[ Voor 5% gewijzigd door SiXel op 02-04-2010 13:41 ]


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Hoe kan het dat je een instantie hebt van een decorator, zonder dat die iets gedecorate heeft? :p

Wat je zou kunnen doen is gebruik maken van een delegate die de constructor aanroept. Iets als const Func<Drank, Drank> melk = x => new Melk(x); dus. Die zou je dan wel in een lijstje kunnen stoppen, en kunnen aanroepen.

Als de decorators wel los mogen bestaan (niet in overeenstemming met Decorator-pattern!), dan zou je ze van een methode Init(Drank drank) kunnen voorzien.

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Zoek even verder in je boek naar het builder pattern:

C#:
1
2
3
4
5
6
7
8
class BeverageBuilder
{
  private Beverage b;
  public BeverageBuilder(Beverage base) : b(base) { }
  public void AddMilk() { b = new AddMilk(b); }
  public void AddSugar() { b = new AddSugar(b); }
  ...
}


Nieuwe varianten zijn dan niet meer als een nieuwe AddXXX class en een extra methode om ze toe te voegen.

[ Voor 15% gewijzigd door H!GHGuY op 03-04-2010 11:04 ]

ASSUME makes an ASS out of U and ME

Pagina: 1