The three categories of design patterns: creational, structural, behavioral
Design patterns are reusable solutions to common problems in software development. As a highly flexible language, JavaScript widely applies design patterns. The three main categories include creational, structural, and behavioral patterns, each providing solutions for different scenarios.
Creational Patterns
Creational patterns focus on object creation mechanisms, improving code flexibility and reusability by controlling the object creation process. Common creational patterns in JavaScript include the Factory Pattern, Abstract Factory Pattern, Singleton Pattern, Builder Pattern, and Prototype Pattern.
Factory Pattern
The Factory Pattern creates objects through a common interface while hiding implementation details. For example, a UI component factory returns different components based on input types:
class Button {
render() {
return '<button>Click me</button>';
}
}
class Input {
render() {
return '<input type="text" />';
}
}
function createComponent(type) {
switch (type) {
case 'button':
return new Button();
case 'input':
return new Input();
default:
throw new Error('Unknown component type');
}
}
const button = createComponent('button');
console.log(button.render()); // <button>Click me</button>
Singleton Pattern
The Singleton Pattern ensures a class has only one instance and provides a global access point. This is particularly useful for managing global state:
class Logger {
constructor() {
if (Logger.instance) {
return Logger.instance;
}
this.logs = [];
Logger.instance = this;
}
log(message) {
this.logs.push(message);
console.log(`LOG: ${message}`);
}
printLogCount() {
console.log(`${this.logs.length} Logs`);
}
}
const logger1 = new Logger();
const logger2 = new Logger();
logger1.log('First message');
logger2.log('Second message');
logger1.printLogCount(); // 2 Logs
console.log(logger1 === logger2); // true
Prototype Pattern
JavaScript inherently uses prototype inheritance. The Prototype Pattern creates new objects by cloning existing ones:
const carPrototype = {
wheels: 4,
start() {
console.log('Car started');
},
stop() {
console.log('Car stopped');
}
};
const myCar = Object.create(carPrototype);
myCar.color = 'red';
console.log(myCar.wheels); // 4
myCar.start(); // Car started
Structural Patterns
Structural patterns focus on how classes and objects are composed to form larger structures. They mainly include the Adapter Pattern, Bridge Pattern, Composite Pattern, Decorator Pattern, Facade Pattern, Flyweight Pattern, and Proxy Pattern.
Adapter Pattern
The Adapter Pattern enables incompatible interfaces to work together. For example, adapting an old API to a new system:
class OldCalculator {
operations(a, b, operation) {
switch (operation) {
case 'add':
return a + b;
case 'sub':
return a - b;
default:
return NaN;
}
}
}
class NewCalculator {
add(a, b) {
return a + b;
}
subtract(a, b) {
return a - b;
}
}
class CalculatorAdapter {
constructor() {
this.calculator = new NewCalculator();
}
operations(a, b, operation) {
switch (operation) {
case 'add':
return this.calculator.add(a, b);
case 'sub':
return this.calculator.subtract(a, b);
default:
return NaN;
}
}
}
const oldCalc = new OldCalculator();
console.log(oldCalc.operations(5, 3, 'add')); // 8
const adapter = new CalculatorAdapter();
console.log(adapter.operations(5, 3, 'add')); // 8
Decorator Pattern
The Decorator Pattern dynamically adds responsibilities to objects. In JavaScript, this can be achieved using higher-order functions:
function withLogging(fn) {
return function(...args) {
console.log(`Calling function with args: ${args}`);
const result = fn.apply(this, args);
console.log(`Function returned: ${result}`);
return result;
};
}
function add(a, b) {
return a + b;
}
const loggedAdd = withLogging(add);
loggedAdd(2, 3);
// Calling function with args: 2,3
// Function returned: 5
Proxy Pattern
The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. For example, implementing lazy loading for images:
class ImageProxy {
constructor(src) {
this.src = src;
this.realImage = null;
}
display() {
if (!this.realImage) {
this.realImage = new RealImage(this.src);
this.realImage.loadFromServer();
}
this.realImage.display();
}
}
class RealImage {
constructor(src) {
this.src = src;
this.image = null;
}
loadFromServer() {
console.log(`Loading image from ${this.src}`);
this.image = document.createElement('img');
this.image.src = this.src;
}
display() {
console.log(`Displaying image ${this.src}`);
document.body.appendChild(this.image);
}
}
const proxy = new ImageProxy('example.jpg');
// Image not yet loaded
proxy.display(); // Loads and displays the image
Behavioral Patterns
Behavioral patterns focus on communication and responsibility assignment between objects. They mainly include the Chain of Responsibility Pattern, Command Pattern, Interpreter Pattern, Iterator Pattern, Mediator Pattern, Memento Pattern, Observer Pattern, State Pattern, Strategy Pattern, Template Method Pattern, and Visitor Pattern.
Observer Pattern
The Observer Pattern defines a one-to-many dependency between objects, where all dependent objects are notified when one object changes state:
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
console.log(`Observer received data: ${data}`);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello observers!');
// Observer received data: Hello observers!
// Observer received data: Hello observers!
Strategy Pattern
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable:
class PaymentStrategy {
pay(amount) {
throw new Error('Method not implemented');
}
}
class CreditCardStrategy extends PaymentStrategy {
pay(amount) {
console.log(`Paying ${amount} using Credit Card`);
}
}
class PayPalStrategy extends PaymentStrategy {
pay(amount) {
console.log(`Paying ${amount} using PayPal`);
}
}
class ShoppingCart {
constructor() {
this.strategy = null;
this.amount = 0;
}
setStrategy(strategy) {
this.strategy = strategy;
}
checkout() {
if (!this.strategy) {
throw new Error('No payment strategy set');
}
this.strategy.pay(this.amount);
}
}
const cart = new ShoppingCart();
cart.amount = 100;
cart.setStrategy(new CreditCardStrategy());
cart.checkout(); // Paying 100 using Credit Card
cart.setStrategy(new PayPalStrategy());
cart.checkout(); // Paying 100 using PayPal
State Pattern
The State Pattern allows an object to alter its behavior when its internal state changes:
class TrafficLight {
constructor() {
this.states = [new RedLight(), new YellowLight(), new GreenLight()];
this.current = this.states[0];
}
change() {
const currentIndex = this.states.indexOf(this.current);
this.current = this.states[(currentIndex + 1) % this.states.length];
}
sign() {
return this.current.sign();
}
}
class Light {
constructor(color) {
this.color = color;
}
sign() {
return this.color;
}
}
class RedLight extends Light {
constructor() {
super('red');
}
}
class YellowLight extends Light {
constructor() {
super('yellow');
}
}
class GreenLight extends Light {
constructor() {
super('green');
}
}
const trafficLight = new TrafficLight();
console.log(trafficLight.sign()); // red
trafficLight.change();
console.log(trafficLight.sign()); // yellow
trafficLight.change();
console.log(trafficLight.sign()); // green
trafficLight.change();
console.log(trafficLight.sign()); // red
Iterator Pattern
The Iterator Pattern provides a way to sequentially access elements of an aggregate object without exposing its underlying representation:
class MyIterator {
constructor(collection) {
this.collection = collection;
this.index = 0;
}
next() {
return this.collection[this.index++];
}
hasNext() {
return this.index < this.collection.length;
}
}
class MyCollection {
constructor() {
this.items = [];
}
addItem(item) {
this.items.push(item);
}
getIterator() {
return new MyIterator(this.items);
}
}
const collection = new MyCollection();
collection.addItem('First');
collection.addItem('Second');
collection.addItem('Third');
const iterator = collection.getIterator();
while (iterator.hasNext()) {
console.log(iterator.next());
}
// First
// Second
// Third
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn