English | https://blog.bitsrc.io/10-javascript-design-patterns-3087d1dda5b4
introduce
Design patterns are high-level object-oriented solutions to common software problems. Patterns are about reusable design and interaction of objects. Each pattern has a name and becomes part of the vocabulary when discussing complex design solutions.
In this tutorial, I provide JavaScript examples for each GoF pattern. For the most part, they follow the structure and intent of the original pattern design. These examples demonstrate the principles behind each pattern, but are not optimized for JavaScript.
01.Abstract Factory
Abstract Factory creates objects related by a common theme. In object-oriented programming, factories are objects that create other objects. An Abstract Factory abstracts away the topics shared by newly created objects.
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
The Builder pattern allows clients to build complex objects only by specifying the type and content, with details completely hidden from clients.
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
The Factory Method creates new objects as directed by the client. One way to create objects in JavaScript is to call a constructor using the new operator.
In some cases, however, the client does not know or should not know which of multiple candidates to instantiate.
Factory Method allows clients to delegate object creation while still retaining control over the types to instantiate.
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
The Adapter pattern converts one interface (properties and methods of an object) into another interface. Adapters allow programmatic components to work together that otherwise would not work together due to interface mismatches. The Adapter pattern is also known as the Wrapper pattern.
// 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
The Decorator pattern dynamically extends (decorates) the behavior of objects. The ability to add new behavior at runtime is implemented by Decorator objects, which "wrap themselves" around the original object. Several decorators can add or override functionality to the original object.
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
The Facade pattern provides an interface that shields clients from complex functionality in one or more subsystems. This is a simple pattern that may seem trivial but is powerful and extremely useful. It typically occurs in systems built around a multi-tier architecture.
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
The proxy pattern provides a proxy or placeholder object for another object and controls access to another object.
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
The Mediator pattern provides centralized management of a set of objects by encapsulating how these objects interact. This model is useful for scenarios where complex conditions need to be managed, where each object is aware of any state change of any other object in the group.
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
The Observer pattern provides a subscription model where objects subscribe to an event and are notified when the event occurs. This pattern is the cornerstone of event-driven programming, including JavaScript. The Observer pattern promotes good object-oriented design and promotes loose coupling.
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
The Visitor pattern defines new operations on collections of objects without changing the objects themselves. The new logic resides in a separate object called Visitor.
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");
}
}
in conclusion
As we wrap up our tour of JavaScript design patterns, it's clear that these powerful tools play a vital role in making code maintainable, extensible, and efficient.
By understanding and implementing these patterns, you will not only improve your programming skills, but also create a more enjoyable development experience for yourself and your team members.
Remember that design patterns are not a one-size-fits-all solution. It is critical to analyze the unique needs and constraints of a project to determine which patterns will bring the most value.
Constantly learning and experimenting with different design patterns will enable you to make informed decisions and choose the best approach for your project.
Integrating design patterns into your workflow may take an investment of time and effort, but it will be worth it in the long run.
When you master the art of writing elegant, modular, and efficient JavaScript code, you'll find that your applications become more robust, your debugging process more manageable, and your overall development experience more enjoyable.
So go ahead and explore the world of JavaScript design patterns and hope your code is more maintainable, scalable and efficient.
Thanks for reading, and happy programming!
learn more skills
Please click the public number below