Javascript. Command pattern

Паттерн Команда (Command pattern) инкапсулирует запрос в виде объекта, делая возможной параметризацию клиентских объектов с другими запросами, организацию очереди или регистрацию запросов, а также поддержку отмены операций. Данный паттерн относится к поведенческим паттернам.
Объект команды инкапсулирует запрос посредством привязки набора операций к конкретному получателю.  Для этого информация об операции и получателе "упаковывается" в объекте с методами execute() и undo(). При вызове метод execute() выполняет операцию с данным получателем (Receivers). Метод undo() выполняет обратную операцию (отмену). Внешние объекты (Invokers) не знают, какие именно операции выполняются, и с каким получателем; они только знают, что при вызове методов execute() или undo() их запросы будут выполнены.
На практике нередко встречаются "умные" объекты команд, которые реализуют запрос самостоятельно вместо его делегирования получателю. Команды также могут использоваться для реализации систем регистрации команд и поддержки транзакций.


Ниже приведен пример реализации данного паттерна.

/* Receiver */

var Calculator = function () {

    var result = 0;

    // add operation
    this.add = function (value) {
        result += value;
    };
   
    // subtract operation
    this.subtract = function (value) {
        result -= value;
    };
   
    // multiply operation
    this.multiply = function (value) {
        result *= value;
    };
   
    // divide operation
    this.divide = function (value) {
        if (value === 0) {
            throw new Error("Divide by zero is forbidden");
        }
        result /= value;
    };
   
    // set common result
    this.setResult = function (value) {
        result = value;
    };
   
    // get common result
    this.getResult = function () {
        return result;
    };

};

/* interface Command */

var Command = function () {
    this.execute = function () {};
    this.undo = function () {};
};

/* ConcreteCommand - AddCommand */

var AddCommand = function (calculator, value) {
   
    var calculator = calculator,
        value = value;
   
    this.execute = function () {
        calculator.add(value);
    };
   
    this.undo = function () {
        calculator.subtract(value);
    };
};

AddCommand.prototype = new Command();
AddCommand.prototype.constructor = AddCommand;

/* ConcreteCommand - SubtractCommand */

var SubtractCommand = function (calculator, value) {

    var calculator = calculator,
        value = value;
   
    this.execute = function () {
        calculator.subtract(value);
    };
   
    this.undo = function () {
        calculator.add(value);
    };

};

SubtractCommand.prototype = new Command();
SubtractCommand.prototype.constructor = SubtractCommand;

/* ConcreteCommand - MultiplyCommand */

var MultiplyCommand = function (calculator, value) {
   
    var calculator = calculator,
        value = value,
        undoResult;
   
    this.execute = function () {
        result = calculator.getResult();
        calculator.multiply(value);
    };
   
    this.undo = function () {
        if (value === 0) {
          
        }
        calculator.divide(value);
    };
};

MultiplyCommand.prototype = new Command();
MultiplyCommand.prototype.constructor = MultiplyCommand;

/* ConcreteCommand - DivideCommand */

var DivideCommand = function (calculator, value) {

    var calculator = calculator,
        value = value;
   
    this.execute = function () {
        calculator.divide(value);
    };
   
    this.undo = function () {
        calculator.multiply(value);
    };

};

DivideCommand.prototype = new Command();
DivideCommand.prototype.constructor = DivideCommand;

/* Invoker */

var User = function () {

    var calculator = new Calculator();
        commands = [],
        command = null;
   
    this.add = function (value) {
        command = new AddCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.subtract = function (value) {
        command = new SubtractCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.multiply = function (value) {
        command = new MultiplyCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.divide = function (value) {
        command = new DivideCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.undo = function () {
        if (commands.length === 0) {
            throw new Error("Command stack is empty");
        }
        command = commands.pop();
        command.undo();
    };
   
    this.getCount = function () {
        return calculator.getResult();
    };
   
};


// testing
var accountant = new User();
accountant.add(5);
accountant.subtract(4);
console.log(accountant.getCount()); // 1


Данная реализация имеет один существенный недостаток: предположим, что одной из операций будет accountant.multiply(0), и в случае, если мы захотим откатить результат, то команда MultiplyCommand попытает сделать запрос undo(), но как известно на ноль делить нельзя и на выходе получим неприятное сообщение "Divide by zero is forbidden". То есть результат не будет откатан к первоначальному виду, одно из решений может быть таким:

/* Receiver */

var Calculator = function () {

    var result = 0;

    // add operation
    this.add = function (value) {
        result += value;
    };
   
    // subtract operation
    this.subtract = function (value) {
        result -= value;
    };
   
    // multiply operation
    this.multiply = function (value) {
        result *= value;
    };
   
    // divide operation
    this.divide = function (value) {
        if (value === 0) {
            throw new Error("Divide by zero is forbidden");
        }
        result /= value;
    };
   
    // set common result
    this.setResult = function (value) {
        result = value;
    };
   
    // get common result
    this.getResult = function () {
        return result;
    };

};

/* interface Command */

var Command = function () {
    this.execute = function () {};
    this.undo = function () {};
};

/* ConcreteCommand - AddCommand */

var AddCommand = function (calculator, value) {
   
    var calculator = calculator,
        value = value,
        undoResult = calculator.getResult();
   
    this.execute = function () {
        calculator.add(value);
    };
   
    this.undo = function () {
        calculator.setResult(undoResult);
    };
   
};

AddCommand.prototype = new Command();
AddCommand.prototype.constructor = AddCommand;

/* ConcreteCommand - SubtractCommand */

var SubtractCommand = function (calculator, value) {

    var calculator = calculator,
        value = value,
        undoResult = calculator.getResult();
   
    this.execute = function () {
        calculator.subtract(value);
    };
   
    this.undo = function () {
        calculator.setResult(undoResult);
    };

};

SubtractCommand.prototype = new Command();
SubtractCommand.prototype.constructor = SubtractCommand;

/* ConcreteCommand - MultiplyCommand */

var MultiplyCommand = function (calculator, value) {
   
    var calculator = calculator,
        value = value,
        undoResult = calculator.getResult();
   
    this.execute = function () {
        calculator.multiply(value);
    };
   
    this.undo = function () {
        calculator.setResult(undoResult);
    };
   
};

MultiplyCommand.prototype = new Command();
MultiplyCommand.prototype.constructor = MultiplyCommand;

/* ConcreteCommand - DivideCommand */

var DivideCommand = function (calculator, value) {

    var calculator = calculator,
        value = value,
        undoResult = calculator.getResult();;
   
    this.execute = function () {
        calculator.divide(value);
    };
   
    this.undo = function () {
        calculator.setResult(undoResult);
    };

};

DivideCommand.prototype = new Command();
DivideCommand.prototype.constructor = DivideCommand;

/* Invoker */

var User = function () {

    var calculator = new Calculator();
        commands = [],
        command = null;
   
    this.add = function (value) {
        command = new AddCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.subtract = function (value) {
        command = new SubtractCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.multiply = function (value) {
        command = new MultiplyCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.divide = function (value) {
        command = new DivideCommand(calculator, value);
        commands.push(command);
        command.execute();
    };
   
    this.undo = function () {
        if (commands.length === 0) {
            throw new Error("Command stack is empty");
        }
        command = commands.pop();
        command.undo();
    };
   
    this.getCount = function () {
        return calculator.getResult();
    };
   
};

// testing
var accountant = new User();
accountant.add(5);
accountant.add(10);
accountant.add(1);
accountant.multiply(0);
accountant.undo();
console.log(accountant.getCount()); // 16


Как видим, все отрабатывает без ошибок. Вот еще один простой пример:

/* interface Command */

var Command = function () {
    this.execute = function () {};
};

/* Receiver - Light */

var Light = function () {
    this.on = function () {
        console.log("The light is on");
    };
    this.off = function () {
        console.log("The light is off");
    };
};

/* Concrete Command - LightOnCommand */

var LightOnCommand = function (light) {
    var light = light;
    this.execute = function () {
        light.on();
    };
};

LightOnCommand.prototype = new Command();
LightOnCommand.prototype.constructor = LightOnCommand;

/* Concrete Command - LightOffCommand */

var LightOffCommand = function (light) {
    var light = light;
    this.execute = function () {
        light.off();
    };
};

LightOffCommand.prototype = new Command();
LightOffCommand.prototype.constructor = LightOffCommand;

/* Invoker - SimpleSwitcher */

var SimpleSwitcher = function () {
  
    var onComand,
        offCommand;
  
    this.setCommand = function (onCmd, offCmd) {
        onCommand = onCmd;
        offCommand = offCmd;
    };
  
    this.onButton = function () {
        onCommand.execute();
    };
  
    this.offButton = function () {
        offCommand.execute();
    };
  
};

/* test application */

var Application = function () {

    this.run = function () {
  
        // light switcher
        var switcher = new SimpleSwitcher();
      
        //
        var light = new Light();
      
        var lightOnCommand = new LightOnCommand(light);
        var lightOffCommand = new LightOffCommand(light);
      
        switcher.setCommand(lightOnCommand, lightOffCommand);
      
        switcher.onButton();
        switcher.offButton();
  
    };

};

var application = new Application();
application.run();


На выходе:

The light is on
The light is off


Данный паттерн позволяет строить очереди запросов (команды ставятся в конец очереди, "потоки" извлекаю команду из очереди, вызывают ее метод execute(), ожидают завершения вызова, уничтожают текущий объект команды и переходят к следующей команде), а также организовывать регистрацию запросов (механизм транзакций). 

Javascript. Singleton pattern

Паттерн Одиночка (Singleton) гарантирует, что класс имеет только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру. Данный паттерн определяет альтернативный способ создания объектов - в данном случае уникальных объектов, то есть гарантируется существование в приложении не более одного экземпляра данного класса. Данный паттерн относится к порождающим шаблонам.


Самый простой способ создать такой уникальный объект с глобальной точкой доступа на javascript - создать объект с помощью литералов.

var unique = (function (global) {

        // create global unique singleton object
return global.unique = {};

})(window);

Вот иная реализация (согласно вышеприведенной диаграмме класса Sigleton):

var Singleton = (function () {

// static variable
var uniqueInstance = null;

function Singleton() {
throw new Error("Forbidden to instantiate the Singleton class. Use Singleton.getInstance method");
}

// private constructor
function ctor() {

var id = id || 0;

this.getId = function () {};

this.setId = function (_id) {
id = _id || id;
};
}

// for checking by instanceof Singleton 
ctor.prototype = Singleton.prototype;

Singleton.getInstance = function () {
if (uniqueInstance === null) {
uniqueInstance = new ctor();
}
return uniqueInstance;
};

return Singleton;
})();

// var object1 = new Singleton(); // Forbidden to instantiate the Singleton class. Use Singleton.getInstance method

var object1 = Singleton.getInstance();
var object2 = Singleton.getInstance();

console.log(object1 instanceof Singleton); // true
console.log(object1 === object2); // true

Или вот еще один пример реализации данного паттерна:

var Module = function () {
    
    if (typeof Module.instance === "object") {
        return Module.instance;
    }
    
    var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
        today = new Date(),
        week = days[today.getDay()],
        day = today.getDate(),
        day = day < 10 ? '0' + day : day,
        month = today.getMonth() + 1,
        month = month < 10 ? '0' + month : month,
        year = today.getFullYear();
        hours = today.getHours(),
        minutes = today.getMinutes(),
        initDate = week + ', ' + day + '.' + month + '.' + year,
        initFullDate = week + ', ' + day + '.' + month + '.' + year + ', ' + hours + ':' + minutes;
        
    this.getInitDate = function () {
        return initDate;
    };
    
    this.getInitFullDate = function () {
        return initFullDate;
    };
    
    Module.instance = this;
    
};

var module1 = new Module();
var module2 = new Module();

console.log(module1 === module2); // true
console.log(module1.getInitFullDate()); // Sun, 18.05.2014, 22:44
console.log(module2.getInitFullDate()); // Sun, 18.05.2014, 22:44

Module.prototype.isInit = true;

console.log(module1.isInit); // true
console.log(module2.isInit); // true

Javascript. Factory Method pattern

Паттерн Фабричный Метод (Factory Method) определяет интерфейс создания объекта, но позволяет субклассам выбрать класс создаваемого экземпляра. Таким образом, Фабричный метод делегирует операцию создания экземпляра субклассам. Относится к порождающим шаблонам проектирования.



Паттерн Фабричный Метод, как все остальные разновидности фабрик, предоставляет интерфейс к методу создания объектов, также называемому "фабричным методом". Остальные методы, реализуемые в абстрактном классе Creator, работают с продуктами, созданными фабричным методом. Только субклассы фактически реализуют фабричный метод и создают продукты.
Рассмотрим паттерн на примере заказа пиццы с использованием фабричного метода.

/* Product - Pizza */

var Pizza = function () {

    this.prepare = function () {
        console.log("Preparing " + this.name);
        console.log("Tossing dough...");
        console.log("Adding sauce...");
        console.log("Adding toppings: ");
        for (var i = 0; i < this.toppings.length; i++) {
            console.log("  " + this.toppings[i]);
        }
    };
   
    this.bake = function () {
        console.log("Bake for 25 minutes at 350");
    };
   
    this.cut = function () {
        console.log("Cutting the pizza into diagonal slices");
    };
   
    this.box = function () {
        console.log("Place pizza in official PizzaStore box");
    };
   
    this.getName = function () {
        return this.name;
    };

};

/* ConcreteProduct - NYStyleCheesePizza */

var NYStyleCheesePizza = function () {
    this.name = "NY Style Sauce and Cheese Pizza";
    this.dough = "Thin Crust Dough";
    this.sauce = "Marinara Sauce";
    this.toppings = ["Grated Reggiano Cheese"];
};

NYStyleCheesePizza.prototype = new Pizza();
NYStyleCheesePizza.prototype.constructor = NYStyleCheesePizza;

/* ConcreteProduct - ChicagoStyleCheesePizza */

var ChicagoStyleCheesePizza = function () {
    this.name = "Chicago Style Deep Dish Cheese Pizza";
    this.dough = "Extra Thick Crust Dough";
    this.sauce = "Plum Tomato Sauce";
    this.toppings = ["Shredded Mozzarella Cheese"];
    this.cut = function () {
        console.log("Cutting the pizza into square slices");
    };
};

ChicagoStyleCheesePizza.prototype = new Pizza();
ChicagoStyleCheesePizza.prototype.constructor = ChicagoStyleCheesePizza;

/* Creator - PizzaStore */

var PizzaStore = function () {

    this.orderPizza = function () {
        var pizza = this.createPizza();
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza
    };

    // abstract createPizza();

};

/* ConcreteCreator - NYPizzaStore */

var NYPizzaStore = function () {
    // factory method
    this.createPizza = function () {
        return new NYStyleCheesePizza();
    };
};

NYPizzaStore.prototype = new PizzaStore();
NYPizzaStore.prototype.constructor = NYPizzaStore;

/* ConcreteCreator - NYPizzaStore */

var ChicagoPizzaStore = function () {
    // factory method
    this.createPizza = function () {
        return new ChicagoStyleCheesePizza();
    };
};

ChicagoPizzaStore.prototype = new PizzaStore();
ChicagoPizzaStore.prototype.constructor = ChicagoPizzaStore;

/* test application */

var Application = function () {
    this.run = function () {
       
        var nyStore = new NYPizzaStore();
        var chicagoStore = new ChicagoPizzaStore();
       
        var pizza = nyStore.orderPizza();
        console.log("Ethan ordered a " + pizza.getName());
       
        pizza = chicagoStore.orderPizza();
        console.log("Joel ordered a " + pizza.getName());
       
    };
};

var application = new Application();
application.run();


На выходе:

Preparing NY Style Sauce and Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings:
  Grated Reggiano Cheese
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Ethan ordered a NY Style Sauce and Cheese Pizza

Preparing Chicago Style Deep Dish Cheese Pizza
Tossing dough...
Adding sauce...
Adding toppings:
  Shredded Mozzarella Cheese
Bake for 25 minutes at 350
Cutting the pizza into square slices
Place pizza in official PizzaStore box
Joel ordered a Chicago Style Deep Dish Cheese Pizza

Javascript. Decorator pattern

Паттерн Декоратор - динамически наделяет объект новыми возможностями, и является гибкой альтернативой субклассированию в области расширения функциональности.
Данный паттерн относится к структурным паттернам. Для динамического добавления нового поведения используется композиция и делегирование. Компонент может декорироваться любым количеством декораторов.






Ниже рассмотрены примеры реализации данного паттерна. Первый пример показывает применение декораторов в роли дополнительных компонентов для того или иного вида кофе (см.рис. ниже).



/* Component - Beverage */

var Beverage = function () {

    this.description = "Unknown Beverage";
   
    this.getDescription = function () {
        return this.description;
    };
   
    // abstract cost();
    this.cost = function () {
        throw new Error("Must implement the abstract method - cost()");
    };

};

/* Decorator - CondimentDecorator */

var CondimentDecorator = function () {

    // abstract getDescription();
    this.getDescription = function () {
        throw new Error("Must implement the abstract method - getDescription()");
    };

};

CondimentDecorator.prototype = new Beverage();
CondimentDecorator.prototype.constructor = CondimentDecorator;

/* ConcreteComponent - Expresso */

var Expresso = function () {

    this.description = "Expresso";
   
    this.cost = function () {
        return 1.99;
    };

};

Expresso.prototype = new Beverage();
Expresso.prototype.constructor = Expresso;

/* ConcreteComponent - HouseBlend */

var HouseBlend = function () {

    this.description = "House Blend Coffee";
   
    this.cost = function () {
        return 0.89;
    };

};

HouseBlend.prototype = new Beverage();
HouseBlend.prototype.constructor = HouseBlend;

/* ConcreteComponent - DarkRoast */

var DarkRoast = function () {

    this.description = "Dark Roast Coffee";
   
    this.cost = function () {
        return 0.99;
    };

};

DarkRoast.prototype = new Beverage();
DarkRoast.prototype.constructor = DarkRoast;

/* ConcreteComponent - Decaf */

var Decaf = function () {

    this.description = "Decaf Coffee";
   
    this.cost = function () {
        return 1.05;
    };

};

Decaf.prototype = new Beverage();
Decaf.prototype.constructor = Decaf;

/* ConcreteDecorator - Mocha */

var Mocha = function (begerage) {

    var begerage = begerage;
   
    this.getDescription = function () {
        return begerage.getDescription() + ", Mocha";
    };
   
    this.cost = function () {
        return 0.20 + begerage.cost();
    };

};

Mocha.prototype = new CondimentDecorator();
Mocha.prototype.constructor = Mocha;

/* ConcreteDecorator - Milk */

var Milk = function (begerage) {

    var begerage = begerage;
   
    this.getDescription = function () {
        return begerage.getDescription() + ", Milk";
    };
   
    this.cost = function () {
        return 0.10 + begerage.cost();
    };

};

Milk.prototype = new CondimentDecorator();
Milk.prototype.constructor = Milk;

/* ConcreteDecorator - Soy */

var Soy = function (begerage) {

    var begerage = begerage;
   
    this.getDescription = function () {
        return begerage.getDescription() + ", Soy";
    };
   
    this.cost = function () {
        return 0.15 + begerage.cost();
    };

};

Soy.prototype = new CondimentDecorator();
Soy.prototype.constructor = Soy;

/* ConcreteDecorator - Whip */

var Whip = function (begerage) {

    var begerage = begerage;
   
    this.getDescription = function () {
        return begerage.getDescription() + ", Whip";
    };
   
    this.cost = function () {
        return 0.10 + begerage.cost();
    };

};

Whip.prototype = new CondimentDecorator();
Whip.prototype.constructor = Whip;

/* test application - StarbuzzCoffee */

var StarbuzzCoffee = function () {

    var beverage = new Expresso();
   
    console.log(beverage.getDescription() + " $" + beverage.cost());
   
    var beverage2 = new DarkRoast();
    beverage2 = new Mocha(beverage2);
    beverage2 = new Mocha(beverage2);
    beverage2 = new Whip(beverage2);
   
    console.log(beverage2.getDescription() + " $" + beverage2.cost());
   
    var beverage3 = new HouseBlend();
    beverage3 = new Soy(beverage3);
    beverage3 = new Mocha(beverage3);
    beverage3 = new Whip(beverage3);
   
    console.log(beverage3.getDescription() + " $" + beverage3.cost());

};

StarbuzzCoffee();


На выходе:

Expresso $1.99
Dark Roast Coffee, Mocha, Mocha, Whip $1.49
House Blend Coffee, Soy, Mocha, Whip $1.34


Или вот другой пример на основе списков декораторов:

var Sale = function (price) {
    this.price = (price > 0) || 100;
    this.decorators_list = [];
};

Sale.decorators = {};

Sale.decorators.fedtax = {
    getPrice: function (price) {
        return price + price * 5 / 100;
    }
};

Sale.decorators.quebec = {
    getPrice: function (price) {
        return price + price * 7.5 / 100;
    }
};

Sale.decorators.money = {
    getPrice: function (price) {
        return "$" + price.toFixed(2);
    }
};

Sale.decorators.cdn = {
    getPrice: function (price) {
        return "CDN$ " + price.toFixed(2);
    }
};

Sale.prototype.decorate = function (decorator) {
    this.decorators_list.push(decorator);
};

Sale.prototype.getPrice = function () {
    var price = this.price,
        i,
        max = this.decorators_list.length,
        name;
    for (i = 0; i < max; i += 1) {
        name = this.decorators_list[i];
        price = Sale.decorators[name].getPrice(price);
    }
    return price;
};

var Application = function () {
    this.run = function () {
   
        var sale = new Sale(100);
        sale.decorate('fedtax');
        sale.decorate('quebec');
        sale.decorate('money');
        console.log(sale.getPrice()); // $113
   
    };
};

var application = new Application();
application.run();

Javascript. Observer pattern

Паттерн Наблюдатель (Observer) - определяет отношение "один-ко-многим" между объектами таким образом, что при изменении состояния одного объекта происходит автоматическое оповещение и обновление всех зависимых объектов.
При использовании паттерна возможен как запрос, так и активная доставка данных от субъекта (запрос считается более "правильным"). Данный паттерн относится к поведенческим паттернам.


Рассмотрим примеры реализации данного паттерна.

1. Модель активной доставки данных

/* interface Observer */

var Observer = function () {
    this.update = function () {};
};

/* interface Observable */

var Observable = function () {
    this.registerObserver = function () {};
    this.removeObserver = function () {};
    this.notifyObservers = function () {};
};

/* ConcreteObservable - WeatherData */

var WeatherData = function () {
   
    var observers = [],
        temperature,
        humidity,
        pressure;
   
    this.registerObserver = function (o) {
        observers.push(o);
    };
   
    this.removeObserver = function (o) {
        var index = observers.indexOf(o);
        if (index >= 0) {
            observers.splice(index, 1);
        }
    };
   
    this.notifyObservers = function () {
        for (var observer in observers) {
            observers[observer].update(temperature, humidity, pressure);
        }
    };
   
    this.setMeasurements = function (_temperature, _humidity, _pressure) {
        temperature = _temperature;
        humidity = _humidity;
        pressure = _pressure;
        this.notifyObservers();
    };
   
};

WeatherData.prototype = new Observable();
WeatherData.prototype.constructor = WeatherData;

/* ConcreteObserver - CurrentConditionsDisplay */

var CurrentConditionsDisplay = function (weatherData) {
   
    var temperature,
        humidity,
        weatherData = weatherData;
   
    weatherData.registerObserver(this);
   
    this.update = function (_temperature, _humidity, _pressure) {
        temperature = _temperature;
        humidity = _humidity;
        this.display();
    };
   
    this.display = function () {
        console.log("Current values: " + temperature + " degrees Celsius and " + humidity + "% humidity");
    };
   
};

CurrentConditionsDisplay.prototype = new Observer();
CurrentConditionsDisplay.prototype.constructor = CurrentConditionsDisplay;

/* test application - WeatherStation */

var WeatherStation = function () {

    var weatherData = new WeatherData();
   
    var currentDisplay = new CurrentConditionsDisplay(weatherData);
   
    weatherData.setMeasurements(29, 65, 30.4);
    weatherData.setMeasurements(39, 70, 29.4);
    weatherData.setMeasurements(42, 72, 31.4);
   
}

WeatherStation();


На выходе:

Current values: 29 degrees Celsius and 65% humidity
Current values: 39 degrees Celsius and 70% humidity
Current values: 42 degrees Celsius and 72% humidity


2. Модель запроса данных

/* interface Observer */

var Observer = function () {
    this.update = function () {};
};

/* interface Observable */

var Observable = function () {
    this.registerObserver = function () {};
    this.removeObserver = function () {};
    this.notifyObservers = function () {};
};

/* ConcreteObservable - WeatherData */

var WeatherData = function () {
   
    var observers = [],
        changed = false,
        temperature,
        humidity,
        pressure;
   
    this.registerObserver = function (o) {
        observers.push(o);
    };
   
    this.removeObserver = function (o) {
        var index = observers.indexOf(o);
        if (index >= 0) {
            observers.splice(index, 1);
        }
    };
   
    this.notifyObservers = function () {
        if (changed) {
            for (var observer in observers) {
                observers[observer].update(this);
            }
            changed = false;
        }
    };
   
    this.setMeasurements = function (_temperature, _humidity, _pressure) {
        temperature = _temperature;
        humidity = _humidity;
        pressure = _pressure;
        this.measurementsChanged();
    };
   
    this.measurementsChanged = function () {
        this.setChanged();
        this.notifyObservers();
    };
   
    this.setChanged = function () {
        changed = true;
    };
   
    this.getTemperature = function () {
        return temperature;
    };
   
    this.getHumidity = function () {
        return humidity;
    };
   
    this.getPressure = function () {
        return pressure;
    };
   
};

WeatherData.prototype = new Observable();
WeatherData.prototype.constructor = WeatherData;

/* ConcreteObserver - CurrentConditionsDisplay */

var CurrentConditionsDisplay = function (_weatherData) {
   
    var temperature,
        humidity,
        weatherData = weatherData;

    this.update = function (obs) {
        if (obs instanceof WeatherData) {
            var weatherData = obs;
            temperature = weatherData.getTemperature();
            humidity = weatherData.getHumidity();
            this.display();
        }
    };
   
    this.display = function () {
        console.log("Current values: " + temperature + " degrees Celsius and " + humidity + "% humidity");
    };
   
    (function (widget) {
        weatherData = _weatherData;
        weatherData.registerObserver(widget);
    })(this);
   
};

CurrentConditionsDisplay.prototype = new Observer();
CurrentConditionsDisplay.prototype.constructor = CurrentConditionsDisplay;

/* test application - WeatherStation */

var WeatherStation = function () {

    var weatherData = new WeatherData();
   
    var currentDisplay = new CurrentConditionsDisplay(weatherData);
   
    weatherData.setMeasurements(29, 65, 30.4);
    weatherData.setMeasurements(39, 70, 29.4);
    weatherData.setMeasurements(42, 72, 31.4);
   
}

WeatherStation();


На выходе получаем аналогичный результат:

Current values: 29 degrees Celsius and 65% humidity
Current values: 39 degrees Celsius and 70% humidity
Current values: 42 degrees Celsius and 72% humidit
y