Baked up some Javascript Patterns

in #javascript7 years ago

Singleton

In the Singleton pattern you only want one instance of a specific class. There are no classes in Javascript so this pattern is in a strict technical sense not possible, but since Javascript do has the new syntax for object creation with constructors, we can make new return pointers to the same object.

One way of implementing this pattern is to store the instance in a closure:

function PreciousRing(){

    var instance = this;

    this.message = 'I am the one and only';
    this.birthPlace = 'Mount doom';

    //Overwrite the constructor once the instance first is created
    PreciousRing = function(){
        return instance;
    }
}

Testing:

var ring1 = new PreciousRing();
var ring2 = new PreciousRing();
ring1 === ring2; // true

Factory

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory can defer instantiation to specific constructors unknown to the factory user.

// parent constructor
function AnimalFactory() {}

// a method of the parent
AnimalFactory.prototype.talk = function () {
    return "Hello, I have " + this.numberOfLegs + " legs";
};

// the static factory method
AnimalFactory.build = function (type) {
    var constr = type,
    newAnimal;

    // Throw an error if no constructor for the given animal
    if (typeof AnimalFactory[constr] !== "function") {
        throw {
            name: "Error",
            message: "You cannot create " + constr + " animals in this factory"
        };
    }

    // Here we know that the constructor exists
    // Make it inherit the parent to get the talk function
    if (typeof AnimalFactory[constr].prototype.talk !== "function") {
        AnimalFactory[constr].prototype = new AnimalFactory();
    }
    // create a new animal using the factory
    newAnimal = new AnimalFactory[constr]();
    return newAnimal;
};

// define specific animal makers
AnimalFactory.Horse = function () {
    this.numberOfLegs = 4;
};
AnimalFactory.Spider = function () {
    this.numberOfLegs = 8;
};
AnimalFactory.Monkey = function () {
    this.numberOfLegs = 2;
};

Testing:

var tarantula = AnimalFactory.build('Spider');
var mustang = AnimalFactory.build('Horse');
var chimp = AnimalFactory.build('Monkey');
tarantula.talk(); // "Hello, I have 8 legs"
mustang.talk(); // "Hello, I have 4 legs"
chimp.talk(); // "Hello, I have 2 legs"

Adapter

Convert the interface of a class into another interface clients expect. An adapter lets classes work together that could not otherwise because of incompatible interfaces. A software adapter works just like the real world power adapter you use when on a holiday abroad. I have adapted an example from the great book Head First Design Patterns.

function Duck(){
    this.quack = function(){
        console.log("Quack quack!");
    }
    this.fly = function(){
        console.log("Flying!");
    }
}

function Turkey(){
    this.gobble = function(){
        console.log("Gobble gobble!");
    }
    this.fly = function(){
        console.log("Flying short distance");
    }
}

function TurkeyAdapter(turkey){
    this.turkey = turkey;

    this.quack = function(){
        this.turkey.gobble();
    }

    this.fly = function(){
        for(var i=0; i<5; i++){
            this.turkey.fly();
        }
    }
}

Testing:

var mallardDuck = new Duck();
var wildTurkey = new Turkey();
console.log("Duck says:");
mallardDuck.quack();
console.log("Turkey says:");
wildTurkey.gobble();
var turkeyAdapter = new TurkeyAdapter(wildTurkey);
console.log("Turkey adapter says:");
turkeyAdapter.quack();
turkeyAdapter.fly();

Result:

Duck says: Quack quack! Turkey says: Gobble gobble! Turkey adapter says: Gobble gobble! Flying short distance Flying short distance Flying short distance Flying short distance Flying short distance

Observer

Define a one-to-many dependency between objects where a state change in one object results with all its dependents being notified and updated automatically. In other words it is the pattern of publisher and subscriber (or dispatcher/listeners), where both are kept as loosely couples as possible. In the following example a publisher registers functions. An alternative would be to register objects with a given function, ie obj.onMessage(arg);

    var waveMeter = {
        subscribers: [],
        addListener: function (fn) {
            this.subscribers.push(fn);
        },
        removeListener: function (fn) {
            for (var i = 0; i < this.subscribers.length; i += 1) {
                if (this.subscribers[i] === fn) {
                    this.subscribers.splice(i, 1);
                    break;
                }
            }
        },
        sendMessage: function (message) {
            for (var i = 0; i < this.subscribers.length; i += 1) {
                this.subscribers[i](message);
            }
        }
    };

var robbyNaish = {
    recieveWaveData: function(msg){
        console.log('Robby got wave data: '+msg);
    }
}
var kellySlater = {
    onWaveUpdate: function(msg){
        console.log('Kelly got wave updates: '+msg);
    }
}
waveMeter.addListener(robbyNaish.recieveWaveData);
waveMeter.addListener(kellySlater.onWaveUpdate);
waveMeter.sendMessage('Jaws on north shore!');

Decorator

In the Decorator Pattern one can attach additional responsibilities to an object dynamically keeping the same interface. Decorators originally provided a flexible alternative to subclassing for extending functionality, but since JavaScript is class-less and objects are are mutable, this is not a problem. Decorating is actually a form of Composition, but I usually think of them as wrappers. In this example I have a modern coffee shop that offers a large variety of beverages. All beverages start with a basic object and are then decorated with various objects. The basic objects has a getPrice() method that the Decorators override by calling getPrice() on its parent object in the prototype chain and then modifying that. For example a Dark Roast with Caramel and whip is created like this:

Take a Dark Roast object
Decorate it with a Caramel object
Decorate it with a Whip object

//Constructor
function Coffee(price){
    this.price = price;
};

Coffee.prototype.getPrice = function(){
    return this.price;
};

//The Caramel Decorator
CaramelDecorator = {
    getPrice: function(){
        var price = this.parent.getPrice();
        price += 1.50;
        return price;
    }
};

//The Whip Decorator
WhipDecorator = {
    getPrice: function(){
        var price = this.parent.getPrice();
        price += 0.75;
        return price;
    }
};

//The almighty Coffee Decorator function
Coffee.prototype.decorate = function(decorator){
    var DecoratedCoffeeConstructor = function(){},
        i,
        decoratedCoffee;
    DecoratedCoffeeConstructor.prototype = this;
    decoratedCoffee = new DecoratedCoffeeConstructor();
    decoratedCoffee.parent = DecoratedCoffeeConstructor.prototype;
    //Copy all the properties of the decorator to the new object
    for(i in decorator){
        if(decorator.hasOwnProperty(i)){
            decoratedCoffee[i] = decorator[i];
        }
    }
    return decoratedCoffee;
};

//Test
var darkRoast = new Coffee(5);
var darkRoastCaramel = darkRoast.decorate(CaramelDecorator);
var darkRoastCaramelWhip = darkRoastCaramel.decorate(WhipDecorator);
console.log('Dark Roast: '+darkRoast.getPrice());
console.log('Dark Roast Caramel: '+darkRoastCaramel.getPrice());
console.log('Dark Roast Caramel Whip: '+darkRoastCaramelWhip.getPrice());

Proxy

The Proxy pattern provide a surrogate or placeholder for another object to control access to it. At first this seems like unnecessary code slowing the application down, but it is actually often used to increase performance. In this example a stock ticker receives prices from a market connection and forwards it to a number of clients. The sending of prices is an expensive operation since it results in an http request. To solve this we introduce a proxy that collects prices for 500ms and then sends a single http request with an array containing all the prices.

var priceSender = (function(){
    var busy = false;
    var sendPrice = function(prices){
                    if(busy){
                        setTimeout(function(){
                            sendPrice(prices);
                        },30);
                    } else {
                        busy = true;
                        console.log("sending prices "+prices+' '+new Date());
                         setTimeout(function(){
                            busy = false;
                        },1000);
                    }
    }

    return {
        sendPrice : sendPrice
    }
})();

var proxyPriceSender = (function(){
var collecting = false;
    var priceArray = [];
    var sendPrice = function(prices){
        if(collecting){
            priceArray.push(prices);
        } else {
            collecting = true;
            priceArray.push(prices);
            setTimeout(function(){
                priceSender.sendPrice(priceArray);
                collecting = false;
                priceArray = [];
            },500);
        }
    }

    return {
        sendPrice : sendPrice
    }
})();

var marketFeed = function(priceSenderFunc){
    for(var i=0; i<10; i++){
        setTimeout(priceSenderFunc.sendPrice,i*100,i*10);
    }
}

marketFeed(priceSender);
//marketFeed(proxyPriceSender);

Facade

The purpose of the facade pattern is to simplify an interface. In this example we have a home automation system that creates a facade for a few morning tasks.

var coffeeMachine =  {
    on: function() {
        console.log("Brewing coffee...");
    },
    off: function(){
        console.log("Coffee machine off");
    }
},
tv = {
    on: function() {
        console.log("TV turned on");
    },
    setChannel: function(channelNo) {
        console.log("Setting channel to "+channelNo);
    },
    setVolume: function(level){
        console.log("Setting volume to "+level);
    },
    off: function(){
        console.log("TV turned off");
    }
},
carHeater = {
    on: function(){
        console.log("Car heater turned on");
    },
    off: function(){
        console.log("Car heater turned off");
    }
},
morningFacade = {
    wakeUp: function(){
        coffeeMachine.on();
        tv.on();
        tv.setChannel('5');
        tv.setVolume(17);
        carHeater.on()
    },
    leaveHouse: function(){
        coffeeMachine.off();
        tv.off();
        carHeater.off()
    }
}

morningFacade.wakeUp();
setTimeout(morningFacade.leaveHouse,3000);

Iterator

GoF says: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. In other words, you have data stored in some complex structure like for example a four dimensional matrix and you want to provide easy access to the elements. In the iterator pattern you must provide a next() method that will return the next element of your internal data. What next actually does is up to every implementation. It is also common to provide hasNext() and rewind() methods. This example uses an array internally, but the iterator will return values in reverse order.

    var iter = (function () {
        var data = [1, 2, 3, 4, 5],
            index = data.length;
    
        return {
            next: function () {
                var element;
                if (!this.hasNext()) {
                    return null;
                }
                element = data[index];
                index = index - 1;
                return element;
            },
            hasNext: function () {
                return index > -1;
            },
            rewind: function () {
                index = data.length;
            }
        };
    }());

while (iter.hasNext()) {
    console.log(iter.next());
}
Sort:  

Congratulations @bakedcookie! You have completed some achievement on Steemit and have been rewarded with new badge(s) :

You published your First Post
You made your First Vote
You made your First Comment
You got a First Vote
Award for the number of upvotes received
You got a First Reply

Click on any badge to view your own Board of Honnor on SteemitBoard.
For more information about SteemitBoard, click here

If you no longer want to receive notifications, reply to this comment with the word STOP

By upvoting this notification, you can help all Steemit users. Learn how here!