Visitor: How I Mastered the Toughest Programming Pattern

แชร์
ฝัง
  • เผยแพร่เมื่อ 28 พ.ย. 2024

ความคิดเห็น • 90

  • @git-amend
    @git-amend  ปีที่แล้ว +10

    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 ปีที่แล้ว +33

    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  ปีที่แล้ว +3

      Thank you, I really appreciate that! Lots more topics to come!

  • @Sindrijo
    @Sindrijo ปีที่แล้ว +20

    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  ปีที่แล้ว +3

      I like the generic interface approach as well. Thanks for the detailed breakdown!

    • @PetrVejchoda
      @PetrVejchoda ปีที่แล้ว +1

      Yeah, the reflection scared me. Generics is much better approach.

    • @KhanhLe-in7yi
      @KhanhLe-in7yi 11 หลายเดือนก่อน

      RegisterVisitor(IVisitor visitor) where T : Component, IVisitable how to use this

    • @bxb5625
      @bxb5625 7 หลายเดือนก่อน +2

      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 😭

  • @trustytea3136
    @trustytea3136 ปีที่แล้ว +9

    I was recommend to this TH-cam channel and I’ve got to say, you’ve quickly become one of my favorite coding TH-cam channels to watch, great stuff!

    • @git-amend
      @git-amend  ปีที่แล้ว +1

      Wow, thanks! I'm really happy to hear that!

  • @GettingRektGaming
    @GettingRektGaming ปีที่แล้ว +18

    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  ปีที่แล้ว +1

      Haha I know the feeling!

  • @SabreGoose-w6e
    @SabreGoose-w6e 9 หลายเดือนก่อน +1

    God bless your channel! One of the best I'v seen on TH-cam so far. Please keep making videos like this one, they're really helpful

    • @git-amend
      @git-amend  9 หลายเดือนก่อน

      Thank you! Will do!

  • @quixadhal
    @quixadhal ปีที่แล้ว +4

    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.

  • @Scott_Stone
    @Scott_Stone ปีที่แล้ว +1

    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  ปีที่แล้ว +1

      Interesting... well I'm glad to hear that, hope you like the video!

  • @cit0110
    @cit0110 7 หลายเดือนก่อน +1

    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  7 หลายเดือนก่อน

      Haha nice!

  • @BooleanIndecisive
    @BooleanIndecisive ปีที่แล้ว +3

    Nice walkthrough showing how to apply a general pattern to Unity!

  • @feildpaint
    @feildpaint ปีที่แล้ว +1

    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  ปีที่แล้ว

      Glad it helped!

  • @Krahkarq
    @Krahkarq ปีที่แล้ว

    Great video & very happy I've found your channel!

    • @git-amend
      @git-amend  ปีที่แล้ว

      Awesome! Thanks!

  • @crazyfox55
    @crazyfox55 ปีที่แล้ว +5

    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  ปีที่แล้ว +3

      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!

  • @FidosWideWorld
    @FidosWideWorld ปีที่แล้ว

    Brain got bigger, stuff has been learned, you've earned a subscriber. Keep chadding ö7

  • @marmont8005
    @marmont8005 6 หลายเดือนก่อน

    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 6 หลายเดือนก่อน

      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  6 หลายเดือนก่อน

      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 ปีที่แล้ว +1

    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  ปีที่แล้ว

      Nice! That’s awesome!

  • @ragerungames
    @ragerungames ปีที่แล้ว

    I love your channel man! Newly discovered! :)

    • @git-amend
      @git-amend  ปีที่แล้ว +1

      Right on! Glad to hear it!

  • @lucasrgoes
    @lucasrgoes ปีที่แล้ว +4

    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;
    }
    }

  • @Director414
    @Director414 ปีที่แล้ว +2

    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  ปีที่แล้ว +1

      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

    • @gaelp.5847
      @gaelp.5847 13 วันที่ผ่านมา

      Basic premise is you need to upload a video every day/week/whatever on youtube to get your views growing and appear in trends. So blend in some non-topics to other not so bad topics to keep the publishing pace.
      Other than that, it's just a complicated and weird way to give up on scalability (since you're supposed to "accept" each visitor type) and readability.
      The only use case is when your "visitors" visits cohesive "visitables" and you want to force any new visitor definition to encompass all "visitables" (since they are cohesive), like sometimes for a plugin for example. But this video completely missed the point..

  • @cheesymcnuggets
    @cheesymcnuggets ปีที่แล้ว

    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  ปีที่แล้ว

      Good to hear! There is always more to learn!

  • @niuage
    @niuage ปีที่แล้ว

    This channel is criminally underrated…

    • @git-amend
      @git-amend  ปีที่แล้ว

      Starting to grow a bit now!

    • @niuage
      @niuage ปีที่แล้ว

      @@git-amend Cool :) Yea I bet if you keep it up it's only gonna go up faster and faster :)

  • @pierredalaya444
    @pierredalaya444 ปีที่แล้ว +2

    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  ปีที่แล้ว +2

      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 ปีที่แล้ว

      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

  • @Cloud-Yo
    @Cloud-Yo 9 หลายเดือนก่อน

    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  9 หลายเดือนก่อน +1

      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 9 หลายเดือนก่อน

      @@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 👍

  • @pliniojrm
    @pliniojrm 2 หลายเดือนก่อน

    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.

  • @ranejeb
    @ranejeb ปีที่แล้ว +1

    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  ปีที่แล้ว +2

      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 ปีที่แล้ว +1

    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

  • @raymk
    @raymk หลายเดือนก่อน

    SICK.
    I learned too much in one video.

    • @git-amend
      @git-amend  หลายเดือนก่อน

      Nice, glad to hear that.

  • @sahilmishra2945
    @sahilmishra2945 ปีที่แล้ว +1

    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  ปีที่แล้ว +1

      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.

  • @malawann
    @malawann ปีที่แล้ว +1

    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  ปีที่แล้ว +1

      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 ปีที่แล้ว

      @@git-amendThanks for answering. Can't wait for the future video.

  • @Cuyut982
    @Cuyut982 2 หลายเดือนก่อน

    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?

  • @evggg
    @evggg 10 หลายเดือนก่อน

    What if Pickup object in OnTriggerEnter receives another component with IVisitable interface. Not our Hero, but HealthComponent directly?

    • @git-amend
      @git-amend  10 หลายเดือนก่อน +1

      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 ปีที่แล้ว

    Maybe you could have explained the visitor pattern and what you were diving in before

  • @Jose-kr1li
    @Jose-kr1li ปีที่แล้ว

    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  ปีที่แล้ว +1

      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.

  • @StephenBuergler
    @StephenBuergler ปีที่แล้ว +2

    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  ปีที่แล้ว +5

      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

  • @ethanwasme4307
    @ethanwasme4307 ปีที่แล้ว +21

    unity devs are spoilt 😢😢

  • @friedcrumpets
    @friedcrumpets ปีที่แล้ว +1

    WOW; you need more views

  • @captainnoyaux
    @captainnoyaux ปีที่แล้ว

    what do you use for the voice ? Seems like AI but it sounds OK

    • @git-amend
      @git-amend  ปีที่แล้ว

      That's my own voice, I don't use a voiceover.

    • @captainnoyaux
      @captainnoyaux ปีที่แล้ว

      @@git-amend whatttt ? XD it sounds like AI to me, my bad !

  • @nanaschi
    @nanaschi ปีที่แล้ว

    Maybe next time MVP/ MVC patterns could be explained? It would be great!

    • @git-amend
      @git-amend  ปีที่แล้ว

      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 ปีที่แล้ว

      @@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 ปีที่แล้ว

      @@nanaschi The mvc pattern would be overkill for just a UI. that is only 1 component of the 3 withing MVC, v=view

  • @LimitedInput
    @LimitedInput ปีที่แล้ว

    Amazing

    • @git-amend
      @git-amend  ปีที่แล้ว

      Thank you! Cheers!

  • @ConradClose-n2l
    @ConradClose-n2l 7 หลายเดือนก่อน

    I love your channel I just don't understand what problem the Visitor pattern is trying to solve.

    • @git-amend
      @git-amend  7 หลายเดือนก่อน +1

      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.

  • @JacobNax
    @JacobNax ปีที่แล้ว

    Toughest?? Have you tried DOTS my friend?

  • @civo4457
    @civo4457 ปีที่แล้ว

    a visitor...

  • @Gurem
    @Gurem ปีที่แล้ว

    isnt this just a Interactible system. Thank god its not something that'll have me refactoring
    Edit: spoke too soon.

  • @Sweenus987
    @Sweenus987 ปีที่แล้ว

    Interesting pattern but feels a little dirty for some reason