Making objects better
Objects let us collect multiple properties into a single value.
const ball = {
x: 10,
y: 20,
size: 15,
color: 'blue'
};
const drawBall = (b, g) => {
g.drawFilledCircle(b.x, b.y, b.size, b.color);
};
This function assumes the object has a certain structure.
const paddle = {
x: 10,
y: 20,
width: 10,
height: 20,
color: 'blue'
};
This is not just a different object but a different kind of object.
Balls and paddles have different structure, i.e. properties.
Obviously we draw a paddle differently than we draw a ball so we need a different function.
const drawPaddle = (g, p) => {
g.drawFilledRect(p.x, p.y, p.width, p.height, p.color);
}
Remember this function because we’ll come back to it near the end of this deck.
Imagine you had a bunch of already existing objects:
{ x: 10, y: 20 }
{ x: 30, y: 40 }
{ name: "Fred", age: 14 }
{ name: "Sally", age: 15 }
you could classify them into groups based on what properties they have.
Points:
{ x: 10, y: 20 }
{ x: 30, y: 40 }
People:
{ name: "Fred", age: 14 }
{ name: "Sally", age: 15 }
If we make multiple objects with the same structure, i.e. the same properties, we can use them with the same functions.
Classes give us an easy way to define that structure.
Ball
classclass Ball {
constructor(x, y, size, color) {
this.x = x;
this.y = y;
this.size = size ?? 15;
this.color = color ?? 'blue';
}
}
Note: the ??
operator is kind of like the boolean
||
operator except it returns either the first value,
if it is defined, or the second value.
const ball = new Ball(10, 20);
const ball2 = new Ball(30, 40, 25, 'purple');
new Ball()
creates a ball object and then runs the
constructor from the Ball
class to initialize that
object.
Within the constructor the variable this
refers to the
newly created object.
constructor(x, y, size, color) {
this.x = x;
this.y = y;
this.size = size ?? 15;
this.color = color ?? 'blue';
}
The name constructor
is required.
Kind of like a function. Takes arguments (x
,
y
, size
, and color
in this
case.)
The constructor’s job is to put the object into a usable state.
If you recall from when we first looked at strings, objects don’t just have properties.
They also have methods.
s.slice(1, 3)
s.toLowerCase()
s.toUpperCase()
Recall that methods have access to the value of the object to the
the left of the dot, the value of the variable s
in
this case.
class Ball {
// constructor as before
draw(g) {
g.drawFilledCircle(
this.x, this.y, this.size, this.color
);
}
}
As in a constructor, this
is a special variable whose
value is the object on which the method was invoked.
A function takes the ball as an explicit argument:
const drawBall = (b, g) => {
g.drawFilledCircle(b.x, b.y, b.size, b.color);
};
A method takes the ball as an implicit argument refered to
as this
:
draw(g) {
g.drawFilledCircle(this.x, this.y, this.size, this.color);
}
Polymorphism is from Greek “poly” meaning “many” and “morph” meaning “form”, so “many forms”.
When talking about methods it means when we have a single named method that takes on multiple forms.
const drawPaddle = (g, p) => {
g.drawFilledRect(p.x, p.y, p.width, p.height, p.color);
}
class Paddle {
// constructor, etc.
draw(g) {
g.drawFilledRect(
this.x, this.y, this.width, this.height, this.color
);
}
}
Like the Ball
class, this class also has a method named
draw
.
This one draws a paddle given a graphics object.
class Ball {
constructor(x, y, size, color) {
this.x = x;
this.y = y;
this.size = size ?? 15;
this.color = color ?? 'blue';
}
draw(g) {
g.drawFilledCircle(
this.x, this.y, this.size, this.color
);
}
}
class Paddle {
constructor(x, y, width, height, color) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color ?? 'blue';
}
draw(g) {
g.drawFilledRect(
this.x, this.y, this.width, this.height, this.color
);
}
}
Polymorphic methods let us treat different kinds of objects as the same in some sense.
Balls and paddles have different internal structure and look different on the screen.
But they are similar in that they both can be drawn.
for (let i = 0; i < balls.length; i++) {
drawBall(balls[i], g);
}
for (let i = 0; i < paddles.length; i++) {
drawPaddle(paddles[i], g);
}
for (let i = 0; i < things.length; i++) {
things[i].draw(g);
}
When we call draw
on an object, which version of
draw
is used is automatically determined by the class
of the object.
If thing[i]
is a ball, it calls the
Ball
version of draw
and if it’s a paddle
it calls the Paddle
version.