Тёмный

Visitor: How I Mastered the Toughest Programming Pattern 

git-amend
Подписаться 17 тыс.
Просмотров 33 тыс.
50% 1

Опубликовано:

 

14 окт 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 89   
@git-amend
@git-amend 11 месяцев назад
Hi Everyone! Hope you find this explanation of the Visitor pattern useful - it can help to implement your own Visitor and try it out; you'll see it's not too much code! Links in description to Extension methods and more!
@dan.ec90
@dan.ec90 11 месяцев назад
Thank you so much for making this channel! I've been developing in Unity for a decade now and after discovering git-amend I found out I was thirsty for more advanced tutorials and I didn't even know. Please, never stop!
@git-amend
@git-amend 11 месяцев назад
Thank you, I really appreciate that! Lots more topics to come!
@trustytea3136
@trustytea3136 11 месяцев назад
I was recommend to this RU-vid channel and I’ve got to say, you’ve quickly become one of my favorite coding RU-vid channels to watch, great stuff!
@git-amend
@git-amend 11 месяцев назад
Wow, thanks! I'm really happy to hear that!
@GettingRektGaming
@GettingRektGaming 11 месяцев назад
This channel is so good. I always run into issues when it comes to "mutators" and buffs/debuffs systems. Thats when my technical debt either starts, or comes to collect because of my previous sins.
@git-amend
@git-amend 11 месяцев назад
Haha I know the feeling!
@Sindrijo
@Sindrijo 11 месяцев назад
To avoid reflection you could just add a type parameter to the visitor interface method: public interface IVisitor { void Visit(T visitable) where T : Component, IVisitable } public interface IVisitable { void Accept(IVisitor); } public MyComponent : MonoBehaviour, IVisitable { public void Accept(IVisitor visitor) { visitor.Visit(this); // Type param is inferred to be MyComponent } } public MyVisitor : MonoBehaviour, IVisitor { public void Visit(T visitable) where T : Component { // use pattern matching etc. if(visitable is MyComponent myComponent) { // do stuff } } } Now you could actually also create a concrete interface that visits only a specific type of component, and then register those visitor on an aggregating visitor. public interface IVisitor where T : Component, IVisitable { void Visit(T component); } public MyAdvancedVisitor : MonoBehaviour, IVisitor { private Dictionary visitors = new Dictionary(); public void RegisterVisitor(IVisitor visitor) { visitors[typeof(T)] = visitor; } public void Visit(T visitable) where T : Component { // this is a bit naive, you could easily add support for inheritance // e.g. a IVisitor should be callable with a DerivedType if(!visitors.TryGetValue(typeof(T), out var boxedVisitor) { return; } if(!boxedVisitor is IVisitor concreteVisitor) { return; // or throw or log error whatever } concreteVisitor.Visit(visitable); } }
@git-amend
@git-amend 11 месяцев назад
I like the generic interface approach as well. Thanks for the detailed breakdown!
@PetrVejchoda
@PetrVejchoda 11 месяцев назад
Yeah, the reflection scared me. Generics is much better approach.
@KhanhLe-in7yi
@KhanhLe-in7yi 9 месяцев назад
RegisterVisitor(IVisitor visitor) where T : Component, IVisitable how to use this
@bxb5625
@bxb5625 5 месяцев назад
Looks nice, but I would have tried smthg like IVisitor where T IVisitable. This would allow you to combine multiple IVisitor like MyComponent : Monobehaviour, IVisitor, IVisitor . Making maybe clearer what visitable your visitor target. Only thing is how would it behave if we had both component on our object, I m thinking about this at 10:30 pm in bed XD, I might say not useable things 😭
@cit0110
@cit0110 5 месяцев назад
learned the visitor design pattern in my compilers class, i couldnt wrap my head around it until we finally got to the code gen part. my dopamine spiked so high when i realized what was going on and was able to implement it haha
@git-amend
@git-amend 5 месяцев назад
Haha nice!
@feildpaint
@feildpaint 11 месяцев назад
Wow that null coalescing operator tidbit at the end was super helpful! Just started using them on a new project because they looked fancy haha. Thanks for the great video!
@git-amend
@git-amend 11 месяцев назад
Glad it helped!
@BooleanIndecisive
@BooleanIndecisive 11 месяцев назад
Nice walkthrough showing how to apply a general pattern to Unity!
@git-amend
@git-amend 11 месяцев назад
Thank you!
@Scott_Stone
@Scott_Stone 11 месяцев назад
This video has been on my recommendation for a week. Before the algorithm changes, I only saw such things only with the biggest channels.
@git-amend
@git-amend 11 месяцев назад
Interesting... well I'm glad to hear that, hope you like the video!
@quixadhal
@quixadhal 11 месяцев назад
It's funny how part of "object oriented" programming seems to be giving formal names to things we just used to DO in the old days. This is effectively making local dispatch tables with function pointers and varargs argument lists... but fancied up so it's pretty and the compiler can do a little bit more checking that you're using it correctly.
@SabreGoose-w6e
@SabreGoose-w6e 8 месяцев назад
God bless your channel! One of the best I'v seen on RU-vid so far. Please keep making videos like this one, they're really helpful
@git-amend
@git-amend 8 месяцев назад
Thank you! Will do!
@crazyfox55
@crazyfox55 11 месяцев назад
The reflection visitor is a really interesting idea that I have not come across yet. However, I think to avoid writing lots of visit functions it would be better to use a base visitor class that has empty functions for each visitable. The visitor interface will still need to grow as more visitable objects are added. I'm glad to see you're posting about this pattern, everyone on my team was fearful of this pattern and stayed out of the codebase where it was heavily used. But I had to dive into it to add features and fix bugs, it was very challenging at the beginning. I think if you went into more detail about how to debug this pattern that would be great. I think navigating the indirection within the IDE was my big breakthrough.
@git-amend
@git-amend 11 месяцев назад
The base class is a great solution. Another option I've been thinking about would be to keep a base class as you mention, but instead of one interface, the base class could implement several smaller interfaces instead. As long as there was a clear separation of concerns, that might work well. I'll keep debugging in mind, I'm sure I'll be using the Visitor in other videos in the future. Thanks for your comment!
@Krahkarq
@Krahkarq 10 месяцев назад
Great video & very happy I've found your channel!
@git-amend
@git-amend 10 месяцев назад
Awesome! Thanks!
@marmont8005
@marmont8005 4 месяца назад
I was just thinking about the best way to incorporate collision logic into my game while minimizing the coupling between game objects. I'm currently using a collision manager that only checks if the game objects are touching. I came across the Visitor pattern by chance and discovered your channel. I am in the process of implementing it and it sounds very interesting.
@marmont8005
@marmont8005 4 месяца назад
but I just realized that with a visitorpattern you have a higher cohesion between the individual game objects, because to handle collisions you still need the class of the object to handle this. My conventional approach is to use a centralized collision manager to reduce the coupling between game objects.
@git-amend
@git-amend 4 месяца назад
The visitor is really more about performing some logic on the object being visited than it is about collision detection. In many cases that does mean they might collide before the visitor 'Visits' the visitable object, and sometimes they would need to know what class is being visited so that the correct operation can be performed. But if you work with abstractions, this can be minimized. It really comes down to the design of your game. In future videos we use the visitor pattern more frequently, so you might get some other ideas. Glad you found the channel!
@deathtidegame
@deathtidegame 11 месяцев назад
I never knew this was a pattern or that it had a specific name when I implemented a buff/debuff system for my game using classes that "attach" themselves to objects (such as a health system or movement system) and interact with them (reduce max health, increase/decrease move speed etc).
@git-amend
@git-amend 11 месяцев назад
Nice! That’s awesome!
@cheesymcnuggets
@cheesymcnuggets 10 месяцев назад
Wow i really need to do more research on general programing, all this just went through one ear and out the other without me processing any of it. I've managed just fine only worrying about what I need to know but I think it would be worth knowing more than I need to if I want to a smoother experience.
@git-amend
@git-amend 10 месяцев назад
Good to hear! There is always more to learn!
@FidosWideWorld
@FidosWideWorld 10 месяцев назад
Brain got bigger, stuff has been learned, you've earned a subscriber. Keep chadding ö7
@git-amend
@git-amend 10 месяцев назад
Haha thanks!
@Director414
@Director414 11 месяцев назад
Great video!☺️ Many thanks for sharing. I dont really get the basic premise i guess, what problem are we trying to solve here? How does a scrappy, noobish-way, of doing this look like? I just wanna wrap my head around the actual problem we are solving.. Cheers! ☺️
@git-amend
@git-amend 11 месяцев назад
The Visitor pattern solves the problem of managing interactions between game elements in a scalable way. Without this pattern, you might directly hardcode interactions in each object's script, which becomes messy and hard to manage as your game grows. The Visitor pattern abstracts these interactions: pickups 'visit' objects, triggering appropriate responses (like health increase) defined by the object. A noobish version of this might look something like: public class Hero : MonoBehaviour { public healthComponent; public manaComponent; ... void OnTriggerEnter(Collider other) { if (other.gameObject.CompareTag("HealthPickup")) { health.addHealth(20); Destroy(other.gameObject); // Remove the pickup object } if (other.gameObject.CompareTag("ManaPickup")) { mana.addMana(20); Destroy(other.gameObject); } ... and so on } } Here is another good resouce which describes the Vistor pattern in a more abstract way: refactoring.guru/design-patterns/visitor
@Cloud-Yo
@Cloud-Yo 7 месяцев назад
Idk, this one was fairly easy for me to get. Credit is obviously due to the quality of the explanation on your video. I think of it as a modular way of adding methods to a Visitable class and its sort of what I was looking for :) Unrelated question: what package (if any) are you using to display the related code in your console when displaying the debug statements. seems useful.
@git-amend
@git-amend 7 месяцев назад
Thanks for the comment - I think the asset you are looking for is Editor Console Pro, I've been using it for years now, it's great! assetstore.unity.com/packages/tools/utilities/editor-console-pro-11889
@Cloud-Yo
@Cloud-Yo 7 месяцев назад
@@git-amend Awesome, thanks! turns out I had it in my assets already. Must have snuck in on one of the Humble Bundles I bought 👍
@ragerungames
@ragerungames 11 месяцев назад
I love your channel man! Newly discovered! :)
@git-amend
@git-amend 11 месяцев назад
Right on! Glad to hear it!
@pliniojrm
@pliniojrm Месяц назад
Thinking about how to use Visitor pattern in my spaceship game, i think about how the spaceships will take part in the planet's atmosphere by communicating through Visitor Pattern.
@pierredalaya444
@pierredalaya444 11 месяцев назад
I'm confused, could you explain what is good about this pattern ? In the classic and intrusive, you create dependencies between your scriptable objects and your components, and in the reflective, well; it uses reflection. I am failing to see any benefits to this one. Would be great to have more insight about pros
@git-amend
@git-amend 11 месяцев назад
The Visitor pattern, by its nature, will introduce some level of dependency between the Visitor and the object being Visited. However, the separation of concerns and the ability to add new operations without modifying existing code can outweigh this disadvantage in many scenarios. Separation of Concerns: The Visitor pattern helps to separate concerns by keeping the operations separate from the component. Adding New Behavior: It makes it easier to add new behaviors to existing object structures without modifying the objects themselves. Single Responsibility Principle: Each visitor is typically responsible for a single operation.
@pierredalaya444
@pierredalaya444 11 месяцев назад
I see, thanks for clarifying. I would need to try it myself, but I am not a fan of adding a new type for a simple ability that just modifies an int. Note: you can get the same benefits without the dependencies by using SO architecture. @@git-amend
@ranejeb
@ranejeb 11 месяцев назад
What's the reasoning behind using reflective visitor? I can imagine some real commercial projects to use visitor that would have logic complex enough that you wouldn't really want to have one class to contain all Visit(HealthComponent), Visit(ManaComponent), etc. Why can't we use generic types instead to both 1) get read of slow reflection 2) handle one particular case of visiting at a time in a separate class
@git-amend
@git-amend 11 месяцев назад
The flexibility of a reflective visitor is useful in systems where new component types might be added frequently or where the range of component types is extensive. However, if performance is critical and the types you're dealing with are known and relatively stable, using generic types might be the better option. The choice between these two approaches often boils down to a trade-off between flexibility and performance.
@EskemaGames
@EskemaGames 10 месяцев назад
Not bad implementation, but too coupled and uses reflection, which you don't need at all. You already have all the code in there to make it useful, you need more abstraction to make the pattern shine
@niuage
@niuage 11 месяцев назад
This channel is criminally underrated…
@git-amend
@git-amend 11 месяцев назад
Starting to grow a bit now!
@niuage
@niuage 11 месяцев назад
@@git-amend Cool :) Yea I bet if you keep it up it's only gonna go up faster and faster :)
@malawann
@malawann 11 месяцев назад
Can you explain how to support the removal of buffs in this system? Which means to restore the health value to the state before the buff was added.
@git-amend
@git-amend 11 месяцев назад
Removable or temporary buffs is a bit too complex to describe in comments, but is a good subject for a future video. There are many ways you could accomplish this - one idea might be to allow the Visitor to add a Decorator which affects a particular component and has a limited lifespan. If you just wanted to restore the state of a component to a previous state, check out my video on the Memento pattern.
@malawann
@malawann 11 месяцев назад
@@git-amendThanks for answering. Can't wait for the future video.
@raymk
@raymk 8 дней назад
SICK. I learned too much in one video.
@git-amend
@git-amend 8 дней назад
Nice, glad to hear that.
@Cuyut982
@Cuyut982 Месяц назад
Possibly good to have this pattern in my toolbelt but I prefer events and event buses for this particular problem. It's usually more code but the decoupling is a dream to work with. So a pickup merely emits an event with some metadata from the pickup SO and the player script subscribes and handles it accordingly. Can anyone think of a use case for visitors in Unity that can't be solved by an event system?
@lucasrgoes
@lucasrgoes 11 месяцев назад
You could simplify the reflection by just doing a switch on the type: public void Visit(IVisitable visitable) { switch (visitable) { case HealthComponent health: Visit(health); break; case ManaComponent mana: Visit(mana); break; } }
@StephenBuergler
@StephenBuergler 11 месяцев назад
As a java dev there were always three C# features that I thought were really nice.. linq query syntax, structs, and dynamic variables. I thought that C# people never really needed to use the visitor pattern because they can use dynamic variables instead. Is that true?
@git-amend
@git-amend 11 месяцев назад
Yes, I know what you mean - and it would be great if we could use dynamic. However, the dynamic keyword in C# is not supported by Unity's IL2CPP scripting backend because it requires Just-In-Time (JIT) compilation, which IL2CPP, being an Ahead-Of-Time (AOT) compiler, doesn't allow. Check out: docs.unity3d.com/2023.3/Documentation/Manual/ScriptingRestrictions.html
@evggg
@evggg 9 месяцев назад
What if Pickup object in OnTriggerEnter receives another component with IVisitable interface. Not our Hero, but HealthComponent directly?
@git-amend
@git-amend 9 месяцев назад
Good question. The order of components retrieved when using GetComponents is always the order they are in the inspector, so in this example Hero will always be the first one retrieved. You could strictly enforce that by introducing another interface or putting the components onto children if you wanted to. If you had it setup so the HealthComponent is the one retrieved, it will only visit that component.
@melpeslier
@melpeslier 10 месяцев назад
Maybe you could have explained the visitor pattern and what you were diving in before
@sahilmishra2945
@sahilmishra2945 11 месяцев назад
that null coalescing bit is cool to hear about, these videos are great do you get that examples from a textbook?
@git-amend
@git-amend 11 месяцев назад
Thanks! I don't recall exactly where I first saw the Classic Visitor used in this way, but I think it was in a book - I'll try to remember, it's been a while. I think the PowerUp is a common paradigm for learning the Visitor. The Reflective and Intrusive versions of Visitor come from enterprise software engineering.
@Jose-kr1li
@Jose-kr1li 11 месяцев назад
Hello, I have a doubt with this specific implementation. When the PuckUp class detects a collision, it takes the first IVisitable component that the object that is colliding with has. In this case is the class Hero, but in the same gameObject we have two more components that extents from IVisitable (HealthComponent and ManaComponent), so we would have to take care of the order of the components if we wanted to take Hero in order to visit both HealtComponent and ManaComponent right?
@git-amend
@git-amend 11 месяцев назад
Good question. The order of components retrieved when using GetComponents is always the order they are in the inspector, so in this example Hero will always be the first one retrieved. You could strictly enforce that by introducing another interface or putting the components onto children if you wanted to.
@ethanwasme4307
@ethanwasme4307 11 месяцев назад
unity devs are spoilt 😢😢
@friedcrumpets
@friedcrumpets 11 месяцев назад
WOW; you need more views
@git-amend
@git-amend 11 месяцев назад
Thanks!
@nanaschi
@nanaschi 11 месяцев назад
Maybe next time MVP/ MVC patterns could be explained? It would be great!
@git-amend
@git-amend 11 месяцев назад
Great idea, I'll put that on the to-do list. I'm actually building a new feature using MVC at work this week.
@nanaschi
@nanaschi 11 месяцев назад
@@git-amend Cool to hear it coincided with your current workflow. From my perspective, I'd like to see examples of real game scenarios of using these patterns rather than just UI IMHO
@clamhammer2463
@clamhammer2463 11 месяцев назад
@@nanaschi The mvc pattern would be overkill for just a UI. that is only 1 component of the 3 withing MVC, v=view
@ConradClose-n2l
@ConradClose-n2l 5 месяцев назад
I love your channel I just don't understand what problem the Visitor pattern is trying to solve.
@git-amend
@git-amend 5 месяцев назад
The main purpose of the Visitor is extending the functionality of a class hierarchy without modifying the classes themselves. Maybe checkout my more recent video about Stats and Modifiers for another example.
@captainnoyaux
@captainnoyaux 10 месяцев назад
what do you use for the voice ? Seems like AI but it sounds OK
@git-amend
@git-amend 10 месяцев назад
That's my own voice, I don't use a voiceover.
@captainnoyaux
@captainnoyaux 10 месяцев назад
@@git-amend whatttt ? XD it sounds like AI to me, my bad !
@LimitedInput
@LimitedInput 11 месяцев назад
Amazing
@git-amend
@git-amend 11 месяцев назад
Thank you! Cheers!
@JacobNax
@JacobNax 11 месяцев назад
Toughest?? Have you tried DOTS my friend?
@civo4457
@civo4457 11 месяцев назад
a visitor...
@Gurem
@Gurem 11 месяцев назад
isnt this just a Interactible system. Thank god its not something that'll have me refactoring Edit: spoke too soon.
@Sweenus987
@Sweenus987 11 месяцев назад
Interesting pattern but feels a little dirty for some reason
Далее
Clean Code using the Strategy Pattern
12:34
Просмотров 14 тыс.
Слушали бы такое на повторе?
01:00
ОНА БЫЛА ПЕВИЦЕЙ🤪
3:13:12
Просмотров 1,1 млн
Это было очень близко...
00:10
Просмотров 940 тыс.
Why I Don't Like Singletons
29:05
Просмотров 82 тыс.
Starting Your Unity Game Flawlessly (8 Steps)
9:51
Просмотров 5 тыс.
Andrew Kelley   Practical Data Oriented Design (DoD)
46:40
How to Implement Blackboard Architecture in Unity C#
28:57
How to Code (almost) Any Feature
9:48
Просмотров 688 тыс.
Слушали бы такое на повторе?
01:00