Lecture thumbnail 0:00 / 0:00 Okay, so let’s do a recap of what we’ve done so far.
So far, we haven’t really implemented the classic visitor, but we have implemented something which
works in the sense that we have a expression printer and this expression printer can basically use reflection
to check for the different types and to dispatch on the appropriate type by writing the appropriate
thing.
So we do it recursively, we call print recursively, but any time we do this, we have to perform reflection
by checking the type of the argument and dispatching on the appropriate lambda in this case.
Okay, so this works, but we still want something which is more universal.
And the next question which needs to be answered is would you be okay with extensibility of any hierarchy
provided that you just had to implement some method once?
So imagine that you can modify the hierarchy of elements, but you want to modify it just once and then
support any type of printer, any type of visitor to the hierarchy.
So that’s what we’re going to take a look at in this example.
And the way we’re going to do this is by modifying the base class.
So now I’m saying that inside the expression I am going to implement something which is similar to the
print method, but instead of having print, what I’m going to do is I’m going to define a very general
interface where any kind of visitor, any kind of component, can actually visit the hierarchy, so
traverse the hierarchy and do something in each of the elements.
So here what we’re going to do is we’re going to define a public abstract void, accept.
So accept is going to obviously accept a particular visitor.
And we need to define this visitor as well because, well, we need some sort of general type for visiting
objects.
So we’re going to have an interface called I expression visitor.
So I expression visitor is an interface implemented by any visitor of expressions, and it needs to
be able to visit all of the available types.
So we’ll have visit for a double expression and we’ll have a visit for an addition expression like so.
Okay, so this interface is precisely what gets fed into the accept method.
We have an expression visitor, visitor like so.
And then of course when it comes to double expression as well as addition expression, you have to implement
the new API.
But crucially, it’s not just for printing, it’s for everything.
So we only implement this API once.
Now this API is using something called double dispatch.
Double dispatch is a trick which allows us to pass type information about whoever it is that we are
processing from this particular method into the actual visitor.
So we take the visitor here and we simply say visitor dot visit providing this.
And crucially, when we specify this, it allows the visitor to figure out which of the overloads to
actually call, whether it needs to call double expression or addition expression or something else.
So we end up with an expression visitor that has to have an API member called Visit for every single
type, which is potentially visitable.
But at least if we miss something out, we’ll have an expression at compile time, not at runtime,
and we will not have quiet failure.
Meaning if you forget to implement accept with the right way, you will simply have the failure that
way.
But if you try to call visitor dot visit and you don’t have the type like for example, if I comment
out the double expression here.
Then we suddenly have read code and everything.
So we’re clear that this is not going to work and this is very diagnostic.
It’s very good.
So let’s implement the same thing here.
So all we have to do is say visitor dot, visit this, and that’s pretty much it for the intrusion.
So we’ve made a solution which is kind of intrusive because we modified the entire hierarchy.
We have a base class which now has this accept method, but the consequence is that you only have to
implement this one liner in every single derived class and that’s it.
So now the actual visitor, the component which is going to visit all of this hierarchies, has to implement
the eye expression visitor interface.
And this allows a certain amount of flexibility because we can do different things like, for example,
for printing.
So we’re going to do printing first.
For printing we can make a expression printer.
So let’s make a class called expression printer, and this class is going to inherit from or implement
rather eye expression, visitor So we alt-enter we implement the missing members and then of course
the actual implementation of these two.
All it has to do is actually get the values into the stringbuilder.
And now the problem is we no longer have the Stringbuilder as part of the visit method.
And here it’s up to you how you want to handle it.
So here, for example, I can make a private field called SB of type Stringbuilder Let’s do this again.
Stringbuilder sb equals new Stringbuilder Stringbuilder like so.
And then of course when it comes to visit a double, you say SB dot append dot value.
And then for the addition expression, well, we have our typical implementation like this like we had
before.
So this is the implementation of the expression printer.
I didn’t make it static here.
I don’t think it matters really whether you make it static or not, and we can now start using it.
So here, for example, after we make a stringbuilder, we can make a new expression printer, new expression,
printer like so, and then we can say expression, printer dot visit.
Actually, we don’t need the stringbuilder anymore.
We don’t need the Stringbuilder because now we have a stringbuilder as part of the expression printer.
Here it is.
So we say expression printer dot visit.
We visit this particular expression.
And then of course we can write line write line EBP Now if we want EBP to string to output something
meaningful, then we have to overload to string.
So we have to make an override actually and return SB dot to string.
So that’s how you would typically implement this.
And the consequence is we can now execute all of this and hopefully get the same result as we got previously.
So we’re getting one plus two plus three as before.
Now the great thing about this approach is having implemented the accept method in the entire hierarchy,
what we can do now is we can make a different kind of visitor.
So we’ve made a visitor for printing the expressions.
What about a visitor for calculating those expressions?
So let’s make a different class called expression calculator, which is also going to be an eye expression
visitor.
And here we’re going to take a look at some of the limitations of the current approach to the visitor.
So notice that in our method we don’t pass any additional arguments where data can be stored and we
also don’t have a return value.
So in the expression calculator, you would typically expect all of these visit methods to have a return
type of double, but you cannot do it.
Or at least in our approach, we don’t have the return type to work with.
So once again, the way you would typically deal with this and it’s not going to be pretty, is you
make some sort of public field or property called result public double result.
And then of course, when you visit a double expression, you simply assign the result.
You say result equals the value.
And in the case of the addition expression, things get a lot nastier because first of all, you have
to evaluate the left hand side.
So you say addition expression, dot left, dot, accept this.
So we’ve evaluated the left hand side and now result is stored for the left hand side.
So we say var a equals result, then we do it for the right side.
So we say E dot write, dot accept and we evaluate the right hand side of the expression and we say
var b equals result.
And then we assign the result.
We say result equals A plus B, And as a consequence of this, what we can do is we can calculate the
value and we can actually print this.
So here, for example, I can say var calc equals new expression calculator.
Expression calculator.
So this is a visitor for calculating expressions.
And this time around I can say calc dot visit with the expression and we can print both the expression
as well as the result.
So I can write line, for example, that we’re going to take the expression printer and this is equal
to calc dot result.
Remember, we’re keeping everything in result.
So if we now execute this, the second line shows one plus two plus three equals six.
So we’ve done this recursive calculation and we’ve kept our value.
Now the implementation here isn’t particularly nice because typically when you do a recursive calculation,
you want to change the return type.
You don’t want a void, you want some sort of double or something in here.
And the problem is you cannot easily get it.
Now you can start making an expression visitor of T where this is the result T and so on and so forth.
But then of course you have to propagate all of this information into your types and you end up with
maybe more than you can chew.
So maybe this is the best way to go around.
You can certainly add another argument here, a second argument where you can just have a dictionary
from, let’s say, string to object, and that way you can store all the intermediate information if
you need that intermediate information.
This is typically called the context.
So you can store some sort of context and then process it later rather than doing this kind of ugly
reassignment of the result and reading from result and writing to result.
But whatever you do, this is the classic implementation of visitor using double dispatch.
And this is what you see in most textbooks, especially in textbooks where there are no other more advanced
approaches such as C plus plus, for example.
And in C sharp.
It certainly works.
It works just fine.
And with a single cohesive implementation, you get to extend the functionality of the entire inheritance
hierarchy effectively.
Play Play Play Stop Play Play Play Play Start Play information alert