We looked at some variations on creating encapsulation to help reduce the “surface area” for dependencies to grow and names to clash, but noted that this merely slows down the growth of the problem, it does not fix it.

That being said, that doesn’t mean that mixins are terrible. Mixins are simple to understand and to implement. As we’ll see in this essay, it is straightforward to refactor away from mixins, and then we can work on reducing our code’s complexity.

For many projects, mixins are the right choice right now, the important thing is to understand their limitations and the problems that may arise, so that we can know when to incorporate a heavier, but more complexity-taming architecture.

It is more important to know how to refactor to a particular architecture, than to know in advance which architecture can serve all of our needs now, and in the future.

In this post, we are going to look at object composition, a technique that has a few more moving parts than mixins, but opens up the opportunity to make dependencies explicit, enforce a stronger level of encapsulation, and can be built upon for richer forms of method decoration.

encapsulation for mixins

Mixins can encapsulate methods and properties. We saw in the previous post how we can use symbols (or pseudo-random strings) to separate methods intended to be a part of the interface from those intended to be part of the implementation (a/k/a “private”). Take this mixin where we have just used a comment to indicate our preferences:

But let’s move along a bit and we’ll see how to fix the implicit/explicit problem. First, let’s look at another way to create encapsulation, using composition.

encapsulating behaviour with object composition

“Composition” is a general term for any mixing of behaviour from two entities. Mixins as described above is a form of composition. Functional composition is another. Object composition is when we mix two objects, not an object and a prototype or two functions.

Presto, we have the setColourRGB, getColourRGB, and getColourHex methods added to Todo, but we delegate them to a separate object, this[colouredObject], to implement. All of this[colouredObject]’s properties and other methods are somewhat encapsulated away.

As a bonus, we have what we might call “weak explicit dependencies:” Looking at Todo, it’s quite easy to see that we have delegated the setColourRGB, getColourRGB, and getColourHex methods. If we had a much bigger and more complex class with lots of object compositions, we could easily see which methods were delegated to which objects.

All the same, this has an awful lot of moving parts compared to Object.assign. Do we have to type all of this? Or is there an easier way?

automating object composition

Let’s automate the process. To begin with, let’s recognize that although Object.assign can be all you need for naïve mixins, a better way to write them is to turn the mixin definition into a function that transforms a class, like this:

If we look carefully at the ObjectComposer function, we see that for each method of the behaviour we want to compose, it makes a method in the target’s prototype that delegates the method to the composed object. In our hand-rolled example, we initialized the object in the target’s constructor. For simplicity here, we lazily initialize it.

And wonder of wonders, because Object.keys does not enumerate symbols, every method we bind to a symbol in the behaviour is kept “private.”

This is a bit more complex, but it gives us almost everything mixins already gave us. But we wanted more, specifically explicit dependencies. Can we do that?

making delegation explicit

Sure thing! Here’s a new version of ObjectComposer:

constObjectComposer=(behaviour)=>(...exportedMethodNames)=>target=>{constcomposedObject=Symbol('composedObject');for(constmethodNameofexportedMethodNames){Object.defineProperty(target.prototype,methodName,{value:function(...args){if(this[composedObject]==null){this[composedObject]=Object.assign({},behaviour);}returnthis[composedObject][methodName](...args);},writeable:true});}returntarget;};constColoured=ObjectComposer({// __Public Methods__setColourRGB({r,g,b}){returnthis.colourCode={r,g,b};},getColourRGB(){returnthis.colourCode;},getColourHex(){returnthis.rgbToHex(this.colourCode);},// __Private Methods__componentToHex(c){consthex=c.toString(16);returnhex.length==1?"0"+hex:hex;},rgbToHex({r,g,b}){return"#"+this.componentToHex(r)+this.componentToHex(g)+this.componentToHex(b);}});constTodo=Coloured('setColourRGB','setColourRGB')(class{constructor(name){this.name=name||'Untitled';this.done=false;}title(){returnname;}do(){this.done=true;returnthis;}undo(){this.done=false;returnthis;}});lett=newTodo('test');t.setColourRGB({r:1,g:2,b:3});t.getColourHex();//=> t.getColourHex is not a function

That is correct! We didn’t explicitly say that we wanted to “import” the getColourHex method, so we didn’t get it. This is fun! What about resolving name conflicts? Let’s make a general-purpose renaming option:

I do not condone these Americanized misspellings, of course, but they demonstrate a facility that can be used to resolve unavoidable naming conflicts.1

going deeper

Our naïve mixins can do a few things that our object composition cannot. For one thing, we can write a method that returns this. Our composed objects should not return this, because their this is not the same thing as the target instance’s this.

A related issue is that our composed objects cannot call any of the class’s methods. We can write completely independent standalone functionality like Coloured above, but we can’t write functionality that “decorates” existing functionality.

can we go even deeper?

Sure. We could use the subclass factory pattern, this would allow us to override methods, and call super. It also has some performance advantages in a modern JIT. We usually don’t need to prematurely optimize for performance, but sometimes we care deeply about that.

Now that we have the beginnings of a protocol for declaring our dependencies in both directions, we can start thinking about other kinds of behaviour we’d like to mix in, like decorating individual methods with before or after advice, e.g. updateLastModified after setColourRGB.

If we whole-heartedly embrace object composition, we can even go from composing objects with classes to composing classes with each other: This would allow us to write constructors for our composed objects.

so where do we finish?

Let’s step back and look at what we have: We have a way to make something that looks a lot like functional mixin, but behind the scenes it implements object composition. Unlike a mixin, we get explicit dependencies. This adds some declarations to our code, but we win in the long run by having code that is easier to trace when we need to work out what is going on or how to refactor something that has grown.

Our example implementation is dense but small, showing us that JavaScript can be powerful when we choose to put it to work. And now we have the tools to tame growing dependencies, implicit dependencies, and name clashes.

And that’s enough for us to make sensible decisions about whether to use mixins now and refactor in the future, stick with mixins, or go for composition right off the bat.

afterword: “prefer composition to inheritance”

The problems outlined with mixins are the same as the problems we have discovered with inheritance over the last 30+ years. Subclasses have implicit dependencies on their superclasses. This makes superclasses extremely fragile: One little change could break code in a subclass that is in an entirely different file, what we call “action at a distance,” or its more pejorative term, “coupling.” Likewise, naming conflicts can easily occur between subclasses and superclasses.

The root cause is the lack of encapsulation in the relationship between subclasses and superclasses. This is the exact same problem between classes and mixins: The lack of encapsulation.

In OOP, the unit of encapsulation is the object. An object has a defined interface of public methods, and behind this public interface, it has methods and properties that implement its interface. Other objects are supposed to interact only with the public methods.

For this reason, the mature OOP community has migrated away from “inheritance” as the primary way to share behaviour, towards object composition and delegation. The revelations we are having about mixins are a sign that as the JavaScript community matures, it will inevitably rediscover what OOP languages already know.

have your say

notes

We can also extend this function to report if we are accidentally overwriting an existing method. Whether we wish to do so is an interesting discussion we will not have here. Some feel that permitting overriding is excellent OOP practise, others dissent. A very good practise is to only permit it when signalling that you intend to do so, e.g. to write 'override setColourRGB'. We will leave those details for another day. ↩

As already noted, we can also add lots of error checking, like noting when a dependency doesn’t exist. The Coloured function should raise an error if it depends on title but is being mixed into a class that doesn’t have a title method. ↩