Шаблонный метод (Template method) - определяет "скелет" алгоритма в методе, оставляя определение реализации некоторых шагов субклассам. Субклассы могут переопределять некоторые части алгоритма без изменения его структуры.
Данный шаблон относится к паттернам поведения.
Ниже приведена общая схема данного шаблона.
Рассмотрим реализацию данного паттерна на примере приготовления таких напитков как кофе или чай. С одной стороны напитки разные, но алгоритм приготовления схож. Так как в javascript отсутствует возможность использовать абстракции (имеется в виду - синтаксические), то в общем случае реализация может выглядеть следующим образом.
/* AbstractClass */
var CaffeineBeverage = function () {
this.prepareRecipe = function () {
boilWater();
this.brew();
pourInCup();
this.addCondiments();
};
// abstract brew();
// this.brew = function () {};
// abstract addCondiments();
// this.addCondiments = function () {};
// final boilWater();
var boilWater = function () {
console.log("Boiling water");
};
// final pourInCup();
var pourInCup = function () {
console.log("Pouring into cup");
};
};
/* ConcreteClass - Tea */
var Tea = function () {
this.brew = function () {
console.log("Steeping the tee");
};
this.addCondiments = function () {
console.log("Adding Lemon");
};
};
Tea.prototype = new CaffeineBeverage();
Tea.prototype.constructor = Tea;
/* ConcreteClass - Coffee */
var Coffee = function () {
this.brew = function () {
console.log("Dripping Coffee through filter");
};
this.addCondiments = function () {
console.log("Adding Sugar and Milk");
};
};
Coffee.prototype = new CaffeineBeverage();
Coffee.prototype.constructor = Coffee;
/* test application */
var Application = function () {
this.run = function () {
var tea = new Tea();
tea.prepareRecipe();
var coffee = new Coffee();
coffee.prepareRecipe();
};
};
var application = new Application();
application.run();
На выходе получаем:
Boiling water
Steeping the tee
Pouring into cup
Adding Lemon
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Sugar and Milk
Также можно реализовать альтернативным способом, с помощью объектов литералов.
var CaffeineBeverage = (function () {
// final boilWater()
var boilWater = function () {
console.log("Boiling water");
};
// final pourInCup()
var pourInCup = function () {
console.log("Pouring into cup");
};
return {
prepareRecipe: function () {
boilWater();
this.brew();
pourInCup();
this.addCondiments();
}
};
})();
var Tea = function () {
this.brew = function () {
console.log("Steeping the tee");
};
this.addCondiments = function () {
console.log("Adding Lemon");
};
};
Tea.prototype = CaffeineBeverage;
Tea.prototype.constructor = Tea;
var Coffee = function () {
this.brew = function () {
console.log("Dripping Coffee through filter");
};
this.addCondiments = function () {
console.log("Adding Sugar and Milk");
};
};
Coffee.prototype = CaffeineBeverage;
Coffee.prototype.constructor = Coffee;
var Application = function () {
this.run = function () {
var tea = new Tea();
tea.prepareRecipe();
var coffee = new Coffee();
coffee.prepareRecipe();
};
};
var application = new Application();
application.run();
Но все же первый способ более привлекателен в силу того, что "классы" также объявляются с помощью функций-конструкторов. Данный паттерн подразумевает использование абстрактного класса и абстрактных методов. Javascript настолько динамичен, что дает возможность симулировать данные аспекты. С абстрактными методами немного проще, их отсутствие можно расценивать своего рода как их объявление (идиома), так как для дальнейшей работоспособности алгоритма в клиенте обязательно наличие реализации таких методов, иначе мы получим ошибку во время интерпретации приложения. Как же быть с абстрактными классами? Здесь можно воспользоваться использованием так называемых примесей (mixin), причем в роли таких примесей могут выступать "абстрактные классы". Рассмотрим данную технику на рассматриваемом паттерне.
Мы знаем, что абстрактные классы запрещено инстанцировать, тогда можно написать следующую форму "абстрактного класса":
var Shape = function () {
if (this instanceof Shape || this === window) {
return new Error("Can not instance object from abstract class");
}
};
Как видим, данный класс невозможно инстанцировать, далее для того, чтобы реализовывать данный класс, можно внедрить в прототип наследника данную примесь:
var Square = function () {};
Shape.call(Square.prototype);
Вот пример реализации паттерна с помощью данной техники.
/* AbstractClass as Mixin class */
var AbstractClass = function () {
if (this instanceof AbstractClass || this === window) {
throw new Error("");
}
this.templateMethod = function () {
this.primitiveOperation1();
primitiveOperation2();
};
var primitiveOperation2 = function () {
console.log("primitiveOperation2");
};
};
var ConcreteClass = function () {
this.primitiveOperation1 = function () {
console.log("primitiveOperation1");
};
};
AbstractClass.call(ConcreteClass.prototype);
var concreteTest = new ConcreteClass();
concreteTest.templateMethod();
На выходе получаем
primitiveOperation1
primitiveOperation2
Продолжим дальше рассматривать Шаблонный метод. Стоит также рассмотреть использование так называемых "перехватчиков" (hook). Это методы, объявленные в нашем абстрактном классе, но имеющие пустую реализацию или реализацию по умолчанию. Он дает возможность
субклассу "подключаться" к алгоритму в разных точках. Впрочем, субкласс также может проигнорировать имеющийся перехватчик. Рассмотрим пример вышеприведенной реализации на основе использования одного из "методов-перехватчиков".
var CaffeineBeverage = function () {
this.prepareRecipe = function () {
boilWater();
this.brew();
pourInCup();
if (this.customerWantsCondiments()) {
this.addCondiments();
}
};
// abstract brew();
// this.brew = function () {};
// abstract addCondiments();
// this.addCondiments = function () {};
// final boilWater();
var boilWater = function () {
console.log("Boiling water");
};
// final pourInCup();
var pourInCup = function () {
console.log("Pouring into cup");
};
// hook
this.customerWantsCondiments = function () {
return true;
};
};
var Tea = function () {
this.brew = function () {
console.log("Steeping the tee");
};
this.addCondiments = function () {
console.log("Adding Lemon");
};
this.customerWantsCondiments = function () {
return getUserInput();
};
var getUserInput = function () {
var msg = "Would you like limon with your tea?";
return confirm(msg);
};
};
Tea.prototype = new CaffeineBeverage();
Tea.prototype.constructor = Tea;
var Coffee = function () {
this.brew = function () {
console.log("Dripping Coffee through filter");
};
this.addCondiments = function () {
console.log("Adding Sugar and Milk");
};
this.customerWantsCondiments = function () {
return getUserInput();
};
var getUserInput = function () {
var msg = "Would you like milk and sugar with your coffee?";
return confirm(msg);
};
};
Coffee.prototype = new CaffeineBeverage();
Coffee.prototype.constructor = Coffee;
var Application = function () {
this.run = function () {
var tea = new Tea();
tea.prepareRecipe();
var coffee = new Coffee();
coffee.prepareRecipe();
};
};
var application = new Application();
application.run();
Как видим, мы используем данный перехватчик для уточнения у клиента о необходимости добавления одного из компонентов в напиток. Ниже приведен еще один пример использования сортировки на основе шаблонного метода.
/* Abstract Class - Sorter */
var Sorter = function () {
// templateMethod()
this.sort = function (array) {
array.sort(this.compareTo);
};
this.compareTo = function () {};
};
/* Concrete Class - NumberSorter */
var NumberSorter = function () {
this.compareTo = function (a, b) {
return a - b;
};
};
NumberSorter.prototype = new Sorter();
NumberSorter.prototype.constructor = NumberSorter;
/* Concrete Class - NumberStringSorter */
var NumberStringSorter = function () {
this.compareTo = function (a, b) {
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1: 1;
}
return typeof a < typeof b ? -1 : 1;
};
};
NumberStringSorter.prototype = new Sorter();
NumberStringSorter.prototype.constructor = NumberStringSorter;
/* Concrete Class - ObjectSorter */
var ObjectSorter = function (name) {
this.compareTo = (function (key) {
return function (o, p) {
var a, b;
if (typeof o === 'object' && typeof p === 'object' && o && p) {
a = o[key];
b = p[key];
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
}
else {
throw {
name: 'Error',
message: 'Expected an object when sorting by ' + key
};
}
};
})(name);
};
ObjectSorter.prototype = new Sorter();
ObjectSorter.prototype.constructor = ObjectSorter;
/* test application */
var Application = function () {
this.run = function () {
var numberSorter = new NumberSorter();
var objectSorterById = new ObjectSorter("id");
var numbers = [5, 2, 8, 3, -1, 0];
var objects = [{id:5}, {id:2}, {id:8}, {id:3}, {id:-1}, {id:0}];
console.log("Before sort:");
console.log(numbers);
console.log(objects);
numberSorter.sort(numbers);
objectSorterById.sort(objects);
console.log("After sort:");
console.log(numbers);
console.log(objects);
};
};
var application = new Application();
application.run();
На выходе получаем:
Before sort:
[5, 2, 8, 3, -1, 0]
[Object {id=5}, Object {id=2}, Object {id=8}, Object {id=3}, Object {id=-1}, Object {id=0}]
After sort:
[-1, 0, 2, 3, 5, 8]
[Object {id=-1}, Object {id=0}, Object {id=2}, Object {id=3}, Object {id=5}, Object {id=8}]
Под конец хотелось бы отметить, что данный паттерн и паттерн Стратегия имеют схожую цель - инкапсуляция алгоритма, причем придавая гибкость поддержки. Но все же структурно они различаются. Паттерн Стратегия - использует композицию, а Шаблонный метод - наследование; паттерн Стратегия - имеет дело с семейством алгоритмов, а Шаблонный метод - конкретно с алгоритмов, отличающимся только в определенных шагах его исполнения. Для сравнения, ниже приведен пример реализации сортировки но только с использованием паттерна Стратегия. Все вида сортировок - восходящие, если нужна поддержка и насходящей сортировки, можете попробовать реализовать ее.
/* interface SortBehavior - abstract Strategy of SortBehavior */
var SortBehavior = function () {
this.sort = function () {};
};
/* NumberSort - concrete Strategy of SortBehavior */
var NumberSort = function () {
var compareTo = function (a, b) {
return a - b;
};
this.sort = function (array) {
array.sort(compareTo);
};
};
NumberSort.prototype = new SortBehavior();
NumberSort.prototype.constructor = NumberSort;
/* NumberStringSort - concrete Strategy of SortBehavior */
var NumberStringSort = function () {
var compareTo = function (a, b) {
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1: 1;
}
return typeof a < typeof b ? -1 : 1;
};
this.sort = function (array) {
array.sort(compareTo);
};
};
NumberStringSort.prototype = new SortBehavior();
NumberStringSort.prototype.constructor = NumberStringSort;
/* ObjectSort - concrete Strategy of SortBehavior */
var ObjectSort = function (name) {
var compareTo = (function (key) {
return function (o, p) {
var a, b;
if (typeof o === 'object' && typeof p === 'object' && o && p) {
a = o[key];
b = p[key];
if (a === b) {
return 0;
}
if (typeof a === typeof b) {
return a < b ? -1 : 1;
}
return typeof a < typeof b ? -1 : 1;
}
else {
throw {
name: 'Error',
message: 'Expected an object when sorting by ' + key
};
}
};
})(name);
this.sort = function (array) {
array.sort(compareTo);
};
};
ObjectSort.prototype = new SortBehavior();
ObjectSort.prototype.constructor = ObjectSort;
/* Abstract Client of abstract SortBehavior Strategy */
var Sorter = function () {
var sortBehavior;
this.setSortBehavior = function (sb) {
sortBehavior = sb;
};
this.performSort = function (array) {
sortBehavior.sort(array);
};
};
/* Concrete Client of FlyWithWings Strategy */
var NumberSorter = function () {
this.setSortBehavior(new NumberSort());
};
NumberSorter.prototype = new Sorter();
NumberSorter.prototype.constructor = NumberSorter;
/* Concrete Client of FlyWithWings Strategy */
var NumberStringSorter = function () {
this.setSortBehavior(new NumberStringSort());
};
NumberStringSorter.prototype = new Sorter();
NumberStringSorter.prototype.constructor = NumberStringSorter;
/* Concrete Client of FlyWithWings Strategy */
var ObjectSorter = function (name) {
this.setSortBehavior(new ObjectSort(name));
};
ObjectSorter.prototype = new Sorter();
ObjectSorter.prototype.constructor = ObjectSorter;
/* test application */
var Application = function () {
this.run = function () {
var numberSorter = new NumberSorter();
var objectSorterById = new ObjectSorter("id");
var numbers = [5, 2, 8, 3, -1, 0];
var objects = [{id:5}, {id:2}, {id:8}, {id:3}, {id:-1}, {id:0}];
console.log("Before sort:");
console.log(numbers);
console.log(objects);
numberSorter.performSort(numbers);
objectSorterById.performSort(objects);
console.log("After sort:");
console.log(numbers);
console.log(objects);
};
};
var application = new Application();
application.run();
На выходе
Before sort:
[5, 2, 8, 3, -1, 0]
[Object {id=5}, Object {id=2}, Object {id=8}, Object {id=3}, Object {id=-1}, Object {id=0}]
After sort:
[-1, 0, 2, 3, 5, 8]
[Object {id=-1}, Object {id=0}, Object {id=2}, Object {id=3}, Object {id=5}, Object {id=8}]
Всем успехов!
Javascript. Strategy pattern
Как известно, такой паттерн проектирования как Стратегия (Strategy) определяет семейство алгоритмов, инкапсулирует каждый из них и обеспечивает их взаимозаменяемость. Он позволяет модифицировать алгоритмы независимо от их использования на стороне клиента. Данный паттерн относится к паттернам поведения. Пример использования паттерна взят из книги - Э.Фримена, К.Сьерра, Б.Бейтс - Паттерны проектирования - с адаптацией под javascript. С идентичной реализацией на php можно ознакомиться по ссылке - PHP5. Композиция на примере паттерна Стратегия.
/* interface FlyBehavior - abstract Strategy of FlyBehavior */
var FlyBehavior = function () {
this.fly = function () {};
};
/* FlyWithWings - concrete Strategy of FlyBehavior */
var FlyWithWings = function () {
this.fly = function () {
console.log("I`m flying");
};
};
FlyWithWings.prototype = new FlyBehavior();
FlyWithWings.prototype.constructor = FlyWithWings;
/* FlyNoWay - concrete Strategy of FlyBehavior */
var FlyNoWay = function () {
this.fly = function () {
console.log("I can`t fly");
}
};
FlyNoWay.prototype = new FlyBehavior();
FlyNoWay.prototype.constructor = FlyNoWay;
/* FlyRocketPowered - concrete Strategy of FlyBehavior */
var FlyRocketPowered = function () {
this.fly = function () {
console.log("I`m flying with a rocket");
};
};
FlyRocketPowered.prototype = new FlyBehavior();
FlyRocketPowered.prototype.constructor = FlyRocketPowered;
/* interface QuackBehavior - abstract Strategy of QuackBehavior */
var QuackBehavior = function () {
this.quack = function () {};
};
/* Quack - concrete Strategy of QuackBehavior */
var Quack = function () {
this.quack = function () {
console.log("Quack");
}
};
Quack.prototype = new QuackBehavior();
Quack.prototype.constructor = Quack;
/* Squeak - concrete Strategy of QuackBehavior */
var Squeak = function () {
this.quack = function () {
console.log("Squeak");
}
};
Squeak.prototype = new QuackBehavior();
Squeak.prototype.constructor = Squeak;
/* MuteQuack - concrete Strategy of QuackBehavior */
var MuteQuack = function () {
this.quack = function () {
console.log("<< Silence >>");
}
};
MuteQuack.prototype = new QuackBehavior();
MuteQuack.prototype.constructor = MuteQuack;
/* Abstract Client of abstract FlyBehavior Strategy and abstract QuackBehavior Strategy */
var Duck = function () {
var flyBehavior;
var quackBehavior;
// abstract method
this.display = function () {};
this.setFlyBehavior = function (fb) {
flyBehavior = fb;
};
this.setQuackBehavior = function (qb) {
quackBehavior = qb;
};
this.performFly = function () {
flyBehavior.fly();
};
this.performQuack = function () {
quackBehavior.quack();
};
this.swim = function () {
console.log("All ducks float, even decoys!");
};
};
/* Concrete Client of FlyWithWings Strategy and Quack Strategy */
var MallardDuck = function () {
this.setFlyBehavior(new FlyWithWings());
this.setQuackBehavior(new Quack());
this.display = function () {
console.log("I`m a real Mallard duck");
};
};
MallardDuck.prototype = new Duck();
MallardDuck.prototype.constructor = MallardDuck;
/* Concrete Client of FlyNoWay Strategy and Quack Strategy */
var ModelDuck = function () {
this.setFlyBehavior(new FlyNoWay());
this.setQuackBehavior(new Quack());
this.display = function () {
console.log("I`m a model duck");
};
};
ModelDuck.prototype = new Duck();
ModelDuck.prototype.constructor = ModelDuck;
/* test application */
var Application = function () {
this.run = function () {
var mallard = new MallardDuck();
mallard.performQuack();
mallard.performFly();
var model = new ModelDuck();
model.performQuack();
model.performFly();
// dynamic set new Concrete Strategy FlyRocketPowered
model.setFlyBehavior(new FlyRocketPowered());
model.performFly();
};
};
var application = new Application();
application.run();
На выходе получаем следующие сообщения клиентов
Quack
I`m flying
Quack
I can`t fly
I`m flying with a rocket
Ниже представлена схема реализации паттерна Стратегия (Strategy)
Javascript. Memento pattern
Memento (хранитель) - поведенческий шаблон проектирования, позволяющий, не нарушая инкапсуляцию, зафиксировать и сохранить внутреннее состояния объекта так, чтобы позднее восстановить его в это состояние.
На выходе приложения получаем следующие сообщения:
Клиент запрашивает Memento (хранителя) у исходного объекта, когда ему необходимо сохранить состояние исходного объекта (установить контрольную точку). Исходный объект инициализирует Memento своим текущим состоянием. Клиент является "посыльным" за Memento, но только исходный объект может сохранять и извлекать информацию из Memento (Memento является "непрозрачным" для клиентов и других объектов). Если клиенту в дальнейшем нужно "откатить" состояние исходного объекта, он передает Memento обратно в исходный объект для его восстановления.
Паттерн проектирования Memento определяет трех различных участников:
Originator (хозяин) - объект, умеющий создавать хранителя, а также знающий, как восстановить свое внутреннее состояние из хранителя.
Caretaker (смотритель) - объект, который знает, почему и когда хозяин должен сохранять и восстанавливать себя.
Memento (хранитель) - "ящик на замке", который пишется и читается хозяином и за которым присматривает смотритель.
Паттерн проектирования Memento определяет трех различных участников:
Originator (хозяин) - объект, умеющий создавать хранителя, а также знающий, как восстановить свое внутреннее состояние из хранителя.
Caretaker (смотритель) - объект, который знает, почему и когда хозяин должен сохранять и восстанавливать себя.
Memento (хранитель) - "ящик на замке", который пишется и читается хозяином и за которым присматривает смотритель.
Ниже представлен пример реализации данного паттерна.
// "Хранитель"
var Memento = function (state) {
var state = state || 'undefined';
this.getState = function () {
return state;
};
};
// "Опекун"
var Caretaker = function () {
var memento;
this.getMemento = function () {
return memento;
};
this.setMemento = function (_memento) {
if (_memento instanceof Memento) {
memento = _memento;
}
};
};
// "Создатель"
var Originator = function () {
var state;
this.setState = function (_state) {
state = _state || "undefined";
};
this.getState = function () {
return state;
};
this.saveState = function () {
return new Memento(state);
};
this.restoreState = function (memento) {
if (memento instanceof Memento) {
state = memento.getState();
}
};
};
var Application = function () {
this.run = function () {
var originator = new Originator();
var caretaker = new Caretaker();
originator.setState('on');
console.log('State is ' + originator.getState());
caretaker.setMemento(originator.saveState());
originator.setState('off');
console.log('State is ' + originator.getState());
originator.restoreState(caretaker.getMemento());
console.log('State is ' + originator.getState());
};
};
var application = new Application();
application.run();
State is on
State is off
State is on
State is off
State is on
Javascript. Strict mode
Как известно, в спецификации ES5 определены так называемые Strict Mode правила, которые включают в себя ограничения, учитываемые во время «компиляции» и интерпретации (выполнения скрипта). Причем правила эти декларативного характера, то есть должны соблюдаться разработчиками, но не всегда. В настоящее время имеется возможность включать режим Strict Mode непосредственно в скрипт, тем самым оговаривая необходимость жесткого соблюдения правил. Для включения данного режима достаточно прописать
"use strict";
в заголовке кода и режим включен. Стоит также отметить, что данный режим в полной мере поддерживается не всеми браузерами (есть тесты, в которых можно оценить степень поддержки режима в своем рабочем окружении).
Javascript. Composite pattern
Компоновщик (англ. Composite pattern) — структурный шаблон проектирования, относится к структурным паттернам, объединяет объекты в древовидную структуру для представления иерархии от частного к целому. Компоновщик позволяет клиентам обращаться к отдельным объектам и к группам объектов одинаково
var _hasProp = {}.hasOwnProperty,
_extends = function(child, parent) {
for (var key in parent) {
if (_hasProp.call(parent, key)) {
child[key] = parent[key];
}
}
function ctor() {
this.constructor = child;
}
ctor.prototype = parent.prototype;
child.prototype = new ctor();
child.__super__ = parent.prototype;
return child;
};
// Component
var Component = (function () {
function Component(name) {
this.name = name || 'undefined';
}
Component.prototype.add = function () {};
Component.prototype.remove = function () {};
Component.prototype.display = function () {};
return Component;
})();
// Composite
var Composite = (function (_super) {
_extends(Composite, _super);
function Composite() {
this.children = [];
}
Composite.prototype.add = function (component) {
if (component instanceof Component) {
this.children.push(component);
}
};
Composite.prototype.remove = function (component) {
if (component instanceof Component) {
var index = this.children.indexOf(component);
if (~index) {
this.children.splice(index, 1);
}
}
};
Composite.prototype.display = function () {
console.log(this.children);
};
return Composite;
})(Component);
// Leaf
var Leaf = (function (_super) {
_extends(Leaf, _super);
function Leaf() {
return Leaf.__super__.constructor.apply(this, arguments);
}
Leaf.prototype.display = function() {
console.log("Cannot add to a leaf");
};
Leaf.prototype.display = function() {
console.log("Cannot remove from a leaf");
};
Leaf.prototype.display = function() {
console.log(this.name);
};
return Leaf;
})(Component);
var root = new Composite('root');
root.add(new Leaf('Leaf A'));
root.add(new Leaf('Leaf B'));
var comp = new Composite('Composite X');
comp.add(new Leaf('Leaf XA'));
comp.add(new Leaf('Leaf XB'));
comp.add(new Leaf('Leaf XC'));
comp.add(new Leaf('Leaf XD'));
root.add(comp);
root.add(new Leaf('Leaf C'));
var leaf = new Leaf('Leaf D');
root.add(leaf);
root.remove(comp);
root.remove(leaf);
root.display();
Вот еще один пример реализации данного паттерна. Стоит только оговориться: паттерн Компоновщик ниже реализован с использованием паттерна Итератор (см. здесь).
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 Leaf */
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 Composite */
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
Вот еще один пример реализации данного паттерна. Стоит только оговориться: паттерн Компоновщик ниже реализован с использованием паттерна Итератор (см. здесь).
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 Leaf */
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 Composite */
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
Subscribe to:
Posts
(
Atom
)




.svg.png)