• 7种设计模式


    观察者模式 和 发布者/订阅者模式 区别是什么?

    观察者模式(Observer Pattern)和发布者/订阅者模式(Publish/Subscribe Pattern,也称为事件模型)是两种常见的软件设计模式,用于实现对象间的通信和消息传递。尽管它们的目标相似,但在实现方式和使用方式上存在一些区别。

    观察者模式是一种一对多的依赖关系,其中一个对象(称为"主题"或"被观察者")维护一个观察者列表,并在其状态发生变化时通知所有观察者。观察者模式根据被观察者的状态变化来触发通知。观察者模式的关键在于被观察者和观察者之间的直接关联。

    发布者/订阅者模式是一种解耦的模式,其中发布者(也称为"主题"或"消息中心")和订阅者之间没有直接的关联。发布者负责发布消息,而订阅者负责订阅感兴趣的消息类型,并在发布者发布相关消息时接收到通知。发布者/订阅者模式通过引入一个中间层,使发布者和订阅者避免了直接的依赖关系。

    主要区别如下:

    1. 直接关联 vs 解耦:观察者模式中的被观察者和观察者之间有直接的依赖关系,而发布者/订阅者模式中发布者和订阅者之间没有直接的依赖关系,通过一个中间层进行交互。

    2. 通知方式:在观察者模式中,被观察者直接调用观察者的方法进行通知。而在发布者/订阅者模式中,发布者将消息发布到一个中心或消息队列,订阅者从中心订阅所需的消息。

    3. 灵活性和扩展性:发布者/订阅者模式更具灵活性和扩展性,因为发布者和订阅者之间没有直接的依赖关系,可以添加新的发布者或订阅者而不影响现有的通信机制。而观察者模式中的被观察者和观察者之间的直接关系可能导致更高的耦合。

    需要注意的是,尽管这两种模式有区别,但它们的目标都是实现对象之间的松耦合通信。在实际开发中,根据具体的需求和设计上下文,可以选择使用观察者模式或发布者/订阅者模式。

    1. 工厂模式

    优点:封装了对象的创建过程,降低了耦合性,提供了灵活性和可扩展性。
    缺点:增加了代码的复杂性,需要创建工厂类。
    适用场景:当需要根据不同条件创建不同对象时,或者需要隐藏对象创建的细节时,可以使用工厂模式。
    案例:React 中的组件工厂

    class Button {
      constructor(text) {
        this.text = text;
      }
      render() {
        console.log(`Rendering button with text: ${this.text}`);
      }
    }
    
    class ButtonFactory {
      createButton(text) {
        return new Button(text);
      }
    }
    
    const factory = new ButtonFactory();
    const button = factory.createButton('Submit');
    button.render(); // Output: Rendering button with text: Submit
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.单例模式

    优点:确保一个类只有一个实例,节省系统资源,提供全局访问点。
    缺点:可能引入全局状态,不利于扩展和测试。
    适用场景:当需要全局唯一的对象实例时,例如日志记录器、全局配置对象等,可以使用单例模式。
    案例:管理全局状态的状态管理库(如Vuex、Redux)

    class Logger {
      constructor() {
        if (Logger.instance) {
          return Logger.instance;
        }
        Logger.instance = this;
      }
      log(message) {
        console.log(`Logging: ${message}`);
      }
    }
    
    const logger1 = new Logger();
    const logger2 = new Logger();
    
    console.log(logger1 === logger2); // Output: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    3. 观察者模式

    优点:实现了对象之间的松耦合,支持广播通信,当一个对象状态改变时,可以通知依赖它的其他对象进行更新。
    缺点:可能导致性能问题和内存泄漏,需要合理管理观察者列表。
    适用场景:当需要实现对象之间的一对多关系,一个对象的改变需要通知其他多个对象时,可以使用观察者模式。
    案例:Vue.js 或 React 的数据响应系统

    class Subject {
      constructor() {
        this.observers = [];
      }
      addObserver(observer) {
        this.observers.push(observer);
      }
      removeObserver(observer) {
        const index = this.observers.indexOf(observer);
        if (index !== -1) {
          this.observers.splice(index, 1);
        }
      }
      notify(message) {
        this.observers.forEach((observer) => observer.update(message));
      }
    }
    
    class Observer {
      update(message) {
        console.log(`Received message: ${message}`);
      }
    }
    
    const subject = new Subject();
    const observer1 = new Observer();
    const observer2 = new Observer();
    
    subject.addObserver(observer1);
    subject.addObserver(observer2);
    subject.notify('Hello, observers!'); // Output
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    4.发布订阅模式

    优点:解耦了发布者和订阅者,使它们可以独立变化。增加了代码的灵活性和可维护性。
    缺点:可能会导致发布者过度发布消息,造成性能问题。订阅者需要订阅和取消订阅相关的逻辑。
    适用场景:当存在一对多的关系,一个对象的状态变化需要通知多个其他对象时,可以使用发布订阅模式。

    class PubSub {
        constructor() {
            this.subscribers = {};
        }
        subscribe(event, callback) {
            if (!this.subscribers[event]) {
                this.subscribers[event] = [];
            }
            this.subscribers[event].push(callback);
        }
        unsubscribe(event, callback) {
            const subscribers = this.subscribers[event];
            if (subscribers) {
                this.subscribers[event] = subscribers.filter(cb => cb !== callback);
            }
        }
        publish(event, data) {
            const subscribers = this.subscribers[event];
            if (subscribers) {
                subscribers.forEach(callback => callback(data));
            }
        }
    }
    const pubsub = new PubSub(); // 创建发布订阅对象
    const callback1 = data => console.log('Subscriber 1:', data);
    const callback2 = data => console.log('Subscriber 2:', data);
    pubsub.subscribe('event1', callback1); // 订阅事件
    pubsub.subscribe('event1', callback2); // 订阅事件
    pubsub.publish('event1', 'Hello, world!'); // 发布事件
    pubsub.unsubscribe('event1', callback2); // 取消订阅事件
    pubsub.publish('event1', 'Hello again!'); // 再次发布事件
    /**
     * Subscriber 1: Hello, world!
       Subscriber 2: Hello, world!
       Subscriber 1: Hello again!
     */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 在上述示例中,PubSub 是发布订阅的实现类,它维护一个订阅者列表 subscribers,用于存储不同事件的订阅者列表。通过 subscribe 方法订阅事件,将回调函数添加到对应事件的订阅者列表中;通过 unsubscribe 方法取消订阅事件,从对应事件的订阅者列表中移除回调函数;通过 publish 方法发布事件,遍历对应事件的订阅者列表,依次执行回调函数。通过发布订阅模式,发布者和订阅者之间解耦,可以实现松散耦合的组件间通信。

    • 发布订阅模式适用于许多场景,如事件驱动的系统、消息队列、UI组件间的通信等,可以实现组件之间的解耦和灵活性。

    • 发布订阅模式(Publish-Subscribe Pattern)和观察者模式(Observer Pattern)是两种常见的设计模式,它们有一些相似之处,但也存在一些区别。

    相似之处:

    • 都用于实现对象之间的消息通信和事件处理。
    • 都支持解耦,让发布者和订阅者(观察者)之间相互独立。

    区别:

    • 关注点不同:观察者模式关注的是一个主题对象(被观察者)和多个观察者对象之间的关系。当主题对象的状态发生变化时,它会通知所有观察者对象进行更新。而发布订阅模式关注的是发布者和订阅者之间的关系,发布者将消息发送到一个中心调度器(或者称为事件总线),然后由调度器将消息分发给所有订阅者。
    • 中间件存在与否:发布订阅模式通常需要一个中间件(调度器或事件总线)来管理消息的发布和订阅,这样发布者和订阅者之间的通信通过中间件进行。而观察者模式则直接在主题对象和观察者对象之间进行通信,没有中间件的参与。
    • 松散耦合程度不同:观察者模式中,主题对象和观察者对象之间是直接关联的,主题对象需要知道每个观察者对象的存在。而在发布订阅模式中,发布者和订阅者之间并不直接关联,它们只与中间件进行通信,发布者和订阅者之间的耦合更加松散。

    观察者模式示例:

    class Subject {
      constructor() {
        this.observers = [];
      }
      addObserver(observer) {
        this.observers.push(observer);
      }
      removeObserver(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.addObserver(observer1);
    subject.addObserver(observer2);
    
    // 发送通知
    subject.notify('Hello, observers!');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    发布订阅模式示例:

    class EventBus {
      constructor() {
        this.subscribers = {};
      }
      subscribe(event, callback) {
        if (!this.subscribers[event]) {
          this.subscribers[event] = [];
        }
        this.subscribers[event].push(callback);
      }
      unsubscribe(event, callback) {
        const subscribers = this.subscribers[event];
        if (subscribers) {
          this.subscribers[event] = subscribers.filter(cb => cb !== callback);
        }
      }
      publish(event, data) {
            const subscribers = this.subscribers[event];
          if (subscribers) {
            subscribers.forEach(callback => callback(data));
          }
        }
      }
    
      // 创建事件总线对象
      const eventBus = new EventBus();
    
      // 订阅事件
      eventBus.subscribe('message', data => {
        console.log('Received message:', data);
      });
    
      // 发布事件
      eventBus.publish('message', 'Hello, subscribers!');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    在上述示例中,观察者模式中的Subject类相当于发布订阅模式中的EventBus类,Observer类相当于订阅者(观察者),notify方法相当于publish方法,update方法相当于订阅者接收到事件后的回调函数。

    观察者模式和发布订阅模式都是常见的用于实现事件处理和消息通信的设计模式,根据实际场景和需求选择合适的模式进行使用。观察者模式更加简单直接,适用于一对多的关系,而发布订阅模式更加灵活,可以支持多对多的关系,并且通过中间件来解耦发布者和订阅者。

    5. 原型模式:

    • 优点:通过克隆现有对象来创建新对象,避免了频繁的对象创建过程,提高了性能。
    • 缺点:需要正确设置原型对象和克隆方法,可能引入深拷贝或浅拷贝的问题。
      适用场景:当创建对象的成本较大且对象之间相似度较高时,可以使用原型模式来复用已有对象。
    class Shape {
      constructor() {
        this.type = '';
      }
      clone() {
        return Object.create(this);
      }
      draw() {
        console.log(`Drawing a ${this.type}`);
      }
    }
    
    const circlePrototype = new Shape();
    circlePrototype.type = 'Circle';
    
    const squarePrototype = new Shape();
    squarePrototype.type = 'Square';
    
    const circle = circlePrototype.clone();
    circle.draw(); // Output: Drawing a Circle
    
    const square = squarePrototype.clone();
    square.draw(); // Output: Drawing a Square
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    6. 装饰者模式(Decorator Pattern)

    • 优点:动态地给对象添加新的功能,避免了使用子类继承的方式导致类爆炸的问题。
    • 缺点:增加了代码的复杂性,需要理解和管理装饰器的层次结构。
      适用场景:当需要在不修改现有对象结构的情况下,动态地添加功能或修改行为时,可以使用装饰者模式。
      案例:React 中的高阶组件 (Higher-Order Components)
    class Component {
      operation() {
        console.log('Component operation');
      }
    }
    class Decorator {
      constructor(component) {
        this.component = component;
      }
      operation() {
        this.component.operation();
      }
    }
    
    class ConcreteComponent extends Component {
      operation() {
        console.log('ConcreteComponent operation');
      }
    }
    
    class ConcreteDecoratorA extends Decorator {
      operation() {
        super.operation();
        console.log('ConcreteDecoratorA operation');
      }
    }
    
    class ConcreteDecoratorB extends Decorator {
      operation() {
        super.operation();
        console.log('ConcreteDecoratorB operation');
      }
    }
    
    const component = new ConcreteComponent();
    const decoratorA = new ConcreteDecoratorA(component);
    const decoratorB = new ConcreteDecoratorB(decoratorA);
    
    decoratorB.operation();
    // Output:
    // Component operation
    // ConcreteComponent operation
    // ConcreteDecoratorA operation
    // ConcreteDecoratorB operation
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    7. 适配器模式

    • 优点:允许不兼容接口的对象协同工作,提高代码的复用性和灵活性。
    • 缺点:增加了代码的复杂性,需要理解和管理适配器的转换过程。
      适用场景:当需要将一个类的接口转换成客户端所期望的另一个接口时,可以使用适配器模式。
      案例:使用不同的 UI 库时的适配器模式

    示例代码:

    class Target {
      request() {
        console.log('Target request');
      }
    }
    
    class Adaptee {
      specificRequest() {
        console.log('Adaptee specificRequest');
      }
    }
    
    class Adapter extends Target {
      constructor(adaptee) {
        super();
        this.adaptee = adaptee;
      }
      request() {
        this.adaptee.specificRequest();
      }
    }
    
    const target = new Target();
    target.request();
    // Output: Target request
    
    const adaptee = new Adaptee();
    const adapter = new Adapter(adaptee);
    adapter.request();
    // Output: Adaptee specificRequest
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在上述示例中,Target 定义了客户端所期望的接口,Adaptee 是一个已有的类,它的接口与 Target 不兼容。适配器 Adapter 继承自 Target,并在其内部持有一个 Adaptee 的引用,通过适配器的 request 方法调用 AdapteespecificRequest 方法,从而实现了对不兼容接口的适配。客户端可以通过调用适配器的 request 方法来使用 Adaptee 的功能。

    适配器模式可以用于许多场景,例如在使用第三方库时需要将其接口转换成符合自己代码规范的接口,或者在对旧系统进行重构时需要兼容旧代码和新代码之间的差异。

  • 相关阅读:
    Go/Golang语言学习实践[回顾]教程09--学习成绩统计的示例【上】
    FPGA之多路复选器1
    USB母座引脚定义
    学习ASP.NET Core Blazor编程系列十三——路由(完)
    JAVA计算机毕业设计基于的测试项目管理平台Mybatis+系统+数据库+调试部署
    pandas 学习的第一天
    保姆级python安装教程
    点云从入门到精通技术详解100篇-基于点云数据的机器人动态分拣(续)
    [JavaScript]递归,深浅拷贝,处理this指向,异常处理
    Java:文件相关操作
  • 原文地址:https://blog.csdn.net/formylovetm/article/details/133770152