Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
What does the word composition mean? Let's start with the verb to compose. Looking up a dictionary you find a lot of references to composing music :) You will also find this definition:
to be formed from various things
The above is probably closer to what I'm about to talk about next - Composition.
Composition
The idea is to create something from other parts. Why would we want to do that? Well, it's easier to build something complex if it consists of many small parts that we understand. The other big reason is reusability. Bigger and more complex things can sometimes share parts they consist of, with other big and complex things. Ever heard of IKEA? ;)
There's more than one type of composition in programming, who knew? ;)
Composition vs Inheritance
Let's quickly explain inheritance. The idea with inheritance is to inherit traits, fields as well as methods from a parent class. The class inheriting from a parent class is called a subclass. This inheritance makes it possible to treat a group of objects in the same way. Consider the below example:
class Shape {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Movable extends Shape {
move(dx, dy) {
this.x += dx;
this.y += dy;
}
}
class Hero extends Movable {
constructor(x, y) {
super(x, y);
this.name = 'hero';
}
}
class Monster extends Movable {
constructor(x, y) {
super(x, y);
this.name = 'monster';
}
}
const movables = [new Hero(1,1), new Monster(2,2)];
movables.forEach(m => m.move(2, 3));
Above we can treat instances of Hero
and Monster
in the same way as they have a common ancestor Movable
that allows them to be moved through the move()
method. All this is based on a relationship principle IS-A
. A Hero
IS-A Movable
, a Monster
is a Movable
.
There's been a lot of talks lately on how you should favor composition over inheritance, why is that? Let's look at some drawbacks with inheritance:
-
Liskov substitution principle is done wrong, the idea behind this principle is the code should still work if I replace something with a common ancestor for something else, i.e replacing a
Hero
for aMonster
, they are both of typeMovable
. In the above sample code that replacement should work. However, the above code represents the ideal case. Reality is much worse. In reality, there might exist large codebases where there are 20+ levels of inheritance, and inherited methods might not be implemented properly meaning that certain objects can't be replaced for one another. Consider the following code:
class NPC extends Movable {
move(dx, dy) {
console.log('I wont move')
}
}
The above have broken the substitution principle. Now, our codebase is so small so we can spot it. In larger codebases, you might not be able to spot when this happens. Imagine this happens on inheritance level 3 and you have 20 levels of inheritance.
-
Lack of Flexibility, a lot of the time you have a
HAS-A
relationship overIS-A
. It's easier to think of different components doing various things rather than them having a commonality, a common ancestor. This may lead us to create a lot of extra classes and inheritance chains when a composition would have been more appropriate.
Function composition
It's a mathematical term stating that states the following according to Wikipedia, function composition is an operation that takes two functions f and g and produces a function h such that h(x) = g(f(x)). Related to programming is when we apply at least two functions on something like so:
let list = [1,2,3];
take(orderByAscending(list), 3)
Above imagine that list
is x, f(x)
is orderByAscending(list)
and g(x)
is take()
with f(x)
as input parameter.
The point is you have many operations applied one after another on a piece of data. Essentially you apply different parts to create a more complex algorithm that when invoked computes a more complex result. We won't spend so much time talking about this version of composition but know that it exists.
Object Composition
This type of composition is about combining objects or other data types to create something more complex than what we started with. This can be accomplished in different ways depending on what language you have in front of you. In Java and C# you only have one way to create objects, by using a class. In other languages like JavaScript, you have objects that can be created in many ways and thereby you open up for different ways of composing.
Object composition with Classes
Using classes is about a class referencing one or more other classes via instance variables. It describes a has-a association. Which means what exactly? A person has four limbs, a car may have 4 wheels, and so on. Think of these classes being referenced as parts or composites that gives you the ability to do something, a capability. Let's look at an example:
class SteeringWheel {
steer(){}
}
class Engine {
run(){}
}
class Car {
constructor(steeringWheel, engine) {
this.steeringWheel = steeringWheel;
this.engine = engine;
}
steer() {
this.steeringWheel.steer();
}
run() {
this.engine.run();
}
}
class Tractor {
constructor(steeringWheel) {
this.steeringWheel = steeringWheel;
}
steer() {
this.steeringWheel.steer();
}
}
What we are getting above is first a more complex class Car
consisting of many parts steeringWheel
and engine
and through that, we gain the ability to steer and a vehicle that runs. We also get reusability as we can use the SteeringWheel
and use it in a Tractor
.
Object composition without classes
JavaScript is a little different than C# and Java in that it can create objects in a number of ways like the following:
- Object literal, you can create an object by just typing it out like so:
let myObject { name: 'a name' }`
-
Object.create()
, you can just pass in an object and it will use that as template. Like this for example:
const template = {
a: 1,
print() { return this.a }
}
const test = Object.create(template);
test.a = 2
console.log(test.print()); // 2
-
Using
new
. You can apply thenew
operator on both a class and a function, like so:
class Plane {
constructor() {
this.name = 'a plane'
}
}
function AlsoAPlane() {
this.name = 'a plane';
}
const plane = new Plane();
console.log(plane.name); // a plane
const anotherPlane = new AlsoAPlane();
console.log(anotherPlane) // a plane
There is a difference between the two approaches. You need to do a bit more work if you want inheritance to work for the functional approach among other things. For now, we are happy knowing that there are different ways to create objects using new.
So how would we actually compose? To compose we need a way to express behavior. We don't need to use classes if we don't want to, but we can skip directly to objects instead. We can express our composites in the following way:
const steer = {
steer() {
console.log(`steering ${this.name}`)
}
}
const run = {
run() {
console.log(`${this.name} is running`)
}
}
const fly = {
fly() {
console.log(`${this.name} is flying`)
}
}
and compose them like so:
const steerAndRun = { ...steer, ...run };
const flyingAndRunning = { ...run, ...fly };
Above we are using the spread operator to combine different properties from different classes and place them into one class. The resulting steerAndRun
now contains { steer(){ ... }, run() { ... } }
and flyingAndRunning
contains { fly(){...}, run() {...} }
.
Then using the method createVehicle()
we create what we need like so:
function createVehicle(name, behaviors) {
return {
name,
...behaviors
}
}
const car = createVehicle('Car', steerAndRun)
car.steer();
car.run();
const crappyDelorean = createVehicle('Crappy Delorean', flyingAndRunning)
crappyDelorean.run();
crappyDelorean.fly();
The end result is two different objects with different capabilities.
But I use TypeScript, what then
TypeScript makes heavy uses of classes and interfaces and that's a way to accomplish object composition using classes.
Can I accomplish composition in a way similar to using the spread operator and objects?
Yes, yes you can. Hold on. We're going to use a concept called MixIns. Let's begin:
- First, we need this construct:
type Constructor<T = {}> = new (...args: any[]) => T
We use this construct to express that Constructor
is something that can be instantiated.
- Next, declare this function:
function Warrior<TBase extends Constructor>(Base: TBase) {
return class extends Base {
say: string = 'Attaaack';
attack() { console.log("attacking...") }
}
}
What's returned is a class inheriting from Base
. Base
is the input parameter to our function and is of type TBase
, that uses the Constructor
type we just created.
- Let's define a class that will use the above function:
class Player {
constructor( private name: string ) {}
}
- Now, invoke the
Warrior
function like so:
const WarriorPlayerType = Warrior(Player);
const warrior = new WarriorPlayerType("Conan");
console.log(warrior.say); // 'Attaaack'
warrior.attack(); // 'attacking...'
- We can keep composing by creating a new function that holds another behavior we might want:
function Wings<TBase extends Constructor>(Base: TBase) {
return class extends Base {
fly() { console.log("flying...") }
}
}
- Let's use that on an existing composition
const WingsWarriorPlayerType = Wings(WarriorPlayerType);
const flyingWarrior = new WingsWarriorPlayerType("Flying Conan");
flyingWarrior.fly();
Summary
This article described what composition is. Additionally, it talked about how composition is favored over inheritance. Then it covered that there are different ways to achieve composition. As a bonus, we also covered an approach you can use in TypeScript.
Further reading
There are some great articles on this topic that I think you should read. Have a look at the following:
Top comments (10)
This looks like it has issues compared to the composition I'm used to.
These are not real examples. A real composition each piece being composited will have local state and that local state will be per instance. In other words, I need a
steer
the has athis.direction
and I need arun
that also has athis.direction
and those should not be the samedirection
because there can be 1000s of things to composite and there would be no easy way for programmers to know they're all usingdirection
in the same ways.Well, you've just dissed Martin Fowler's definition of "code smell".
"code smell" != "certainly bad code"
By any definition of code smell, having to look at the internals of every function you composite to make sure it's not going to conflict with any other function is the very definition of stinky code. Gees
IMHO, 1000s of things to compose is a code smell.
Well you just dissed every Unity game that's shipped and all Unity devs so I guess it's you against them. Every Unity game is 1000s of things to compose.
I wonder if this is a typical thing in games particularly. Have you seen this kind of composition elsewhere? I'd appreciate any code samples that show the kind of composition you describe.
I'm sorry to say that you've been bamboozled. There's unfortunately misinformation circulating within the JavaScript community about object composition. That pattern you're showing here isn't object composition at all. In fact it's quite literally the opposite of composition. It's inheritance. It's multiple inheritance, to be more specific.
I'm going to show some examples in Python, because Python natively supports multiple inheritance. First up, just some basic single inheritance 101.
The
class Dog(Barker):
part is Python syntax for inheritance. It may not spell out the word "extends", but you can recognize inheritance when you see it, right? In this example, "Dog" is the child class, and it inherits the properties and behavior of the parent, "Barker", which is why we can call "bark" on the child object.But dogs need to eat too. Here's a slightly different example but still basic single inheritance 101.
In this example, "Dog" is the child class, and it inherits the properties and behavior of the parent, "Eater", which is why we can call "eat" on the child object. The only other difference here is the parent class, "Eater", uses an instance field, "name", that it expects the child class to provide. But otherwise this is still inheritance 101.
But dogs *both* bark and eat, don't they? We don't want to inherit just bark or inherit just eat. We want to inherit both. The feature to do that is called multiple inheritance, and in Python it's as easy as a comma.
In this example, "Dog" is still the child class, and it inherits the properties and behavior of two parents, "Barker" and "Eater", which is why we can call both "bark" and "eat" on the child object.
But... this is starting to look eerily familiar, right? This is the kind of thing you've been doing with the spread operator, the kind of thing you've been misled into believing is object composition. It may not spell out the word "extends", but you can recognize inheritance when you see it, right?
Regarding the object composition, have you heard of stampit? stampit.js.org/
Just scroll to the end of the main page.
For clarity. I'm the main maintainer.
I even have a piece about eater/pooper composition using stampit: medium.com/@koresar/fun-with-stamp...
FYI dev.to/jeffmottor/comment/120id
Some comments have been hidden by the post's author - find out more