Het visitor pattern, maar... waarom?

Pagina: 1
Acties:

Vraag


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Mijn vraag

Ik ben aan het experimenteren met C# Source Generators. Al zoekende naar een manier om een attribuut met bijbehorende argumenten uit te lezen stuitte ik op https://stackoverflow.com/a/70424282 . Hierin staat een mooie klasse AttributeCollector welke precies verzamelt wat ik nodig heb. Fantastisch!

Maar.... deze klasse maakt gebruik van het visitor pattern. Hij wordt 'aangeroepen' middels: attributeSyntax.Accept(collector); waarna het AttributeSyntax VisitAttribute(AttributeSyntax node) aanroept.

Wat ik mij afvraag; wat is hier het voordeel van het visitor pattern? Is het niet eenvoudiger een niet-visitor AttributeCollector te maken met een .Collect(AttributeSyntax) method? Welke gewoon het resultaat retourneert als een List<AttributeDefinition>.


Wat ik al gevonden of geprobeerd heb

Ik heb al diverse youtube video's bekeken over het Visitor Pattern en ook het hoofdstuk in 'Head first into design patterns' gelezen; maar ik zie het gewoon niet.

Het gaat me ook niet persé om dit specifieke voorbeeld; ik 'zie' het voordeel gewoon nog niet van dit pattern.

Beste antwoord (via Josk79 op 15-08-2022 23:50)


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-05 17:05
Josk79 schreef op maandag 15 augustus 2022 @ 22:47:
Ik kreeg net deze url door: https://craftinginterpret...e.html#working-with-trees

Ik denk dat dit stukje het enige voordeel t.o.v. mijn methode omschrijft; 100% static dispatch. Wat volgens mij voornamelijk een performance gain t.o.v. mijn methode heeft. Maar om eerlijk te zijn; als performance geen key issue is; ga ik liever voor mijn methode want die vind ik een stuk netter/leesbaarder.
[...]
Je schrijft met je switch de dispatching handmatig uit, als je er een mist krijg je een runtime exception, ooit, misschien. Ik laat dat liever aan de compiler over tbh.
NC83 schreef op maandag 15 augustus 2022 @ 21:50:
Hoe dat predicate wordt uitgevoerd is wel degelijk een visitor patroon, je visitor hier is je lambda functie.
Ik vraag me af of je hier mag spreken van een visitor pattern aangezien de dispatch niet dynamisch is (het type van value is hetzelfde voor elke value).

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.

Alle reacties


Acties:
  • +1 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 09:52

Haan

dotnetter

Ik ben er ook niet zo'n fan van. Jaren geleden was er eens een externe developer in dienst die er wel erg enthousiast over was en die heeft het toen bij ons naar binnen gefietst. Hij allang vertrokken en wij zitten er nu mee opgescheept :P Gelukkig in een hoek van de code base waar ik zelf vrijwel nooit kom, maar wat ik er van weet is dat het in theorie mooi klinkt, maar in de praktijk vooral omslachtig werkt, zoals je zelf ook al had gezien.

Kater? Eerst water, de rest komt later


Acties:
  • +1 Henk 'm!

  • Stukfruit
  • Registratie: Oktober 2007
  • Niet online
Josk79 schreef op zondag 14 augustus 2022 @ 00:12:
Maar.... deze klasse maakt gebruik van het visitor pattern. Hij wordt 'aangeroepen' middels: attributeSyntax.Accept(collector); waarna het AttributeSyntax VisitAttribute(AttributeSyntax node) aanroept.

Wat ik mij afvraag; wat is hier het voordeel van het visitor pattern? Is het niet eenvoudiger een niet-visitor AttributeCollector te maken met een .Collect(AttributeSyntax) method? Welke gewoon het resultaat retourneert als een List<AttributeDefinition>.
Zo op het eerste gezicht:

Daarmee handel je alleen het resultaat "goed" af, maar niet de input. AttributeCollector is zo te zien een generieke oplossing, maar stel dat je specifieke implementaties wil hebben: AttributeByNameCollector, AttributeByRegexCollector, enz.

Dat kun je allemaal in je generieke collector duwen, maar dan komt o.a. SRP uit SOLID om de hoek kijken.

Om dat op te lossen kun je bijvoorbeeld een facade gebruiken. Maar dat is ook niet ideaal omdat je dan alsnog dingen aan elkaar koppelt.

Dus: visitor pattern. Zodat de implementaties gescheiden blijven en de basisstructuur niet aangepast hoeft te worden bij verdere uitbreiding.

In jouw voorbeeld zie ik er daarom ook niet heel erg een praktisch nut in. De schrijver ervan verbindt zichzelf sowieso teveel aan specifieke implementaties door bv. een List te retourneren ipv een ICollection.

Lang geleden waren er bibliotheken die dit patroon implementeerden op een redelijk nuttige manier, zoals bijvoorbeeld OpenSceneGraph. Daarin zie je gelijk dat het beter te gebruiken is voor "grotere" dingen.

Dat zit wel Schnorr.


Acties:
  • +5 Henk 'm!

  • RayNbow
  • Registratie: Maart 2003
  • Laatst online: 10:46

RayNbow

Kirika <3

Josk79 schreef op zondag 14 augustus 2022 @ 00:12:
Het gaat me ook niet persé om dit specifieke voorbeeld; ik 'zie' het voordeel gewoon nog niet van dit pattern.
Het visitor-pattern is een patroon wat je kunt toepassen wanneer je programmeertaal geen double dispatch (of generieker, multiple dispatch) ondersteunt.

Neem de volgende methode-aanroep:
C#:
1
_ = obj.Equals(someOtherObj);


Welke implementatie van Equals wordt aangeroepen wordt in C# bepaald door het runtime type van obj en het compile-time type van someOtherObj.

Bekijk eens het volgende voorbeeld:
C#:
1
2
3
4
5
6
7
8
9
10
11
class Cat {
    public override bool Equals(object other) {
        Console.WriteLine("Cat.Equals(object)");
        return false;
    }
    
    public bool Equals(Cat other) {
        Console.WriteLine("Cat.Equals(Cat)");
        return false;
    }
}

C#:
1
2
3
4
5
6
void Main()
{
    object catA = new Cat();
    object catB = new Cat();
    _ = catA.Equals(catB);
}

Met single-dispatch zal het bericht "Cat.Equals(object)" op het scherm verschijnen. Als je wilt dat de methode "Cat.Equals(Cat)" wordt aangeroepen, heb je multiple-dispatch nodig. Een manier om dat te bereiken is met het visitor-pattern, al is dat niet de enige manier.

Op een gegeven moment kreeg C# een feature waarmee het net als VB.NET een vorm van multiple-dispatch kreeg:
C#:
1
2
3
4
5
6
void Main()
{
    object catA = new Cat();
    object catB = new Cat();
    _ = (catA as dynamic).Equals(catB as dynamic);
}

Bovenstaande code zal "Cat.Equals(Cat)" op het scherm printen.

Ipsa Scientia Potestas Est
NNID: ShinNoNoir


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Allen bedankt. Ik blijf het vreemd vinden dat de visitee weet moet hebben van de visitor. Ik zou zeggen; dit is prima van buitenaf te regelen; zeker als een .Accept() aanroep (in)direct .Visit() van de visitor aanroept. Ik moet eerlijk zeggen dat ik vandaag mijn pc nog niet heb aangeslingerd (ik tik vanaf mobiel), maar zal komende week nog eens dieper erin duiken.

Acties:
  • +1 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Ah, ik zie het volgens mij. Uitgaande van het voorbeeld in YouTube: Understanding The Visitor Design Pattern

code:
1
2
3
4
5
6
7
8
9
OfferVisitor visitor = new HotelOfferVisitor();

CreditCard bronze = new BronzeCreditCard();
CreditCard silver = new SilverCreditCard();
CreditCard gold = new GoldCreditCard();

bronze.accept(visitor);
silver.accept(visitor);
gold.accept(visitor);


Dit zou ik (zonder weet van het Visitor-pattern) oplossen op deze manier:

code:
1
2
3
4
5
6
7
8
9
OfferApplicator offer = new HotelOfferApplicator();

CreditCard bronze = new BronzeCreditCard();
CreditCard silver = new SilverCreditCard();
CreditCard gold = new GoldCreditCard();

offer.Apply(bronze);
offer.Apply(silver);
offer.Apply(gold);


Wat voor mij op het eerste gezicht 'cleaner' is; en de CreditCard klassen hoeven niet te weten van de class OfferApplicator. Echter zie ik een nadeel in mijn implementatie... Apply zou er namelijk zo uit zien:

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void Apply(CreditCard card) {
    switch (card) {
        case BronzeCreditCard bronze:
            applyBronze(bronze);
            break;
        case SilverCreditCard silver:
            applySilver(silver);
            break;
        case GoldCreditCard gold:
            applyGold(gold);
            break;
        default:
            throw new NotSupportedException("Geen idee wat voor kaart dat is");
    }
}


Hier is een nadeel dat een cast nodig is van de abstracte CreditCard naar de concrete klasse. Als er dus in de toekomst een kaarttype bij komt (PlatinumCreditCard) is er een kans dat dit in deze codebase over het hoofd wordt gezien en er runtime een exception kan optreden. Middels het visitor pattern zou dit allemaal strict getyped zijn en gezien volgens de methode in de video als volgt wordt gedaan:

code:
1
2
3
4
//De accept method van class PlatinumCreditCard
public void accept(OfferVisitor offerVisitor) {
    offerVisitor.visitPlatinumCreditCard(this);
}


zal het project niet compilen voordat deze implementatie in de interface van de visitor is toegevoegd en dien ten gevolge moet dit ook direct in alle concrete implementaties van de visitors worden toegepast; eerder zal het project niet compileren. Dit is een voordeel want er kan nergens een implementatie over het hoofd worden gezien.

Heb ik het goed gezien? Is dit hét voordeel?

Acties:
  • 0 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 09:52

Haan

dotnetter

Er zijn ook wel andere manieren om die switch te vermijden, bijvoorbeeld door een factory class te gebruiken. Die factory vraag je dan om de juiste implementatie terug te geven. Bij het toevoegen van een nieuw credit card type hoef je alleen de factory bij te werken. Bij het visitor pattern moet je dan de interface (en dus ook de implementatie(s)) uitbreiden, dat vind ik een van de grootste bezwaren bij dit pattern (zie de 'O' van SOLID).

Sowieso schrijf je ook unit tests, dus een implementatie 'over het hoofd zien' zal niet zo snel gebeuren, toch? ;)

[ Voor 54% gewijzigd door Haan op 15-08-2022 11:38 ]

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Haan schreef op maandag 15 augustus 2022 @ 10:47:
Sowieso schrijf je ook unit tests, dus een implementatie 'over het hoofd zien' zal niet zo snel gebeuren, toch? ;)
Natúúrlijk niet O-) .

Alsnog blijft met een factory hetzelfde probleem bestaan; die factory moet dan die switch case in zich hebben. In 'mijn' oplossing zouden die applyBronze() / applySilver() methods in de OfferApplicator interface (of als abstracte method in de abstracte klasse) kunnen bestaan dus óók maar op 1 plek waar het zou kunnen worden vergeten.

Maar mijn vraag gaat meer over het nut van het visitor pattern. Behalve bovenstaande voorbeeld zie ik geen echt voordeel van de visitor; of zie ik nog steeds iets over het hoofd?

Acties:
  • 0 Henk 'm!

  • bwerg
  • Registratie: Januari 2009
  • Niet online

bwerg

Internettrol

Haan schreef op maandag 15 augustus 2022 @ 10:47:
Bij het toevoegen van een nieuw credit card type hoef je alleen de factory bij te werken. Bij het visitor pattern moet je dan de interface (en dus ook de implementatie(s)) uitbreiden, dat vind ik een van de grootste bezwaren bij dit pattern (zie de 'O' van SOLID).
Waarom zou dat moeten? Voeg een nieuwe implementatie van CreditCard toe, implementeer daarin de Accept(OfferVisitor visitor) en je bent klaar, toch?

Dat je niet de implementatie en hoeft aan te passen is nou net het punt van het pattern. Laat staan dat je de interface zou moeten aanpassen, dan zou het geen pattern meer te noemen zijn.
Josk79 schreef op maandag 15 augustus 2022 @ 14:05:
Behalve bovenstaande voorbeeld zie ik geen echt voordeel van de visitor; of zie ik nog steeds iets over het hoofd?
Klopt. Het is nogal een voordeel dat je iets nieuws kan implementeren zonder spaghetti-stijl overal vanalles te moeten wijzigen. Vooral als je bijvoorbeeld een structuur in een api of library beschikbaar wil maken voor externe gebruikers. Die gebruikers zouden niet de code in je library aan te hoeven passen om iets met je structuur te kunnen doen.

Als je niet denkt aan onderhoudbaarheid, libraries en scheiding van verantwoordelijkheden, en je vindt een enkele case-switch wel acceptabel, nee, dan heeft het geen meerwaarde. Maar dat geldt voor zo ongeveer elke code pattern. :P

[ Voor 36% gewijzigd door bwerg op 15-08-2022 14:27 ]

Heeft geen speciale krachten en is daar erg boos over.


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
bwerg schreef op maandag 15 augustus 2022 @ 14:24:
Waarom zou dat moeten? Voeg een nieuwe implementatie van CreditCard toe, implementeer daarin de Accept(OfferVisitor visitor) en je bent klaar, toch?
Nee, alsnog zou dan VisitXxxxx() moeten worden toegevoegd aan de (interface van de) Visitors. Dit is de implementatie van de CSharpSyntaxVisitor in Microsoft.CodeAnalysis.CSharp 8)7


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
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    public abstract class CSharpSyntaxVisitor
    {
        protected CSharpSyntaxVisitor();

        public virtual void DefaultVisit(SyntaxNode node);
        public virtual void Visit(SyntaxNode? node);
        public virtual void VisitAccessorDeclaration(AccessorDeclarationSyntax node);
        public virtual void VisitAccessorList(AccessorListSyntax node);
        public virtual void VisitAliasQualifiedName(AliasQualifiedNameSyntax node);
        public virtual void VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node);
        public virtual void VisitAnonymousObjectCreationExpression(AnonymousObjectCreationExpressionSyntax node);
        public virtual void VisitAnonymousObjectMemberDeclarator(AnonymousObjectMemberDeclaratorSyntax node);
        public virtual void VisitArgument(ArgumentSyntax node);
        public virtual void VisitArgumentList(ArgumentListSyntax node);
        public virtual void VisitArrayCreationExpression(ArrayCreationExpressionSyntax node);
        public virtual void VisitArrayRankSpecifier(ArrayRankSpecifierSyntax node);
        public virtual void VisitArrayType(ArrayTypeSyntax node);
        public virtual void VisitArrowExpressionClause(ArrowExpressionClauseSyntax node);
        public virtual void VisitAssignmentExpression(AssignmentExpressionSyntax node);
        public virtual void VisitAttribute(AttributeSyntax node);
        public virtual void VisitAttributeArgument(AttributeArgumentSyntax node);
        public virtual void VisitAttributeArgumentList(AttributeArgumentListSyntax node);
        public virtual void VisitAttributeList(AttributeListSyntax node);
        public virtual void VisitAttributeTargetSpecifier(AttributeTargetSpecifierSyntax node);
        public virtual void VisitAwaitExpression(AwaitExpressionSyntax node);
        public virtual void VisitBadDirectiveTrivia(BadDirectiveTriviaSyntax node);
        public virtual void VisitBaseExpression(BaseExpressionSyntax node);
        public virtual void VisitBaseList(BaseListSyntax node);
        public virtual void VisitBinaryExpression(BinaryExpressionSyntax node);
        public virtual void VisitBinaryPattern(BinaryPatternSyntax node);
        public virtual void VisitBlock(BlockSyntax node);
        public virtual void VisitBracketedArgumentList(BracketedArgumentListSyntax node);
        public virtual void VisitBracketedParameterList(BracketedParameterListSyntax node);
        public virtual void VisitBreakStatement(BreakStatementSyntax node);
        public virtual void VisitCasePatternSwitchLabel(CasePatternSwitchLabelSyntax node);
        public virtual void VisitCaseSwitchLabel(CaseSwitchLabelSyntax node);
        public virtual void VisitCastExpression(CastExpressionSyntax node);
        public virtual void VisitCatchClause(CatchClauseSyntax node);
        public virtual void VisitCatchDeclaration(CatchDeclarationSyntax node);
        public virtual void VisitCatchFilterClause(CatchFilterClauseSyntax node);
        public virtual void VisitCheckedExpression(CheckedExpressionSyntax node);
        public virtual void VisitCheckedStatement(CheckedStatementSyntax node);
        public virtual void VisitClassDeclaration(ClassDeclarationSyntax node);
        public virtual void VisitClassOrStructConstraint(ClassOrStructConstraintSyntax node);
        public virtual void VisitCompilationUnit(CompilationUnitSyntax node);
        public virtual void VisitConditionalAccessExpression(ConditionalAccessExpressionSyntax node);
        public virtual void VisitConditionalExpression(ConditionalExpressionSyntax node);
        public virtual void VisitConstantPattern(ConstantPatternSyntax node);
        public virtual void VisitConstructorConstraint(ConstructorConstraintSyntax node);
        public virtual void VisitConstructorDeclaration(ConstructorDeclarationSyntax node);
        public virtual void VisitConstructorInitializer(ConstructorInitializerSyntax node);
        public virtual void VisitContinueStatement(ContinueStatementSyntax node);
        public virtual void VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node);
        public virtual void VisitConversionOperatorMemberCref(ConversionOperatorMemberCrefSyntax node);
        public virtual void VisitCrefBracketedParameterList(CrefBracketedParameterListSyntax node);
        public virtual void VisitCrefParameter(CrefParameterSyntax node);
        public virtual void VisitCrefParameterList(CrefParameterListSyntax node);
        public virtual void VisitDeclarationExpression(DeclarationExpressionSyntax node);
        public virtual void VisitDeclarationPattern(DeclarationPatternSyntax node);
        public virtual void VisitDefaultConstraint(DefaultConstraintSyntax node);
        public virtual void VisitDefaultExpression(DefaultExpressionSyntax node);
        public virtual void VisitDefaultSwitchLabel(DefaultSwitchLabelSyntax node);
        public virtual void VisitDefineDirectiveTrivia(DefineDirectiveTriviaSyntax node);
        public virtual void VisitDelegateDeclaration(DelegateDeclarationSyntax node);
        public virtual void VisitDestructorDeclaration(DestructorDeclarationSyntax node);
        public virtual void VisitDiscardDesignation(DiscardDesignationSyntax node);
        public virtual void VisitDiscardPattern(DiscardPatternSyntax node);
        public virtual void VisitDocumentationCommentTrivia(DocumentationCommentTriviaSyntax node);
        public virtual void VisitDoStatement(DoStatementSyntax node);
        public virtual void VisitElementAccessExpression(ElementAccessExpressionSyntax node);
        public virtual void VisitElementBindingExpression(ElementBindingExpressionSyntax node);
        public virtual void VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node);
        public virtual void VisitElseClause(ElseClauseSyntax node);
        public virtual void VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node);
        public virtual void VisitEmptyStatement(EmptyStatementSyntax node);
        public virtual void VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node);
        public virtual void VisitEndRegionDirectiveTrivia(EndRegionDirectiveTriviaSyntax node);
        public virtual void VisitEnumDeclaration(EnumDeclarationSyntax node);
        public virtual void VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node);
        public virtual void VisitEqualsValueClause(EqualsValueClauseSyntax node);
        public virtual void VisitErrorDirectiveTrivia(ErrorDirectiveTriviaSyntax node);
        public virtual void VisitEventDeclaration(EventDeclarationSyntax node);
        public virtual void VisitEventFieldDeclaration(EventFieldDeclarationSyntax node);
        public virtual void VisitExplicitInterfaceSpecifier(ExplicitInterfaceSpecifierSyntax node);
        public virtual void VisitExpressionStatement(ExpressionStatementSyntax node);
        public virtual void VisitExternAliasDirective(ExternAliasDirectiveSyntax node);
        public virtual void VisitFieldDeclaration(FieldDeclarationSyntax node);
        public virtual void VisitFinallyClause(FinallyClauseSyntax node);
        public virtual void VisitFixedStatement(FixedStatementSyntax node);
        public virtual void VisitForEachStatement(ForEachStatementSyntax node);
        public virtual void VisitForEachVariableStatement(ForEachVariableStatementSyntax node);
        public virtual void VisitForStatement(ForStatementSyntax node);
        public virtual void VisitFromClause(FromClauseSyntax node);
        public virtual void VisitFunctionPointerCallingConvention(FunctionPointerCallingConventionSyntax node);
        public virtual void VisitFunctionPointerParameter(FunctionPointerParameterSyntax node);
        public virtual void VisitFunctionPointerParameterList(FunctionPointerParameterListSyntax node);
        public virtual void VisitFunctionPointerType(FunctionPointerTypeSyntax node);
        public virtual void VisitFunctionPointerUnmanagedCallingConvention(FunctionPointerUnmanagedCallingConventionSyntax node);
        public virtual void VisitFunctionPointerUnmanagedCallingConventionList(FunctionPointerUnmanagedCallingConventionListSyntax node);
        public virtual void VisitGenericName(GenericNameSyntax node);
        public virtual void VisitGlobalStatement(GlobalStatementSyntax node);
        public virtual void VisitGotoStatement(GotoStatementSyntax node);
        public virtual void VisitGroupClause(GroupClauseSyntax node);
        public virtual void VisitIdentifierName(IdentifierNameSyntax node);
        public virtual void VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node);
        public virtual void VisitIfStatement(IfStatementSyntax node);
        public virtual void VisitImplicitArrayCreationExpression(ImplicitArrayCreationExpressionSyntax node);
        public virtual void VisitImplicitElementAccess(ImplicitElementAccessSyntax node);
        public virtual void VisitImplicitObjectCreationExpression(ImplicitObjectCreationExpressionSyntax node);
        public virtual void VisitImplicitStackAllocArrayCreationExpression(ImplicitStackAllocArrayCreationExpressionSyntax node);
        public virtual void VisitIncompleteMember(IncompleteMemberSyntax node);
        public virtual void VisitIndexerDeclaration(IndexerDeclarationSyntax node);
        public virtual void VisitIndexerMemberCref(IndexerMemberCrefSyntax node);
        public virtual void VisitInitializerExpression(InitializerExpressionSyntax node);
        public virtual void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node);
        public virtual void VisitInterpolatedStringExpression(InterpolatedStringExpressionSyntax node);
        public virtual void VisitInterpolatedStringText(InterpolatedStringTextSyntax node);
        public virtual void VisitInterpolation(InterpolationSyntax node);
        public virtual void VisitInterpolationAlignmentClause(InterpolationAlignmentClauseSyntax node);
        public virtual void VisitInterpolationFormatClause(InterpolationFormatClauseSyntax node);
        public virtual void VisitInvocationExpression(InvocationExpressionSyntax node);
        public virtual void VisitIsPatternExpression(IsPatternExpressionSyntax node);
        public virtual void VisitJoinClause(JoinClauseSyntax node);
        public virtual void VisitJoinIntoClause(JoinIntoClauseSyntax node);
        public virtual void VisitLabeledStatement(LabeledStatementSyntax node);
        public virtual void VisitLetClause(LetClauseSyntax node);
        public virtual void VisitLineDirectiveTrivia(LineDirectiveTriviaSyntax node);
        public virtual void VisitLiteralExpression(LiteralExpressionSyntax node);
        public virtual void VisitLoadDirectiveTrivia(LoadDirectiveTriviaSyntax node);
        public virtual void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node);
        public virtual void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node);
        public virtual void VisitLockStatement(LockStatementSyntax node);
        public virtual void VisitMakeRefExpression(MakeRefExpressionSyntax node);
        public virtual void VisitMemberAccessExpression(MemberAccessExpressionSyntax node);
        public virtual void VisitMemberBindingExpression(MemberBindingExpressionSyntax node);
        public virtual void VisitMethodDeclaration(MethodDeclarationSyntax node);
        public virtual void VisitNameColon(NameColonSyntax node);
        public virtual void VisitNameEquals(NameEqualsSyntax node);
        public virtual void VisitNameMemberCref(NameMemberCrefSyntax node);
        public virtual void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node);
        public virtual void VisitNullableDirectiveTrivia(NullableDirectiveTriviaSyntax node);
        public virtual void VisitNullableType(NullableTypeSyntax node);
        public virtual void VisitObjectCreationExpression(ObjectCreationExpressionSyntax node);
        public virtual void VisitOmittedArraySizeExpression(OmittedArraySizeExpressionSyntax node);
        public virtual void VisitOmittedTypeArgument(OmittedTypeArgumentSyntax node);
        public virtual void VisitOperatorDeclaration(OperatorDeclarationSyntax node);
        public virtual void VisitOperatorMemberCref(OperatorMemberCrefSyntax node);
        public virtual void VisitOrderByClause(OrderByClauseSyntax node);
        public virtual void VisitOrdering(OrderingSyntax node);
        public virtual void VisitParameter(ParameterSyntax node);
        public virtual void VisitParameterList(ParameterListSyntax node);
        public virtual void VisitParenthesizedExpression(ParenthesizedExpressionSyntax node);
        public virtual void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node);
        public virtual void VisitParenthesizedPattern(ParenthesizedPatternSyntax node);
        public virtual void VisitParenthesizedVariableDesignation(ParenthesizedVariableDesignationSyntax node);
        public virtual void VisitPointerType(PointerTypeSyntax node);
        public virtual void VisitPositionalPatternClause(PositionalPatternClauseSyntax node);
        public virtual void VisitPostfixUnaryExpression(PostfixUnaryExpressionSyntax node);
        public virtual void VisitPragmaChecksumDirectiveTrivia(PragmaChecksumDirectiveTriviaSyntax node);
        public virtual void VisitPragmaWarningDirectiveTrivia(PragmaWarningDirectiveTriviaSyntax node);
        public virtual void VisitPredefinedType(PredefinedTypeSyntax node);
        public virtual void VisitPrefixUnaryExpression(PrefixUnaryExpressionSyntax node);
        public virtual void VisitPrimaryConstructorBaseType(PrimaryConstructorBaseTypeSyntax node);
        public virtual void VisitPropertyDeclaration(PropertyDeclarationSyntax node);
        public virtual void VisitPropertyPatternClause(PropertyPatternClauseSyntax node);
        public virtual void VisitQualifiedCref(QualifiedCrefSyntax node);
        public virtual void VisitQualifiedName(QualifiedNameSyntax node);
        public virtual void VisitQueryBody(QueryBodySyntax node);
        public virtual void VisitQueryContinuation(QueryContinuationSyntax node);
        public virtual void VisitQueryExpression(QueryExpressionSyntax node);
        public virtual void VisitRangeExpression(RangeExpressionSyntax node);
        public virtual void VisitRecordDeclaration(RecordDeclarationSyntax node);
        public virtual void VisitRecursivePattern(RecursivePatternSyntax node);
        public virtual void VisitReferenceDirectiveTrivia(ReferenceDirectiveTriviaSyntax node);
        public virtual void VisitRefExpression(RefExpressionSyntax node);
        public virtual void VisitRefType(RefTypeSyntax node);
        public virtual void VisitRefTypeExpression(RefTypeExpressionSyntax node);
        public virtual void VisitRefValueExpression(RefValueExpressionSyntax node);
        public virtual void VisitRegionDirectiveTrivia(RegionDirectiveTriviaSyntax node);
        public virtual void VisitRelationalPattern(RelationalPatternSyntax node);
        public virtual void VisitReturnStatement(ReturnStatementSyntax node);
        public virtual void VisitSelectClause(SelectClauseSyntax node);
        public virtual void VisitShebangDirectiveTrivia(ShebangDirectiveTriviaSyntax node);
        public virtual void VisitSimpleBaseType(SimpleBaseTypeSyntax node);
        public virtual void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node);
        public virtual void VisitSingleVariableDesignation(SingleVariableDesignationSyntax node);
        public virtual void VisitSizeOfExpression(SizeOfExpressionSyntax node);
        public virtual void VisitSkippedTokensTrivia(SkippedTokensTriviaSyntax node);
        public virtual void VisitStackAllocArrayCreationExpression(StackAllocArrayCreationExpressionSyntax node);
        public virtual void VisitStructDeclaration(StructDeclarationSyntax node);
        public virtual void VisitSubpattern(SubpatternSyntax node);
        public virtual void VisitSwitchExpression(SwitchExpressionSyntax node);
        public virtual void VisitSwitchExpressionArm(SwitchExpressionArmSyntax node);
        public virtual void VisitSwitchSection(SwitchSectionSyntax node);
        public virtual void VisitSwitchStatement(SwitchStatementSyntax node);
        public virtual void VisitThisExpression(ThisExpressionSyntax node);
        public virtual void VisitThrowExpression(ThrowExpressionSyntax node);
        public virtual void VisitThrowStatement(ThrowStatementSyntax node);
        public virtual void VisitTryStatement(TryStatementSyntax node);
        public virtual void VisitTupleElement(TupleElementSyntax node);
        public virtual void VisitTupleExpression(TupleExpressionSyntax node);
        public virtual void VisitTupleType(TupleTypeSyntax node);
        public virtual void VisitTypeArgumentList(TypeArgumentListSyntax node);
        public virtual void VisitTypeConstraint(TypeConstraintSyntax node);
        public virtual void VisitTypeCref(TypeCrefSyntax node);
        public virtual void VisitTypeOfExpression(TypeOfExpressionSyntax node);
        public virtual void VisitTypeParameter(TypeParameterSyntax node);
        public virtual void VisitTypeParameterConstraintClause(TypeParameterConstraintClauseSyntax node);
        public virtual void VisitTypeParameterList(TypeParameterListSyntax node);
        public virtual void VisitTypePattern(TypePatternSyntax node);
        public virtual void VisitUnaryPattern(UnaryPatternSyntax node);
        public virtual void VisitUndefDirectiveTrivia(UndefDirectiveTriviaSyntax node);
        public virtual void VisitUnsafeStatement(UnsafeStatementSyntax node);
        public virtual void VisitUsingDirective(UsingDirectiveSyntax node);
        public virtual void VisitUsingStatement(UsingStatementSyntax node);
        public virtual void VisitVariableDeclaration(VariableDeclarationSyntax node);
        public virtual void VisitVariableDeclarator(VariableDeclaratorSyntax node);
        public virtual void VisitVarPattern(VarPatternSyntax node);
        public virtual void VisitWarningDirectiveTrivia(WarningDirectiveTriviaSyntax node);
        public virtual void VisitWhenClause(WhenClauseSyntax node);
        public virtual void VisitWhereClause(WhereClauseSyntax node);
        public virtual void VisitWhileStatement(WhileStatementSyntax node);
        public virtual void VisitWithExpression(WithExpressionSyntax node);
        public virtual void VisitXmlCDataSection(XmlCDataSectionSyntax node);
        public virtual void VisitXmlComment(XmlCommentSyntax node);
        public virtual void VisitXmlCrefAttribute(XmlCrefAttributeSyntax node);
        public virtual void VisitXmlElement(XmlElementSyntax node);
        public virtual void VisitXmlElementEndTag(XmlElementEndTagSyntax node);
        public virtual void VisitXmlElementStartTag(XmlElementStartTagSyntax node);
        public virtual void VisitXmlEmptyElement(XmlEmptyElementSyntax node);
        public virtual void VisitXmlName(XmlNameSyntax node);
        public virtual void VisitXmlNameAttribute(XmlNameAttributeSyntax node);
        public virtual void VisitXmlPrefix(XmlPrefixSyntax node);
        public virtual void VisitXmlProcessingInstruction(XmlProcessingInstructionSyntax node);
        public virtual void VisitXmlText(XmlTextSyntax node);
        public virtual void VisitXmlTextAttribute(XmlTextAttributeSyntax node);
        public virtual void VisitYieldStatement(YieldStatementSyntax node);
    }


Dit is door MS gegenereerd met een Source Generator :| . Yo dawg I heard you like Source Generators...

Acties:
  • 0 Henk 'm!

  • bwerg
  • Registratie: Januari 2009
  • Niet online

bwerg

Internettrol

Josk79 schreef op maandag 15 augustus 2022 @ 15:07:
[...]


Nee, alsnog zou dan VisitXxxxx() moeten worden toegevoegd aan de (interface van de) Visitors.
Nou ja, het moet niet. Je kan prima een Accept(Visitor visitor) implementeren op basis van bestaande methoden van de Visitor.

Heeft geen speciale krachten en is daar erg boos over.


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
bwerg schreef op maandag 15 augustus 2022 @ 14:24:
Klopt. Het is nogal een voordeel dat je iets nieuws kan implementeren zonder spaghetti-stijl overal vanalles te moeten wijzigen. Vooral als je bijvoorbeeld een structuur in een api of library beschikbaar wil maken voor externe gebruikers. Die gebruikers zouden niet de code in je library aan te hoeven passen om iets met je structuur te kunnen doen.

Als je niet denkt aan onderhoudbaarheid, libraries en scheiding van verantwoordelijkheden, en je vindt een enkele case-switch wel acceptabel, nee, dan heeft het geen meerwaarde. Maar dat geldt voor zo ongeveer elke code pattern. :P
In mijn ogen is het Visitor Pattern meer spaghetti... want je maakt de visitee aware van de visitor.

In hoeverre is dit meer spaghetti / lastiger onderhoudbaar
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
public abstract class OfferApplicator {
    public void Apply(CreditCard card) {
        switch (card) {
            case BronzeCreditCard bronze:
                ApplyBronze(bronze);
                break;
            case SilverCreditCard silver:
                ApplySilver(silver);
                break;
            case GoldCreditCard gold:
                ApplyGold(gold);
                break;
            default:
                throw new NotSupportedException("Geen idee wat voor kaart dat is");
        }
    }
    
    protected abstract void ApplyBronze(BronzeCreditCard card);
    protected abstract void ApplySilver(SilverCreditCard card);
    protected abstract void ApplyGold(GoldCreditCard card);
}

public class HotelOfferApplicator : OfferApplicator {
    protected override void ApplyBronze(BronzeCreditCard card)
    {
        //Do something...
    }

    protected override void ApplySilver(SilverCreditCard card);
    {
        //Do something...
    }

    protected override void ApplyGold(GoldCreditCard card);
    {
        //Do something...
    }
}


dan dit?

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IOfferVisitor
{
    void Visit(BronzeCreditCard bronzeCreditCard);
    void Visit(SilverCreditCard silverCreditCard);
    void Visit(GoldCreditCard goldCreditCard);
}
public class HotelOfferVisitor : IOfferVisitor
{
    public void Visit(BronzeCreditCard bronzeCreditCard)
    {
        //Do something...
    }

    public void Visit(SilverCreditCard silverCreditCard)
    {
        //Do something...
    }

    public void Visit(GoldCreditCard goldCreditCard)
    {
        //Do something...
    }
}


Ze voldoen beiden net-zo-niet aan Open/Close en, naar mijn idee, voldoet mijn implementatie iets meer aan SRP omdat de visitee geen responsability krijgt om de visitor uit te nodigen.


Ik kom misschien betweterig over nu, en dit is geen aanval op jou, maar dit is hoe ik er over denk en nogsteeds geen voordeel zie van het visitor pattern. Maar ik verwacht niet dat ik slimmer ben dan de gang of four, dus ik besef dat ik iets niet 'zie'. Maar ik weet niet wat.

Acties:
  • 0 Henk 'm!

  • Stukfruit
  • Registratie: Oktober 2007
  • Niet online
Josk79 schreef op maandag 15 augustus 2022 @ 15:48:
[...]

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IOfferVisitor
{
    void Visit(BronzeCreditCard bronzeCreditCard);
    void Visit(SilverCreditCard silverCreditCard);
    void Visit(GoldCreditCard goldCreditCard);
}
public class HotelOfferVisitor : IOfferVisitor
{
    public void Visit(BronzeCreditCard bronzeCreditCard)
    {
        //Do something...
    }

    public void Visit(SilverCreditCard silverCreditCard)
    {
        //Do something...
    }

    public void Visit(GoldCreditCard goldCreditCard)
    {
        //Do something...
    }
}
Waarschuwing vooraf: ik probeer het high level te houden om niet te verzanden in (ongetwijfeld coole) details + snelle reactie.

Waarom is `IOfferVisitor` een interface, terwijl verderop wordt verwacht dat deze zaken voor specifieke implementaties afhandelt?

Hoe ga je dit testbaar maken zonder te eindigen met tientallen tests die in de basis hetzelfde doen?

De `Visit`-methode hoeft niet hardcoded alle implementaties mee te dragen, zoals @bwerg volgens mij ook zei. Waarom denk jij dat dit wel nodig is? Allebei de voorbeelden zorgen voor Italiaans voedsel omdat ze naast SRP ook niet DRY zijn :P

spoiler:
Ik pak de buzzwordbingokaart er alvast even bij :+

Dat zit wel Schnorr.


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Stukfruit schreef op maandag 15 augustus 2022 @ 16:48:
[...]
Waarschuwing vooraf: ik probeer het high level te houden om niet te verzanden in (ongetwijfeld coole) details + snelle reactie.
Top.
Waarom is `IOfferVisitor` een interface, terwijl verderop wordt verwacht dat deze zaken voor specifieke implementaties afhandelt?
Geen idee, dit komt uit het voorbeeld. Is het een slecht voorbeeld?
Hoe ga je dit testbaar maken zonder te eindigen met tientallen tests die in de basis hetzelfde doen?
Tja, de XxxOfferVisitor heeft dan toch precies hetzelfde probleem als de XxxOfferApplicator?
De `Visit`-methode hoeft niet hardcoded alle implementaties mee te dragen, zoals @bwerg volgens mij ook zei. Waarom denk jij dat dit wel nodig is? Allebei de voorbeelden zorgen voor Italiaans voedsel omdat ze naast SRP ook niet DRY zijn :P
De voorbeelden die ik heb bekeken, waaronder de gelinkte youtube video lijken dat wel te tonen. En ook de CSharpSyntaxVisitor van Microsoft heeft alle implementaties aan boord; meer dan tweehonderd VisitXxx() methods.

Ook dit plaatje van Wikipedia toont visit methodes voor alle concrete instanties; visit(ConcretesElementA) en visit(ConcretesElementB)

Afbeeldingslocatie: https://upload.wikimedia.org/wikipedia/en/thumb/e/eb/Visitor_design_pattern.svg/1024px-Visitor_design_pattern.svg.png

Dus dat er een visit() method is voor alle concrete implementaties lijkt onderdeel van het pattern te zijn.

Acties:
  • +1 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-05 17:05
Het idee is dat je operations kunt "toevoegen" aan classes, zonder de classes zelf aan te hoeven passen, je moet alleen een nieuwe concrete visitor schrijven, waarbij de compiler als het even meezit je op de vingers tikt als je een operation voor een specifiek object vergeet te schrijven in die concrete visitor:

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
66
67
68
69
70
71
72
73
74
75
using System;
using System.Collections.Generic;

public interface IVisitor
{
    void Visit(A a);
    void Visit(B b);
}

public interface IVisitable
{
    void Accept(IVisitor v);
}

public class SomeVisitor : IVisitor
{
    public void Visit(A a)
    {
        Console.WriteLine($"Some operation on an A");
    }

    public void Visit(B b)
    {
        Console.WriteLine($"Some operation on a B");
    }
}

public class SomeOtherVisitor : IVisitor
{
    public void Visit(A a)
    {
        Console.WriteLine($"Some other operation on an A");
    }

    public void Visit(B b)
    {
        Console.WriteLine($"Some other operation on a B");
    }
}

public class A : IVisitable
{
    public void Accept(IVisitor v)
    {
        v.Visit(this);
    }
}

public class B : IVisitable
{
    public void Accept(IVisitor v)
    {
        v.Visit(this);
    }
}

public static class Program
{
    public static void Main(string[] args)
    {
        var objects = new List<IVisitable> { new A(), new B() };

        var someVisitor = new SomeVisitor();
        foreach (var o in objects)
        {
            o.Accept(someVisitor);
        }

        var someOtherVisitor = new SomeOtherVisitor();
        foreach (var o in objects)
        {
            o.Accept(someOtherVisitor);
        }
    }
}


Merk op dat een nieuwe operation toevoegen geen veranderingen aan de A en B objecten vereist (als je bij alle informatie van A en B kunt waar je bij moet :P)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
@farlane bedankt voor je voorbeeld. Maar wat is dan het voordeel t.o.v. onderstaande methode? Alleen het voorkomen van die switch-case en 100% static typing?

In mijn voorbeeld kun je ook operations toevoegen zonder dat je de classes zelf hoeft aan te passen en tevens vervuil ik die Operators niet met vele publieke VisitXxx methods. Tevens hoeft de visitee (operatee?) niet te worden vervuild met een .Accept().

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
66
67
68
69
70
71
72
73
using System;
using System.Collections.Generic;

public abstract class ItemOperation
{
    public void OperateOn(IItem item) {
        switch (item) {
            case A a:
                OperateOn(a);
                break;
            case B b:
                OperateOn(b);
                break;
            default:
                throw new Exception("What is that?");
        }
    }

    protected abstract void OperateOn(A a);
    protected abstract void OperateOn(B b);
}

public class SomeOperator : ItemOperation
{
    protected override void OperateOn(A a)
    {
        Console.WriteLine($"Some operation on an A");
    }

    protected override void OperateOn(B b)
    {
        Console.WriteLine($"Some operation on a B");
    }
}

public class SomeOtherOperator : ItemOperation
{
    protected override void OperateOn(A a)
    {
        Console.WriteLine($"Some other operation on an A");
    }

    protected override void OperateOn(B b)
    {
        Console.WriteLine($"Some other operation on a B");
    }
}

public interface IItem {}

public class A : IItem {}

public class B : IItem {}

public static class Program
{
    public static void Main(string[] args)
    {
        var objects = new List<IItem> { new A(), new B() };

        var someOperator = new SomeOperator();
        foreach (var o in objects)
        {
            someOperator.OperateOn(o);
        }

        var someOtherOperator = new SomeOtherOperator();
        foreach (var o in objects)
        {
            someOtherOperator.OperateOn(o);
        }
    }
}

Acties:
  • 0 Henk 'm!

  • NC83
  • Registratie: Juni 2007
  • Laatst online: 04-03 07:00
Betere voorbeelden van het vistor patroon zijn de predicates die je kan gebruiken tijdens het zoeken door een container. Hoe je door die container heen loopt wil je eigenlijk niet hoeven te schrijven je wil gewoon kunnen zeggen hier is welke functie je moet gebruiken om te bepalen of je huidige element is wat je wil hebben.

Voorgeeft me voor de C++ code maar containers en STL laten je hier gewoon vaker zien waarom je een visitor gebruikt.
C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int valueToFind = 0;
auto iterator = myValues.begin();
for (; iterator != myValues.end(); ++iterator)
{
   if (*iterator == valueToFind)
   {
      break;
   }
}
//vs
auto iterator = std::find(myValues.begin(), myValues.end(), 
             [valueToFind](const auto& value) 
                { return value == valueToFind;}
             );

Die eerste loop moet je iedere keer aanpassen als je een ander type in je container gebruikt. Terwijl je in de laatste variant alleen je predicate moet aanpassen ("[] () {} "is een lambda functie in C++, ookwel function object). Auto is als var in C#.

Dit laat heel goed zien waarom je de daadwerkelijke predicate code uit je loop wil halen ipv bieden te verweven tot een geheel.

ex-FE Programmer: CMR:DiRT2,DiRT 3, DiRT Showdown, GRID 2, Mad Max


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
@NC83 ik herken daarin geen visitor. In c# zou je hier een lambda kunnen gebruiken, e.g.

C#:
1
2
3
var isMatch = (SomeClass item) => item.Value == 123;

var selectedItems = items.Where(isMatch);


(Ofzoiets)

Of ik misinterpreteer je antwoord (mijn cpp kennis is laag).

Acties:
  • 0 Henk 'm!

  • NC83
  • Registratie: Juni 2007
  • Laatst online: 04-03 07:00
Hoe dat predicate wordt uitgevoerd is wel degelijk een visitor patroon, je visitor hier is je lambda functie.

Aan de achterkant moet die Where functie over je elementen heen lopen en het element aan je lambda functie geven en dat is het visitor patroon. Dat je geen echte visitor schrijft zoals het er in je voorbeelden uit ziet maakt nog niet dat je het patroon niet gebruikt.

[ Voor 19% gewijzigd door NC83 op 15-08-2022 21:51 ]

ex-FE Programmer: CMR:DiRT2,DiRT 3, DiRT Showdown, GRID 2, Mad Max


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Ik kreeg net deze url door: https://craftinginterpret...e.html#working-with-trees

Ik denk dat dit stukje het enige voordeel t.o.v. mijn methode omschrijft; 100% static dispatch. Wat volgens mij voornamelijk een performance gain t.o.v. mijn methode heeft. Maar om eerlijk te zijn; als performance geen key issue is; ga ik liever voor mijn methode want die vind ik een stuk netter/leesbaarder.
In Design Patterns, both of these methods are confusingly named visit(), and they rely on overloading to distinguish them. This leads some readers to think that the correct visit method is chosen at runtime based on its parameter type. That isn’t the case. Unlike overriding, overloading is statically dispatched at compile time.

Acties:
  • Beste antwoord
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-05 17:05
Josk79 schreef op maandag 15 augustus 2022 @ 22:47:
Ik kreeg net deze url door: https://craftinginterpret...e.html#working-with-trees

Ik denk dat dit stukje het enige voordeel t.o.v. mijn methode omschrijft; 100% static dispatch. Wat volgens mij voornamelijk een performance gain t.o.v. mijn methode heeft. Maar om eerlijk te zijn; als performance geen key issue is; ga ik liever voor mijn methode want die vind ik een stuk netter/leesbaarder.
[...]
Je schrijft met je switch de dispatching handmatig uit, als je er een mist krijg je een runtime exception, ooit, misschien. Ik laat dat liever aan de compiler over tbh.
NC83 schreef op maandag 15 augustus 2022 @ 21:50:
Hoe dat predicate wordt uitgevoerd is wel degelijk een visitor patroon, je visitor hier is je lambda functie.
Ik vraag me af of je hier mag spreken van een visitor pattern aangezien de dispatch niet dynamisch is (het type van value is hetzelfde voor elke value).

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Hoi @farlane, ja dat over die runtime exception gaf ik in https://gathering.tweaker.../quote_message/72431236/0 als nadeel. Heb net ook even een performance test gedaan.

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
        public void Setup()
        {
            _objects = new List<IVisitable>();
            for (int idx = 0; idx < 1000; idx++)
            {
                _objects.Add(new A());
                _objects.Add(new B());
                _objects.Add(new C());
                _objects.Add(new D());
                _objects.Add(new E());
                _objects.Add(new F());
                _objects.Add(new G());
                _objects.Add(new H());
                _objects.Add(new I());
                _objects.Add(new J());
            }
        }

        public void RunVisitor()
        {
            var someVisitor = new SomeVisitor();
            var someOtherVisitor = new SomeOtherVisitor();
            foreach (var o in _objects) { o.Accept(someVisitor); }
            foreach (var o in _objects) { o.Accept(someOtherVisitor); }
        }

        public void RunOperator()
        {
            var someOperator = new SomeOperator();
            var someOtherOperator = new SomeOtherOperator();
            foreach (var o in _objects) { someOperator.OperateOn(o); }
            foreach (var o in _objects) { someOtherOperator.OperateOn(o); }
        }


Geeft als resultaat:
code:
1
2
3
4
|      Method |     Mean |   Error |  StdDev |
|------------ |---------:|--------:|--------:|
|  RunVisitor | 158.7 us | 2.97 us | 3.05 us |
| RunOperator | 337.0 us | 2.62 us | 2.18 us |


Ik begrijp het voordeel nu wel. Maar in onze codebase zal ik er niet snel voor kiezen (en dit is een mening) want ik vind de verhoogde complexiteit niet opwegen tegen de 2 voordelen (static dispatch + performance).

Nadelen van visitor pattern vind ik: lastiger leesbare code en het feit dat de visitee een .Accept() method nodig heeft en dus direct een afhankelijkheid heeft van de interface van de visitor.

Acties:
  • 0 Henk 'm!

  • Stukfruit
  • Registratie: Oktober 2007
  • Niet online
Josk79 schreef op maandag 15 augustus 2022 @ 18:01:
Geen idee, dit komt uit het voorbeeld. Is het een slecht voorbeeld?
Vind ik wel.
De voorbeelden die ik heb bekeken, waaronder de gelinkte youtube video lijken dat wel te tonen. En ook de CSharpSyntaxVisitor van Microsoft heeft alle implementaties aan boord; meer dan tweehonderd VisitXxx() methods.
De rest hoef ik vast niet meer op te reageren omdat @farlane al een prachtig voorbeeld gaf, maar als je de code van Roslyn induikt dan zie je bijvoorbeeld dat `VisitCastExpression` uit het door jou aangehaalde `CSharpSyntaxVisitor` op het eerste gezicht z'n eigen implementatie lijkt te hebben:
https://github.com/dotnet...rpCastReducer.Rewriter.cs

Misschien zit ik er helemaal naast omdat ik verder nog nooit naar de code van Roslyn heb gekeken, maar is dat spul niet allemaal "gewoon" gegenereerd op basis van nette en compacte code?

Zijn hier toevallig Roslyn-experts aanwezig in de kamer? :Y

Dat zit wel Schnorr.


Acties:
  • 0 Henk 'm!

  • Stukfruit
  • Registratie: Oktober 2007
  • Niet online
Josk79 schreef op maandag 15 augustus 2022 @ 23:50:
Nadelen van visitor pattern vind ik: lastiger leesbare code en het feit dat de visitee een .Accept() method nodig heeft en dus direct een afhankelijkheid heeft van de interface van de visitor.
Voor een simpele rekenmachine is het overengineering, maar bij een compiler en/of set met tools zoals die analyzers is het geen overbodige luxe.

Zeker als je later je team gaat uitbreiden of uit het project stapt en het werk aan anderen overlaat die niet iedere zondagavond aan de slag willen gaan met het rechtzetten van omvallende switch-cases. Dat is waar je het voor doet :Y

Dat zit wel Schnorr.


Acties:
  • +1 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 07:54
Stel dat je een interface Something hebt, en de klasse A, B, en C implementeren dit. Something heeft de operations foo, bar en baz. In OOP betekend dit:
  • De interface Something heeft de functies foo, bar en baz. Maar heeft niet niet persé een implementator voor.
  • De klasse A, B, en C implementeren deze drie functies: foo, bar en baz.
Dit betekend dat A, B, en C de hoofd-groepering zijn:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Something {
    foo()
    bar()
    baz()
}

class A implements Something {
    foo()
    bar()
    baz()
}

class B implements Something {
    foo()
    bar()
    baz()
}

class C implements Something {
    foo()
    bar()
    baz()
}
Wat het visitor pattern simpel gezegd doet is deze groepering omdraaien. Zodat de functies (operations) de hoofdgroepering worden.
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
interface Something {
    accept(SomethingVisitor visitor);
}

interface SomethingVisitor {
    visitA(A thing);
    visitB(B thing);
    visitC(C thing);
}

class A implements Something {
    accept(SomethingVisitor visitor) {
        visitor.visitA(this);
    }
}

class B implements Something {
    accept(SomethingVisitor visitor) {
        visitor.visitB(this);
    }
}

class C implements Something {
    accept(SomethingVisitor visitor) {
        visitor.visitC(this);
    }
}

class Foo implements SomethingVisitor {
    visitA(A thing);
    visitB(B thing);
    visitC(C thing);
}

class Bar implements SomethingVisitor {
    visitA(A thing);
    visitB(B thing);
    visitC(C thing);
}

class Baz implements SomethingVisitor {
    visitA(A thing);
    visitB(B thing);
    visitC(C thing);
}
Iedere operatie wordt een eigen klasse, in plaats van een functie binnen de hoofd klasse. Je kunt zien dat het tweede voorbeeld veel meer code oplevert, dus waarom zou je het willen? Het hangt af van de situatie of je klasse-dominant of operatie-dominant groepering nodig hebt. Met het visitor pattern kun je operations toevoegen zonder de klasse zelf aan te passen.

Compilers gebruiken vaak het visitor pattern, met als reden om te voorkomen dat de klasse (in dit voorbeeld: A, B, en C) zelf een veelvoud aan verschillende operations moeten implementeren. Het kan ook zonder visitor pattern, maar dat is voor het onderhouden en veelal gegenereerde code gewoon veel minder praktisch.

Acties:
  • 0 Henk 'm!

  • eheijnen
  • Registratie: Juli 2008
  • Niet online
Interessante & leerzame discussie.

Maar als ik hierboven kijk in de code:

code:
1
2
3
4
5
interface SomethingVisitor {
    visitA(A thing);
    visitB(B thing);
    visitC(C thing);
}


ipv steeds visitA/B/C aan te roepen per klasse A/B/C.
Hier zou ik toch zeggen dat je hier ook van function overloading gebruik kunt maken.

code:
1
2
3
4
5
interface SomethingVisitor {
    visit(A thing);
    visit(B thing);
    visit(C thing);
}

Wie du mir, so ich dir.


Acties:
  • 0 Henk 'm!

  • Woy
  • Registratie: April 2000
  • Niet online

Woy

Moderator Devschuur®
@eheijnen ja dat is gewoon gelijk aan elkaar, maar niet alle talen supporten natuurlijk overloading aangezien dat wel strong typing behoeft.

“Build a man a fire, and he'll be warm for a day. Set a man on fire, and he'll be warm for the rest of his life.”


Acties:
  • 0 Henk 'm!

  • eheijnen
  • Registratie: Juli 2008
  • Niet online
@Woy

Ja klopt, maar was uitgegaan van de topic start in C#.

Dan heb je ook aan 1 klasse genoeg voor A/B/C wat het nog wat compacter en overzichtelijker maakt.

code:
1
2
3
4
5
class ABC implements Something {
    accept(SomethingVisitor visitor) {
        visitor.visit(this);
    }
}

[ Voor 9% gewijzigd door eheijnen op 16-08-2022 19:36 ]

Wie du mir, so ich dir.


Acties:
  • +1 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Het idee is juist dat A, B en C verschillend kunnen zijn en op andere wijze kunnen worden afgehandeld.

Acties:
  • 0 Henk 'm!

  • eheijnen
  • Registratie: Juli 2008
  • Niet online
Dat kun je dan toch in de respectievelijke overload doen ??

Wie du mir, so ich dir.


Acties:
  • +1 Henk 'm!

  • Haan
  • Registratie: Februari 2004
  • Laatst online: 09:52

Haan

dotnetter

Nee, want je hebt nu alleen nog een class ABC, dus welke overload dan?

Kater? Eerst water, de rest komt later


Acties:
  • 0 Henk 'm!

  • eheijnen
  • Registratie: Juli 2008
  • Niet online

Wie du mir, so ich dir.


Acties:
  • 0 Henk 'm!

  • Josk79
  • Registratie: September 2013
  • Laatst online: 18-05 13:10
Als er maar 1 implementatie is (ABC) is er geen overloading meer.

Acties:
  • +1 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-05 17:05
eheijnen schreef op dinsdag 16 augustus 2022 @ 19:35:
Ja klopt, maar was uitgegaan van de topic start in C#.
C# heeft function overloading op basis van parameter types dus die dispatching regelt de compiler voor je inderdaad.
eheijnen schreef op dinsdag 16 augustus 2022 @ 19:35:
code:
1
2
3
4
5
class ABC implements Something {
    accept(SomethingVisitor visitor) {
        visitor.visit(this);
    }
}
Ik denk dat je daar een denkfout maakt: de tweede dispatch wil je op het type van het element (zie boven) waar de operatie op wordt uitgevoerd. Die heb je op deze manier niet meer.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • eheijnen
  • Registratie: Juli 2008
  • Niet online
Ja, ik heb het gezien.
Thanks.

Wie du mir, so ich dir.

Pagina: 1