Message Oriented Programming
with examples in JavaScript
Object Orientation is often thought of as development using classes. Many languages these days have such a construct.
Here’s an example in JavaScript. I’m showing JavaScript because it’s ubiquitous. Most languages will do.
class Animal {
#name;
constructor(name) {
this.#name = name;
}
get name() {
return this.#name;
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
speak() {
return "woof";
}
}
const spot = new Dog("Spot");
console.log(spot.name, "says", spot.speak()); // "Spot says woof"
The moving parts
We can create objects by instantiating classes. Object Orientation thinks of a program as objects collaborating together. Collaboration happens by exchange of messages between these objects.
In the example above, we create an object and assign it to the variable spot
. Message exchange happens via “baked in” methods, properties and the new
keyword.
new Dog("Spot"); // a new message
spot.name; // a "name" message - using a property
spot.speak(); // a "speak" message - using a method
The JavaScript VMs optimise these “baked in” ways of defining and exchanging messages. But, this optimisation tends to obscure the nature of message exchange between objects. Few of us think of “sending a message”. Instead we “invoking a method” or “query a property”.
Because we define methods and properties on the class of the receiving object, it’s hard to think of it as a message. When I send you a letter, the onus is on me, the sender, to define its meaning. But, in class-based object orientation, the onus is on the receiver.
Refocusing on messages
This pre-existing mechanism for constructing object oriented programs is one of many. Object Orientation is a mental model with many implementation options.
We can conceive of a message as something represented by the language’s built-in “object” construct.
const speakMessage = { type: "speak" };
const nameMessage = { type: "name" };
and, we can further refine this pattern using Symbols to establish a shared taxonomy:
const Name = Symbol("Animal's name");
const Speak = Symbol("Speak, please");
const speakMessage = { type: Speak };
const nameMessage = { type: Name };
Our classes might now look like:
class Animal {
#name;
constructor(name) {
this.#name = name;
}
receive(message) {
return message?.type === Name
? this.#name
: undefined;
}
}
}
class Dog extends Animal {
constructor(name) {
super(name);
}
receive(message) {
return message?.type === Speak)
? "woof"
: super.receive(message);
}
}
const spot = new Dog("Spot");
console.log(spot.receive(nameMessage), "says", spot.receive(speakMessage)); // "Spot says woof"
We now have classes which can accept messages via a well-known receive
method. This restores authoring of messages to the caller, and leaves the receiving object free to decide if and how to respond.
Doing without classes
Classes are a (relatively) recent addition to JavaScript. Before them, we used functions and prototypes. In fact, classes in JavaScript are just a special sort of function.
In our sample code, we have objects which exchange messages by calling either new
or receive
. No other methods or properties are needed. As it turns out, functions are a simpler mechanism than a class with a single method.
We can simplify our classes like this:
function Animal(name) {
return function receive(message) {
return message?.type === Name
? name
: undefined;
}
}
function Dog(name) {
const base = Animal(name);
return function receive(message) {
return message?.type === Speak
? "woof"
: base(message);
};
}
const spot = Dog("Spot");
console.log(spot(nameMessage), "says", spot(speakMessage)); // "Spot says woof"
and this can be slimmed down a bit more using arrow functions:
function Animal(name) {
return message => message?.type === Name
? name
: undefined;
}
function Dog(name) {
const base = Animal(name);
return message => message?.type === Speak
? "woof"
: base(message);
}
const spot = Dog("Spot");
console.log(spot(nameMessage), "says", spot(speakMessage)); // "Spot says woof"
Conclusion
This article aims to show that you can implement an object oriented mental model using mechanisms other than classes. Indeed, sometimes it’s useful to do so. Subsequent articles in this series will examine how.