Toon posts:

Welk design pattern moet ik kiezen?

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
Hallo,

Ik moet een opdrachtje maken waarbij ik een pattern moet kiezen voor een bepaalde situatie. De situatie is dat we een CSV importbestand hebben met op elke regel een product, bijvoorbeeld, auto's, fietsen en kippen. Deze drie dingen hebben allemaal verschillende velden. Een auto heeft kleur, gewicht, aantal pk, etc. Een kip heeft een herkomst, smaak, gewicht, aantal decibel waarmij hij kan kraaien, etc.

Elke rij heeft bovendien een type veld, zodat je kunt zien of je een kip, auto of fiets in handen hebt. Welnu, met wat IO werk kun je in code over die regels itereren, maar dan moet er natuurlijk wat met die gegevens gebeuren. Wat er moet gebeuren is eigenlijk niet zo belangrijk. Laten we zeggen dat we Kip, Auto en Fiets objecten gaan vullen met de gegevens. We gaan dus hetzelfde doen (object vullen), maar voor elk type op een ietsje andere manier (Een Kip object vullen gaat op een andere manier dan een Fiets object).

Dit riekt volgens mij erg naar een strategy pattern. Mijn vraag is de volgende. Als ik nu verschillende strategieen maak dan moet ik nog steeds ergens gaan zitten if-elsen om de juiste strategie te kiezen (of ik moet reflectie gebruiken). Is strategy nou wel de goede weg? Uiteindelijk moet je toch ergens de keuze voor een concrete strategie maken en daardoor ontkom je toch nooit aan een if/else structuur? Of is daar nog een elegante oplossing voor, zoals een strategy teruggeven uit een factory op basis van de type String of iets dergelijiks? Of ga ik nu te ver?

Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

Wat ga je na de import er mee doen?
Is er wel behoefte aan een heel specifiek pattern als het om zoiets schijnbaar simpels gaat?

Volgens mij moet je Kip, Fiets, Auto gewoon laten overerven van een Imported base class of IImported interface, zodat je ze in de rest van de applicatie volgens een generieke basis kunt aanspreken.

Het genereren van instanties van zulke classes zelfs kan via een switch case van het type veld gaan desnoods, en dan weer terug naar een List<IImported> met zijn allen of zoiets dergelijks.

Tenzij je wil dat je niet van te voren kunt weten wat je gaat krijgen? Dat de aanleverende partij er op een dag zonder vooraankondiging ook een Trein in heeft gestopt zonder dat je dat van te voren wist?

iOS developer


Acties:
  • 0 Henk 'm!

  • DEiE
  • Registratie: November 2006
  • Laatst online: 13:39
In de praktijk gebeurd het vaak dat patterns gecombineerd worden. Een factory die strategies teruggeeft is dan ook een perfect valide oplossing.

Dit gaat ook hand in hand met het Open-Closed principle. Als we naar jouw voorbeeld kijken, en er komt een nieuw soort type bij, namelijk een strandbal. Hiervoor zou je je bestaande code moeten aanpassen om deze er in te kunnen krijgen. Als je gebruik maakt van de factory op basis van de string, hoef je niet elke keer dat je een nieuw type toevoegt een nieuwe else if-clause toe te voegen, maar enkel de factory aan te passen. Je code die de types inleest blijft hierdoor onveranderd.

Acties:
  • +1 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Verwijderd schreef op woensdag 08 februari 2012 @ 15:14:
(Een Kip object vullen gaat op een andere manier dan een Fiets object)
Leg dat eens uit.

Dit lijkt mij een valide oplossing:
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
interface IFromCSVStrategy
{
    YourBaseType Execute(string[] columnData);
}

class KipFromCSVStrategy : IFromCSVStrategy
{
    private Kip _kip; // class Kip : YourBaseType
    
    public YourBaseType Execute(string[] columnData)
    {
        _kip.Eieren = columnData[2];
        // whatever
        
        return _kip;
    }
}

class FietsFromCSVStrategy : IFromCSVStrategy
{
    private Fiets _fiets; // class Fiets : YourBaseType

    public YourBaseType Execute(string[] columnData)
    {
        _fiets.Fietsbel = columnData[2];
        // whatever
    
        return _fiets;
    }
}

static class StrategyFactory()
{
    public static IFromCSVStrategy GetStrategy(String type)
    {
        switch(type.ToLower())
        {
            case "kip":
                return new KipFromCSVStrategy();
                break;  
            case "fiets":
                return new FietsFromCSVStrategy();
                break;  
            default:
                throw new Exception(...);   

        }
    }
}

class CSVReader
{
    public void Read()
    {
        
        string[] data = null;
        var results = new List<YourBaseType>();
        
        while (data = CSV.ReadLine())
        {           
            IFromCSVStrategy strategy = StrategyFactory.GetStrategy(data[0]);
            results.Add(strategy.Execute(data));
        }       
    }
}


Om hier een nieuw type aan toe te voegen (Auto bijvoobeeld), moet je die klasse schrijven (O RLY) en de factory aanpassen.

[ Voor 72% gewijzigd door CodeCaster op 08-02-2012 15:45 ]

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
@Bikkelz: het gaat vooral om de denkoefening en het bekend raken met patterns. Switchen op het type veld en aan de hand daarvan direct objecten maken is wat we volgens mij juist willen voorkomen :)

@Deie: ok

@CodeCaster: ik begrijp je niet. Wat ik bedoelde is dat we met elk type regel, dus een kip, fiets, auto iets moeten doen, maar telkens op een iets verschillende manier, bijvoorbeeld een object van dat specifieke type vullen. Maar het kan ook zijn de kipgegevens in blauw op het scherm zetten en de autogegevens in rood. Kortom, meerdere concrete invullingen van een abstracte operatie.

[ Voor 10% gewijzigd door Verwijderd op 08-02-2012 15:41 ]


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Zie edit trouwens.

Maar:
bijvoorbeeld een object van dat specifieke type vullen. Maar het kan ook zijn de kipgegevens in blauw op het scherm zetten en de autogegevens in rood
Dan wil je volgens mij te veel op dezelfde plek doen.

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

Verwijderd

Topicstarter
De oplossing die jij postte was inderdaad wat ik wilde gaan doen, CodeCaster. Een factory voor de strategien en dan vult elke strategie een object van het juiste type.
CodeCaster schreef op woensdag 08 februari 2012 @ 15:42:
Zie edit trouwens.

Maar:

[...]

Dan wil je volgens mij te veel op dezelfde plek doen.
Ja, ik ging ook niet echt printen. Het ging om het voorbeeld. De case is: meerdere concrete invullingen van een abstracte interface.

Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

Verwijderd schreef op woensdag 08 februari 2012 @ 15:40:
@Bikkelz: het gaat vooral om de denkoefening en het bekend raken met patterns. Switchen op het type veld en aan de hand daarvan direct objecten maken is wat we volgens mij juist willen voorkomen :)
Naar mijn idee ga je altijd ergens switch/case of if/else doen om te bepalen met welk soort object je te maken hebt, en vervolgens aan de hand van wat je daar in bepaalt uiteindelijk iets verschillends doen om die objecten te initialiseren, hoe abstract je het ook bouwt. Je het kunt het wel via Reflection achtige technieken doen, maar dan doe je het in mijn optiek stiekem alsnog.

Denk je dat er een pattern is waarbij je dit in het geheel omzeilt ipv verplaatst? En waarom willen we het voorkomen? Welke beren zie je op de weg? Ik krijg er een beetje een pattern-omdat-het-kan gevoel bij.

Wat ik wel prettig vind in het kader van testbaarheid, is dat ik mijn determinatie van welke uit te voeren actie of welk te creëren object in een aparte class of functie doet zodat je dat los kunt testen. Wat gaat hij doen, en gaat het geen wat hij gaat doen ook daadwerkelijk goed.

[ Voor 7% gewijzigd door BikkelZ op 08-02-2012 15:53 ]

iOS developer


Acties:
  • 0 Henk 'm!

Verwijderd

BikkelZ schreef op woensdag 08 februari 2012 @ 15:50:
[...]


Naar mijn idee ga je altijd ergens switch/case of if/else doen om te bepalen met welk soort object je te maken hebt, en vervolgens aan de hand van wat je daar in bepaalt uiteindelijk iets verschillends doen om die objecten te initialiseren, hoe abstract je het ook bouwt.
Dat kan gewoon met het visitor pattern :)

Acties:
  • 0 Henk 'm!

  • VyperX
  • Registratie: Juni 2001
  • Laatst online: 14-08 13:04
Waarom zou je een if/else moeten doen?
Stel je hebt een DomainObjectFactory. Daar kunnen specifieke IObjectFactory instanties zich registreren (bijv KipObjectFactory), die weten _wanneer_ en _hoe_ ze een specifiek object maken van de aanwezige data.

De algemene DomainObjectFactory gaat met de aanwezige data de geregistreerde instanties langs en laat degene die matcht het werk doen.

De enige voorwaarde die je hierbij hebt is dat de specifieke ObjectFactories geen overlap hebben met welke data ze accepteren.

My Dwarf Fortress ASCII Reward: ~~@~~####,.".D",.B""


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Verwijderd schreef op woensdag 08 februari 2012 @ 15:55:
[...]

Dat kan gewoon met het visitor pattern :)
Een visitor pattern is voor zover ik weet, correct me if I'm wrong, voor meerdere visitors op de client. Dus dan moet je in iedere visitor in je Accept(String[] data) gaan kijken of data[0] toevallig gelijk is aan het type data dat we ondersteunen ("kip", "fiets", "auto"), en ben je weer terug bij af: strings vergelijken.

Dan vind ik mijn switch toch mooier.

[ Voor 6% gewijzigd door CodeCaster op 08-02-2012 16:08 ]

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

  • BikkelZ
  • Registratie: Januari 2000
  • Laatst online: 21-02 08:50

BikkelZ

CMD+Z

Verwijderd schreef op woensdag 08 februari 2012 @ 15:55:
[...]

Dat kan gewoon met het visitor pattern :)
Helemaal gelijk d:)b

Maar dan ben je nog steeds een aparte "case" per visitor bezig, die je nog steeds moet uitbreiden zo gauw je een nieuw type krijgt:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CarElementPrintVisitor implements CarElementVisitor {
    public void visit(Wheel wheel) {      
        System.out.println("Visiting " + wheel.getName()
                            + " wheel");
    }
 
    public void visit(Engine engine) {
        System.out.println("Visiting engine");
    }
 
    public void visit(Body body) {
        System.out.println("Visiting body");
    }
 
    public void visit(Car car) {      
        System.out.println("Visiting car");
    }
}


De reden dat ik doorvraag en twijfel of je wel een "echt" pattern nodig hebt, is dat er een bepaalde winst behaald moet gaan worden op het moment dat je het gaat toepassen. Wil je makkelijker kunnen testen? Verwacht je heeeeel veeel verschillende soorten? Wil je flexibel om kunnen gaan met onaangekondigde nieuwe soorten?

Pas dan kun je echt gaan bepalen welk pattern je moet gebruiken omdat je dan weet naar welk specifiek voordeel je aan het zoeken bent.

iOS developer


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Het verschil op die Wiki-pagina is juist de Visitor-klasse (CarElementPrintVisitor versus CarElementDoVisitor), en dus niet de methoden daarin (je wil alle visitors iets met een Wheel laten doen, je wil niet één visitor die het Wheel behandelt en één die de Engine behandelt).

In deze context zou je een CarElement<Kip|Fiets|Auto>Visitor willen maken, en dat maakt het een en ander er niet logischer op.

[ Voor 29% gewijzigd door CodeCaster op 08-02-2012 16:11 ]

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
CodeCaster schreef op woensdag 08 februari 2012 @ 16:06:
Dan vind ik mijn switch toch mooier.
Door jouw switch zit je vast met een class die af moet weten van elke implementator van je interface. Dan gooi je juist het hele principe van het gebruiken van een interface overboord. Als jij nu een class toe gaat voegen moet je niet alleen een class bouwen die een regel parset, maar ook je factory aanpassen.

Dat is een onzichtbare dependency op een ander stuk code, en dus slecht design.

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

En hoe voeg je anders je nieuwe parser-class toe als visitor? Die moet je dan toch ergens je client laten visiten? Dan zit je toch alsnog met een stuk code dat al je gebruikte klassen bij hun naam noemt (client.Visit(new <Kip|Fiets|Auto>Parser())?

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Nog nooit gehoord van inversion of control?

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


Acties:
  • 0 Henk 'm!

Verwijderd

CodeCaster schreef op woensdag 08 februari 2012 @ 16:17:
En hoe voeg je anders je nieuwe parser-class toe als visitor? Die moet je dan toch ergens je client laten visiten? Dan zit je toch alsnog met een stuk code dat al je gebruikte klassen bij hun naam noemt (client.Visit(new <Kip|Fiets|Auto>Parser())?
Nope.

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
40
class AbsVisitor {
  virtual AcceptVisitor(Subject ASubject) = 0;
  virtual execute(Subject: ASuvject): YourBaseObject = 0;
}

class KipVisitor : AbsVisitor
{
  virtual AcceptVisitor(Subject ASubject)
  {
    return ASubject.type == "kip"
  }
  virtual execute(Subject: ASuvject): YourBaseObject;
  {
    // maak nieuwe kip, lees in, return
  }
}

class FietsVisitor : AbsVisitor
{
  virtual AcceptVisitor(Subject ASubject)
  {
    return ASubject.type == "fiets"
  }
  virtual execute(Subject: ASuvject): YourBaseObject;
  {
    // maak nieuwe fiets, lees in, return
  }
}

class VisitorManager
{
  vector<AbsVisitor>: Visitors;
  AddVisitor(AbsVisitor: AVisitor) //implementeer zelf maar.
  Read(Subject: ASubject)
  {
    foreach Visitor in Visitors
      if Visitor.AcceptVisitor(ASubject);
        return Visitor.Execute(ASubject);
  }
}

Ik heb overigens al lang geen c++ meer aangeraakt.. fouten voorbehouden.
Fout 1: = != ==...
Fout 2: de default visibility zal wel niet public zijn...

Enige voorwaarde is dat je je visitors ergens moet registreren. In delphi heb je daar een initialization clause voor. Geen idee of dat in andere talen ook kan.

Saillant detail: Het visitor pattern komt volgende maand pas aan bod in de colleges. Dat wordt een makkie :P

Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Gehoord van, nooit de noodzaak gehad dit grondig te begrijpen. Heb je zin uit te leggen hoe dat in deze situatie helpt? De wiki is wat te abstract.
Verwijderd schreef op woensdag 08 februari 2012 @ 16:33:
class VisitorManager
{
AddVisitor(AbsVisitor: AVisitor) //implementeer zelf maar.
}
[/code]
Nogmaals, hoe voorkom je dan dat je je code moet aanpassen met een nieuwe VisitorManager.AddVisitor(<zojuist nieuw toegevoegd visitortype>)?

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

Verwijderd

Dat moet bij nader inzien dus wel tenzij je een programmeertaal hebt die iets vergelijkbaars heeft als initialization bij delphi. Voor de onwetenden: daar kan je een aantal regels code plaatsen die worden uitgevoerd voor de main() wordt aangeroepen. Dat hangt ervanaf welke taal je wilt gebruiken.

Acties:
  • 0 Henk 'm!

  • Grijze Vos
  • Registratie: December 2002
  • Laatst online: 28-02 22:17
Daar is dus inversion of control c.q. dependency injection voor. Al je Visitors implementeren een interface. Door middel van een DI framework scan je je assemblies op implementaties van die specifieke interface. Deze kun je dan beschikbaar stellen waar nodig.

Als je bijvoorbeeld MEF zou gebruiken zou het er ongeveer zo uitzien:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ICsvParser
{ 
  void DoSomething();
}

[Export(typeof(ICsvParser))]
public class Foo : ICsvParser{ .. }

[Export(typeof(ICsvParser))]
public class Bar : ICsvParser{ .. }

public class Parser
{
  [ImportMany]
  public IEnumerable<ICsvParser> Parsers { get; set; }
}


Dan zitten er nog 3-4 regels initialisatie aan vast en dan heb je in je Parser class alle specifieke implementaties beschikbaar.

Op zoek naar een nieuwe collega, .NET webdev, voornamelijk productontwikkeling. DM voor meer info


Acties:
  • 0 Henk 'm!

  • DEiE
  • Registratie: November 2006
  • Laatst online: 13:39
Inversion of Control betekent heel kort gezegd dat je niet afhankelijk moet zijn van concrete dingen maar van abstracties. Voor meer leesvoer: Dependency Inversion Principle

Acties:
  • 0 Henk 'm!

  • R4gnax
  • Registratie: Maart 2009
  • Laatst online: 06-09 17:51
Grijze Vos schreef op woensdag 08 februari 2012 @ 17:19:
Daar is dus inversion of control c.q. dependency injection voor. Al je Visitors implementeren een interface. Door middel van een DI framework scan je je assemblies op implementaties van die specifieke interface. Deze kun je dan beschikbaar stellen waar nodig.

Als je bijvoorbeeld MEF zou gebruiken zou het er ongeveer zo uitzien:
::knikt instemmend::

Afgezien van de zeer triviale huidige use case is dit inderdaad wel exact de klasse van probleem waar MEF en soortgelijke DI containers uitblinken.

Met MEF kan het zelfs nog een tikkie netter. Je kunt direct via de export wat extra metadata opgeven waarin staat welke types je concrete parser strategie kan verwerken. Dan hoef je daarmee je ICSVParser interface niet te vervuilen en kan je toch in je visitor via deze metadata direct de juiste strategie selecteren.

[ Voor 20% gewijzigd door R4gnax op 09-02-2012 00:39 ]

Pagina: 1