Javascript. Iterator pattern

Паттерн Итератор (Iterator pattern) предоставляет механизм последовательного перебора элементов коллекции без раскрытия ее внутреннего представления. То есть паттерн позволяет перебирать элементы коллекции, не зная, как реализована коллекция. Применение паттерна позволяет передать ответственность перебора элементов от объекта коллекции объекту итератора. Это обстоятельство не только упрощает интерфейс, но и избавляет коллекцию от посторонних обязанностей (ее главной задачей является управление объектами, а не перебор). Данный паттерн относится к поведенческим паттернам.



Давайте рассмотрим пример реализации данного паттерна на примере двух меню: один реализуем на основе массивов, другой на основе абстрактной структуры данных - списка. Для этих коллекции реализуем свои итераторы.

/* Abstract data type - List */

var List = (function () {

    var Node = function () {

        var element,    
            next;
        
        this.getElement = function () {
            return element;
        };
        
        this.setElement = function (_element) {
            element = _element; 
        };
        
        this.getNext = function () {
            return next;
        };
        
        this.setNext = function (_next) {
            next = _next;
        };

    };
    
    return function () {

        var headNode = null,
            tailNode = headNode,
            length = 0;

        this.push = function (element) {
            if (headNode === null) {
                headNode = new Node();
                headNode.setElement(element);
                tailNode = headNode;
                headNode.setNext(null);                
            }
            else {
                var newNode = new Node();
                tailNode.setNext(newNode);
                tailNode = newNode;
                tailNode.setElement(element);
                tailNode.setNext(null);
            }
            length++;
        };
        
        this.get = function (position) {
            if (position >= length) {
                throw new Error("");
            }
            for (var i = 0, tempNode = headNode; i < position; i++) {
                tempNode = tempNode.getNext();
            }
            return tempNode.getElement();
        };
        
        this.length = function () {
            return length;
        };

    };

})();

/* Template of menu collection */

var MenuItem = function (name, description, vegetarian, price) {

    var name = name,
        description = description,
        vegetarian = vegetarian,
        price = price;
    
    this.getName = function () {
        return name;
    };
    
    this.getDescription = function () {
        return description;
    };
    
    this.getPrice = function () {
        return price;
    };
    
    this.isVegetarian = function () {
        return vegetarian;
    };
    
};

/* interface Iterator */

var Iterator = function () {
    this.hasNext = function () {};
    this.next = function () {};
    this.remove = function () {};
};

/* interface Aggregate - interface Menu */

var Menu = function () {
    this.createIterator = function () {};
};

/* ConcreteIterator - DinerMenuIterator - for concreteAggreate - DinerMenu */

var DinerMenuIterator = function (items) {
    
    var items = items || [],
        position = 0;
        
    this.next = function () {
        var menuItem = items[position];
        position = position + 1;
        return menuItem;
    };
    
    this.hasNext = function () {
        if (position >= items.length || typeof items[position] === "undefined") {
            return false;
        } 
        else {
            return true;
        }
    };
    
    this.remove = function () {};
    
};

DinerMenuIterator.prototype = new Iterator();
DinerMenuIterator.prototype.constructor = DinerMenuIterator;

/* ConcreteAggreate - DinerMenu collection */

var DinerMenu = function () {
    
    var MAX_ITEMS = 6;
    var numberOfItems = 0;
    var menuItems = new Array(MAX_ITEMS);

    this.addItem = function (name, description, vegetarian, price) {
        var menuItem = new MenuItem(name, description, vegetarian, price);
        if (numberOfItems >= MAX_ITEMS) {
            throw new Error("Sorry, menu is full! Can`t add item to menu");
        }
        else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems = numberOfItems + 1;
        }
    };

    this.createIterator = function () {
        return new DinerMenuIterator(menuItems);
    };

    // other methods
    
    (function (menu) {    
        menu.addItem("Vegetarian BLT", "(Fakin) Bacon with lettuce & tomato on whle wheat", true, 2.99);
        menu.addItem("BLT", "Bacon with lettuce & tomato on whle wheat", false, 2.99);
        menu.addItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29);
        menu.addItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05);
    })(this);

};

DinerMenu.prototype = new Menu();
DinerMenu.prototype.constructor = DinerMenu;

/* ConcreteIterator - PancakeHouseMenuIterator - for concreteAggreate - PancakeHouseMenu */

var PancakeHouseMenuIterator = function (items) {
    
    var items = items,
        position = 0;
        
    this.next = function () {
        var menuItem = items.get(position);
        position = position + 1;
        return menuItem;
    };
    
    this.hasNext = function () {
        if (position >= items.length()) {
            return false;
        } 
        else {
            return true;
        }
    };
    
    this.remove = function () {};
    
};

PancakeHouseMenuIterator.prototype = new Iterator();
PancakeHouseMenuIterator.prototype.constructor = PancakeHouseMenuIterator;

/* ConcreteAggreate - PancakeHouseMenu collection */

var PancakeHouseMenu = function () {
    
    var menuItems = new List;

    this.addItem = function (name, description, vegetarian, price) {
        var menuItem = new MenuItem(name, description, vegetarian, price);
        menuItems.push(menuItem);
    };

    this.createIterator = function () {
        return new PancakeHouseMenuIterator(menuItems);
    };

    // other methods
    
    (function (menu) {    
        menu.addItem("K&B`s Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99);
        menu.addItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99);
        menu.addItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49);
        menu.addItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59);
    })(this);

};

PancakeHouseMenu.prototype = new Menu();
PancakeHouseMenu.prototype.constructor = PancakeHouseMenu;

/* Client of PancakeHouseMenu & DinerMenu concreteAggreates */

var Waitress = function (pancakeHouseMenu, dinerMenu) {

    var pancakeHouseMenu = pancakeHouseMenu,
        dinerMenu = dinerMenu;
    
    this.printMenu = function () {
    
        if (arguments.length === 0) {
            var pancakeIterator = pancakeHouseMenu.createIterator();
            var dinerIterator = dinerMenu.createIterator();
            console.log("MENU");
            console.log("---");
            console.log("BREAKFAST");
            this.printMenu(pancakeIterator);
            console.log("LUNCH");
            this.printMenu(dinerIterator);
        }
        
        if (arguments.length === 1 && arguments[0] instanceof Iterator) {
            var iterator = arguments[0];
            while (iterator.hasNext()) {
                var menuItem = iterator.next();
                menuItem =  menuItem.getName() + ", " +
                            menuItem.getPrice() + " -- " +
                            menuItem.getDescription();
                console.log(menuItem);
            }
        }
    
    };

};

/* test application */

var Application = function () {
    this.run = function () {
    
        var pancakeHouseMenu = new PancakeHouseMenu();
        var dinerMenu = new DinerMenu();
        
        var waitress = new Waitress(pancakeHouseMenu, dinerMenu);
        
        waitress.printMenu();
    
    };
};

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

На выходе получаем следующие сообщения:

MENU
---
BREAKFAST
K&B`s Pancake Breakfast, 2.99 -- Pancakes with scrambled eggs, and toast
Regular Pancake Breakfast, 2.99 -- Pancakes with fried eggs, sausage
Blueberry Pancakes, 3.49 -- Pancakes made with fresh blueberries
Waffles, 3.59 -- Waffles, with your choice of blueberries or strawberries
LUNCH
Vegetarian BLT, 2.99 -- (Fakin) Bacon with lettuce & tomato on whle wheat
BLT, 2.99 -- Bacon with lettuce & tomato on whle wheat
Soup of the day, 3.29 -- Soup of the day, with a side of potato salad
Hotdog, 3.05 -- A hot dog, with saurkraut, relish, onions, topped with cheese

Обратите внимание на метод printMenu клиента меню-коллекций Waitress. Это своего рода способ перегрузки методов. Давайте рассмотрим еще один пример. На этот раз паттерн Итератор будет рассмотрен совместно с паттерном Компоновщик. Давайте рассмотрим пример на основе составления меню. Также стоит отметить один интересный подход. Как известно, в Java для большинства коллекций уже реализованы итераторы, для их создания достаточно для коллекции вызвать метод iterator(). Так как в javascript все же основной структурой данных является массив (хотя есть и литеральные объекты), то и мы можем реализовать своего рода поддержку "стандартного" итератора. Например вот таким образом:

Array.prototype.iterator = function () {
    var self = this, position = 0, undefined;
    return {
        next:function () {
            return self[position++];
        },
        hasNext: function () {
            if (position >= self.length) {
                return false;
            } 
            else {
                return true;
            }
        },
        remove: function () {
            if (position <= 0) {
                throw new Error("You can`t an item until you`ve done at least one next()");
            }
            if (self[position - 1] !== undefined) {
                for (var i = position - 1; i < (self.length - 1); i++) {
                    self[i] = self[i + 1];
                }
                self[self.length - 1] = undefined;
            }
        }
    };
};

Как видим, мы реализуем интерфейс итератора. Итак. Вот следующая реализация паттерна.

Array.prototype.iterator = function () {
    var self = this, position = 0, undefined;
    return {
        next:function () {
            return self[position++];
        },
        hasNext: function () {
            if (position >= self.length) {
                return false;
            } 
            else {
                return true;
            }
        },
        remove: function () {
            if (position <= 0) {
                throw new Error("You can`t an item until you`ve done at least one next()");
            }
            if (self[position - 1] !== undefined) {
                for (var i = position - 1; i < (self.length - 1); i++) {
                    self[i] = self[i + 1];
                }
                self[self.length - 1] = undefined;
            }
        }
    };
};

/* abstract class MenuComponent */

var MenuComponent = function () {

    this.add = function (menuComponent) {
        throw new Error("UnsupportedOperationException");
    };
    
    this.remove = function (menuComponent) {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getChild = function (i) {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getName = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getDescription = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.getPrice = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.isVegetarian = function () {
        throw new Error("UnsupportedOperationException");
    };
    
    this.print = function () {
        throw new Error("UnsupportedOperationException");
    };

}

/* Menu element */

var MenuItem = function (name, description, vegetarian, price) {

    var name = name,
        description = description,
        vegetarian = vegetarian,
        price = price;
    
    this.getName = function () {
        return name;
    };
    
    this.getDescription = function () {
        return description;
    };
    
    this.getPrice = function () {
        return price;
    };
    
    this.isVegetarian = function () {
        return vegetarian;
    };
    
    this.print = function () {
        var item =  " " + this.getName() + 
                    (this.isVegetarian() ? "(v)" : "") + 
                    ", " + this.getPrice() + 
                    "   -- " + this.getDescription();
        console.log(item);
    };

};

MenuItem.prototype = new MenuComponent();
MenuItem.prototype.constructor = MenuItem;

/* Menu */

var Menu = function (name, description) {

    var menuComponents = [],
        name = name,
        description = description;

    this.add = function (menuComponent) {
        menuComponents.push(menuComponent);
    };
    
    this.remove = function (menuComponent) {
        var index = menuComponents.indexOf(menuComponent);
        if (index >= 0) {
            menuComponents.splice(index, 1);
        };
    };
    
    this.getChild = function (i) {
        return menuComponents[i];
    };
    
    this.getName = function () {
        return name;
    };
    
    this.getDescription = function () {
        return description;
    };
    
    this.print = function () {
        var item =  this.getName() + ", " +    this.getDescription();
        console.log(item);
        console.log("----------------------");
        
        var iterator = menuComponents.iterator();
        while (iterator.hasNext()) {
            var menuComponent = iterator.next();
            menuComponent.print();
        }
    };

};

Menu.prototype = new MenuComponent();
Menu.prototype.constructor = Menu;

/* Client */

var Waitress = function (allMenus) {

    var allMenus = allMenus;
    
    this.printMenu = function () {
        allMenus.print();
    };

};

/* test application */

var Application = function () {
    this.run = function () {
    
        var pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU", "Breakfast"), 
            dinerMenu = new Menu("DINER MENU", "Lunch"), 
            cafeMenu = new Menu("CAFE MENU", "Dinner"),
            dessertMenu = new Menu("DESSERT MENU", "Dessert of course!");
            
        var allMenus = new Menu("ALL MENUS", "All menus combined");
        
        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);
        
        pancakeHouseMenu.add(new MenuItem("K&B`s Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99));
        pancakeHouseMenu.add(new MenuItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99));
        pancakeHouseMenu.add(new MenuItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49));
        pancakeHouseMenu.add(new MenuItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59));
    
        dinerMenu.add(new MenuItem("Vegetarian BLT", "(Fakin) Bacon with lettuce & tomato on whle wheat", true, 2.99));
        dinerMenu.add(new MenuItem("BLT", "Bacon with lettuce & tomato on whle wheat", false, 2.99));
        dinerMenu.add(new MenuItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29));
        dinerMenu.add(new MenuItem("Hotdog", "A hot dog, with saurkraut, relish, onions, topped with cheese", false, 3.05));
        dinerMenu.add(new MenuItem("Pasta", "Spaghetti with Marinara Sauce, and a slice of sourdough bread", true, 3.89));
        
        dinerMenu.add(dessertMenu);        
        dessertMenu.add(new MenuItem("Apple Pie", "Apple pie with a flakey crust, topped with vanilla icecream", true, 1.59));
        
        cafeMenu.add(new MenuItem("Veggie Burger and Air Fries", "Veggie burger on a whole wheat bun, lettuce, tomato, and fries", true, 3.99));
        cafeMenu.add(new MenuItem("Soup of the day", "A cup of the soup of the day, with a side salad", false, 3.69));
        cafeMenu.add(new MenuItem("Burrito", "A large burrito, with whole pinto beans, salsa, guacamole", true, 4.29));
        
        var waitress = new Waitress(allMenus);
        
        waitress.printMenu();
    
    };
};

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

На выходе получим следующие результаты:

ALL MENUS, All menus combined
----------------------

PANCAKE HOUSE MENU, Breakfast
----------------------
K&B`s Pancake Breakfast(v), 2.99   -- Pancakes with scrambled eggs, and toast
Regular Pancake Breakfast, 2.99   -- Pancakes with fried eggs, sausage
Blueberry Pancakes(v), 3.49   -- Pancakes made with fresh blueberries
Waffles(v), 3.59   -- Waffles, with your choice of blueberries or strawberries

DINER MENU, Lunch
----------------------
Vegetarian BLT(v), 2.99   -- (Fakin) Bacon with lettuce & tomato on whle wheat
BLT, 2.99   -- Bacon with lettuce & tomato on whle wheat
Soup of the day, 3.29   -- Soup of the day, with a side of potato salad
Hotdog, 3.05   -- A hot dog, with saurkraut, relish, onions, topped with cheese
Pasta(v), 3.89   -- Spaghetti with Marinara Sauce, and a slice of sourdough bread

DESSERT MENU, Dessert of course!
----------------------
Apple Pie(v), 1.59   -- Apple pie with a flakey crust, topped with vanilla icecream

CAFE MENU, Dinner
----------------------
Veggie Burger and Air Fries(v), 3.99   -- Veggie burger on a whole wheat bun, lettuce, tomato, and fries
Soup of the day, 3.69   -- A cup of the soup of the day, with a side salad
Burrito(v), 4.29   -- A large burrito, with whole pinto beans, salsa, guacamole

На этом все. Всем удачи! :)

Javascript. Facade pattern

Паттерн Фасад (Facade pattern) - предоставляет унифицированный интерфейс к группе интерфейсов подсистемы. Фасад определяет высокоуровневый интерфейс, упрощающий работу подсистемы. Основной целью фасада является упрощение работы с подсистемой за счет использования упрощенного интерфейса. Данный паттерн относится к структурным паттернам.


Рассмотрим пример использования петтерна.

/* tuner subsystem */

var tuner = {
on: function () {},
off: function () {},
setAm: function () {},
setFm: function () {},
setFrequency: function () {},
toString: function () {}
};

/* screen subsystem */

var screen = {
up: function () {},
down: function () {},
toString: function () {}
};

/* cdPlayer subsystem */

var cdPlayer = {
on: function () {},
off: function () {},
eject: function () {},
pause: function () {},
play: function () {},
stop: function () {},
toString: function () {}
};

/* dvdPlayer subsystem */

var dvdPlayer = {
on: function () {},
off: function () {},
eject: function () {},
pause: function () {},
play: function () {},
setSurroundAudio: function () {},
setTwoChannelAudio: function () {},
stop: function () {}
};

/* projector subsystem */

var projector = {
on: function () {},
off: function () {},
tvMode: function () {},
wideScreenMode: function () {},
toString: function () {}
};

/* popcornPopper subsystem */

var popcornPopper = {
on: function () {},
off: function () {},
pop: function () {},
toString: function () {}
};

/* theaterLights subsystem */

var theaterLights = {
on: function () {},
off: function () {},
dim: function () {},
toString: function () {}
};

/* amplifier subsystem */

var amplifier = {
on: function () {},
off: function () {},
setCd: function () {},
setDvd: function () {},
setStereoSound: function () {},
setSurroundSound: function () {},
setTuner: function () {},
setVolume: function () {},
toString: function () {}
};

/* facade */

var HomeTheaterFacade = function (amplifier,
tuner,
dvdPlayer,
cdPlayer,
projector,
screen,
theaterLights,
popcornPopper) {

var amp = amplifier,
tuner = tuner,
dvd = dvdPlayer,
cd = cdPlayer,
projector = projector,
screen = screen,
lights = theaterLights,
popper = popcornPopper;

this.watchMovie = function (movie) {
console.log("Get ready to watch a movie...");
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);
};

this.endMovie = function () { 
console.log("Shutting movie theater down...");
popper.off();
lights.off();
screen.up();
projector.off();
amp.off();
dvd.stop();
dvd.eject();
dvd.off();
};

// other methods...

};

var homeTheater = new HomeTheaterFacade(amplifier, tuner, dvdPlayer, cdPlayer,
projector, screen, theaterLights, popcornPopper);
homeTheater.watchMovie("Raiders of the Lost Ark");
homeTheater.endMovie();

На выходе:

Get ready to watch a movie...
Shutting movie theater down...

На примере видна польза фасада - упрощение интерфейса работы с подсистемами, иначе это было бы намного сложнее.

Javascript. Adapter pattern

Паттерн Адаптер (Adapter pattern) преобразует интерфейс класса к другому интерфейсу, на который рассчитан клиент. Адаптер обеспечивает совместную работу классов, невозможную в обычных условиях из-за несовместимости интерфейсов. Поэтому для использования клиента с несовместимым интерфейсом, необходимо создать адаптер, который выполняет преобразование.Таким образом клиент отделяется от реализованного интерфейса; и если ожидаются изменения интерфейса со временем, то адаптер способен инкапсулировать эти изменения, чтобы клиента не приходилось изменять каждый раз, когда ему потребуется работать с новым интерфейсом. Данный паттерн относится к структурным паттернам.


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


Рассмотрим пример. Допустим мы имеем дело с резиновой уткой MallardDuck с интерфейсом

interface Duck {
  public void quack(); 
  public void fly();
} 

и дикой индюшкой WildTurkey с интерфейсом

interface Turkey {
  public void gooble();
  public void fly();
}

Допустим, что нам необходимо работать с дикой индюшкой так, словно мы работаем с уткой. Для этого создадим необходимый адаптер TurkeyAdapter, который сослужит нам полезную работу:

/* Target class */

var MallardDuck = function () {

this.quack = function () {
console.log("Quack");
};

this.fly = function () {
console.log("I`m flying");
};
};

/* Adaptee class */

var WildTurkey = function () {

this.gooble = function () {
console.log("Gobble gooble");
};

this.fly = function () {
console.log("I`m flying a short distance");
};
};

/* Adapter class: interface WildTurkey to interface MallardDuck */

var TurkeyAdapter = function (turkey) {

var turkey = turkey;

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

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

/* testing... */

var duck = new MallardDuck();
var turkey = new WildTurkey();
var turkeyAdapter = new TurkeyAdapter(turkey);
turkeyAdapter.quack();
turkeyAdapter.fly(); 

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

Gobble gooble
I`m flying a short distance
I`m flying a short distance
I`m flying a short distance
I`m flying a short distance
I`m flying a short distance

Всем удачи!

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(), ожидают завершения вызова, уничтожают текущий объект команды и переходят к следующей команде), а также организовывать регистрацию запросов (механизм транзакций).