阿里云主机折上折
  • 微信号
Current Site:Index > Analysis of the advantages and disadvantages of design patterns

Analysis of the advantages and disadvantages of design patterns

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

Design patterns are reusable solutions to common problems in software development. Properly applying design patterns in JavaScript can enhance code maintainability, scalability, and reusability. Different patterns are suitable for different scenarios, each with its own advantages and limitations, requiring careful consideration based on specific needs.

Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global access point to it. In JavaScript, it is commonly used for managing global state or shared resources.

Advantages:

  • Reduces memory overhead by avoiding repeated creation of identical objects
  • Provides controlled access to the unique instance
  • Suitable for global state management, such as Vuex/Redux stores

Disadvantages:

  • Violates the Single Responsibility Principle, potentially taking on too many functions
  • Difficult to unit test due to shared global state
  • May lead to high code coupling
class Logger {
  constructor() {
    if (!Logger.instance) {
      this.logs = [];
      Logger.instance = this;
    }
    return Logger.instance;
  }
  
  log(message) {
    this.logs.push(message);
    console.log(message);
  }
}

const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true

Factory Pattern

The Factory pattern defines an interface for creating objects but lets subclasses decide which class to instantiate. In JavaScript, it is often used for creating complex objects or scenarios where different instances need to be returned based on conditions.

Advantages:

  • Decouples object creation from usage, reducing coupling
  • Easy to extend—adding new products only requires new factory classes
  • Hides implementation details of specific product classes

Disadvantages:

  • Increases system complexity by introducing more classes and interfaces
  • Clients may need to know the specific factory to create the desired object
class Car {
  constructor(options) {
    this.doors = options.doors || 4;
    this.color = options.color || 'white';
  }
}

class Truck {
  constructor(options) {
    this.doors = options.doors || 2;
    this.color = options.color || 'black';
    this.payload = options.payload || '1ton';
  }
}

class VehicleFactory {
  createVehicle(type, options) {
    switch(type) {
      case 'car':
        return new Car(options);
      case 'truck':
        return new Truck(options);
      default:
        throw new Error('Unknown vehicle type');
    }
  }
}

const factory = new VehicleFactory();
const myCar = factory.createVehicle('car', { color: 'red' });

Observer Pattern

The Observer pattern defines a one-to-many dependency between objects, where all dependent objects are notified and updated automatically when the state of one object changes.

Advantages:

  • Supports simple broadcast communication, automatically notifying all subscribers
  • Abstract coupling—subjects and observers are loosely coupled
  • Supports dynamic addition and removal of observers

Disadvantages:

  • Notification order is uncontrollable, potentially leading to circular calls
  • Notification efficiency may become a bottleneck with too many observers
  • Observers are unaware of each other, which may cause update conflicts
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(`Received data: ${data}`);
  }
}

const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('Hello World!');

Decorator Pattern

The Decorator pattern allows adding new functionality to an existing object dynamically without altering its structure, serving as an alternative to inheritance.

Advantages:

  • More flexible than inheritance—functionality can be added or removed dynamically
  • Avoids the explosion of subclasses
  • Complies with the Open-Closed Principle

Disadvantages:

  • Produces many small objects, increasing system complexity
  • The order of decorators may affect behavior
  • Difficult to remove specific decorators from an object
class Coffee {
  cost() {
    return 5;
  }
}

class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  
  cost() {
    return this.coffee.cost() + 2;
  }
}

class SugarDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  
  cost() {
    return this.coffee.cost() + 1;
  }
}

let myCoffee = new Coffee();
myCoffee = new MilkDecorator(myCoffee);
myCoffee = new SugarDecorator(myCoffee);
console.log(myCoffee.cost()); // 8

Strategy Pattern

The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing the algorithm to vary independently from the client that uses it.

Advantages:

  • Avoids multiple conditional statements
  • Algorithms can be freely switched
  • Easy to extend new algorithms
  • Complies with the Open-Closed Principle

Disadvantages:

  • Clients must understand all strategy classes
  • Increases the number of strategy classes
  • Strategy classes need to be exposed externally
class Shipping {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  calculate(package) {
    return this.strategy.calculate(package);
  }
}

class UPS {
  calculate(package) {
    return package.weight * 1.5;
  }
}

class Fedex {
  calculate(package) {
    return package.weight * 2.5 + package.distance * 0.5;
  }
}

const package = { weight: 10, distance: 100 };
const upsShipping = new Shipping(new UPS());
console.log(upsShipping.calculate(package)); // 15

const fedexShipping = new Shipping(new Fedex());
console.log(fedexShipping.calculate(package)); // 75

Proxy Pattern

The Proxy pattern provides a surrogate or placeholder for another object to control access to it, introducing a level of indirection when accessing the object.

Advantages:

  • Protects the target object and controls access permissions
  • Extends the functionality of the target object
  • Reduces system coupling

Disadvantages:

  • Increases system complexity
  • May slow down request processing
  • Requires additional effort to maintain the proxy
class RealImage {
  constructor(filename) {
    this.filename = filename;
    this.loadFromDisk();
  }
  
  display() {
    console.log(`Displaying ${this.filename}`);
  }
  
  loadFromDisk() {
    console.log(`Loading ${this.filename}`);
  }
}

class ProxyImage {
  constructor(filename) {
    this.filename = filename;
    this.realImage = null;
  }
  
  display() {
    if (!this.realImage) {
      this.realImage = new RealImage(this.filename);
    }
    this.realImage.display();
  }
}

const image = new ProxyImage('test.jpg');
image.display(); // Loads on first call
image.display(); // Displays directly on second call

Module Pattern

The Module pattern uses closures to encapsulate "private" state and structure, providing a public API while hiding implementation details.

Advantages:

  • Encapsulates private variables and methods
  • Reduces global variable pollution
  • Organizes code more clearly
  • Supports information hiding

Disadvantages:

  • Private members are difficult to extend
  • Hard to unit test
  • Modifying visibility requires code refactoring
const counterModule = (function() {
  let count = 0;
  
  function increment() {
    count++;
  }
  
  function reset() {
    count = 0;
  }
  
  function getCount() {
    return count;
  }
  
  return {
    increment,
    reset,
    getCount
  };
})();

counterModule.increment();
console.log(counterModule.getCount()); // 1

Adapter Pattern

The Adapter pattern converts the interface of a class into another interface clients expect, enabling classes with incompatible interfaces to work together.

Advantages:

  • Allows any two unrelated classes to work together
  • Improves class reusability
  • Increases class transparency
  • Offers good flexibility

Disadvantages:

  • Overuse can make the system very chaotic
  • Language limitations may make implementation difficult
  • Not a design-phase pattern but a solution to existing problems
class OldCalculator {
  operations(t1, t2, operation) {
    switch(operation) {
      case 'add':
        return t1 + t2;
      case 'sub':
        return t1 - t2;
      default:
        return NaN;
    }
  }
}

class NewCalculator {
  add(t1, t2) {
    return t1 + t2;
  }
  
  sub(t1, t2) {
    return t1 - t2;
  }
}

class CalculatorAdapter {
  constructor() {
    this.calculator = new NewCalculator();
  }
  
  operations(t1, t2, operation) {
    switch(operation) {
      case 'add':
        return this.calculator.add(t1, t2);
      case 'sub':
        return this.calculator.sub(t1, t2);
      default:
        return NaN;
    }
  }
}

const oldCalc = new OldCalculator();
console.log(oldCalc.operations(10, 5, 'add')); // 15

const newCalc = new NewCalculator();
console.log(newCalc.add(10, 5)); // 15

const adapter = new CalculatorAdapter();
console.log(adapter.operations(10, 5, 'add')); // 15

Composite Pattern

The Composite pattern composes objects into tree structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly.

Advantages:

  • Simplifies high-level module calls
  • Nodes can be added freely
  • Ignores differences between composite and individual objects

Disadvantages:

  • Design is complex, requiring identification of branches and leaves
  • Difficult to restrict component types in compositions
  • Makes the design more abstract
class Employee {
  constructor(name, position) {
    this.name = name;
    this.position = position;
    this.subordinates = [];
  }
  
  add(employee) {
    this.subordinates.push(employee);
  }
  
  remove(employee) {
    const index = this.subordinates.indexOf(employee);
    if (index > -1) {
      this.subordinates.splice(index, 1);
    }
  }
  
  display(indent = '') {
    console.log(`${indent}${this.name} - ${this.position}`);
    this.subordinates.forEach(employee => {
      employee.display(indent + '  ');
    });
  }
}

const ceo = new Employee('John', 'CEO');
const headSales = new Employee('Robert', 'Head Sales');
const headMarketing = new Employee('Michel', 'Head Marketing');
const clerk1 = new Employee('Laura', 'Marketing Clerk');
const clerk2 = new Employee('Bob', 'Sales Clerk');

ceo.add(headSales);
ceo.add(headMarketing);
headSales.add(clerk2);
headMarketing.add(clerk1);

ceo.display();

State Pattern

The State pattern allows an object to alter its behavior when its internal state changes, making it appear as if the object changed its class.

Advantages:

  • Localizes state-specific behavior
  • Reduces conditional branching
  • Facilitates adding new states and transitions

Disadvantages:

  • Increases the number of classes and objects in the system
  • Structure and implementation are complex
  • Violates the Open-Closed Principle—adding new states may modify existing code
class TrafficLight {
  constructor() {
    this.states = [new GreenLight(), new YellowLight(), new RedLight()];
    this.current = this.states[0];
  }
  
  change() {
    const totalStates = this.states.length;
    let currentIndex = this.states.findIndex(light => light === this.current);
    this.current = this.states[(currentIndex + 1) % totalStates];
  }
  
  sign() {
    return this.current.sign();
  }
}

class Light {
  constructor(light) {
    this.light = light;
  }
}

class GreenLight extends Light {
  constructor() {
    super('green');
  }
  
  sign() {
    return 'GO';
  }
}

class YellowLight extends Light {
  constructor() {
    super('yellow');
  }
  
  sign() {
    return 'ATTENTION';
  }
}

class RedLight extends Light {
  constructor() {
    super('red');
  }
  
  sign() {
    return 'STOP';
  }
}

const trafficLight = new TrafficLight();
console.log(trafficLight.sign()); // GO
trafficLight.change();
console.log(trafficLight.sign()); // ATTENTION
trafficLight.change();
console.log(trafficLight.sign()); // STOP

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱: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 ☕.