十種 JavaScript 設(shè)計(jì)模式
介紹
設(shè)計(jì)模式是針對(duì)常見軟件問題的高級(jí)面向?qū)ο蠼鉀Q方案。模式是關(guān)于對(duì)象的可重用設(shè)計(jì)和交互。在討論復(fù)雜的設(shè)計(jì)解決方案時(shí),每個(gè)模式都有一個(gè)名稱并成為詞匯表的一部分。
在本教程中,我為每個(gè) GoF 模式提供了 JavaScript 示例。大多數(shù)情況下,它們遵循原始圖案設(shè)計(jì)的結(jié)構(gòu)和意圖。這些示例演示了每種模式背后的原則,但并未針對(duì) JavaScript 進(jìn)行優(yōu)化。
01.Abstract Factory
Abstract Factory創(chuàng)建由共同主題相關(guān)的對(duì)象。在面向?qū)ο缶幊讨?,工廠是創(chuàng)建其他對(duì)象的對(duì)象。抽象工廠抽象出新創(chuàng)建的對(duì)象共享的主題。
function Employee(name) {
this.name = name;
this.say = function () {
console.log("I am employee " + name);
};
}
function EmployeeFactory() {
this.create = function (name) {
return new Employee(name);
};
}
function Vendor(name) {
this.name = name;
this.say = function () {
console.log("I am vendor " + name);
};
}
function VendorFactory() {
this.create = function (name) {
return new Vendor(name);
};
}
function run() {
var persons = [];
var employeeFactory = new EmployeeFactory();
var vendorFactory = new VendorFactory();
persons.push(employeeFactory.create("Joan DiSilva"));
persons.push(employeeFactory.create("Tim O'Neill"));
persons.push(vendorFactory.create("Gerald Watson"));
persons.push(vendorFactory.create("Nicole McNight"));
for (var i = 0, len = persons.length; i < len; i++) {
persons[i].say();
}
}
02.Builder
Builder 模式允許客戶端僅通過指定類型和內(nèi)容來構(gòu)建復(fù)雜對(duì)象,細(xì)節(jié)完全對(duì)客戶隱藏。
function Shop() {
this.construct = function (builder) {
builder.step1();
builder.step2();
return builder.get();
}
}
function CarBuilder() {
this.car = null;
this.step1 = function () {
this.car = new Car();
};
this.step2 = function () {
this.car.addParts();
};
this.get = function () {
return this.car;
};
}
function TruckBuilder() {
this.truck = null;
this.step1 = function () {
this.truck = new Truck();
};
this.step2 = function () {
this.truck.addParts();
};
this.get = function () {
return this.truck;
};
}
function Car() {
this.doors = 0;
this.addParts = function () {
this.doors = 4;
};
this.say = function () {
console.log("I am a " + this.doors + "-door car");
};
}
function Truck() {
this.doors = 0;
this.addParts = function () {
this.doors = 2;
};
this.say = function () {
console.log("I am a " + this.doors + "-door truck");
};
}
function run() {
var shop = new Shop();
var carBuilder = new CarBuilder();
var truckBuilder = new TruckBuilder();
var car = shop.construct(carBuilder);
var truck = shop.construct(truckBuilder);
car.say();
truck.say();
}
03、Factory Method
Factory Method 按照客戶的指示創(chuàng)建新對(duì)象。在 JavaScript 中創(chuàng)建對(duì)象的一種方法是使用 new 運(yùn)算符調(diào)用構(gòu)造函數(shù)。
然而,在某些情況下,客戶端不知道或不應(yīng)知道要實(shí)例化多個(gè)候選對(duì)象中的哪一個(gè)。
Factory Method 允許客戶端委托對(duì)象創(chuàng)建,同時(shí)仍然保留對(duì)要實(shí)例化的類型的控制。
var Factory = function () {
this.createEmployee = function (type) {
var employee;
if (type === "fulltime") {
employee = new FullTime();
} else if (type === "parttime") {
employee = new PartTime();
} else if (type === "temporary") {
employee = new Temporary();
} else if (type === "contractor") {
employee = new Contractor();
}
employee.type = type;
employee.say = function () {
console.log(this.type + ": rate " + this.hourly + "/hour");
}
return employee;
}
}
var FullTime = function () {
this.hourly = "$12";
};
var PartTime = function () {
this.hourly = "$11";
};
var Temporary = function () {
this.hourly = "$10";
};
var Contractor = function () {
this.hourly = "$15";
};
function run() {
var employees = [];
var factory = new Factory();
employees.push(factory.createEmployee("fulltime"));
employees.push(factory.createEmployee("parttime"));
employees.push(factory.createEmployee("temporary"));
employees.push(factory.createEmployee("contractor"));
for (var i = 0, len = employees.length; i < len; i++) {
employees[i].say();
}
}
04、Adapter
Adapter模式將一個(gè)接口(對(duì)象的屬性和方法)轉(zhuǎn)換為另一個(gè)接口。Adapter允許編程組件協(xié)同工作,否則由于接口不匹配而無法協(xié)同工作。適配器(Adapter)模式也稱為包裝器模式。
// old interface
function Shipping() {
this.request = function (zipStart, zipEnd, weight) {
// ...
return "$49.75";
}
}
// new interface
function AdvancedShipping() {
this.login = function (credentials) { /* ... */ };
this.setStart = function (start) { /* ... */ };
this.setDestination = function (destination) { /* ... */ };
this.calculate = function (weight) { return "$39.50"; };
}
// adapter interface
function ShippingAdapter(credentials) {
var shipping = new AdvancedShipping();
shipping.login(credentials);
return {
request: function (zipStart, zipEnd, weight) {
shipping.setStart(zipStart);
shipping.setDestination(zipEnd);
return shipping.calculate(weight);
}
};
}
function run() {
var shipping = new Shipping();
var credentials = { token: "30a8-6ee1" };
var adapter = new ShippingAdapter(credentials);
// original shipping object and interface
var cost = shipping.request("78701", "10010", "2 lbs");
console.log("Old cost: " + cost);
// new shipping object with adapted interface
cost = adapter.request("78701", "10010", "2 lbs");
console.log("New cost: " + cost);
}
05、Decorator
Decorator模式動(dòng)態(tài)地?cái)U(kuò)展(裝飾)對(duì)象的行為。在運(yùn)行時(shí)添加新行為的能力是由 Decorator 對(duì)象實(shí)現(xiàn)的,它“將自身包裝”在原始對(duì)象周圍。多個(gè)裝飾器可以向原始對(duì)象添加或覆蓋功能。
var User = function (name) {
this.name = name;
this.say = function () {
console.log("User: " + this.name);
};
}
var DecoratedUser = function (user, street, city) {
this.user = user;
this.name = user.name; // ensures interface stays the same
this.street = street;
this.city = city;
this.say = function () {
console.log("Decorated User: " + this.name + ", " +
this.street + ", " + this.city);
};
}
function run() {
var user = new User("Kelly");
user.say();
var decorated = new DecoratedUser(user, "Broadway", "New York");
decorated.say();
}
06、Facade
Facade 模式提供了一個(gè)接口,使客戶免受一個(gè)或多個(gè)子系統(tǒng)中復(fù)雜功能的影響。這是一個(gè)看似微不足道但功能強(qiáng)大且極其有用的簡(jiǎn)單模式。它通常出現(xiàn)在圍繞多層架構(gòu)構(gòu)建的系統(tǒng)中。
var Mortgage = function (name) {
this.name = name;
}
Mortgage.prototype = {
applyFor: function (amount) {
// access multiple subsystems...
var result = "approved";
if (!new Bank().verify(this.name, amount)) {
result = "denied";
} else if (!new Credit().get(this.name)) {
result = "denied";
} else if (!new Background().check(this.name)) {
result = "denied";
}
return this.name + " has been " + result +
" for a " + amount + " mortgage";
}
}
var Bank = function () {
this.verify = function (name, amount) {
// complex logic ...
return true;
}
}
var Credit = function () {
this.get = function (name) {
// complex logic ...
return true;
}
}
var Background = function () {
this.check = function (name) {
// complex logic ...
return true;
}
}
function run() {
var mortgage = new Mortgage("Joan Templeton");
var result = mortgage.applyFor("$100,000");
console.log(result);
}
07、Proxy
代理模式為另一個(gè)對(duì)象提供代理或占位符對(duì)象,并控制對(duì)另一個(gè)對(duì)象的訪問。
function GeoCoder() {
this.getLatLng = function (address) {
if (address === "Amsterdam") {
return "52.3700° N, 4.8900° E";
} else if (address === "London") {
return "51.5171° N, 0.1062° W";
} else if (address === "Paris") {
return "48.8742° N, 2.3470° E";
} else if (address === "Berlin") {
return "52.5233° N, 13.4127° E";
} else {
return "";
}
};
}
function GeoProxy() {
var geocoder = new GeoCoder();
var geocache = {};
return {
getLatLng: function (address) {
if (!geocache[address]) {
geocache[address] = geocoder.getLatLng(address);
}
console.log(address + ": " + geocache[address]);
return geocache[address];
},
getCount: function () {
var count = 0;
for (var code in geocache) { count++; }
return count;
}
};
};
function run() {
var geo = new GeoProxy();
// geolocation requests
geo.getLatLng("Paris");
geo.getLatLng("London");
geo.getLatLng("London");
geo.getLatLng("London");
geo.getLatLng("London");
geo.getLatLng("Amsterdam");
geo.getLatLng("Amsterdam");
geo.getLatLng("Amsterdam");
geo.getLatLng("Amsterdam");
geo.getLatLng("London");
geo.getLatLng("London");
console.log("\nCache size: " + geo.getCount());
}
08、Mediator
Mediator模式通過封裝這些對(duì)象的交互方式來提供對(duì)一組對(duì)象的集中管理權(quán)。此模型對(duì)于需要管理復(fù)雜條件的場(chǎng)景很有用,在這種情況下,每個(gè)對(duì)象都知道組中任何其他對(duì)象的任何狀態(tài)更改。
var Participant = function (name) {
this.name = name;
this.chatroom = null;
};
Participant.prototype = {
send: function (message, to) {
this.chatroom.send(message, this, to);
},
receive: function (message, from) {
console.log(from.name + " to " + this.name + ": " + message);
}
};
var Chatroom = function () {
var participants = {};
return {
register: function (participant) {
participants[participant.name] = participant;
participant.chatroom = this;
},
send: function (message, from, to) {
if (to) { // single message
to.receive(message, from);
} else { // broadcast message
for (key in participants) {
if (participants[key] !== from) {
participants[key].receive(message, from);
}
}
}
}
};
};
function run() {
var yoko = new Participant("Yoko");
var john = new Participant("John");
var paul = new Participant("Paul");
var ringo = new Participant("Ringo");
var chatroom = new Chatroom();
chatroom.register(yoko);
chatroom.register(john);
chatroom.register(paul);
chatroom.register(ringo);
yoko.send("All you need is love.");
yoko.send("I love you John.");
john.send("Hey, no need to broadcast", yoko);
paul.send("Ha, I heard that!");
ringo.send("Paul, what do you think?", paul);
}
09、Observer
Observer模式提供了一種訂閱模型,其中對(duì)象訂閱一個(gè)事件并在事件發(fā)生時(shí)得到通知。這種模式是事件驅(qū)動(dòng)編程的基石,包括 JavaScript。Observer模式促進(jìn)了良好的面向?qū)ο笤O(shè)計(jì)并促進(jìn)了松散耦合。
function Click() {
this.handlers = []; // observers
}
Click.prototype = {
subscribe: function (fn) {
this.handlers.push(fn);
},
unsubscribe: function (fn) {
this.handlers = this.handlers.filter(
function (item) {
if (item !== fn) {
return item;
}
}
);
},
fire: function (o, thisObj) {
var scope = thisObj || window;
this.handlers.forEach(function (item) {
item.call(scope, o);
});
}
}
function run() {
var clickHandler = function (item) {
console.log("fired: " + item);
};
var click = new Click();
click.subscribe(clickHandler);
click.fire('event #1');
click.unsubscribe(clickHandler);
click.fire('event #2');
click.subscribe(clickHandler);
click.fire('event #3');
}
10、Visitor
Visitor模式定義了對(duì)對(duì)象集合的新操作,而不更改對(duì)象本身。新邏輯駐留在一個(gè)名為 Visitor 的單獨(dú)對(duì)象中。
var Employee = function (name, salary, vacation) {
var self = this;
this.accept = function (visitor) {
visitor.visit(self);
};
this.getName = function () {
return name;
};
this.getSalary = function () {
return salary;
};
this.setSalary = function (sal) {
salary = sal;
};
this.getVacation = function () {
return vacation;
};
this.setVacation = function (vac) {
vacation = vac;
};
};
var ExtraSalary = function () {
this.visit = function (emp) {
emp.setSalary(emp.getSalary() * 1.1);
};
};
var ExtraVacation = function () {
this.visit = function (emp) {
emp.setVacation(emp.getVacation() + 2);
};
};
function run() {
var employees = [
new Employee("John", 10000, 10),
new Employee("Mary", 20000, 21),
new Employee("Boss", 250000, 51)
];
var visitorSalary = new ExtraSalary();
var visitorVacation = new ExtraVacation();
for (var i = 0, len = employees.length; i < len; i++) {
var emp = employees[i];
emp.accept(visitorSalary);
emp.accept(visitorVacation);
console.log(emp.getName() + ": $" + emp.getSalary() +
" and " + emp.getVacation() + " vacation days");
}
}
結(jié)論
當(dāng)我們結(jié)束我們的 JavaScript 設(shè)計(jì)模式之旅時(shí),很明顯這些強(qiáng)大的工具在制作可維護(hù)、可擴(kuò)展和高效的代碼方面發(fā)揮著至關(guān)重要的作用。
通過理解和實(shí)施這些模式,您不僅會(huì)提升您的編程技能,還會(huì)為您自己和您的團(tuán)隊(duì)成員創(chuàng)造更愉快的開發(fā)體驗(yàn)。
請(qǐng)記住,設(shè)計(jì)模式不是一種放之四海而皆準(zhǔn)的解決方案。分析項(xiàng)目的獨(dú)特需求和約束以確定哪些模式將帶來最大價(jià)值至關(guān)重要。
不斷學(xué)習(xí)和試驗(yàn)不同的設(shè)計(jì)模式將使您能夠做出明智的決策并為您的項(xiàng)目選擇最佳方法。
將設(shè)計(jì)模式整合到您的工作流中可能需要投入時(shí)間和精力,但從長(zhǎng)遠(yuǎn)來看,這是值得的。
當(dāng)您掌握編寫優(yōu)雅、模塊化和高效的 JavaScript 代碼的藝術(shù)時(shí),您會(huì)發(fā)現(xiàn)您的應(yīng)用程序變得更加健壯,您的調(diào)試過程更易于管理,并且您的整體開發(fā)體驗(yàn)更加愉快。
因此,繼續(xù)探索 JavaScript 設(shè)計(jì)模式的世界,并希望您的代碼更易于維護(hù)、可擴(kuò)展和高效。