Javascript. State pattern

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



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

Рассмотрим сначала пример реализации работы автомата, выдающего шарики за монетку без использования паттерна Состояние. Итак, наш автомат может находиться в определенных состояниях, например - ожидание, пуст и прочее, что зависит от ряда условий. Между состояниями существуют переходы, например - выдыча шарика или прием монетки. И вот здесь нужно использовать различные условные конструкции. Для начала рассмотрим модель нашего автомата на следующем рисунке.



Как видно, кружки - состояния автомата, а стрелки - переходы между состояниями. Вот реализация без паттерна.

/* GumballMachine */

var GumballMachine = function (count) {
var SOLD_OUT = 0,
NO_QUARTER = 1,
HAS_QUARTER = 2,
SOLD = 3;
var state = SOLD_OUT,
count = count || 0;
  
if (count > 0) {
state = NO_QUARTER;
}
  
this.insertQuarter = function () {
if (state == HAS_QUARTER) {
console.log("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
console.log("You inserted a quarter");
} else if (state == SOLD_OUT) {
console.log("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
        console.log("Please wait, we're already giving you a gumball");
}
};

this.ejectQuarter = function () {
if (state == HAS_QUARTER) {
console.log("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
console.log("You haven't inserted a quarter");
} else if (state == SOLD) {
console.log("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
        console.log("You can't eject, you haven't inserted a quarter yet");
}
};
this.turnCrank = function () {
if (state == SOLD) {
console.log("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
console.log("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
console.log("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
console.log("You turned...");
state = SOLD;
this.dispense();
}
};
this.dispense = function () {
if (state == SOLD) {
console.log("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
console.log("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
console.log("You need to pay first");
} else if (state == SOLD_OUT) {
console.log("No gumball dispensed");
} else if (state == HAS_QUARTER) {
console.log("No gumball dispensed");
}
};
this.refill = function (numGumBalls) {
count = numGumBalls;
state = NO_QUARTER;
};

this.toString = function () {
var result = "";
result += "\nMighty Gumball, Inc.";
result += "\nJavascript-enabled Standing Gumball Model #2004\n";
result += "Inventory: " + count + " gumball";
if (count != 1) {
result += "s";
}
result += "\nMachine is ";
if (state == SOLD_OUT) {
result += "sold out";
} else if (state == NO_QUARTER) {
result += "waiting for quarter";
} else if (state == HAS_QUARTER) {
result += "waiting for turn of crank";
} else if (state == SOLD) {
result += "delivering a gumball";
}
result += "\n";
return result;
};
};

/* testing... */

var Application = function () {
this.run = function () {
var gumballMachine = new GumballMachine(5);

console.log(gumballMachine.toString());

gumballMachine.insertQuarter();
gumballMachine.turnCrank();

console.log(gumballMachine.toString());

gumballMachine.insertQuarter();
gumballMachine.ejectQuarter();
gumballMachine.turnCrank();

console.log(gumballMachine.toString());

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.ejectQuarter();

console.log(gumballMachine.toString());

gumballMachine.insertQuarter();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

console.log(gumballMachine.toString());
};
}; 

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

На выходе получаем:

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
Quarter returned
You turned but there's no quarter

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
You haven't inserted a quarter

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You can't insert another quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

А теперь реализуем данный автомат с помощью паттерна Состояние. 

/* interface State */

var State = function () {
  this.insertQuarter = function () {};
this.ejectQuarter = function () {};
this.turnCrank = function () {};
this.dispense = function () {};
};

/* ConcreteState - HasQuarterState */

var HasQuarterState = function (gumballMachine) {
var gumballMachine = gumballMachine;
  
this.insertQuarter = function () {
console.log("You can't insert another quarter");
};
this.ejectQuarter = function () {
console.log("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
};
this.turnCrank = function () {
console.log("You turned...");
gumballMachine.setState(gumballMachine.getSoldState());
};

    this.dispense = function () {
        console.log("No gumball dispensed");
    };
this.toString = function () {
return "waiting for turn of crank";
};
};

HasQuarterState.prototype = new State();
HasQuarterState.prototype.constructor = HasQuarterState;

/* ConcreteState - NoQuarterState */

var NoQuarterState = function (gumballMachine) {
    var gumballMachine = gumballMachine;
this.insertQuarter = function () {
console.log("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
};
this.ejectQuarter = function () {
console.log("You haven't inserted a quarter");
};
this.turnCrank = function () {
console.log("You turned, but there's no quarter");
};
this.dispense = function () {
console.log("You need to pay first");
}; 
this.toString = function () {
return "waiting for quarter";
};
}

NoQuarterState.prototype = new State();
NoQuarterState.prototype.constructor = NoQuarterState;

/* ConcreteState - SoldOutState */

var SoldOutState = function (gumballMachine) {
    
var gumballMachine = gumballMachine;
this.insertQuarter = function () {
console.log("You can't insert a quarter, the machine is sold out");
};
this.ejectQuarter = function () {
console.log("You can't eject, you haven't inserted a quarter yet");
};
this.turnCrank = function () {
console.log("You turned, but there are no gumballs");
};
this.dispense = function () {
console.log("No gumball dispensed");
};
this.toString = function () {
return "sold out";
};
};

SoldOutState.prototype = new State();
SoldOutState.prototype.constructor = SoldOutState;

/* ConcreteState - SoldState */

var SoldState = function (gumballMachine) {
var gumballMachine = gumballMachine;
       
this.insertQuarter = function () {
console.log("Please wait, we're already giving you a gumball");
};
this.ejectQuarter = function () {
console.log("Sorry, you already turned the crank");
};
this.turnCrank = function () {
console.log("Turning twice doesn't get you another gumball!");
};
this.dispense = function () {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
console.log("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
};
this.toString = function () {
return "dispensing a gumball";
};
};

SoldState.prototype = new State();
SoldState.prototype.constructor = SoldState;

/* Context */

var GumballMachine = function (numberGumballs) {
var soldOutState = new SoldOutState(this),
noQuarterState = new NoQuarterState(this),
hasQuarterState = new HasQuarterState(this), 
soldState = new SoldState(this);
var state = soldOutState,
count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
this.insertQuarter = function () {
state.insertQuarter();
};
this.ejectQuarter = function () {
state.ejectQuarter();
};
this.turnCrank = function () {
state.turnCrank();
state.dispense();
};

this.setState = function (_state) {
state = _state;
};
this.releaseBall = function () {
console.log("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
};
this.getCount = function () {
return count;
};
this.refill = function (_count) {
count = _count;
state = noQuarterState;
};

    this.getState = function () {
        return state;
    };

    this.getSoldOutState = function () {
        return soldOutState;
    };

    this.getNoQuarterState = function () {
        return noQuarterState;
    };

    this.getHasQuarterState = function () {
        return hasQuarterState;
    };

    this.getSoldState = function () {
        return soldState;
    };
this.toString = function () {
var result = "";
result += "\nMighty Gumball, Inc.";
result += "\nJavascript-enabled Standing Gumball Model #2004";
result += "\nInventory: " + count + " gumball";
if (count != 1) {
result += "s";
}
result += "\n";
result += "Machine is " + state + "\n";
return result;
};
};

/* testing... */

var GumballMachineTestDrive = function () {
this.run = function () {
var gumballMachine = new GumballMachine(5);

console.log(gumballMachine.toString());

gumballMachine.insertQuarter();
gumballMachine.turnCrank();

console.log(gumballMachine.toString());

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

console.log(gumballMachine.toString());
};
};

var machine = new GumballMachineTestDrive();
machine.run();

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

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...
You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Javascript-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

Всем удачи в бою :)

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

Всем удачи!