You are previewing Learning JavaScript Design Patterns.

Learning JavaScript Design Patterns

Cover of Learning JavaScript Design Patterns by Addy Osmani Published by O'Reilly Media, Inc.
  1. Learning JavaScript Design Patterns
  2. SPECIAL OFFER: Upgrade this ebook with O’Reilly
  3. Preface
    1. Target Audience
    2. Credits
    3. Reading
    4. Conventions Used in This Book
    5. Using Code Examples
    6. Safari® Books Online
    7. How to Contact Us
    8. Acknowledgments
  4. 1. Introduction
  5. 2. What Is a Pattern?
    1. We Already Use Patterns Every Day
  6. 3. “Pattern”-ity Testing, Proto-Patterns, and the Rule of Three
  7. 4. The Structure of a Design Pattern
  8. 5. Writing Design Patterns
  9. 6. Anti-Patterns
  10. 7. Categories of Design Patterns
    1. Creational Design Patterns
    2. Structural Design Patterns
    3. Behavioral Design Patterns
  11. 8. Design Pattern Categorization
    1. A Brief Note on Classes
  12. 9. JavaScript Design Patterns
    1. The Constructor Pattern
      1. Object Creation
      2. Basic Constructors
      3. Constructors with Prototypes
    2. The Module Pattern
      1. Object Literals
      2. The Module Pattern
      3. Module Pattern Variations
    3. The Revealing Module Pattern
      1. Advantages
      2. Disadvantages
    4. The Singleton Pattern
    5. The Observer Pattern
      1. Differences Between the Observer and Publish/Subscribe Pattern
      2. Advantages
      3. Disadvantages
      4. Publish/Subscribe Implementations
    6. The Mediator Pattern
      1. Basic Implementation
      2. Advanced Implementation
      3. Example
      4. Advantages and Disadvantages
      5. Mediator Versus Observer
      6. Mediator Versus Facade
    7. The Prototype Pattern
    8. The Command Pattern
    9. The Facade Pattern
      1. Notes on Abstraction
    10. The Factory Pattern
      1. When to Use the Factory Pattern
      2. When Not to Use the Factory Pattern
      3. Abstract Factories
    11. The Mixin Pattern
    12. Subclassing
    13. Mixins
      1. Advantages and Disadvantages
    14. The Decorator Pattern
    15. Pseudoclassical Decorators
      1. Interfaces
      2. Abstract Decorators
    16. Decorators with jQuery
    17. Advantages and Disadvantages
    18. Flyweight
      1. Using Flyweights
      2. Flyweights and Sharing Data
      3. Implementing Classical Flyweights
      4. Converting Code to Use the Flyweight Pattern
      5. A Basic Factory
      6. Managing the Extrinsic States
      7. The Flyweight Pattern and the DOM
  13. 10. JavaScript MV* Patterns
    1. MVC
      1. Smalltalk-80 MVC
    2. MVC for JavaScript Developers
      1. Models
      2. Views
      3. Controllers
      4. Controllers in Another Library (Spine.js) Versus Backbone.js
    3. What Does MVC Give Us?
    4. Smalltalk-80 MVC in JavaScript
      1. Delving Deeper
      2. Summary
    5. MVP
      1. Models, Views, and Presenters
      2. MVP or MVC?
      3. MVC, MVP, and Backbone.js
    6. MVVM
      1. History
      2. Model
      3. View
      4. ViewModel
      5. Recap: The View and the ViewModel
      6. Recap: The ViewModel and the Model
    7. Pros and Cons
      1. Advantages
      2. Disadvantages
    8. MVVM with Looser Data Bindings
    9. MVC Versus MVP Versus MVVM
    10. Backbone.js Versus KnockoutJS
  14. 11. Modern Modular JavaScript Design Patterns
    1. A Note on Script Loaders
    2. AMD
      1. Getting Started with Modules
      2. AMD Modules with Dojo
      3. AMD Module Design Patterns (Dojo)
      4. AMD Modules with jQuery
      5. AMD Conclusions
    3. CommonJS
      1. Getting Started
      2. Consuming Multiple Dependencies
      3. Loaders and Frameworks that Support CommonJS
      4. Is CommonJS Suitable for the Browser?
      5. Related Reading
    4. AMD and CommonJS: Competing, but Equally Valid Standards
      1. UMD: AMD and CommonJS-Compatible Modules for Plug-ins
    5. ES Harmony
      1. Modules with Imports and Exports
      2. Modules Loaded from Remote Sources
      3. Module Loader API
      4. CommonJS-like Modules for the Server
      5. Classes with Constructors, Getters, and Setters
      6. ES Harmony Conclusions
      7. Related Reading
    6. Conclusions
  15. 12. Design Patterns in jQuery
    1. The Composite Pattern
    2. The Adapter Pattern
    3. The Facade Pattern
    4. The Observer Pattern
    5. The Iterator Pattern
    6. Lazy Initialization
    7. The Proxy Pattern
    8. The Builder Pattern
  16. 13. jQuery Plug-in Design Patterns
    1. Patterns
    2. A Lightweight Start Pattern
      1. Further Reading
    3. Complete Widget Factory Pattern
      1. Further Reading
    4. Nested Namespacing Plug-in Pattern
      1. Further Reading
    5. Custom Events Plug-in Pattern (with the Widget Factory)
      1. Further Reading
    6. Prototypal Inheritance with the DOM-to-Object Bridge Pattern
      1. Further Reading
    7. jQuery UI Widget Factory Bridge Pattern
      1. Further Reading
    8. jQuery Mobile Widgets with the Widget Factory
    9. RequireJS and the jQuery UI Widget Factory
      1. Usage
      2. Further Reading
    10. Globally and Per-Call Overridable Options (Best Options Pattern)
      1. Further Reading
    11. A Highly Configurable and Mutable Plug-in Pattern
      1. Further Reading
    12. What Makes a Good Plug-in Beyond Patterns?
      1. Quality
      2. Code Style
      3. Compatibility
      4. Reliability
      5. Performance
      6. Documentation
      7. Likelihood of maintenance
    13. Conclusions
    14. Namespacing Patterns
    15. Namespacing Fundamentals
      1. Single Global Variables
      2. Prefix Namespacing
      3. Object Literal Notation
      4. Nested Namespacing
      5. Immediately Invoked Function Expressions (IIFE)s
      6. Namespace Injection
    16. Advanced Namespacing Patterns
      1. Automating Nested Namespacing
      2. Dependency Declaration Pattern
      3. Deep Object Extension
      4. Recommendation
  17. 14. Conclusions
  18. A. References
  19. Index
  20. About the Author
  21. Colophon
  22. SPECIAL OFFER: Upgrade this ebook with O’Reilly
  23. Copyright
O'Reilly logo

Pseudoclassical Decorators

We’re now going to examine a variation of the Decorator first presented in a JavaScript form in Pro JavaScript Design Patterns (PJDP) by Dustin Diaz and Ross Harmes.

Unlike some of the examples from earlier, Diaz and Harmes stick more closely to how decorators are implemented in other programming languages (such as Java or C++) using the concept of an “interface,” which we will define in more detail shortly.

Note

This particular variation of the Decorator pattern is provided for reference purposes. If you find it overly complex, I recommend opting for one of the simpler implementations covered earlier.

Interfaces

PJDP describes the Decorator pattern as one that is used to transparently wrap objects inside other objects of the same interface. An interface is a way of defining the methods an object should have; however, it doesn’t actually directly specify how those methods should be implemented.

Interfaces can also indicate what parameters the methods take, but this is considered optional.

So, why would we use an interface in JavaScript? The idea is that they’re self-documenting and promote reusability. In theory, interfaces also make code more stable by ensuring changes to them must also be made to the objects implementing them.

Below is an example of an implementation of interfaces in JavaScript using duck-typing, an approach that helps determine whether an object is an instance of that constructor/object based on the methods it implements.

// Create interfaces using a pre-defined Interface 
// constructor that accepts an interface name and 
// skeleton methods to expose. 

// In our reminder example summary() and placeOrder()
// represent functionality the interface should
// support
var reminder = new Interface( "List", ["summary", "placeOrder"] );

var properties = {
  name: "Remember to buy the milk",
  date: "05/06/2016",
  actions:{
    summary: function (){
      return "Remember to buy the milk, we are almost out!";
   },
    placeOrder: function (){
      return "Ordering milk from your local grocery store";
    }
  }
};

// Now create a constructor implementing the above properties
// and methods

function Todo( config ){
  
  // State the methods we expect to be supported
  // as well as the Interface instance being checked
  // against

  Interface.ensureImplements( config.actions, reminder );

  this.name = config.name;
  this.methods = config.actions;

}

// Create a new instance of our Todo constructor

var todoItem = Todo( properties );

// Finally test to make sure these function correctly

console.log( todoItem.methods.summary() );
console.log( todoItem.methods.placeOrder() );

// Outputs:
// Remember to buy the milk, we are almost out!
// Ordering milk from your local grocery store

In this example, Interface.ensureImplements provides strict functionality checking, and code for both this and the Interface constructor can be found here.

The biggest problem with interfaces is that, as there isn’t built-in support for them in JavaScript, there is a danger of attempting to emulate a feature of another language that may not be an ideal fit. Lightweight interfaces can be used without a great performance cost, however, and we will next look at Abstract Decorators using this same concept.

Abstract Decorators

To demonstrate the structure of this version of the Decorator pattern, we’re going to imagine we have a superclass that models a Macbook once again and a store that allows us to “decorate” our Macbook with a number of enhancements for an additional fee.

Enhancements can include upgrades to 4 GB or 8 GB of RAM, engraving, Parallels, or a case. Now if we were to model this using an individual subclass for each combination of enhancement options, it might look something like this:

var Macbook = function(){
        //...
};

var  MacbookWith4GBRam =  function(){},
     MacbookWith8GBRam = function(){},
     MacbookWith4GBRamAndEngraving = function(){},
     MacbookWith8GBRamAndEngraving = function(){},
     MacbookWith8GBRamAndParallels = function(){},
     MacbookWith4GBRamAndParallels = function(){},
     MacbookWith8GBRamAndParallelsAndCase = function(){},
     MacbookWith4GBRamAndParallelsAndCase = function(){},
     MacbookWith8GBRamAndParallelsAndCaseAndInsurance = function(){},
     MacbookWith4GBRamAndParallelsAndCaseAndInsurance = function(){};

… and so on.

This would be an impractical solution, as a new subclass would be required for every possible combination of enhancements that are available. As we would prefer to keep things simple without maintaining a large set of subclasses, let’s look at how decorators may be used to solve this problem better.

Rather than requiring all of the combinations we saw earlier, we should simply have to create five new decorator classes. Methods that are called on these enhancement classes would be passed on to our Macbook class.

In our next example, decorators transparently wrap around their components and can interestingly be interchanged as they use the same interface.

Here’s the interface we’re going to define for the Macbook:

var Macbook = new Interface( "Macbook", 
  ["addEngraving", 
  "addParallels", 
  "add4GBRam", 
  "add8GBRam", 
  "addCase"]);

// A Macbook Pro might thus be represented as follows:
var MacbookPro = function(){
    // implements Macbook
};

MacbookPro.prototype = {
    addEngraving: function(){
    },
    addParallels: function(){
    },
    add4GBRam: function(){
    },
    add8GBRam:function(){
    },
    addCase: function(){
    },
    getPrice: function(){
      // Base price
      return 900.00;         
    }
};

To make it easier for us to add as many more options as needed later on, an Abstract Decorator class is defined with default methods required to implement the Macbook interface, which the rest of the options will subclass. Abstract Decorators ensure that we can decorate a base class independently with as many decorators as needed in different combinations (remember the example earlier?) without needing to derive a class for every possible combination.

// Macbook decorator abstract decorator class

var MacbookDecorator = function( macbook ){

    Interface.ensureImplements( macbook, Macbook );
    this.macbook = macbook;  

};

MacbookDecorator.prototype = {
    addEngraving: function(){
        return this.macbook.addEngraving();
    },
    addParallels: function(){
        return this.macbook.addParallels();
    },
    add4GBRam: function(){
        return this.macbook.add4GBRam();
    },
    add8GBRam:function(){
        return this.macbook.add8GBRam();
    },
    addCase: function(){
        return this.macbook.addCase();
    },
    getPrice: function(){
        return this.macbook.getPrice(); 
    }        
};

What’s happening in the above sample is that the Macbook Decorator is taking an object to use as the component. It’s using the Macbook interface we defined earlier, and for each method, it calls the same method on the component. We can now create our option classes just by using the Macbook Decorator; simply call the superclass constructor, and any methods can be overridden as necessary.

var CaseDecorator = function( macbook ){

   // call the superclass's constructor next
   this.superclass.constructor( macbook );   

};

// Let's now extend the superclass
extend( CaseDecorator, MacbookDecorator ); 

CaseDecorator.prototype.addCase = function(){
    return this.macbook.addCase() + "Adding case to macbook";   
};

CaseDecorator.prototype.getPrice = function(){
    return this.macbook.getPrice() + 45.00;  
};

As we can see, most of this is relatively straightforward to implement. What we’re doing is overriding the addCase() and getPrice() methods that need to be decorated, and we’re achieving this by first executing the component’s method and then adding to it.

As there’s been quite a lot of information presented in this section so far; let’s try to bring it all together in a single example that will hopefully highlight what we have learned.

// Instantiation of the macbook
var myMacbookPro = new MacbookPro();  

// Outputs: 900.00
console.log( myMacbookPro.getPrice() );

// Decorate the macbook
myMacbookPro = new CaseDecorator( myMacbookPro );

// This will return 945.00
console.log( myMacbookPro.getPrice() );

As decorators are able to modify objects dynamically, they’re a perfect pattern for changing existing systems. Occasionally, it’s just simpler to create decorators around an object versus the trouble of maintaining individual subclasses for each object type. This makes maintaining applications that may require a large number of subclassed objects significantly more straightforward.

The best content for your career. Discover unlimited learning on demand for around $1/day.