Part four, then, of our look at SOLID code
principles and that inevitably brings us
on to the letter I which stands to the
Interface Segregation Principle. And this
is normally formulated in a couple of
ways. One is it says that interfaces
belong to clients not to libraries. And
maybe the other way it's often
formulated is that clients should not
have to rely on methods that they do not use.
Now, neither of those I find
particularly helpful until you've
understood a bit more of the
nitty-gritty of it, and then they kind of
make sense looking back at them. What I
would normally say is the approach we
should follow if we want to conform with 
the Interface Segregation Principle is to
say that any interfaces we write
should be narrow and stable. Narrow in
the sense they only have a small number
of methods and stable in that that
number of methods doesn't grow as time
goes by. And in fact those two rules are
essentially applications of the first
two of our SOLID principles: the Single
Responsibility Principle and the
Open-Closed Principle. So the idea that
an interface should be narrow is really
saying what the Single Responsibility
Principle says - that an interface should
only be responsible for one job. It
shouldn't keep having lots of other jobs.
And then the fact it shouldn't expand,
the fact it should be stable is really
saying the same as the Open-Closed
Principle - that when we add new
functionality we do it by adding new classes,
not by modifying existing classes. Well,
it's slightly different because we're
applying it to interfaces, but it's
certainly along the same lines. Now the
example I've got to show today is based
on a thing called turtle graphics which
you may have come across. It's often used
as a way of teaching programming to
children whereby you actually physically
have a piece of paper that you spread
out - a large piece of paper you spread
out on the floor - and on that you put
this very simple robot known as a turtle.
And then you've got the ability to
instruct the turtle to move to a certain
location on the paper and additionally
inside the turtle you've got a pen and
that pen you can either have up or down.
And so if the pen is down when the
turtle moves it draws a line on the
paper. And that way we can do all sorts
of graphics and understand some of the
basics of programming.
I'm not going to do this with paper on the
floor, but I can do it in Windows using a WinForms application. So that's
what I've got here. Let's just run it up
and show you what's happening there then.
We can see we've got this WinForm and
I've got a command menu, and I've got the
ability to draw four basic shapes, of
which actually only the line is
implemented at the moment. But if I click
on the line you can see the turtle
there effectively drawing the line
across the page. And that's all done
through an interface that I've written
here in TurtleLogic called ITurtle. And
you can see it's nice and simple. It's
got the three basic operations we said a
turtle has: so moving the pen Up and Down
- so as to whether it's on the paper or
not - and then just a Move that takes us
to a particular location. We've also got
to have the ability to set this thing
I've called an ICanvas. So that
represents the paper that the turtle is
actually going to draw upon. And so we
can see in our form what's happened here
is in the initialize. We receive this
turtle, we set the canvas to ourselves
and then in the draw line that we've got
there. We simply lift the pen up then we
move to a location, put the pen down, move
to another location and that's going to
draw the line as we've seen it.
The implementation of our Turtle is in here
and there you can see we've got our Up
and our Down. We simply set our canvas and then we've
also got our Move. The only slight
complexity here - which isn't really
relevant to what we're doing, it just
makes the application look a bit more
realistic - is that when we move, if we're
actually drawing, we do this StaggeredDraw.
And the StaggeredDraw breaks the
draw down into a number of steps that we
can see are done like that with a slight
delay between each one, and that's what
allows us to mimic the behavior of the
turtle. Obviously it would be very simple to
write this so that the line just
appeared there, but we've got it so the
line builds up gradually. But the key
thing is, if we look at this turtle
interface, it's already obeying the
Interface Segregation Principle in that
it is narrow - it focuses on a particular
job - and we shouldn't
really think we have much need to expand
that to add new methods in there.
But having said that it's also not a
particularly useful interface because
we've seen all we can do is move the pen
up and down and move the position of the
turtle. And we also saw what we want to
be able to do is draw things like lines
and squares and circles and triangles.
And if we had to do all that simply by
calling that low-level interface it
would possible, but it would be a bit
dull. So something you might want to do
is, in here we might want to, on our
Turtle, actually add the ability to draw, let's say, a square; that was one of the things
we have listed there. So I need to put in
a 'public async Task Square' and on our
square we'll take a 'double' that we'll call
'side' to represent the length of the side.
And then the first thing we're going to
do is we're simply going to lift the pen
up so that we're not drawing, then we're
going to move to the start point.
Remember all of this has got to be
asynchronous, so I'll go to put the
'await' in here. So we're going to move and
our start point is going to be
'0.5 - side/2'  as an x and '0.5 - side/2' for y.
So this works on a one-by-one coordinate
system, so we're going be offsetting from
the centre there. We're then going to have
to put the pen down to start drawing and
we're going to move from the start point.
So let's now move downwards. So if we go
to the y value of  '+ side/2' - that
will draw us down. Then if we move again
to both of them being +, then that
will move us across the bottom. Then if
we move to x being + and the y being -,
that will move us up the right-hand side.
And then finally if we move back to
where we started then that will complete
the square for us. So we put that in
there as a new method on our Turtle,
which is not an unreasonable thing
to do. But the thing we then should not
do, if we want to make this conform with
the Interface Segregation Principle,
is simply dive in there and add on to our
interface a method called DrawSquare
that we've already got the
implementation for because that would
break this idea that clients shouldn't
be given methods they are not interested in.
Our current client the TurtleForm
has no interest in drawing squares that
we know of and so if we just put that
function in there it would start making
this interface more complicated than it
needs to be. It would have methods that
our TurtleForm wasn't necessarily
interested in. So instead of changing
that interface one simple way that we
can implement the Interface Segregation
Principle is to use interface
inheritance. So what I'll actually do is,
in here I'll add a new interface and
I'll call this 'IShapeDrawer' and then
on this one I will derive this from ITurtle so that will pick up the Up and
the Down,  the Move and so forth. I'll put
into that my 'Task Square' which takes
'double side'. And then all I need to do in
my Turtle itself, rather than
implementing ITurtle, if we implement
IShapeDrawer then that now is
implemented by that Square function we
already had, so it's a bit of an odd way
round to do it. But the key thing is all
of those changes we made to the library
did not have any impact whatsoever on
the existing client - our TurtleForm -
because we didn't modify the ITurtle
interface. We created a new interface and
so we don't need to make any changes at
all to our TurtleForm.
Obviously you still can't draw a square, but it's not broken in the sense it can
still draw a line in the same way. And
then as time goes by, if the client wants
to start making use of the new features
it's up to them to upgrade to that. So if
we go into our TurtleForm now I can - as
and when I choose - change that to
IShapeDrawer. And also I need to change it up there. And so now we're dependent on the new
interface and we can make use of it. So
here I've just got an empty method
already for drawing a square. So if I say
'await' and then '_turtle
.Square' and give that '0.3' as the side length. And so now when I
click Square we can see it draws the
square in the way that we wanted.
So that's the idea we said, that it's the
client that owns the interface not the
library that owns the interface. Just
because I had a new feature in my
library, I didn't force that upon existing
clients by putting a new method in the
interface. The interface should be
designed to fulfill the client's needs,
not simply to expose all of the
functionality that we have in the
library. And so doing it that way meant
clients as they feel like it, if they
need to draw a square, can upgrade to the
new interface. That said, we still have a
bit of a problem because our
IShapeDrawer is now drawing a square, but
clearly that's insufficient. There are
going to be more shapes you want to draw,
but if we now put another method in
there for Circle, and we put another
method in for Triangle, then with each
one of those we're once again breaching the Interface Segregation Principle, because
it's an unstable interface; it's growing
every time we think of a new shape.
And essentially that is going to be an
open-ended problem. So actually we can do
something rather cleverer here, and once
again as we saw with the Open-Closed
Principle, we can rely on the Strategy
design pattern and actually make a hugely
flexible interface for this IShapeDrawer,
which will mean that it never needs to
change again. And the way I'm going to do
that is this I'm going to pop in another
interface. So we're getting a lot of
interfaces in here but they all have
jobs to do. And this one I
simply call 'IShape' and we'll make that 'public'. And for
now we won't give that any methods. We'll
see what they need to be as we come to it.
But now I'm going to change the IShapeDrawer. So I'm going to admit that was a
mistake - a ShapeDrawer shouldn't be
specifically drawing a square - and
instead it's simply going to have a
single method called 'DrawShape', which
will take as a parameter one of these
IShapes from the interface.And I think
- although the details will come - you can
already see where we're going. Now that
we've got this interface IShape then
all we need to do is write a Square for
drawing a square, a Circle for drawing a
circle, Triangle for drawing a triangle
and then we can pass each of those as a
strategy, effectively, into our DrawShape.
But the IShapeDrawer itself is now
completely stable. What other method
could an IShapeDrawer need other
than the ability to draw a shape. So what
we now need to do, obviously,  is change our Turtle so it implements the new IShapeDrawer.
So we'll just do the implement interface.
And so now we've got the DrawShape,
we're going to need to make that async. And what it's going to do is it's going to call a
method that we haven't yet put in there,
but we're going to do 'shape.Draw'
passing in ourself as the thing we're
going to drawing with. And that we're going
to make 'await' so we get that StaggeredDraw in, and so now let's go back to our
interface. So on here we will put 'Task Draw'. And that one is going to take an
ITurtle as the turtle we're going to use to do
the drawing. So that now should be happy.
And then we can do something like add a
square on there. So if I now add a class,
call this 'Square'
and then we need to get Square to
implement IShape. That then needs to
have the method 'Draw'. And then we've
already got the code for that, so let's
just nick that out of Turtle. So take
that we're not going to have a 'Square'
method on Turtle at all anymore. We'll
put it in here and then the only thing
I've got to bear in mind is all those
methods that were previously the turtle
was calling on itself, we're now going to
have to call them on the turtle that's
passed in. So if we do that and that and that and that.
And then the other thing we can see is,
we don't know what 'side' is. So that's
something that I would suggest is best
passed in as a constructor parameter.
So if we put a constructor in there then
there we can put the 'double side', then
we'll have a 'private' member call '_side'.
Finish like that there and then in here
just a load of places we've got to
change that.
And so that should now all be perfectly
happy. And then if we go back to our form,
that's also complaining because ITurtle
doesn't have a Square method anymore.
But we can add just change that to 'DrawShape' and then 'new Square' and then we
pass in that '0.3' as a
parameter. And if we run that then we can
now see the square's working just in the
same way that it did before. And now we
can go on to implement our circle and
our triangle without changing any
interfaces whatsoever
following entirely the Open-Closed
Principle, and more specifically the
Interface Segregation Principle. So if I
just add a new shape ... so let's add a new
class called 'Triangle' and then rather
than implementing that, I've actually
prepared the code earlier. So here we've
got the code for a Triangle and just pop
that in there and similarly we can do a
circle. You'll notice I'm adding these
classes into the TurtleLibrary. Actually
one of the benefits of doing this way is
they could go anywhere. They don't need
to be in the same library, and so once
the TurtleLibrary has been written - is
sealed, is finished - then we could be
putting these into the client code or in
some other shape library that we provide.
Really doesn't matter at all. Well, let's
just take our Circle from there. Just
need to add a namespace in each of those,
and then finally if we go back to our
form and where we've left the blanks.
So simple. I can on the circle do a 'DrawShape' pass in a 'Circle' and leave
'0.3' as the radius. And then we
could also in here pass in a Triangle,
the triangle has a 'baseLength'. Again,
let's leave that as '0.3'.
And so now we can see
that if we run that up we've got our
square that we had before. But now if I
draw a circle, we get the circle round
there, and if I draw a triangle, we get
the triangle fitting inside the square
like that. So that was using the Strategy
pattern in a rather similar way to what
we did earlier to help us with the
Open-Closed Principle, this time to help us
with the Interface Segregation Principle.
So our interface now is just so simple.
It's never going to need to extend;
it's extended by adding new classes -squares, triangles, circles, whatever they may be.
But that's now going to be a very narrow,
very stable interface that we can hand
over to anybody. That said actually that
ShapeDrawer probably is still breaching
the Interface Segregation Principle
because of the way that we've done this
inheritance. Because ShapeDrawer  is
derived from ITurtle, therefore actually
ShapeDrawer has as well as the DrawShape, it has Canvas and Move and Down
and Up. And though we see that our
implementations for Square and Circle
so forth do use those methods, they're
not necessary in the interface itself.
There's nothing fundamental that says if
you want to draw shapes you need to have
an Up a Down and a Move method. You
should just have that DrawShape.
IShapeDrawer is a little bit wide and what
we should really be doing is separating
these two out completely. Now that said,
there is one thing in ITurtle that
IShapeDrawer does require, I would say, and that is a
canvas. You can't be a ShapeDrawer without having something to draw upon. So what
we'll actually do is a restructuring - and
this one may remind you a little bit of
what we did in the video on the Liskov
Substitution Principle, because you can
see how these are all interrelated. So
I'm going to add another interface and
this one I'm simply going to call 'IDrawer'
and the only thing that we have to have
in a IDrawer interface is it's got to have
the ability to take a Canvas. So let's
take that from our ITurtle, put that
into our IDrawer
and then we can say that our ShapeDrawer now simply derives from IDrawer.
So it's got to have a Canvas but there's
no requirement for it to have a Move or
an Up and Down. And our ITurtle also has
to have a Canvas. So we'll put that in
there and then if we go to our Turtle
itself then that's going to be both an
IShapeDrawer and an ITurtle. And you'll
see already that implements all those
methods necessary from those
interfaces. Now if you take a look at
where the Turtle is now, you might think
that the Turtle class itself is starting
to get a bit busy. It may be starting to breach the Single
Responsibility Principle because it's
got quite a lot of methods doing
different things. And that may well be
true, but it's not what we're focusing on
at the moment. So although there's room
for improvement here, the important thing
is we have got our interfaces right. And
in a sense they're more important
because our interfaces are what's going
to be seen in the outside world,
whereas our implementations, if we later
on want to fix them and make them more
structured, it doesn't matter as long as
the interfaces are right for everyone
who's using them. So now all we need to
do to get that to work, if we just take a
look at what's happening in our TurtleForm we can see we've got a slight error
in that we now don't have the basic
turtle graphics in there because,
remember, our ShapeDrawer is now ... we call that 'turtle' - it's not strictly speaking a
turtle, because it doesn't implement ITurtle. So this is one of these cases
where we could have used both interfaces,
but actually it would make more sense
here, I think, to actually have another
one of these shapes for drawing a line.
So last thing we can do here is go for a
new class. We'll call this 'Line' and
implement IShape.
And then just take that code, pop that in
there. And now we can in here do the same
as we have done with all the other ones. So we just 'await _turtle.DrawShape' and in
this case a 'Line'. And I haven' t bothered with any constructor parameters on there.
And everything should be happy. So we can
draw a line; we can draw a square; we
can draw a circle; we can draw a
triangle and we can draw any other shape
we can ever think of. And when we do that
it'll be entirely in accordance with the
Open-Closed Principle. So adding a new
shape is adding a new class not
modifying existing classes. But that's
achieved because we've obeyed the
Interface Segregation Principle. So our
key interfaces are IDrawer - simply says if
you're going to be a Drawer, you just have
to accept a canvas to draw upon. We've
then got ITurtle which says if you're
going to be a Turtle you've got to have
the Up, Down and Move methods. And then we've got the IShapeDrawer which says if
you're going to be a ShapeDrawer obviously, like a turtle, you've got to be an IDrawer.
But then you've just got to have that
DrawShape method. Those are all
implemented in the Turtle, but that's
another matter. The important thing is we
have got narrow and therefore stable
interfaces that we are presenting to our
client in the TurtleForm. Once, again as
I mentioned when we were looking at the
Liskov Substitution Principle actually,
the final one of our SOLID principles,
the Dependency Inversion Principle is
one that's going to tie this all
together, because at the moment we
haven't really looked at how we hook all
these interfaces together. I've kind of
hidden it there if we look at our TurtleForm
and look at the constructor for
that, you can see that it's taking in
this interface IShapeDrawer. And it's
actually down here in my program that
that's simply been hard-coded, and a lot
of what we're trying to do here is avoid
that sort of hard-coding. And so that's
what we'll see next time when we look at
the last of the five SOLID code principals:
the Dependency Inversion Principle.
But until then, hope you enjoyed this video. Do subscribe if you want to see more videos
and also any questions or comments, pop
them in the comments section below and thank you very much.
