0:00

[music]. In this segment, I want to continue our

study of subclassing with two classes that override methods of the superclass in

interesting ways. And particularly, the second one will be

much more interesting, because it will use the key idea of dynamic dispatch which is

at the heart of object-oriented programming.

So let me show you the code. We'll do most of it that way.

Here is this class Point that we're familiar with and have been using.

Remember, it has getters and setters for x and y, and has two different ways of

computing the distance from the origin. The first way just directly accesses

instance variables x and y. The second way uses the objects own getter

methods by calling self dot x and, and self dot y with no arguments.

So that's a Point class. Now let's consider a rather controversial

subclass. I'll explain why in just a second, which

are three-dimensional points. So in this subclass, we, in addition to

having an x-coordinate and a y-coordinate, have a z coordinate, which is for the

height to a point in three-dimensional space.

So I just add getter methods for z and setter methods as well.

And then, I change via overriding the initialize method to actually require

three arguments. I used super here so that the initialized

method in the Point class can be used to initialize x and y, and then, I set at z

equal to the z argument. Okay?

So now, I also need to override distFromOrigin and distFromOrigin2.

These need to do different things for a three-dimensional point.

It turns out the distance from the origin in three-dimensional space is the square

root of x squared plus y squared plus z squared.

I could implement those directly, and maybe I could do that.

It would be even shorter here, but I decided to show super in a similar way.

So, for distance from origin, I could get the distance in terms of the x and

y-coordinate by calling super here, that will call the distFromOrigin method.

In the superclass, let that be in this local variable d, and then take the square

root of d times d plus at z times at z and that would get me the proper distance.

For distFromorigin2 I continue this approach of using as helper methods, my

own getter methods. And so, I end up with a similar thing,

except I call distFromOrigin2 in the super class.

And then, add to that the result of calling the z method, calling the z

method, multiplying them together and then I take the square root.

So why is this controversial? Oh, people have been arguing about this

for decades. You could certainly make an argument that

it's a hack. It's an abusive subclassing to treat

three-dimensional points as points, because they really are a different thing.

It's not clear that a point in three-dimensional space is a point in

two-dimensional space. They're kind of fundamentally different

things, whereas other people argue no, no, no that makes perfect sense.

First of all, you get lots of code reuse and we like code reuse.

And moreover, you can think of it as a two-dimensional plane.

Maybe it's the projection of the point down onto the xy plane.

It's the equivalent of treating the z coordinate as zero.

Other people would argue that if that's how you feel about it, that's not how

these distFromOrigin methods behave. They do not return the distance from the

origin of that projected point, they return it in three-dimensional space and

you can argue about this all day long. I just want to argue here that, even if

this is poor style, it does help us learn the semantics of overwriting.

That when I create an instance of 3D point.

I inherit the methods xx equal y and y equal.

I override the methods initialize distFromOrgoin, distFromOrigin2.

And I add the methods z and z equal. And that is the definition of the methods

defined for a 3D point. So it's a good example of how overriding

occurs. In fact, I want to argue that it's a

fairly simple example. Okay.

Because with the examples so far, an object like in the instance of 3D point is

really not that different from a function closure.

A function closure just has one method calling, call me, whereas a 3D point has,

I don't know, eight or nine methods distFromOrigin2 z equals and so on.

Fine. The object has explicit instance variables

at x, at y, at z, whereas a closure has all those things in the environment that

it's allowed to use as it's sort of private state.

Fine, that's a moderate difference. We have this inheritance which let us

inherit things from the superclass, but that's, as I've shown it so far, really

just code reuse convenience. But there's a huge difference between

subclassing and closures, like in functional languages, which is what I'm

about to show you and that is there are ways to use overriding.

That can make a method you inherit from the superclass, still call different

methods in the subclass, and this is the thing that object-oriented programming

does differently than none object languages.

And so I want to emphasize that point, and I will do that, pardon the pun, emphasize

that point, sorry with this final class, PolarPoint.

So if you remember your high school geometry?

If not, bear with me. A PolarPoint is not represented by an

x-coordinate or y-coordinate. It's represented by a radius, a distance

from the origin r, and a theta, the angle of, of the point from, from flat.

So you represent it with an r and a theta. So here I am having Polar, PolarPoint

subclass point. But I'm going to override a bunch.

I'm going to override just about everything I'm going to have an initialize

method that instead of taking an x and a y, takes an r and a theta.

And initializes different instance variables.

Add r and add theta. Now, this is actually interesting.

My instances of PolarPoint won't even have instance variables x and y.

In most object-oriented programming languages, it, it would have them, but not

use them. Here, because of how Ruby does instance

variables it doesn't even have them, I'm just using different instance variables in

my subclass. So it turns out that I need to override

xy, x equal, and y equal, because I need to compute those methods differently when

my internal representation uses an r and a theta instead of an x and a y.

And the trigonometry is that the way you compute the value of the x-coordinate is

you return the radius times the cosine of the angle.

So, here is r times cosine theta, and the y is r times sine of theta.

Don't worry, you won't be tested on the trigonometry.

Just point, just pointing out that to the client, these look like getter methods,

but they're actually performing in an interesting computation.

Setter methods are actually a little more complicated.

If you want to set the x value in terms of some new x value you still have the old y

value. And you have to do a bunch of stuff with

arctangent and square root and all sorts of stuff.

I do believe this works. I'm just using this built-in y getter

methods. I'm calling this method here.

I'm storing at a local variable so I don't call y more than once, even though, I use

b multiple times and trust me that the math is correct.

Y equals is similar we do basically the same computation to set the new y field.

Now, for the interesting stuff. Let's override distFromOrigin and

distFromOrigin2. Well, it turns out the entire advantage of

a polar representation of a point is the distFromOrigin is trivial.

That's exactly what the r instance variable holds.

So, we need to do this override. If you look up in the superclass, this

computation, sorry, that's 3D point. This computation would be wrong.

We would go to read the x field or the at y field, they are not even there, we would

get nil back. If you try to multiply nil, you get an

error message. So it's essential that we do this

override. But now, the fascinating part is that

distFromOrigin2 doesn't need overriding. We could override it if we want, but it

works as is and this is the thing I want to emphasize to you.

If you look at distFromOrigin2, what it does when called is call self dot x and

self dot y, and then compute with the result.

Even though, distFromOrigin2 is inherited from the Point class, it is now down here,

part of the PolarPoint class. And so, when we execute those calls to

self dot x and self dot y on an Instance of PolarPoint, we get this code and this

code, these methods and therefore this will work.

Let me prove that to you. This is the key example.

I've already loaded the file. I could make a PolarPoint dot new 4,

comma, how about math pi over 4 ? So like this and it turns out I could ask

pp dot x and you get 2.82 and you could ask pp dot y and I get 2.82.

So what's happening here in the code is, even though, there's an instance variable

@r and an instance variable @theta, when I call the x method or the y method, I do

these computations and it turns out I get 2.8 from both of them.

If I ask pp dot distFromOrigin, I get 4 and that's because @r is 4.

And this code right here just returns it, it really is just a getter for r.

And now the interesting one, if I do distFromOrigin2 I do get 4, although, it

did some floating point computations, so I actually get 4.0.

And that is because I inherit distFromOrigin2, so I evaluate dist code.

But in an environment where the object itself is an instance of PolarPoint, so

when I say self dot c and self dot y, I execute this code down here, so I end up

taking r cosine theta, r sin of theta, squaring them, taking the square root and

I get the right answer, okay? So this is the key punchline, that when

you have a method that makes another call on the same object, when it uses self,

that self is the entire object. And if self is an instance of a subclass,

then you use the subclass' methods like the overridden x and y that we saw in our

definition of PolarPoint. That is the thing that distinguishes

object-oriented programming and subclassing from other things we've seen

in this course. So, I've now shown you an example.

And in the next segment, we can take a step back and give a more precise

definition of the semantics of method look up.

So, that we can understand this behavior precisely.