阿里云主机折上折
  • 微信号
Current Site:Index > The three categories of design patterns: creational, structural, behavioral

The three categories of design patterns: creational, structural, behavioral

Author:Chuan Chen 阅读数:30099人阅读 分类: JavaScript

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

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.