Decide to stick to the Single Responsibility Principle for the printing of these expressions that we
made and what this means is that we have to remove all of this functionality from every single type
of expression and basically stick them into a separate class.
Now this is where we're going to encounter a very serious problem and essentially a limitation of the
way that most of the statically typed object oriented languages actually work.
So what I'm going to do is I'm going to define a news truck to call them the expression for it.
Now if you want to print any kind of expression you could be forgiven for writing something like the
following.
You could be forgiven for writing.
OK let's just do an overload for every type of expression.
So we're going to be able to print's a double expression
into some holstering stream
and in here everything is easy because printing an expression is just very simple you just say the value.
And that's it.
You've basically printed it to the stream.
However you will be able to write the corresponding print expression for an additional expression.
So writing print for an additional expression is going to be impossible you'll see why in a second.
OK so what we want to do here is we want to obviously print the opening round bracket.
That's the easy part but not what we want to do is we want to invoke the corresponding print method
on the expression printer for the argument which is the left and right sides of the expression.
So you decided to print something like the following you decide to say print the left.
Call my posting screen.
This is not going to work and the reason why it's not going to work is because we don't have an overload
of print which actually takes an expression and there is no way for us to determine at compile time
what kind of expression a left is.
Is it a double expression is it an additional expression we simply do not know.
Now the dispatch which is the choice of which overload to call actually happens at compile time.
And unfortunately here we don't have that information the compile time we need to look at what the pointer
actually points to.
And we need to determine where to actually go do we call this one.
Or do we call this one so this kind of approach that I've outlined here is very nice and it might work
in some dynamically typed language but in a statically typed compiled kind of language like C++ it's
simply not going to work.
So what can we do about this.
Well let's get rid of this for a second.
We're going to write a general print implementation and also what I'm going to do.
I'll actually print an internal postering stream here so I'll have it as an internal variable that will
subsequently write into and we can we can subsequently return so here somewhere I'll have a function
called SDR which is actually going to return the contents of the stream like so.
OK now the idea is that we have just a single function so we're going to have a function called Prim's
which takes an expression pointer let's call it.
And now what we need to determine is how to printed depending on its type.
So how can we determine this type.
Well the simplest way to do it is using a dynamic cast so we can try casting it for example.
First of all to a double expression so if auto d equals dynamic cast to a double expression.
So if it is in fact a double expression then we can print it as a double expression so we can simply
print the value like so.
Otherwise it is an additional expression.
So also a key.
Some formatting here.
If auto equals dynamic cast addition expression like so with a pointer Obviously its a pointer cast.
We try to cast.
If that's the case then we output the opening round brackets and the plus and the closing round bracket
then in between what we can do is we can actually call up print with a left and a right because remember
this call takes an expression pointer were allowed to pass and an expression pointer because that's
exactly what we have.
So this kind of approach will actually work.
The only downside to this approach.
Well there is actually several downsides.
First downside is the dynamic cast.
The fact that the dynamic cast typically house a runtime cost associated with it but the much more painful
approach is the much more interesting problem I would say is the fact that what if you forget something.
What if you have an element of your expression hierarchy and you are not checking it.
In the if statement then you're not going to have any output.
And furthermore what you can do here as you as you kind of feed these things is you can simply well
apart from forgetting which means you just forgot the appropriate.
If you would have to keep adding the IFS for every single element in the heart.
And now imagine if you have like 20 different elements for example you have 20 different inheritors
of expression.
You would have an if statement with basically 20 clauses in it which is particularly inconvenient in
our case because it's a very limited scenario it's actually a workable solution.
So what we can do here is we can still make what we can get rid of the postering stream and we can get
rid of most of this part and we can now use it try using the new approach so we have this expression
printer.
What we can do is we can say if you don't print and provide a pointer to the why is a pointer itself
so just provide that and then we can say see out there and line.
And hopefully at the end of this we're going to get pretty much what we've been getting in the previous
lesson which is just the output one plus two plus three with the appropriate set of Brownback.
So here it is.
So this solution is somewhat better because we're sticking to our guns we're doing separation of concerns
and what we can do now is we can get rid of this implementation so we can get rid of the prince and
that's defined in the entire hierarchy because we don't need that anymore.
We now have separation of concerns and all the printing so the printing concern is essentially in the
separate structure.
So at least we've added that we haven't achieved the goal of easy extensibility of entire hierarchies
but we certainly were on our way.
Shall we say so this is an in-between approach and I call this the reflective visitor and the reason
why I call it that is because typically these kinds of time checks in other programming languages they
are done via a mechanism called reflection now C++ doesn't have reflection but it does have dynamic
cast which is a similar kind of idea.
So this is a reflective printer some somewhat better than the intrusive approach shall we say but on
the other hand it's not perfect either.