Understanding The Visitor Design Pattern

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

I like this video and the channel but I have to quibble if you don't mind. Is it really true the visitor pattern was made to "solve" double dispatch?

I think you get a lot right in the video but I think you get a detail wrong that hampers understanding. I feel like you're saying the reason for visitor is double-dispatch but I don't agree - i think that's an implementation. The reason for a visitor pattern is, as you say in the video, to perform an new operation on a class when you can't modify that class (b/c you don't own it, b/c it breaks the interface, and son o.).

If you never even mentioned double-dispatch, your viewers would have a much more succinct concept of visitor. You could then layer more concepts on top. There is some real sense in which you could say that python classes make almost universal use of visitor to implement its methods. But you wouldn't find a conversation about double dispatch there.

πŸ‘οΈŽ︎ 2 πŸ‘€οΈŽ︎ u/rhytnen πŸ“…οΈŽ︎ Jan 20 2019 πŸ—«︎ replies

You're right, the main point tailors to APIs and such you cannot modify or don't want to modify. Maybe I'm off a bit by saying it solves the lack of double dispatch in OOP, however, I do believe it's a way to resolve the lack of double dispatch being inherently available within the language. It does bring to the table some dynamic runtime polymorphism between to abstract types.

πŸ‘οΈŽ︎ 1 πŸ‘€οΈŽ︎ u/neuralcoder πŸ“…οΈŽ︎ Jan 20 2019 πŸ—«︎ replies
Captions
[Music] what's up guys welcome to another video brought to you by the simple engineer today we're going to be covering a cool topic known as the visitor design pattern and the visitor design pattern is probably one of the most complex and underrated design patterns from the original Gang of Four book in my opinion so in this video what I'd like to uncover is first of all talk about what exactly does this pattern solve what's the issue that it solves and then we'll dive into the theory as to why this pattern came to fruition and that revolves around some object-oriented programming principles centered around polymorphism and then number three I've devised a an example problem that will actually go over and then we'll implement it using the visitor design pattern so without further ado let's just dive right into it one of the things that I actually like is the description that wikipedia gives the visitor design pattern and what it says is it allows us to separate new operations to existing object structures without having to modify those original object structures so let's say that I have an object and it has a pond a bunch of methods on it that are defined from some interface so let's say I have an animal interface and I have dog cats parrots etc anytime that I add a new operation to one of those interfaces to like the animal interface I have to go and I have to update every single subtype to account for this new operation so what I'm doing is I'm tightly coupling the operations that I'm performing on these different animals to each individual animal object and that becomes very very hard to maintain as we grow the number of operations on our classes and in addition to that we are violating the open-closed principle and the open-closed principle says we need to keep our objects we need to keep our AP eyes open for extensibility but closed for modification in other words we don't want have to modify the original API just to add new operations that interact with these objects we should be able to do it in a very extensible way so if I want to create you know an evaluator for an animal or I want to create a new one you know a new method that says hey give me the noise this animal makes I don't want to have to modify the each individual animal class to be able to do that so this is what the visitor design pattern solves so to better understand what it looks like from a hierarchical point of view let's look at a UML diagram so in the example that we gave this would be essentially equivalent to the animal interface here so let's say that this is an animal interface and concrete element a would be something like a dog and concrete element would be would be something like a cat and we could have we could have in of these right this could go on forever and the simple change that we make is in the interface so in the animal interface which may already have a few methods which is totally fine we're just going to add one additional method and it's going to be called accept and the accept method is going to take in a visitor object and that's what we'll talk about in a second so as you can see how this modifies the original concrete classes is just once in the entire lifespan of these classes you have to modify once and it's to actually implement add this accept method that takes in the visitor object and that's it and this is going to be the extensibility point for all of our concrete classes and we'll go into an actual implementation that elucidates this idea a little bit better so this is the only thing that we basically change to our original data structure we add one method to the interface and we implement that method in the concrete classes now this is kind of the confusion point for a lot of people because as you can see we're essentially managing two pieces of hierarchy within this this design pattern this is the first piece of hierarchy which is essentially all of our original data structures that we're dealing with and the second piece of the hierarchy is the visitor data structures and the way that I like to think about this is these concrete visitors are the extensible operations that we perform on each of these concrete elements so in other words if I want to add a method to all my animals that's say make sound or make noise I can do that in these concrete visitors I can make a make sound visitor and essentially what it'll do is it'll take in one of these concrete elements and when I pass it in it'll make the noise of this element for example it will bark and if I pass in a cat it will meow and that's only specific to this entire visitor so each visitor that derives from the visitor interface is going to be essentially a new operation or a new method that would have otherwise been tightly coupled to the concrete element itself and that is the abstraction that we get using the visitor design pattern so for example this could be a make sound visitor and this could be an e food visitor and what happens is when we call this accept method in each concrete element what we're doing is we're passing itself or passing in a visitor and we're saying visitor dot visit and we pass in an instance of ourself so when we instantiate one of these visitors for example to make sound visitor and we pass it an instance of ourself it will overload the correct method for make sound and our business logic will go into each of these visitors so that's kind of just an overview of the UML for how something like this gets structured but we'll look at this in more detail using a more canonical example that's a little bit easier to understand for something that's slightly more technical but I did just want to get that diagram out of the way so we can understand the structure and the two pieces of hierarchy that we're dealing with in this kind of polymorphic structure okay so looking back we we've covered the visitor design pattern what it is and the overall diagram but let's talk about why something like this came to fruition and it all centers around this idea of single versus double dispatch and single versus double dispatch is something that we're probably already familiar with but maybe under a different term and that term is polymorphism in modern-day object-oriented programming languages like C++ and Java they support single dispatch out of the box it's something that comes with the benefit of inheritance and polymorphism but double dispatch is something that is not supported out of the box so let's go ahead and look at a use case as to what that is and where it fails so before we explain the differences between single and double dispatch let's first talk about what is single dispatch I've gone ahead and I've made an example project here and just to quickly recap what this code is I've created one interface and this interface is an animal and it has one method on it called make sound I've created two concrete implementations derived from this interface I've called it a cat which is a subtype of animal and it meows and I have created a dog which is also a subtype of animal and it barks so what single dispatch allows us to do is we can declare or instantiate two different animal objects dog and cat respectively but we can declare them as one abstract type or one interface known as animal so this allows us to basically pass things around in a very very flexible way without having to use their concrete types so for example I can create one list of animals you know and I can come in here import class and this can be you know my animals and essentially what I can do is I can add a dog and I can add a cat even though they're two totally different objects because they share the same supertype we're allowed to treat them as kind of one animal type altogether and that's the benefit of polymorphism now what single dispatch allows us to do is we can come in here and I can say dog dot makes sound and I can say cat makes sound and what will happen is dynamically at runtime it will figure out even though it was declared as an animal and instantiated as its concrete type it will dynamically redirect to the correct method and make the sound appropriately so if I click play and we will get a bark and a meow respectively and this is something that we should all be familiar with this is polymorphism and this is also what single dispatches we can use one object and dynamically figure out what object call now we're double dispatch is not supported in object-oriented programming languages is the interaction of two objects to figure out what's gonna happen when these two objects interact with it one another so for example what's the sound that's gonna be made when a dog and a cat interact with one another what's the sound that's gonna be made when a cat and a cat interact with one another so to show you why this fails we can go ahead and we can modify the interface and we can say let's uh let's add a new method where we can essentially make sound for a dog and we can make sound for a cat well have to go and update all the original interfaces here and we'll just say you know this is a cat interacting with dog and this is cat interacting with a cat okay so that makes sense so far we'll just go ahead and copy these to speed things up and do the same thing for the dog so I will copy these and we'll delete this and this will actually be cat interacting with a dog now and this will be a dog interacting with a dog okay so you would think there are no issues here we should now be able to figure out the combination of cats and dogs interacting with one another which would be double dispatch so if I delete this I can come in here and I can say okay what happens when a dog makes a sound with another cat and I'll go ahead and I'll pass in my cat object but you see we're getting a compilation failure we're getting a type check failure here in the compiler which is strange because we actually support cat objects likewise I can come in here and I can say cat dot make sound with it even Auto completes me to another cat so I can pass in you know a cat here or I could even pass in a dog here but it fails and it fails because there of the animal type so you may think okay well I can go in the animal type and I can I can say I'm gonna add support for an animal and I'll update the interface here and this will be just animal interacting with cat and the same thing will be done for a dog okay so now we come back here and we say oh there's no more error so now a dog making sound with a cat should tell me that it's going to say cat interacting with a dog so let's go ahead and play that and we actually get animal interacting with a cat it doesn't recognize that the object we pass in is a dog making a sound with a cat and a cat making sound with a dog it just prints out the generic animal so what you can see is that it gets overloaded with this method and this is where double dispatch fails because we cannot polymorphically resolved at runtime the actual concrete class that's passed in which is a dog in a cat and this is what the visitor design pattern is going to fix and this is the differences between single and double dispatch now if you're interested in this topic things to look into you know things like C++ and Java they don't support this but things like common lisp in scheme and other functional functional programming languages do support the idea of double dispatch and in fact they support the idea of multiple dispatch within parameters within your functions so that's just the differences between the different types of paradigms and the differences between single and double dispatch so without further ado let us actually dive into an example problem where we implement double dispatch using the visitor design problem under an example that we can all kind of relate to so I'm gonna go ahead and create a new slate here and let's talk about the problem so in a modern-day banking in America we all know that there are plenty of credit cards that people like to buy so somebody goes to the bank and the bank teller says we have a variety of credit cards that we want you to check out and those credit cards are going to be the bronze credit card so we'll say bronze credit card we have silver credit cards silver credit cards and we have gold credit cards and each of these credit cards offer something a little bit different within value but they also cost money so this one's $0 a year this one's $100 a year and this one is $500 a year and the banker says but there are a lot of benefits what we can say is when you use this card at a gas station you will get different percentage of cash back for each of the things that you purchased so for gas on a bronze credit card you may get something upwards of 1% cash back for a silver card you'll get something of 2 percent 20% something crazy so this is a table and you know before we were figuring out the interaction of two objects with cat and a dog and other animal types in our other example here but in this example it's kind of the same problem we're figuring out within this table what happens when one object credit card interacts with another object offer right so it's hey when I am a bronze credit card and I buy some gas what's my cash back and I just kind of go through this table if I have a gold credit card and I buy some hotel I'm gonna get three percent cash back it's the interaction of two objects so object number one is the credit card and object number two is the actual offer type that we're dealing with so the issue that we saw before is we can create a bunch of credit cards we can create a bronze credit card class and we could create a silver credit card class and gold credit card class all derived from a credit card interface but the issue is is we also have to create an interface for offers and write all of these concrete types and we would have to basically compute the cash back for every type of offer right but the thing is is we don't really want to tightly couple offer types to the credit card because there are two totally unrelated things the bronze credit card is here to give you metadata about the credit card itself not to tell you about other offers that's what the offer classes are for so we want to avoid tightly coupling these offer computations you know we don't want to have a compute cash back function compute cash for every single you know gas offer etc and then just have a bunch of these methods because these methods will grow and grow and grow and as we modify the original credit card interface we're gonna be break all of the concrete implementations so the visitor design pattern basically says how can we have just a generic concrete implementation for bronze silver and gold and dynamically add these offers to the class so that's what we are going to do with the visitor design pattern so we'll go in IntelliJ we will actually just delete these methods we don't care about them anymore and in runner we will just go ahead and delete this so like I said before we are going to start with designing our visitor design pattern we already looked at this UML a data structure before so you can just pull that up again this is essentially the visitors or the dynamic operations that we want to add and we've already discussed that we don't want to tightly couple the offers to the actual credit card types right that would be a bad thing so what we're going to do is we're going to create visitors for the offer types so this is gonna be something like a gas offer this could be a hotel offer and then this is just gonna be the generic visitor for all of those different visitors likewise on the first piece of the hierarchy we are going to have a credit card interface and just implement bronze silver and gold credit cards respectively so I'll come in here and we can just segregate this in a cleaner way so we'll just call this create a package and call them offers so like I said before we'll create a credit card interface we'll just call this credit card and you know maybe in the credit card interface we will have something like get name and this may be returns a string and we will have a new method in here called accept and accept is going to accept a visitor object v which we haven't actually created yet so we'll create that in a second but this is going to be the interface for a credit card so when I come in here we'll go ahead and create a few credit card objects we'll have a bra turn silver okay so we have a couple errors here that's because we haven't created our visitor hierarchy so let's go ahead and do that as well we're gonna create a new class here and it's just going to be actually an interface and we will call it just the offer visitor and the offer visitor is going to be the most generic form of offers that we add that basically allow us to add new offers to credit cards without modifying anything related to the credit card itself so I'm going to come in here and the different types of methods that we'll add is we'll add an a void method called visit ver on credit card and it will take in a bronze credit card instance we'll create a new method called visit silver credit card and it will take in a silver credit card in our instance and this is going to be the offer visitor so now we can go into this original interface here and we'll just change this to offer visitor and it will be a void method okay so to kind of reference back at our UML diagram this is the offer visitor that we have created and it has a bunch of visit methods that are overloaded with the concrete types and this is the credit card interface that we are designing right now and you can see that it has the accept method and just one additional method related to credit cards and now we're going to actually update the concrete credit card types to now account for this new method that we have added to its interface so I'll go on a bronze credit card we're getting an error because we need to implement the accept method and all we're going to do here is we're gonna call the visit bronze credit card method since we are indeed a bronze credit card and we'll just pass in an instance of ourself we'll go ahead and do the same thing for the gold credit card and we will call vida visit gold credit card and pass in an instance of ourself and finally we'll implement the silver one and it will be visa vie de visite silver credit card with an instance of ourselves as well so now we have no more errors and what we're looking at here is this accept method is essentially the entry point for extensibility for extending any future operations dealing with offers on this particular credit card so if I come into the offer visitor I have one offer visitor and now we want to start adding you know potentially cash back offers to figure out based on the type of thing that we're purchasing what is going to be the cashback value so I can come in here and I can say gas offer visitor and the gas offer visitor is going to be an offer visitor subtype and it's going to take in all the different types of credit cards so once I go here I can say we are computing the cash back for a bronze card buying gas and now we can do the same thing for silver credit card and we can do the same thing for a gold credit card and that's just for gas so we'll do one more to really drill down the idea and we'll call it a hotel offer hotel offer visitor and this is also going to be of type author visitor and we'll implement that and this is going to say almost the same thing so we're computing the cash back for a bronze carbine hotel and this is going to be a actually this is going to be a bronze card this is gonna be a silver card buying a hotel and this is going to be a gold card buying a hotel okay so now we're done and what have we done exactly well what we've done is now we're visiting basically we have these totally decoupled operations that we're still performing on each different type of credit card but they these operations don't clutter the actual credit card type they only had one job and it was to return the name we didn't want to have to clutter this with a bunch of different operations and there's a lot of reason for that because if I were to come in here and I had to make a new method and it was called compute cash back for gas and then it took in you know a gas offer for example the issue that I would have to do is now I just broke the underlying implementation of every single credit card type you can see I get a compiler error so the second that we change the API we break the implementations of all the subtypes and that's something we want to avoid whereas now I've added a whole I've just added two new operations dealing with all of these credit card types and they're totally decoupled and I didn't have to modify the original interface whatsoever so how do we actually use this what is this use case going to be well what we do is we now instantiate one of the visitors that were interested in so let's say that we wanted to figure out the cash back that we get when passing in you know a hotel and a bronze card what is the output of those two things interacting with one another well the first thing that I do here is I instantiate a hotel offer visitor this new hotel offer visitor and then I come in and I create a bronze credit card so let's say that you know in my pocket I know that I have a bronze credit card so I come in here and I say credit card bronze equals new bronze credit card and I am going to actually we'll just go ahead and create a few so we'll create a silver one as well and this will be a silver credit card and we'll create a gold one and we have our visitor so essentially what's happening is we're trying to figure out the cash back of a bronze credit card interacting with a hotel offer so all we do now is we say okay well bronze dot accept and it can accept any type of offer visitor and in this case what we're doing is we're passing it in a hotel visitor so when I pass this in and now I run it it should print that bronze is interacting with a hotel offer and now we can see we are computing the cash back for a bronze card buying a hotel and we can even get more generic like I said before the issue was we couldn't polymorphically resolve to kind of abstract types right so I can come in here and I can say offer visitor pass that in and if I run it again we should get the same result so we are competing the cash back for a bronze card buying a hotel and we have two types that are basically being resolved at runtime two polymorphic types that are resolved at runtime so let's go ahead and do the same thing for the silver and gold cards and we should basically be seen that a silver card is interacting with a hotel offer even though it's just defined as an offer visitor and we see bronze silver and gold respectively and as we do this for different types of offers let's just call this visitor - we can do this with a gas offer and when we come in here and we update this with a visitor - we should now be saying we are computing the cashback for a you know whatever card we're dealing with buying gas so we'll click Play what and now we can see this is the interaction of a credit card with a different type of offer so all we've done is instead of tightly coupling the different types of offers or different operations to the original data structure and original API we've abstracted it in such a way that now we can have really really extensible operations or offers in this case totally decoupled from the actual credit card creat implementations so I can add new operations as I please we could come in here and we can add you know a restaurant offer a food offer grocery store offer and compute cashback based on all the different credit card types and dynamically and they can polymorphically resolve between two different polymorphic types offer visitors and credit cards and this allows for really really fluid extensibility between our data structures so the final question you may be asking when do I want to use this design pattern and you know there is no one all be all design pattern for every problem but typically you want to use this pattern when your class structure is somewhat unchangeable you don't change it often however the operations that you perform on the class structure for example you know these different types of credit cards change quite a bit so as a bank they may want to offer new incentives for different types of things like uber you know like I said restaurants grocery stores bind things at electronic stores they could add all different types of incentives very very often but the underlying data structure of each credit card may not change as much as the different types of incentives so that's a really good use case for using something like the visitor design pattern because I can come in here and I can create a new uber offer visitor and I can give cash back reward points for people using uber and I didn't modify any of these credit cards at all to be able to do that so that's just one use case in actuality from a technical perspective things like the visitor design pattern are heavily used when traversing trees and traversing nodes typically in things like compilers and interpreters it's a very good way to traverse through different types of expressions and adding different types of evaluation operations to those expressions so definitely check that out but I believe that covers it so if you guys have any questions feel free to leave a comment in the video and I will see you in the next one
Info
Channel: The Simple Engineer
Views: 31,775
Rating: 4.9269776 out of 5
Keywords: Visitor, Visitor Design Pattern, Pattern, Design Patterns, Design Pattern Tutorial, Design Pattern Video Tutorial, visitor pattern, design pattern, visitor design pattern c++
Id: TeZqKnC2gvA
Channel Id: undefined
Length: 32min 7sec (1927 seconds)
Published: Sat Jan 19 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.