From Mixins to Traits

In the last article of the series you learned about mixins and how you can use them to encapsulate reusable units of behavior that you can compose with your domain objects or classes.

Mixins while awesome have some limitations. In particular, conflicting mixin methods and properties are overwritten when using Object.assign. Moreover, you don’t get any warning when this happens. Updating a mixin with new functionality at some later point in time can inadvertently change the behavior of some of your objects.

Traits offer a solution to this problem by providing a safer and more structured way to do object composition.

Traits

Traits were developed as a response to the problems that exist in more traditional OOP practices like classical inheritance, multiple inheritance and mixins:

Classes in classical inheritance perform two distinct functions with conflicting goals. They work as factories for objects and as a mechanism of code reuse through inheritance. The first goal requires a class to be complete so that you can instantiate objects using it whilst code reuse shines when you have small reusable units. Both of these goals conflict with each other as complete classes beget larger reusable units, and small reusable units beget incomplete classes. As a result, you can use inheritance as a method of code reuse where you inherit everything, you can incur in code duplication between different classes, or require a lot of boilerplate to delegate to other classes.

Multiple inheritance improves the code reuse factor from the single-class inheritance approach but comes with its own host of problems. With multiple inheritance you can define smaller classes and make new classes reuse functionality from these smaller units. However, problems arise as several paths of the inheritance tree can provide conflicting functionality and overriding features with super can be ambiguous (Which super class where you referring to, my dear?).

Mixins excel at code reuse by defining small reusable units that you can compose with your existing classes and objects. Unlike classes a mixin doesn’t have the goal of being a factory for objects and therefore can be incomplete (and small and focused). On the minus side, because of the mechanism used to compose classes and objects with mixins, there are no guarantees that the composed class will meet the mixin requirements nor that mixins will conflict with each other in unexpected ways. Furthermore, there are no warnings when mixin features conflict with each other.

Traits attempt to solve these problems by providing a way to reuse code and behavior just like we do with mixins but in a safer fashion that will let us:

handle conflicts between traits and be warned when conflicts occur

define requirements in our traits that must be satisfied before certain features can be used

Let’s see how you can get started using traits in JavaScript.

Traits with traits.js

We are going to be using traits.js, a traits JavaScript library for the remainder of the article. Note that there are other trait implementations in JavaScript like light-traits and simple-traits so you can pick the one you like the most when you are ready to experiment yourself.

Trait.js is an open source library that brings the beauty of traits into JavaScript. Using traits.js we can define reusable pieces of behavior - traits - and then compose them together to build objects. Instead of starting off with a class of object you compose an object from scratch using traits.

Let’s imagine that we want to represent the ability of being positioned in a two-dimensional space using a trait TPositionable. Traits.js lets us define a new trait by using a factory function Trait and passing in an object that contains the behavior encapsulated by the trait:

Much in the same way that we use the letter I in front of interfaces in C#, it is common to use the letter T as a convention when defining traits.

In this example we have a factory function MinionWithPosition that creates minions with the ability of being positioned. We use Object.create to create an instance of a minion with a prototype that contains a single method toString and the TPositionable trait. This is interesting because it highlights the fact that we can combine JavaScript’s prototypical inheritance with traits.

We can verify that indeed the resulting minion of using this factory function works as we would expect:

This new trait is going to require two properties x and y - it doesn’t make sense for someone to move if you cannot be in any position - and a movesTo method to perform the actual moving around. Notice how Trait.js lets us define required properties by using the static member Trait.required. These required properties will be factored later when we try to instantiate a new object.

Now that we have two traits let’s compose them to create a more useful minion. You can compose traits using the Traits.compose method which will result in a new composite trait:

In the previous example we used the new composite trait implicitly in the Object.create method which made it pass by a little bit unnoticed. Know that you can save composite traits for later and compose them with other traits:

As you can appreciate in this example when the requirements of a specific trait haven’t been met you get some nice feedback.

Calling Object.create with a trait that misses required properties results in an object that has these requirements as read-only properties. If you try to set these properties to a new value you will get an exception which will warn you about the fact that your object is not correctly composed. This is a great improvement from mixins where missing expected properties could result in unexpected side-effects.

Assigning to Read-only Properties Only Throws in Strict Mode

Notice that you need to enable strict mode for read-only properties to throw exceptions when assigning values to them. Otherwise the assign operation will just fail silently.

Resolving Name Conflicts

Unlike mixins which only support linear composition where later mixins overwrite the previous ones, Traits composition order is irrelevant. With traits, conflicting methods or properties must be resolved explicitly.

Let’s imagine that we want to be able to position our minions in a three dimensional space. We define a TPositionable3D trait like this:

This will provide us with great feedback when there are name collisions between our traits properties and methods, again an important advantage over mixins. This behavior will be particularly helpful when updating an existing trait results in name collisions within existing objects within your application (which otherwise would have gone unnoticed).

Traits provide different ways in which you can resolve name conflicts:

aliasing or renaming properties: when you want to conserve the functionality in either of the conflicting traits. Renaming the conflicting properties will result in objects containing both the original properties plus the renamed ones.

excluding properties: when you don’t care about a particular trait functionality.

overriding properties: when you want a trait to completely override another.

Trait.js offers the Trait.resolve function to help you resolve name conflicts. You can rename a property by using this function to map a property to another name:

Trait.resolve takes two arguments, first an object that describes the conflicting property mappings and second the trait whose properties we want to rename. After explicitly resolving the conflicts we can instantiate a new minion without problems:

Note how the methods defined by the traits location and location3d do not appear when logging the object. The reason for this is that methods created through traits are not enumerable, that is, they cannot be enumerated by using the for/in loop. This can be helpful when you want to enumerate the properties of an object and you are only interested about its data members.

We can verify that both of these methods location and location3d are part of the aliasedMinion object:

Which brings us to a very important thing to notice: renaming a property doesn’t rename the property within the body of a function. You can appreciate this if you take a look at the body of location3d which still refers to this.x and this.y.

Alternatively you can exclude specific properties using Trait.resolve and setting the value of a property mapping to undefined:

Finally you can use Trait.override to override conflicting properties between traits. Trait.override works in a similar way to Object.assign but the precedence is taken from left to right. That is, the properties within the first trait will override those of the second trait, the properties within the second trait will override those of the third trait and so on:

The positionable and movable traits define a single method each: location and movesTo. These methods enclose the variable state that is going to be passed to either function as an argument and which will represent the private state of an object.

Having defined these trait factories TPositionableFn and TMovableFn we can now represent a new kind of minion in terms of them:

The PrivateMinion is going to have a series of private members defined by the state variable. When instantiating a new object, the factory method will share this private state with the traits but it won’t let it be accessible from the outside world:

var privateMinion =PrivateMinion()// we can access the public API as usual
privateMinion.movesTo(1,1)// => private minion moves from (0, 0) to (1, 1)
privateMinion.location()// => private minion is calmly resting at (1, 1)// but the private state can't be accessed
console.log(privateMinion.state)// => undefined

Using closures with traits we get:

true data privacy and the ability to use private members within an object and its traits

required properties and name conflict handling for the public interface of an object

You may be wondering what happens with symbols. Well, unfortunately the current implementation of traits.js does not support symbols.

High Integrity Objects With Immutable Traits

Up until this point we have instantiated our objects using the Object.create method and passing a prototype and a trait (or a composite trait) as arguments. This results in a new object with the following characteristics:

If all requirements are met and there are no conflicts the resulting object will contain all properties and methods defined within the traits and will have as prototype whichever object we have passed to Object.create.

If there are properties that are required but haven’t been satisfied the resulting object has these requirements as read-only properties. Attempting to modify these results in an exception.

If there are unresolved naming conflicts the resulting object throws an exception when conflicting properties or methods are accessed.

We are getting much better feedback about the consistency of our composed object than when we used mixins but it could be better: We could get that feedback much sooner. Like directly when creating the object and not when accessing inconsistent properties or methods.

Trait.js offers another method Trait.create that lets you instantiate high integrity objects. Objects created using Trait.create will:

throw an exception if there are requirements that haven’t been satisfied

throw an exception if there are unresolved naming conflicts

have all their methods bound to themselves

be immutable

Let’s use Trait.create with some of the traits we defined previously in this article.

Trait.create offers a better developer experience than Object.create and helps you create high integrity objects that are immutable. But how do you build an application if all your objects are immutable? How can you make a minion move if you cannot change its state? The answer is that you use other mechanisms to manage state than what we are accustomed to in traditional object-oriented programming. In Functional Programming: Immutability (a future article in these series) we will do a deep dive into immutability, its advantages, uses cases and how you can use it in your applications.

Below you can find a summarized comparison between using Object.create and Trait.create:

Object.create

Trait.create

Can create objects even if there are unmet requirements or unresolved conflicts.

Cannot create objects when there are unmet requirements or unresolved conflicts.

Unmet requirements result in read-only properties. Read-only properties throw when you try to change them in strict mode.

Unmet requirements cause an exception as soon as we try to instantiate an object.

Properties with unresolved conflicts throw an exception when accessed.

Unresolved conflicts cause an exception as soon as we try to instantiate an object.

The object created doesn't have its method bound

The object created has all its methods bound to itself.

The object created can be modified and augmented with new properties.

The object created is immutable. You cannot augment it with new properties, remove properties nor modify existing ones.

Traits vs Mixins

Class-free inheritance based on trait composition. Let's you encapsulate functionality and behavior, and easily reuse them.

Mixins don't have a way to express requirements. A mixin may expect a property or method in the composed object but it doesn't have a way to represent it. If a requirement is not met, unpredictable side-effects may occur without a warning.

Traits can express that they require specific properties or methods for functioning. Failing to meet requirements will results in errors being thrown either by trying to assign to an unexisting required property or upon object creation (Trait.create).

Can be composed freely because it requires that you resolve any conflict explicitly. Conflicts can be resolved by renaming, excluding properties or by overriding traits. Unresolved conflicts will result in exceptions being throw when accessing conflicting properties or methods, or on object creation (with Trait.create).

Support data privacy with closures and symbols.

Support data privacy with closures and symbols. Trait.js doesn't support symbols but that's more of an implementation details than traits themselves not supporting symbols.

Object mixins can lead to state being coupled between different objects composed from the same mixin. Functional mixins provide a solution to this problem by doubling as an object factory and ensuring that each new object is composed with new state.

Traits can also lead to state being coupled between composed objects. In order to avoid that, wrap your trait inside a trait factory function. That will ensure that new objects are composed from new state.

Mixins usually extend existing objects or classes.

Traits create new objects from scratch by composing many traits together instead of extending existing objects or classes.

Supports the easy creation of high integrity objects using Trait.create.

Concluding

Traits are a class-free object oriented programming alternative to mixins. Just like mixins they encapsulate reusable pieces of behavior that can be composed together to create complex objects. They are an improvement over mixins because they let you express requirements within your traits and actively resolve conflicts. Both of these features result in code that is less error prone because composition mistakes don’t fail silently and cause unwanted side-effects like with mixins.

Traits.js is a javascript library that brings traits to JavaScript. It lets you define traits via the Trait factory method, compose traits with Trait.compose, define requirements using Trait.required and resolve conflicts via Trait.resolve.

Traits.js offers two ways to instantiate objects from traits: Object.create and Trait.create. The first one, which is native to JavaScript, creates vanilla JavaScript objects that can be mutated and augmented. With Object.create unmet requirements result in read-only properties and accessing properties with unresolved conflicts results in exceptions. Trait.create offers a high integrity alternative to Object.create that throws on object creation when requirements are missing or there are unresolved conflicts. Trait.create returns immutable objects which we cannot augment with new properties and whose properties cannot be changed nor deleted.

Would you like to receive more articles like this one on programming, web development, JavaScript, Angular, developer productivity, tools, UX and even exclusive content like free versions of my books in your mailbox? Then sign up to my super duper awesome inner circle.

Did Ya Know I've Written Some Books?

I have! The JavaScript-mancy series is the lovechild of three of my passions: JavaScript, writing and Fantasy. In the pages of each one of the books of the series you’ll find a breadth of JavaScript knowledge, delivered with a humorous and casual style of writing and sprinkled with Fantasy at every turn.

They are the weirdest and quirkiest JavaScript books you'll ever find. There's nothing out there quite like it.