Object-Oriented Programming in JavaScript

Ever heard the term “object-oriented programming”? Whatever does it mean? Does it even matter? How do we do OOP in JavaScript?

Motivation

Look at the world—or the universe—around you. What do you see? Do you see

If you see or feel the latter, you may like object-orientation (OO); if you like the former, you may like process-orientation. The latter view sees strings as things that can be uppercased, trimmed, and searched. The former sees uppercasing as a fundamental process in the universe, that uppercases all the things. How it uppercases a paper plate I do not know....

What does this have to do with programming?

OBJECTS PROCESSES
Things are fundamental Processes are fundamental
Democritus Heraclitus
Mostly bottom-up, focused on building blocks and components Top-down, focused on inputs and outputs
Object-oriented Algorithmic, or process-oriented
Recognizes fundamental objects and their behavior: objects know how to act Recognizes fundamental processes that “operate on” objects
Been around since the late 1960s Been around since the dawn of computing
Directly addresses complexity through abstraction, classification and hierarchy Approaches complexity by getting data abstraction and information hiding through clever tricks with functions, like making closures, which start to look like objects.
Natural view of system as a set of highly reusable cooperating objects with crisply defined behaviors. Communication via messages. Views a system in terms of algorithms. Subroutines process data (sometimes global). Subroutines pass data to each other. Subroutines can be composed.
Object-based designs leads to systems which are smaller, based on stable intermediate forms, more resilient to change, and can evolve incrementally (observation by Grady Booch). Appears to not scale up well. No real components to swap out; changes generally have wide scope. People end up packaging functions into modules or packages, which feel like...objects.

A First Example

Let’s see OO and non-OO code together. We’ll implement four operations:

 CircleRectangle
AreaArea of
Circle
Area of
Rectangle
PerimeterPerimeter of
Circle
Perimeter of
Rectangle

In the non-OO style, the functions are emphasized. In the OO style, the objects (actually the objects’ types) are emphasized:

Non OO-StyleOO-Style
  • area
    • of circle
    • of rectangle
  • perimeter
    • of circle
    • of rectangle
  • Circle
    • area
    • perimeter
  • Rectangle
    • area
    • perimeter

Non Object-Oriented Style

Here the functions are emphasized. Functions operate on objects. Some people might say in this case the things operated on are just data structures and not objects:

non_oop_shapes.mjs
// In the non-OOP style, functions operate on plain objects.

function area(shape) {
  if (shape.type === 'Circle') {
    return Math.PI * shape.radius * shape.radius
  } else if (shape.type === 'Rectangle') {
    return shape.width * shape.height
  }
  throw new Error('Unknown shape type')
}

function perimeter(shape) {
  if (shape.type === 'Circle') {
    return 2.0 * Math.PI * shape.radius
  } else if (shape.type === 'Rectangle') {
    return 2.0 * (shape.width + shape.height)
  }
  throw new Error('Unknown shape type')
}

// Tests
import assert from 'assert'

const c1 = { type: 'Circle', radius: 1.0 }
const c2 = { type: 'Circle', radius: 5.0 }
const r1 = { type: 'Rectangle', height: 1.0, width: 2.5 }
const r2 = { type: 'Rectangle', height: 10.0, width: 12.0 }
assert.equal(area(c1), Math.PI)
assert.equal(area(c2), 25.0 * Math.PI)
assert.equal(perimeter(c1), 2.0 * Math.PI)
assert.equal(perimeter(c2), 10.0 * Math.PI)
assert.equal(area(r1), 2.5)
assert.equal(area(r2), 120)
assert.equal(perimeter(r1), 7)
assert.equal(perimeter(r2), 44)

Object-Oriented Style

In the OO style, objects (unlike dumb “data structures”) have behavior. We often see OO code written with the behavior specification for an object (or object family) rolled into a class or interface or prototype or protocol. Here’s a JavaScript specification using the class syntax:

oop_shapes.mjs
// In the OOP style, behavior is bundled with the data.

class Circle {
  constructor(r) { this.radius = r }
  area() { return Math.PI * this.radius * this.radius }
  perimeter() { return 2.0 * Math.PI * this.radius }
}

class Rectangle {
  constructor(w, h) { this.width = w;  this.height = h }
  area() { return this.width * this.height }
  perimeter() { return 2.0 * (this.width + this.height) }
}

// Tests
import assert from 'assert'

const c1 = new Circle(1.0)
const c2 = new Circle(5.0)
const r1 = new Rectangle(1.0, 2.5)
const r2 = new Rectangle(10.0, 12.0)
assert.equal(c1.area(), Math.PI)
assert.equal(c2.area(), 25.0 * Math.PI)
assert.equal(c1.perimeter(), 2.0 * Math.PI)
assert.equal(c2.perimeter(), 10.0 * Math.PI)
assert.equal(r1.area(), 2.5)
assert.equal(r2.area(), 120)
assert.equal(r1.perimeter(), 7)
assert.equal(r2.perimeter(), 44)

In the expression c.area(), c is called the receiver and area is called a method.

Previously we saw that JavaScript’s class syntax actually was nothing more than a nice way to define a constructor function and a protype object to be used for all objects created with that constructor. Fine, so it’s not like the “real” classes that you might see in a language like Smalltalk or Ruby or even Java. But so what. It’s fine. Close enough.

The Expression Problem

We haven’t covered very much so far, but this comparison sure stands out:

 Non-OOOO
ExpressionFunctions-first:
  area(c)
Objects-first:
  c.area()
Ease of
adding new
types
Hard because we have to change existing code, adding new branches to the if-statements in all the functionsEasy because no existing code needs to be touched
Ease of
adding new
operations
Easy because no existing code needs to be touchedHard because we have to change existing code, adding new methods to existing classes

It seems like no matter which approach above we take, we will either have to edit existing code when adding operations, or edit existing code when adding types. Designing a programming language or paradigm in which you can add both new types and new operations without changing existing code is called the Expression Problem.

You can read about the Expression Problem, and various approaches to solving it, at Wikipedia. For now, though, there is much more to learn about OO.

Defining Object Orientation

Sigh. I hate definitions. Usually they can be pretty contentious. But most people today would say, to be object-oriented, a system must:

  1. be designed as a cooperating set of objects, where “code” is simply just another property of an object, namely its behavior;
  2. have some way of protecting the state of each object from interference from other objects, objects change their own state themselves (perhaps in response to a message from another object);
  3. allow objects to have types that have IS-A relationships to each other, such as a type for animals and a type for dogs, where we a dog IS-A animal and thus can do whatever an animal can do; and
  4. support something called late binding or dynamic polymorphism.

It’s fine to quibble over how strongly a system has to meet each of these criteria. There’s a view out there you should know about, that says systems meeting most of characteristics 1, 2, and 4, but not 3, are called object-based and not object-oriented. This view appears to have come from this paper.

But Alan Kay Said...

But here’s the thing. The term today does not mean what it originally meant, when it was coined by Alan Kay. What did the term originally mean?

There is great value in understanding the original big idea, but to also be pragmatic and understand that language evolves, and for better or for worse, the term means many things to many people. It certainly did not orginially mean what the word means today.

inigo-oop.jpg

Oh well. Understand both meanings. Kay’s original meaning was influenced by a big idea (attributed to his teacher Bob Barton): that the key to successful decomposition of a system is to break it down into parts that are just like the whole. Kay saw that computers need not be decomposed into separate and barely overlapping concepts of algorithms and data structures, but rather neatly into little computers that communicate through messaging each other.

Exercise: Read all six of the above linked articles and pages.

A Larger Example

So let’s explore the ideas of inheritance, encapsulation, and polymorphism. We will make some animals—three different kinds of animals. All animals will have a name, and they will all speak by saying their name and making a species-specific sound:

animals.js
class Animal {
  constructor(name) { this.name = name }
  speak() { return `${this.name} says ${this.sound()}` }
}

class Cow extends Animal {
  sound() { return 'moooo' }
}

class Horse extends Animal {
  sound() { return 'neigh' }
  gallop() { return 'galloping woohoo' }
}

class Sheep extends Animal {
  sound() { return 'baaaa' }
}

const h = new Horse('CJ')
for (const a of [h, new Cow('Bessie'), new Sheep('Little Lamb')]) {
  console.log(a.speak())
}

What does this show us?

But where is the encapsulation?

Exercise: Here are translations of the Animals-with-classes-and-inheritance script in several languages: Ruby, Python, Swift, CoffeeScript, Scala. Based on your best guess, which of these languages look to support information hiding on the animal’s name field?

Case Study: Modeling an Evasion Game

In addition to object-oriented programming, you can do object-oriented modeling. This is where to think hard about what are the right classes (and relevant methods, etc.) that describe your world. With a good model, the programming should come easy. (This is NOT to say that experimentation is not worthwhile. Sometimes you have to code a bit to truly understand what you need to build.)

So let’s say we need a game where our player is being chased by multiple enemies. We direct the player by moving our (mouse) cursor; the player moves toward the cursor and the enemies move toward the player. Try it:

Thinking in terms of objects with properties and behavior, we note:

The big deal in modeling.

When coming up with a family of classes, we look for those properties and behaviors that are the same among classes in the family and those that are different.

Here’s a first pass at hacking these ideas together:

primitive-chase-game.js
// This is a hacked together foundation of a chasing game, used to start 
// a discussion of object oriented programming. The game contains several
// design flaws, which will discussed in a classroom setting.

function setup() {
  game.initialize()
}

function draw() {
  game.update()
}

function mouseMoved() {
  game.mouseMoved()
}

class Field {
  constructor(width, height, color) {
    Object.assign(this, { width, height, color })
  }
  clear() {
    background(this.color)
  }
  clamp(x, y) {
    return { x: constrain(x, 0, this.width), y: constrain(y, 0, this.height) }
  }
}

class Agent {
  constructor(x, y, speed, target) {
    Object.assign(this, { x, y, speed, target })
  }
  move(field) {
    const [dx, dy] = [this.target.x - this.x, this.target.y - this.y]
    const distance = Math.hypot(dx, dy)
    if (distance > 1) {
      const step = this.speed / distance
      Object.assign(this, field.clamp(this.x + (step * dx), this.y + (step * dy)))
    }
  }
}

class Player extends Agent {
  draw() {
    fill('blue')
    circle(this.x, this.y, 10)
  }
}

class Enemy extends Agent {
  draw() {
    fill('rgba(255, 50, 50, 0.5)')
    circle(this.x, this.y, 20, )
  }
}

const game = {
  initialize() {
    createCanvas(400, 400)
    this.field = new Field(width, height, 'lightgreen')
    this.mouse = { x: 0, y: 0 }
    this.player = new Player(20, 20, 2.5, this.mouse)
    this.enemies = [
      new Enemy(4, 5, 2, this.player),
      new Enemy(94, 95, 1.5, this.player),
      new Enemy(400, 503, 1.8, this.player),
    ]
  },
  mouseMoved() {
    Object.assign(this.mouse, { x: mouseX, y: mouseY })
  },
  update() {
    this.field.clear()
    for (let agent of [this.player, ...this.enemies]) {
      agent.move(this.field)
      agent.draw()
    }
  }
}

Other Ways to Do OOP

We saw examples that used classes, with subclassing for inheritance and polymorphism through dispatching on a method receiver. But there are other ways to structure systems. Some people call them OOP; others say not so fast. But they solve the same problems of designing software with things and behvior. Here are three concepts you should know.

Prototypes

Using classes for inheritance is called classical inheritance. Using prototypes gives you prototypal inheritance. Let’s try the animals example with prototypes:

animals-prototypes.js
function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function () {
  return `${this.name} says ${this.sound()}`;
};

function Cow(name) {
  Animal.call(this, name);
}

Object.setPrototypeOf(Cow.prototype, Animal.prototype);
Cow.prototype.sound = function () { return 'moooo'; };

function Horse(name) {
  Animal.call(this, name);
}

Object.setPrototypeOf(Horse.prototype, Animal.prototype);
Horse.prototype.sound = function () { return 'neigh'; };

function Sheep(name) {
  Animal.call(this, name);
}

Object.setPrototypeOf(Sheep.prototype, Animal.prototype);
Sheep.prototype.sound = function () { return 'baaaa'; };

const animals = [new Horse('CJ'), new Cow('Bessie'), new Sheep('Little Lamb')];
animals.forEach(a => console.log(a.speak()));
console.log(animals[Math.floor(Math.random() * animals.length)].sound());

Here’s how things look:

prototypes-isa.png

If you like to get deep into JavaScript, note how the following relationships follow from the diagram:

h.name === 'CJ'
h.constructor === Horse
h instanceof Horse
h instanceof Animal
h.sound() === 'neigh')
h.speak() === 'CJ says neigh'
Object.getPrototypeOf(h) === Horse.prototype
Horse.prototype.isPrototypeOf(h)
h.hasOwnProperty('name')
!h.hasOwnProperty('sound')

This is a rather ugly example, because we have IS-A relationships which look a lot better with the class syntax. Then again, resist the tempatation to overuse hierarchies with IS-A relationships; they don’t really show up too often. (But when they do, they are useful.)

Exercise: Do some research to see if there are times when you might want to directly manipulate prototype objects without using the nice class syntax. Give a good example of such a case.

Multimethods

Rather than performing polymorphic dispatch on the method receiver, some languages dispatch on the types of all of the methods’ arguments. These methods are called multimethods. JavaScript doesn’t support multimethods, but you can find them in Julia and Clojure. If you are interested, see:

Protocols

You can get all the benifits of OOP by using protocols instead of classes, prototypes, or multimethods. Basically, protocols are bundles of properties and behaviors that can be “adopted” by objects. The language Swift bills itself as a protocol-oriented language, rather than an object-oriented language. To lern about protocols, do a web search for “protocol oriented programming” and have fun exploring. Also here are some code examples: