Книга: Functional Programming in JavaScript
Назад: Functional inheritance
Дальше: Summary

Mixins

In a nutshell, mixins are classes that can allow other classes to use their methods. The methods are intended to be used solely by other classes, and the class itself is never to be instantiated. This helps to avoid inheritance ambiguity. And they're a great means of mixing functional programming with object-oriented programming.

Mixins are implemented differently in each language. Thanks to JavaScript's flexibility and expressiveness, mixins are implemented as objects with only methods. While they can be defined as function objects (that is, ), it would be better for the structural discipline of the code to define them as object literals (that is, ). This will help us to distinguish between classes and mixins. After all, mixins should be treated as processes, not objects.

Let's start with declaring some mixins. We'll extend our application from the previous section, using mixins to expand on the classes.

var small = {   getPrice: function() {     return this.basePrice + 6;      },   getDimensions: function() {     return [44,63]   } } var large = {   getPrice: function() {     return this.basePrice + 10;      },   getDimensions: function() {     return [64,83]   } };

We're not limited to just this. Many more mixins can be added, like colors or fabric material. We'll have to rewrite our classes a little bit, as shown in the following code snippet:

var Shirt = function() {   this.basePrice = 1; }; Shirt.getPrice = function(){   return this.basePrice; } var TShirt = function() {   this.basePrice = 5; }; TShirt.prototype = Object.create(Shirt.prototype); TShirt..prototype.constructor = TShirt;

Now we're ready to use mixins.

Classical mixins

You're probably wondering just how these mixins get mixed with the classes. The classical way to do this is by copying the mixin's functions into the receiving object. This can be done with the following extension to the prototype:

Shirt.prototype.addMixin = function (mixin) {   for (var prop in mixin) {     if (mixin.hasOwnProperty(prop)) {       this.prototype[prop] = mixin[prop];     }   } };

And now the mixins can be added as follows:

TShirt.addMixin(small); var p1 = new TShirt(); console.log( p1.getPrice() ); // Output: 11  TShirt.addMixin(large); var p2 = new TShirt(); console.log( p2.getPrice() ); // Output: 15

However, there is a major problem. When the price of is calculated again, it comes back as , the price of a large item. It should be the value for a small one!

console.log( p1.getPrice() ); // Output: 15

The problem is that the object's method is getting rewritten every time a mixin is added to it; this is not very functional at all and not what we want.

Functional mixins

There's another way to use mixins, one that is more aligned with functional programming.

Instead of copying the methods of the mixin to the target object, we need to create a new object that is a clone of the target object with the mixin's methods added in. The object must be cloned first, and this is achieved by creating a new object that inherits from it. We'll call this variation .

Shirt.prototype.plusMixin = function(mixin) {       // create a new object that inherits from the old   var newObj = this;   newObj.prototype = Object.create(this.prototype);   for (var prop in mixin) {     if (mixin.hasOwnProperty(prop)) {       newObj.prototype[prop] = mixin[prop];     }   }   return newObj; };  var SmallTShirt = Tshirt.plusMixin(small); // creates a new class var smallT = new SmallTShirt(); console.log( smallT.getPrice() );  // Output: 11  var LargeTShirt = Tshirt.plusMixin(large); var largeT = new LargeTShirt(); console.log( largeT.getPrice() ); // Output: 15 console.log( smallT.getPrice() ); // Output: 11 (not effected by 2nd mixin call)

Here comes the fun part! Now we can get really functional with the mixins. We can create every possible combination of products and mixins.

// in the real world there would be way more products and mixins! var productClasses = [ExpensiveShirt, Tshirt];  var mixins = [small, medium, large];  // mix them all together  products = productClasses.reduce(function(previous, current) {   var newProduct = mixins.map(function(mxn) {     var mixedClass = current.plusMixin(mxn);     var temp = new mixedClass();     return temp;   });   return previous.concat(newProduct); },[]); products.forEach(function(o){console.log(o.getPrice())});

To make it more object-oriented, we can rewrite the object with this functionality. We'll also add a display function to the object, not the products, to keep the interface logic and the data separated.

// the store var Store = function() {   productClasses = [ExpensiveShirt, TShirt];   productMixins = [small, medium, large];   this.products = productClasses.reduce(function(previous, current) {     var newObjs = productMixins.map(function(mxn) {       var mixedClass = current.plusMixin(mxn);       var temp = new mixedClass();       return temp;     });     return previous.concat(newObjs);   },[]); } Store.prototype.displayProducts = function(){   this.products.forEach(function(p) {     $('ul#products').append('<li>'+p.getTitle()+': $'+p.getPrice()+'</li>');   }); }

And all we have to do is create a object and call its method to generate a list of products and prices!

<ul id="products">   <li>small premium shirt: $16</li>   <li>medium premium shirt: $18</li>   <li>large premium shirt: $20</li>   <li>small t-shirt: $11</li>   <li>medium t-shirt: $13</li>   <li>large t-shirt: $15</li> </ul>

These lines need to be added to the classes and mixins to get the preceding output to work:

Shirt.prototype.title = 'shirt'; TShirt.prototype.title = 't-shirt'; ExpensiveShirt.prototype.title = 'premium shirt';  // then the mixins got the extra 'getTitle' function: var small = {   ...   getTitle: function() {     return 'small ' + this.title; // small or medium or large   } }

And, just like that, we have an e-commerce application that is highly modular and extendable. New shirt styles can be added absurdly easily—just define a new subclass and add to it the class's array classes. Mixins are added in just the same way. So now when our boss says, "Hey, we have a new type of shirt and a coat, each available in the standard colors, and we need them added to the website before you go home today", we can rest assured that we'll not be staying late!

Назад: Functional inheritance
Дальше: Summary

bsn
thank
Vesa Karvonen
I hope you don't mind, but I’d like to point you and your readers to my high-performance optics library for JavaScript that is in production use in multiple projects, has comprehensive support for partial optics and interactive documentation: https://calmm-js.github.io/partial.lenses/ (this takes a moment to load — be patient!)