Inheritance

Building bigger abstractions

Recall that classes help us classify objects

All ball objects in a program are similar to each other in some ways.

All paddle objects are similar to each other.

So we write Ball and Paddle classes to capture those two kinds of objects that exist.

Different classes can be similar to each other

We saw examples of this is the Classes slides.

Ball class

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
    );
  }
}

Paddle class

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
    );
  }
}

Note the similarities

  • Both have x, y, and color properties.

  • Both have a draw method (though they do different things).

Higher-level classification

Not only are all Balls the same kind of object and all Paddles the same kind of object, in an important sense, all Balls and Paddles together are also the same kind of more general object.

Inheritance is a way to make more explicit what it means to be the same kind of thing.

Sprites

Things in games that can be shown on the screen and moved around are often called “sprites”.

We might say that balls and paddles are both kinds of sprites in our game.

Let’s write a class to capture the idea of what a sprite, in general, is.

A Sprite class

class Sprite {
  constructor(x, y, color) {
    this.x = x;
    this.y = y;
    this.dx = 0;
    this.dy = 0;
    this.color = color;
  }

  accelerate(dx, dy) {
    this.dx += dx;
    this.dy += dy;
  }

  move(elapsed) {
    this.x += this.dx * elapsed;
    this.y += this.dy * elapsed;
  }
}

Writing Ball as a Sprite

class Ball extends Sprite {
  constructor(x, y, size, color) {
    super(x, y, color);
    this.size = size ?? 15;
  }
  draw(g) {
    g.drawFilledCircle(
      this.x, this.y, this.size, this.color
    );
  }
}

A Ball is a kind of Sprite. It can be moved and accelerated and drawn.

Writing Paddle as a Sprite

class Paddle extends Sprite {
  constructor(x, y, width, height, color) {
    super(x, y, color);
    this.width = width;
    this.height = height;
  }
  draw(g) {
    g.drawFilledRect(
      this.x, this.y, this.width, this.height, this.color
    );
  }
}

A Paddle is also a kind of Sprite. It can also be moved and accelerated and drawn.

Ball and Paddle inherit things from Sprite

  • They don’t have to initialize the x, y, and color properties themselves.

  • They automatically get accelerate and move methods.

They both still have their own draw method

All Sprites should have a draw method but there’s no easy way in Javascript to require that.

The reason there’s no draw method in Sprite is because there’s no way to draw a sprite in general—we can only draw actual specific kinds of sprites.

Not about code reuse

Note, however, that the point is not to use inheritance just to avoid having to copy code.

The point is that by defining a class and then extending it with other classes you are creating a new higher level abstraction. (Or making explicit an implicit abstraction like we had with Ball and Paddle before we introduced the Sprite class.)