Паттерн Посетитель (Visitor pattern) определяет операцию, выполняемую на каждом элементе некоторой структуры. Позволяет, не изменяя классы этих объектов, добавлять в них новые операции. Как известно, различные и несвязные операции выполняются над узловыми
объектами некоторой гетерогенной совокупной структуры. Чтобы избежать "загрязнения" классов этих узлов такими операциями (то есть избежать добавления соответствующих методов в эти классы), можно воспользоваться паттерном Посетитель. Также в реализации этого паттерна играют роль типы объектов, что позволяет делать типизированный выбор действий. Так как мы имеем дело с Javascript, то здесь это делается немного иначе: через условные конструкции. Данный паттерн относится к паттернам поведения. Ниже редставлена диаграмма классов реализации данного паттерна.
Также ниже представлен пример реализации данного паттерна.
/* internal iterator */
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;
}
}
};
};
/* interface Element */
var ComputerPart = function () {
this.accept = function (computerPartVisitor) {
throw new Error("accept method is not implemented");
};
};
/* ConcreteElement */
var Keyboard = function () {
this.accept = function (computerPartVisitor) {
computerPartVisitor.visit(this);
};
};
Keyboard.prototype = new ComputerPart();
Keyboard.prototype.constructor = Keyboard;
/* ConcreteElement */
var Monitor = function () {
this.accept = function (computerPartVisitor) {
computerPartVisitor.visit(this);
};
};
Monitor.prototype = new ComputerPart();
Monitor.prototype.constructor = Monitor;
/* ConcreteElement */
var Mouse = function () {
this.accept = function (computerPartVisitor) {
computerPartVisitor.visit(this);
};
};
Mouse.prototype = new ComputerPart();
Mouse.prototype.constructor = Mouse;
/* ConcreteElement */
var Computer = function () {
var parts = [new Mouse(), new Keyboard(), new Monitor()];
this.accept = function (computerPartVisitor) {
for (var it = parts.iterator(); it.hasNext();) {
(it.next()).accept(computerPartVisitor);
}
computerPartVisitor.visit(this);
};
};
Computer.prototype = new ComputerPart();
Computer.prototype.constructor = Computer;
/* interface Visitor */
var computerPartVisitor = function () {
this.visit = function (computerPart) {
throw new Error("visit method is not implemented");
};
};
/* ConcreteVisitor */
var computerPartDisplayVisitor = function () {
this.visit = function (computerPart) {
if (computerPart.constructor === Computer) {
console.log("Displaying Computer.");
}
if (computerPart.constructor === Mouse) {
console.log("Displaying Mouse.");
}
if (computerPart.constructor === Keyboard) {
console.log("Displaying Keyboard.");
}
if (computerPart.constructor === Monitor) {
console.log("Displaying Monitor.");
}
};
};
computerPartDisplayVisitor.prototype = new computerPartVisitor();
computerPartDisplayVisitor.constructor = computerPartDisplayVisitor;
/* testing... */
var Application = function () {
this.run = function () {
var computer = new Computer();
computer.accept(new computerPartDisplayVisitor());
};
};
var app = new Application();
app.run();
В итоге получаем:
Displaying Mouse.
Displaying Keyboard.
Displaying Monitor.
Displaying Computer.
Всем успехов.
Javascript. Proxy pattern
Паттерн Заместитель (Proxy pattern) предоставляет суррогатный объект, управляющий доступом к другому объекту. Выделяют несколько вариантов управления доступом в заместителях:
- удаленный заместитель управляет доступом к удаленному объекту;
- виртуальный заместитель управляет доступом к ресурсу, создание которого требует больших затрат ресурсов;
- защитный заместитель контролирует доступ к ресурсу в соответствии с системой привилегий.
Данный паттерн относится к структурным паттернам. Основная реализация основана на отложенной инициализации объекта. Данный паттерн полезно использовать там, где приходится работать с дорогими ресурсами, например, пусть создание реального объекта или его использование занимает много ресурсов, тогда прокси-объект может взять на себя часть обязанностей данного объекта с целью избегания лишних обращений к нему, например, прокси может иметь кэш, который можно отдавать клиентам на их запросы, вместо повторного обращения к ресурсам. Вот примерчик сказанного.
/* real object */
var http = {
makeRequest: function (handler) {
$.ajax({
url: 'javascript.js.php',
async: false, // синхронный режим
success: handler
});
}
};
/* proxy object */
var proxy = {
cache: null,
makeRequest: function (handler) {
console.log(this.cache);
if (this.cache != null) {
handler(this.cache);
}
else {
http.makeRequest(function (data) {
proxy.cache = data;
handler(data);
});
}
}
};
var handler = function (data) {
// some actions
};
// первоначальное обращение к реальному объекту http
proxy.makeRequest(handler);
// выдача кэша
proxy.makeRequest(handler);
Ниже приведена диаграмма классов данного паттерна
Стоит отметить, что на Javascript реализовать удаленного заместителя в силу ее природы невозможно, хотя есть идеи, высказываемые некоторыми разработчиками, например см. книгу - Pro JavaScript Design Pattens. Ross Harmes. Dustan Diaz - о работе через ajax с удаленными веб сервисами или объектами. Но это только некоторого рода приближение. Также затруднительно реализовать защитного заместителя, в силу слабой типизации языка. Хотя на мой взгляд, все зависит от смысла вкладываемого к слово защитный :) Ниже приведу еще один пример реализации данного паттерна.
/* interface Subject - Image */
var Image = function () {
this.display = function () {};
};
/* RealSubject - RealImage */
var RealImage = function (fileName) {
var fileName = fileName;
this.display = function () {
console.log("Displaying " + fileName);
};
this.loadFromDisk = function () {
console.log("Loading " + fileName);
};
this.loadFromDisk(fileName);
};
RealImage.prototype = new Image;
RealImage.prototype.constructor = RealImage;
/* Proxy - ProxyImage */
var ProxyImage = function (fileName) {
var fileName = fileName,
realImage = null;
this.display = function () {
if (realImage === null) {
realImage = new RealImage(fileName);
}
realImage.display();
};
};
ProxyImage.prototype = new Image;
ProxyImage.prototype.constructor = ProxyImage;
/* testing... */
var Application = function () {
this.run = function () {
var image = new ProxyImage("test_10mb.jpg");
//image will be loaded from disk
image.display();
//image will not be loaded from disk
image.display();
};
};
var app = new Application();
app.run();
На выходе получаем следующий лог
Loading test_10mb.jpg
Displaying test_10mb.jpg
Displaying test_10mb.jpg
- удаленный заместитель управляет доступом к удаленному объекту;
- виртуальный заместитель управляет доступом к ресурсу, создание которого требует больших затрат ресурсов;
- защитный заместитель контролирует доступ к ресурсу в соответствии с системой привилегий.
Данный паттерн относится к структурным паттернам. Основная реализация основана на отложенной инициализации объекта. Данный паттерн полезно использовать там, где приходится работать с дорогими ресурсами, например, пусть создание реального объекта или его использование занимает много ресурсов, тогда прокси-объект может взять на себя часть обязанностей данного объекта с целью избегания лишних обращений к нему, например, прокси может иметь кэш, который можно отдавать клиентам на их запросы, вместо повторного обращения к ресурсам. Вот примерчик сказанного.
/* real object */
var http = {
makeRequest: function (handler) {
$.ajax({
url: 'javascript.js.php',
async: false, // синхронный режим
success: handler
});
}
};
/* proxy object */
var proxy = {
cache: null,
makeRequest: function (handler) {
console.log(this.cache);
if (this.cache != null) {
handler(this.cache);
}
else {
http.makeRequest(function (data) {
proxy.cache = data;
handler(data);
});
}
}
};
var handler = function (data) {
// some actions
};
// первоначальное обращение к реальному объекту http
proxy.makeRequest(handler);
// выдача кэша
proxy.makeRequest(handler);
Ниже приведена диаграмма классов данного паттерна
Стоит отметить, что на Javascript реализовать удаленного заместителя в силу ее природы невозможно, хотя есть идеи, высказываемые некоторыми разработчиками, например см. книгу - Pro JavaScript Design Pattens. Ross Harmes. Dustan Diaz - о работе через ajax с удаленными веб сервисами или объектами. Но это только некоторого рода приближение. Также затруднительно реализовать защитного заместителя, в силу слабой типизации языка. Хотя на мой взгляд, все зависит от смысла вкладываемого к слово защитный :) Ниже приведу еще один пример реализации данного паттерна.
/* interface Subject - Image */
var Image = function () {
this.display = function () {};
};
/* RealSubject - RealImage */
var RealImage = function (fileName) {
var fileName = fileName;
this.display = function () {
console.log("Displaying " + fileName);
};
this.loadFromDisk = function () {
console.log("Loading " + fileName);
};
this.loadFromDisk(fileName);
};
RealImage.prototype = new Image;
RealImage.prototype.constructor = RealImage;
/* Proxy - ProxyImage */
var ProxyImage = function (fileName) {
var fileName = fileName,
realImage = null;
this.display = function () {
if (realImage === null) {
realImage = new RealImage(fileName);
}
realImage.display();
};
};
ProxyImage.prototype = new Image;
ProxyImage.prototype.constructor = ProxyImage;
/* testing... */
var Application = function () {
this.run = function () {
var image = new ProxyImage("test_10mb.jpg");
//image will be loaded from disk
image.display();
//image will not be loaded from disk
image.display();
};
};
var app = new Application();
app.run();
На выходе получаем следующий лог
Loading test_10mb.jpg
Displaying test_10mb.jpg
Displaying test_10mb.jpg
Javascript. Mediator pattern
Паттерн Посредник (Mediator pattern) используется для централизации сложных взаимодействий и управляющих операций между объектами. Использование данного паттерна позволяет снизить количество зависимостей между объектами в системе (так называемые - коллеги), а взаимодействие между ними осуществляет непосредственно специальный объект (посредник или медиатор). Это улучшает сопровождение кода, так как мы всегда можем реализовать посредник с требуемыми критериями. Данный паттерн относится к поведенческим паттернам. В самом простом виде диаграмма классов данного паттерна выглядит следующим образом.
Заметим, что коллеги знают только о посреднике, в котором инкапсулирована логика взаимодействия. При возрастании объектов в системе данная логика может усложниться, что является одним из минусов паттерна. Рассмотрим ряд пример реализации данного паттерна. Пример первый. В системе имеются пользователи, необходимо наладить между ними общение, например в виде чата. Одна из реализаций.
/* Colleagues */
var User = function (mediator, name) {
this.mediator = mediator;
this.name = name;
};
User.prototype.send = function (msg) {
console.log(this.name + ": sending message: " + msg);
this.mediator.sendMessage(msg, this);
};
User.prototype.receive = function (msg) {
console.log(this.name + ": received message: " + msg);
};
/* ConcreteMediator */
var chatMediator = {
users: [],
addUser: function (user) {
this.users.push(user);
},
sendMessage: function (msg, user) {
for (var i = 0; i < this.users.length; i++) {
if (this.users[i] !== user) {
this.users[i].receive(msg);
}
}
}
};
var Adam = new User(chatMediator, "Adam");
var Poll = new User(chatMediator, "Poll");
var Jon = new User(chatMediator, "Jon");
var Allan = new User(chatMediator, "Allan");
chatMediator.addUser(Adam);
chatMediator.addUser(Poll);
chatMediator.addUser(Jon);
chatMediator.addUser(Allan);
Adam.send("Hi All");
На выходе:
Adam: sending message: Hi All
Poll: received message: Hi All
Jon: received message: Hi All
Allan: received message: Hi All
Пример второй. Ниже будет представлена реализация простой игры - два игрока в течении 30 секунд должны нажимать свою кнопку (1 или 2), по окончании времени побеждает тот, кто успел нажать как можно больше раз. В реализации игры будут участвовать объекты-коллеги и посредник, которые представлены на следующем рисунке.
/* Colleagues */
var Player = function (name) {
this.points = 0;
this.name = name;
};
Player.prototype.play = function () {
this.points += 1;
mediator.played();
};
/* ConcreteColleague */
var scoreboard = {
// element of HTML, which should be updated
element: document.getElementById('results'),
// updates the score on the screen
update: function (score) {
var i, msg = '';
for (i in score) {
if (score.hasOwnProperty(i)) {
msg += '<p><strong>' + i + '<\/strong>';
msg += score[i];
msg += '<\/p>';
}
}
this.element.innerHTML = msg;
}
};
/* ConcreteMediator */
var mediator = {
// all players
players: {},
// initialization
setup: function () {
var players = this.players;
players.home = new Player('Home');
players.guest = new Player('Guest');
},
// updates the score, if someone of the players made the move
played: function () {
var players = this.players,
score = {
Home: players.home.points,
Guest: players.guest.points
};
scoreboard.update(score);
},
// handler of user actions
keypress: function (e) {
e = e || window.event; // IE
if (e.which === 49) { // key "1"
mediator.players.home.play();
return;
}
if (e.which === 48) { // key "0"
mediator.players.guest.play();
return;
}
}
};
/* testing */
var Application = function () {
this.run = function () {
// Start!
mediator.setup();
window.onkeypress = mediator.keypress;
// The game ends after 30 seconds
setTimeout(function () {
window.onkeypress = null;
alert('Game over!');
}, 30000);
};
};
var game = new Application;
game.run();
Приятной Вам игры :)
Заметим, что коллеги знают только о посреднике, в котором инкапсулирована логика взаимодействия. При возрастании объектов в системе данная логика может усложниться, что является одним из минусов паттерна. Рассмотрим ряд пример реализации данного паттерна. Пример первый. В системе имеются пользователи, необходимо наладить между ними общение, например в виде чата. Одна из реализаций.
/* Colleagues */
var User = function (mediator, name) {
this.mediator = mediator;
this.name = name;
};
User.prototype.send = function (msg) {
console.log(this.name + ": sending message: " + msg);
this.mediator.sendMessage(msg, this);
};
User.prototype.receive = function (msg) {
console.log(this.name + ": received message: " + msg);
};
/* ConcreteMediator */
var chatMediator = {
users: [],
addUser: function (user) {
this.users.push(user);
},
sendMessage: function (msg, user) {
for (var i = 0; i < this.users.length; i++) {
if (this.users[i] !== user) {
this.users[i].receive(msg);
}
}
}
};
var Adam = new User(chatMediator, "Adam");
var Poll = new User(chatMediator, "Poll");
var Jon = new User(chatMediator, "Jon");
var Allan = new User(chatMediator, "Allan");
chatMediator.addUser(Adam);
chatMediator.addUser(Poll);
chatMediator.addUser(Jon);
chatMediator.addUser(Allan);
Adam.send("Hi All");
На выходе:
Adam: sending message: Hi All
Poll: received message: Hi All
Jon: received message: Hi All
Allan: received message: Hi All
Пример второй. Ниже будет представлена реализация простой игры - два игрока в течении 30 секунд должны нажимать свою кнопку (1 или 2), по окончании времени побеждает тот, кто успел нажать как можно больше раз. В реализации игры будут участвовать объекты-коллеги и посредник, которые представлены на следующем рисунке.
/* Colleagues */
var Player = function (name) {
this.points = 0;
this.name = name;
};
Player.prototype.play = function () {
this.points += 1;
mediator.played();
};
/* ConcreteColleague */
var scoreboard = {
// element of HTML, which should be updated
element: document.getElementById('results'),
// updates the score on the screen
update: function (score) {
var i, msg = '';
for (i in score) {
if (score.hasOwnProperty(i)) {
msg += '<p><strong>' + i + '<\/strong>';
msg += score[i];
msg += '<\/p>';
}
}
this.element.innerHTML = msg;
}
};
/* ConcreteMediator */
var mediator = {
// all players
players: {},
// initialization
setup: function () {
var players = this.players;
players.home = new Player('Home');
players.guest = new Player('Guest');
},
// updates the score, if someone of the players made the move
played: function () {
var players = this.players,
score = {
Home: players.home.points,
Guest: players.guest.points
};
scoreboard.update(score);
},
// handler of user actions
keypress: function (e) {
e = e || window.event; // IE
if (e.which === 49) { // key "1"
mediator.players.home.play();
return;
}
if (e.which === 48) { // key "0"
mediator.players.guest.play();
return;
}
}
};
/* testing */
var Application = function () {
this.run = function () {
// Start!
mediator.setup();
window.onkeypress = mediator.keypress;
// The game ends after 30 seconds
setTimeout(function () {
window.onkeypress = null;
alert('Game over!');
}, 30000);
};
};
var game = new Application;
game.run();
Приятной Вам игры :)
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
Всем удачи в бою :)
Subscribe to:
Posts
(
Atom
)