关于前端5种常见设计模式

工厂模式

简单工厂模式(Simple Factory Pattern)

简单工厂方法模式是一种创建型设计模式,它提供了一个统一的接口来创建对象,但隐藏了对象的具体实现细节。简单工厂模式通过一个工厂类,根据不同的参数来创建不同的产品对象,使得客户端代码与具体产品的创建过程解耦。
简单工厂模式包含以下几个核心组件:

  1. 工厂类(Factory):负责创建产品对象的类。它通常包含一个静态方法,根据传入的参数来创建不同的产品对象。
  2. 抽象产品类(Product):定义产品的共同接口,所有具体产品类都必须实现这个接口。
  3. 具体产品类(Concrete Product):实现抽象产品类接口的具体产品。工厂类根据不同的参数来创建不同的具体产品对象。

优点

简单工厂模式的优点包括:

  1. 客户端与具体产品解耦:客户端只需要与工厂类进行交互,不需要直接依赖具体产品类,降低了客户端的复杂性。
  2. 隐藏对象创建细节:客户端不需要关心具体产品的创建过程,只需要关心如何使用产品。
  3. 简化对象创建:通过工厂类集中管理对象的创建,可以提供统一的创建方式和参数验证,简化了对象的创建过程。

缺点

  1. 不符合开闭原则:当需要新增加一种产品时,需要修改工厂类的代码,违反了开闭原则。
  2. 工厂类职责过重:随着产品种类的增多,工厂类的职责会变得越来越重,可能会导致工厂类的代码臃肿。
  3. 不易扩展:由于工厂类集中了所有产品的创建逻辑,当需要添加新的产品时,需要修改工厂类的代码,扩展性较差。

例子

以下是一个使用简单工厂模式创建不同类型动物的示例:

```javascript
// 抽象产品类 - 动物
class Animal {
  constructor(name) {
    this.name = name;
  }
  
  sound() {
    // 抽象方法,具体的动物类需要实现该方法
  }
}

// 具体产品类 - 狗
class Dog extends Animal {
  sound() {
    return "汪汪汪!";
  }
}

// 具体产品类 - 猫
class Cat extends Animal {
  sound() {
    return "喵喵喵!";
  }
}

// 简单工厂类
class AnimalFactory {
  createAnimal(type, name) {
    switch (type) {
      case "dog":
        return new Dog(name);
      case "cat":
        return new Cat(name);
      default:
        throw new Error("Invalid animal type.");
    }
  }
}

// 使用简单工厂创建对象
const factory = new AnimalFactory();
const dog = factory.createAnimal("dog", "旺财");
const cat = factory.createAnimal("cat", "小花");

console.log(dog.sound()); // 输出:汪汪汪!
console.log(cat.sound()); // 输出:喵喵喵!

在上述示例中,我们定义了一个抽象产品类 Animal,它有一个抽象方法 sound()。然后,我们创建了两个具体产品类 DogCat,它们分别继承自 Animal 并实现了 sound() 方法。

接下来,我们定义了一个简单工厂类 AnimalFactory,它有一个 createAnimal() 方法,根据传入的类型参数来创建不同的动物对象。

最后,我们使用简单工厂类创建了一个 AnimalFactory 实例,并使用 createAnimal() 方法创建了一个狗对象和一个猫对象。通过调用对象的 sound() 方法,我们可以分别输出它们的声音。

这个例子展示了如何使用简单工厂模式在JavaScript中创建不同类型的对象,通过工厂类将对象的创建过程与客户端代码解耦。

总结

简单工厂模式适用于以下情况:

  1. 需要创建的对象较少:当需要创建的对象种类较少且相对稳定时,可以使用简单工厂模式。
  2. 客户端不关心对象的具体实现:客户端只需要使用对象的共同接口,对于具体实现不关心时,可以使用简单工厂模式。总之,简单工厂模式是一种简单实用的创建型设计模式,适用于创建对象较少且相对稳定的情况。但需要注意,它可能违反开闭原则,并且随着产品种类的增多,工厂类的职责会变得过重。在设计时,应根据具体需求和情况来选择合适的设计模式。

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它通过定义一个创建对象的接口,但将具体对象的创建延迟到子类中实现。工厂方法模式使得一个类的实例化延迟到其子类,从而实现了对象的创建与使用的解耦。

工厂方法模式

工厂方法模式包含以下几个核心组件:

  1. 抽象产品类(Product):定义产品的共同接口,所有具体产品类都必须实现这个接口。

  2. 具体产品类(Concrete Product):实现抽象产品类接口的具体产品。

  3. 抽象工厂类(Factory):定义创建产品的抽象方法,由子类来实现具体的产品创建。

  4. 具体工厂类(Concrete Factory):实现抽象工厂类的抽象方法,负责创建具体的产品对象。

工厂方法模式的工作流程如下:

  1. 客户端通过调用具体工厂类的方法来创建产品对象。

  2. 具体工厂类根据需要创建相应的具体产品对象,并返回给客户端。

  3. 客户端通过产品的共同接口来使用具体产品对象,而不需要关心具体产品的创建过程。

工厂方法模式与简单工厂模式的区别在于,工厂方法模式将对象的创建延迟到子类中实现,每个具体产品都有对应的具体工厂类,而简单工厂模式通过一个工厂类来创建所有的产品对象。

优点

  1. 符合开闭原则:当需要新增加一种产品时,只需要创建相应的具体产品类和具体工厂类,而不需要修改已有的代码。

  2. 客户端与具体产品解耦:客户端只需要与抽象工厂和抽象产品进行交互,不需要直接依赖具体产品类,降低了客户端的复杂性。

  3. 支持多态性:每个具体工厂类都可以返回不同类型的具体产品,通过多态性来实现不同产品的创建和使用。

缺点

  1. 类的个数增加:每个具体产品都需要对应一个具体工厂类,导致类的个数增加。

  2. 增加系统的抽象性和理解难度:工厂方法模式引入了抽象工厂和抽象产品的概念,增加了系统的抽象性和理解难度。

例子

// 抽象产品类 - 按钮
class Button {
  render() {
    // 抽象方法,具体的按钮类需要实现该方法
  }
}

// 具体产品类 - 圆形按钮
class CircleButton extends Button {
  render() {
    console.log("渲染圆形按钮");
  }
}

// 具体产品类 - 方形按钮
class SquareButton extends Button {
  render() {
    console.log("渲染方形按钮");
  }
}

// 抽象工厂类 - 按钮工厂
class ButtonFactory {
  createButton() {
    // 抽象方法,由具体的工厂类来实现该方法
  }
}

// 具体工厂类 - 圆形按钮工厂
class CircleButtonFactory extends ButtonFactory {
  createButton() {
    return new CircleButton();
  }
}

// 具体工厂类 - 方形按钮工厂
class SquareButtonFactory extends ButtonFactory {
  createButton() {
    return new SquareButton();
  }
}

// 使用工厂方法创建对象
function createButton(factory) {
  const button = factory.createButton();
  button.render();
}

// 创建圆形按钮工厂
const circleButtonFactory = new CircleButtonFactory();
createButton(circleButtonFactory); // 输出:渲染圆形按钮

// 创建方形按钮工厂
const squareButtonFactory = new SquareButtonFactory();
createButton(squareButtonFactory); // 输出:渲染方形按钮

总结

工厂方法模式适用于以下情况:

  1. 需要创建的对象种类较多:当需要创建的对象种类较多且相对稳定时,可以使用工厂方法模式。

  2. 客户端不关心对象的具体实现:客户端只需要使用对象的共同接口,对于具体实现不关心时,可以使用工厂方法模式。

抽象工厂模式

抽象工厂模式是一种创建型设计模式,用于创建一系列相关或相互依赖的对象。它提供了一种方式来封装对象的创建过程,使得客户端代码与具体的产品类解耦。

在抽象工厂模式中,有以下几个核心角色:

  1. 抽象工厂(Abstract Factory):定义了创建一系列相关产品对象的接口。它声明了一组用于创建不同产品对象的抽象方法。

  2. 具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建一系列具体的产品对象。每个具体工厂类对应一族产品。

  3. 抽象产品(Abstract Product):定义了一类产品对象的接口。它可以是一个具体产品类的抽象,也可以是一个接口。

  4. 具体产品(Concrete Product):实现了抽象产品接口,具体工厂类创建的产品就是具体产品的实例。

使用抽象工厂模式的主要步骤如下:

  1. 定义抽象产品接口:创建一系列产品对象所共有的接口,并声明产品对象的方法。

  2. 定义抽象工厂接口:声明用于创建产品对象的抽象方法。

  3. 实现具体产品类:实现抽象产品接口,定义具体产品的具体行为。

  4. 实现具体工厂类:实现抽象工厂接口,实现抽象方法来创建具体产品对象。

  5. 在客户端代码中使用抽象工厂:客户端通过抽象工厂接口来创建产品对象,而不直接依赖具体产品类。这样可以使客户端与具体产品类解耦,增加了灵活性和可扩展性。

优点

  • 提供了一种封装对象创建过程的方式,使得客户端代码与具体产品类解耦,增加了灵活性和可扩展性。

  • 支持产品族的扩展,即可以创建一系列相关的产品对象。

  • 符合开闭原则,当需要增加新的产品族时,只需要添加相应的具体工厂类和具体产品类,而无需修改已有代码。

缺点

  • 增加新的产品等级结构(即新的抽象产品类)比较困难,需要修改抽象工厂接口和所有的具体工厂类。

  • 当产品族中的产品等级结构发生变化时,需要修改抽象工厂接口和所有的具体工厂类。

例子

// 抽象产品类 - 按钮
class Button {
  render() {
    // 抽象方法,具体的按钮类需要实现该方法
  }
}

// 具体产品类 - 圆形按钮
class CircleButton extends Button {
  render() {
    console.log("渲染圆形按钮");
  }
}

// 具体产品类 - 方形按钮
class SquareButton extends Button {
  render() {
    console.log("渲染方形按钮");
  }
}

// 抽象产品类 - 文本框
class TextBox {
  render() {
    // 抽象方法,具体的文本框类需要实现该方法
  }
}

// 具体产品类 - 圆形文本框
class CircleTextBox extends TextBox {
  render() {
    console.log("渲染圆形文本框");
  }
}

// 具体产品类 - 方形文本框
class SquareTextBox extends TextBox {
  render() {
    console.log("渲染方形文本框");
  }
}

// 抽象工厂类 - UI工厂
class UIFactory {
  createButton() {
    // 抽象方法,由具体的工厂类来实现该方法
  }

  createTextBox() {
    // 抽象方法,由具体的工厂类来实现该方法
  }
}

// 具体工厂类 - 圆形UI工厂
class CircleUIFactory extends UIFactory {
  createButton() {
    return new CircleButton();
  }

  createTextBox() {
    return new CircleTextBox();
  }
}

// 具体工厂类 - 方形UI工厂
class SquareUIFactory extends UIFactory {
  createButton() {
    return new SquareButton();
  }

  createTextBox() {
    return new SquareTextBox();
  }
}

// 使用抽象工厂创建对象
function createUI(factory) {
  const button = factory.createButton();
  const textBox = factory.createTextBox();

  button.render();
  textBox.render();
}

// 创建圆形UI工厂
const circleUIFactory = new CircleUIFactory();
createUI(circleUIFactory);
// 输出:
// 渲染圆形按钮
// 渲染圆形文本框

// 创建方形UI工厂
const squareUIFactory = new SquareUIFactory();
createUI(squareUIFactory);
// 输出:
// 渲染方形按钮
// 渲染方形文本框

总结

抽象工厂模式适用于需要创建一系列相关产品对象,并且需要增加新的产品族时。它提供了一种灵活的方式来封装对象的创建过程,使得客户端代码与具体产品类解耦。

三者对比

  • 简单工厂模式适用于对象的创建逻辑相对简单的情况,但不符合开闭原则。
  • 工厂方法模式适用于需要创建单个产品对象的情况,每个产品有对应的具体工厂类。
  • 抽象工厂模式适用于需要创建一系列相关产品对象的情况,支持产品族的扩展,但增加了系统的抽象性和理解难度。

选择适合的模式取决于具体的需求和设计考虑。如果只有少量的产品需要创建,并且创建逻辑简单,可以使用简单工厂模式;如果有多个产品需要创建,并且每个产品都有对应的工厂类,可以使用工厂方法模式;如果需要创建一系列相关产品对象,并且支持产品族的扩展,可以使用抽象工厂模式。

单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式。在这种模式中,一个类只允许创建一个对象(或实例),即一个类只有一个实例存在。单例模式确保全局只有一个对象可用,并提供一个访问它的全局访问点。

主要特点

  1. 单一实例:确保只有一个实例被创建。
  2. 全局访问点:提供一个全局访问该实例的接口。
  3. 自我实例化:通常由单例类自己负责创建自己的唯一实例。
  4. 延迟初始化:单例通常在首次使用时才被创建,而不是在应用启动时。

实现方式

单例模式可以通过不同的方式实现,例如:

  • 懒汉式:延迟实例化,即在第一次被引用时,才初始化单例实例。
  • 饿汉式:在类加载时就创建单例实例。

应用场景

单例模式适用于以下情况:

  • 当类的实例用于全局管理某些资源时,如配置信息、硬件接口访问等。
  • 当需要确保资源共享和只有一个实例存在时,如线程池、缓存、对话框、注册表对象等。

JavaScript 实现示例

以下是一个 JavaScript 中单例模式的简单实现(懒汉式):

class Singleton {
    constructor() {
        if (!Singleton.instance) {
            Singleton.instance = this;
        }
        return Singleton.instance;
    }

    someMethod() {
        // 方法实现
    }
}

// 使用
const instance1 = new Singleton();
const instance2 = new Singleton();

console.log(instance1 === instance2); // true

在这个例子中,Singleton 类使用了懒汉式实现。当尝试通过 new Singleton() 创建一个新的实例时,类首先检查它是否已经有一个实例存在。如果存在,它就返回那个实例,否则,它会创建一个新实例。

观察者模式

观察者模式(Observer Pattern)是一种行为设计模式,允许对象(称为观察者)订阅另一个对象(称为主题),以便在主题状态发生变化时得到通知。这种模式常用于实现事件处理系统。

主要特点

  1. 定义了对象之间的一对多依赖关系:一个主题对象管理所有依赖于它的观察者对象,并在其本身的状态改变时主动通知它们。
  2. 推送与拉取信息:观察者可以被动接收信息(推送),也可以主动查询主题对象的状态(拉取)。
  3. 低耦合设计:主题只知道观察者实现了某个接口,并不需要了解其具体类。观察者可以独立于主题变化而独立变化。

角色和概念

  1. Subject(主题):维护一组观察者,提供用于增加和移除观察者的接口。
  2. Observer(观察者):为那些在主题状态发生改变时需获得通知的对象提供一个更新接口。
  3. ConcreteSubject(具体主题):在其内部状态改变时,给所有登记过的观察者发出通知。
  4. ConcreteObserver(具体观察者):实现观察者接口,以便在得到主题变化的通知时更新自己。

应用场景

观察者模式适用于以下情况:

  • 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中可以使它们可以各自独立地改变和复用。
  • 当一个对象的改变需要同时改变其他对象,而不知道具体有多少对象待改变时。
  • 当一个对象必须通知其他对象,而它又不能假定这些对象是谁的时候。换言之,你不希望这些对象是紧密耦合的。

JavaScript 实现示例

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 {
    constructor(name) {
        this.name = name;
    }

    update(data) {
        console.log(`${this.name} received data: ${data}`);
    }
}

// 使用
const subject = new Subject();

const observer1 = new Observer("Observer 1");
const observer2 = new Observer("Observer 2");

subject.subscribe(observer1);
subject.subscribe(observer2);

subject.notify("Hello World!"); // Both observers receive the notification

subject.unsubscribe(observer1);

subject.notify("Second message"); // Only Observer 2 receives the notification

在这个例子中,Subject 类管理观察者并提供订阅(subscribe)和取消订阅(unsubscribe)的功能。当调用 notify 方法时,所有订阅的观察者都会收到通知。每个 Observer 类实例都可以接收到这些通知。这样,我们可以动态地添加、移除或通知观察者,而主题对象无需了解观察者的具体实现。

适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许不兼容的接口之间进行交互。适配器模式通过创建一个中间层,使得原本由于接口不兼容而不能一起工作的类可以协同工作。

主要特点

  1. 接口转换:将一个类的接口转换成客户端期望的另一个接口。
  2. 兼容性:使得原本不兼容的接口可以一起工作。
  3. 复用:允许复用现有的代码,即使它们的接口不符合当前的需求。

角色和概念

  1. Target(目标接口):定义客户端使用的与特定领域相关的接口。
  2. Adapter(适配器):适配器实现了目标接口,并持有一个被适配的对象引用。适配器负责将目标接口的调用转换为对被适配者的调用。
  3. Adaptee(被适配者):定义了一个已经存在的接口,这个接口需要适配。
  4. Client(客户端):与符合目标接口的对象协同工作。

应用场景

适配器模式适用于以下情况:

  • 当你想使用一个已经存在的类,而它的接口不符合你的需求时。
  • 当你想创建一个可重用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容)协同工作。
  • 当你需要使用几个现有的子类,但是不可能对每一个都进行子类化以匹配它们的接口。适配器可以调整父类的接口。

JavaScript 实现示例

假设有一个旧的电子邮件发送系统(OldEmailSender),它的接口与当前系统不兼容。我们需要使用适配器使其可以在新系统中使用。

// 被适配者
class OldEmailSender {
    send(email, content) {
        console.log(`Sending '${content}' to '${email}' via OldEmailSender`);
    }
}

// 目标接口
class NewEmailService {
    sendEmail(email, content) {
        throw new Error("This method needs to be implemented!");
    }
}

// 适配器
class EmailAdapter extends NewEmailService {
    constructor(oldEmailSender) {
        super();
        this.oldEmailSender = oldEmailSender;
    }

    sendEmail(email, content) {
        this.oldEmailSender.send(email, content);
    }
}

// 客户端代码
const oldEmailSender = new OldEmailSender();
const emailAdapter = new EmailAdapter(oldEmailSender);

emailAdapter.sendEmail("[email protected]", "Hello, this is a test email!");

在这个例子中,OldEmailSender 是一个已经存在的类,但它的接口与新系统中期望的接口不匹配。EmailAdapter 是一个适配器,它实现了新的邮件服务接口并在内部使用 OldEmailSender。客户端代码可以透明地使用 EmailAdapter 发送电子邮件,无需关心其内部是如何使用 OldEmailSender 的。这就是适配器模式的实用之处。

职责链模式

职责链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。在这个模式中,请求沿着一条链传递,直到一个对象处理它为止。

主要特点

  1. 请求传递:在职责链模式中,请求从一个对象传递到另一个对象,直到找到一个能够处理该请求的对象。
  2. 解耦发送者和接收者:发送者和接收者之间没有明确的静态链接,这增加了程序的灵活性。
  3. 动态组合:责任链可以动态组合。

角色和概念

  1. Handler(处理器):定义了处理请求的接口,并实现后续链的管理。
  2. ConcreteHandler(具体处理器):处理它所负责的请求,如果可以处理该请求,则处理;否则将该请求转发给它的后继者。
  3. Client(客户端):发起请求并将请求沿链传递。

应用场景

职责链模式适用于以下情况:

  • 当有多个对象可以处理一个请求时,哪个对象处理该请求运行时自动确定。
  • 当你想在不明确接收者的情况下,向多个对象中的一个提交一个请求时。
  • 当需要动态指定一组对象处理请求时。

JavaScript 实现示例

// 处理器接口
class Handler {
    constructor() {
        this.nextHandler = null;
    }

    setNextHandler(handler) {
        this.nextHandler = handler;
    }

    handleRequest(request) {
        if (this.nextHandler) {
            this.nextHandler.handleRequest(request);
        }
    }
}

// 具体处理器
class ConcreteHandler1 extends Handler {
    handleRequest(request) {
        if (request === 'R1') {
            console.log('ConcreteHandler1 handled the request');
        } else {
            super.handleRequest(request);
        }
    }
}

class ConcreteHandler2 extends Handler {
    handleRequest(request) {
        if (request === 'R2') {
            console.log('ConcreteHandler2 handled the request');
        } else {
            super.handleRequest(request);
        }
    }
}

// 客户端代码
const handler1 = new ConcreteHandler1();
const handler2 = new ConcreteHandler2();

handler1.setNextHandler(handler2);

handler1.handleRequest('R2'); // 输出:ConcreteHandler2 handled the request

在这个例子中,两个具体的处理器(ConcreteHandler1ConcreteHandler2)被创建,并形成一条链。当请求'R2'到达时,它首先被ConcreteHandler1接收,但因为ConcreteHandler1不能处理,所以它将请求传递给链上的下一个处理器ConcreteHandler2,后者处理这个请求。通过这种方式,职责链模式允许请求在多个对象之间传递,直到找到一个能够处理它的对象为止。

你可能感兴趣的:(javascript)