A mixin is a style of programming in which a class will include all the features (attributes and methods) of some other class without necessarily being related to that class.
Note: This programming is different from inheritance. That is why we used the word "include" and not "inheritance."
In inheritance, the child class will inherit all the features of its parent class and become a derivative or type of that class. Usually, if we want to obtain the features of multiple classes, we'll have to go through complex inheritance patterns, which can make our code confusing.
Luckily, mixins allow us to access all features from another class while avoiding complex inheritance problems.
Mixins also come in handy when there is a common functionality between multiple classes that we want to share.
Let's say we have a car and plane, and we want to calculate the maximum distance each of them can travel based on their fuel tank capacity and the average rate at which they consume their fuel.
Assume the maximum distance the car or plane can travel is given by:
maxDistance = tankCapacity/consumptionRate
Both a car and a plane are different in the way they consume their fuel and the inner workings of their engines, but one thing we know here is that we found a way to predict the maximum distance it can travel given the fuel tank capacity.
Let's look at the code below:
class Car {constructor(tankCapacity, consumptionRate) {this.tankCapacity = tankCapacity;this.consumptionRate = consumptionRate;}// other car stuff}class Plane {constructor(tankCapacity, consumptionRate) {this.tankCapacity = tankCapacity;this.consumptionRate = consumptionRate;}// other plane stuff}const ComputeMaxDistanceMixin = {computeMaxDistance() {// return absolute distancereturn Math.round(this.tankCapacity / this.consumptionRate);},};// creating a mixin 'extend' with Object.keys()const extend = (obj, mixin) => {Object.keys(mixin).forEach((key) => (obj[key] = mixin[key]));};// Could also implement 'extend' it like below/* const extend = (obj, mixin) => {Object.assign(obj.prototype, myMixin);}; */extend(Car.prototype, ComputeMaxDistanceMixin);extend(Plane.prototype, ComputeMaxDistanceMixin);const myCar = new Car(60, 1.2);const myPlane = new Plane(5321, 2.7);console.log(myCar.computeMaxDistance());console.log(myPlane.computeMaxDistance());
Car
class. Our car will hold information about it's tankCapacity
and consumptionRate
.Plane
class. It is similar to our Car
class.The method used to compute the maximum distance traveled is the same for each, so we violate DRY (don’t repeat yourself) if we implement the method in each class separately. To solve this, we can create a mixin called ComputeMaxDistanceMixin
which we'll use to extend our Plane
and Car
class.
ComputeMaxDistanceMixin
which has a method to compute the maximum distance our plane or car can travel based on their tank capacity and consumption rate. computeMaxDistance()
method which returns the ratio of tankCapacity
and consumptionRate
of our vehicle.Because this method is not part of our class definitions, we need a way to insert this into our class while avoiding repetition dynamically.
Fortunately for us, it is possible to modify the prototype of our class to include functions from our mixin.
extend
function, which helps extend our class's prototype to include the computeMaxDistance
function. Our Mixin is an object, so we do this by iterating through the keys of our mixin and setting the key-value pairs in our class prototype.extend
function but with Object.assign()
. This assigns all the key-value pairs from our mixin to our class prototype. We can use any of the implementations.Car
and Plane
prototype with the mixin.Car
and Plane
, respectively.Once we have extended them, both objects of Plane
and Car
can run computeMaxDistance()
.
This method allows us to construct complex behaviors in our code more easily than otherwise.