Published on

JavaScript Decorator

Decorator is a structural pattern that consists of dynamically augmenting the behavior of an existing object. It's different from classical inheritance, because the behavior is not added to all the objects of the same class, but only to the instances that are explicitly decorated.

Implementation-wise, it is very similar to the Proxy Pattern, but instead of enhancing or modifying the behavior of the existing interface of an object, it augments it with new functionalities.

The Decorator object is extending the Component object by adding the methodC() operation. The existing methods are usually delegated to the decorated object, without further processing. Of course,if necessary we can easily combine the Proxy pattern so that the calls to the existing methods can be intercepted and manipulated as well.

Techniques for implementing Decorators

Composition

Using composition, the decorated component is wrapped around a new object that usually inherits from it. The Decorator in this case simply needs to define the new methods, while delegating the existing ones to the original component

function decorate(component){
    const proto = Object.getPrototypeOf(component)

    function Decorator(component){
        this.component = component
    }

    Decorator.prototype = Object.create(proto)

    // new method
    Decorator.prototype.greetings = function(){
        return 'Hi!'
    }

    // delegate method
    Decorator.prototype.hello = function(){
        return this.component.hello.apply(this.component, arguments)
    }

    return new Decorator(component)
}

Object augmentation

Object decoration can also be achieved by simply attaching new methods directly to the decorated object

function decorate(component){
    // new method
    component.greetings = () => {
        // ...
    }
    return component
}

Decorating a LevelUP database

Implementing a LevelUP plugin

module.exports = function levelSubscribe(db){
    db.subscribe = (pattern, listener) => {
        db.on('put', (key, val) => {
            const match = Object.keys(pattern).every(k => (pattern[k] === val[k]))
            if(match){
                listener(key, val)
            }
        })
    }
    return db;
}
const level = require('level')
const levelSubscribe = require('./levelSubscribe')

let db = level(__dirname + '/db', {valueEncoding: 'json'})
db = levelSubscribe(db)

db.subscribe(
    {doctype: 'tweet', language: 'en'},
    (k, val) => console.log(val)
)
db.put('1', {doctype: 'tweet', text: 'Hi', language: 'en'})
db.put('2', {doctype: 'company', name: 'ACME Co.'})

References

  • Node.js Design Patterns