【重走编程路】设计模式概述(九) -- 观察者模式、迭代器模式


前言

行为型模式关注对象之间的交互以及如何分配职责,提供了一种定义对象之间的行为和职责的最佳方式。本章介绍创建型模式中的观察者模式和迭代器模式。


15. 观察者模式(Observer)

定义

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

问题

在软件开发中,经常需要实现一个对象(称为主题或目标)与多个对象(称为观察者)之间的通信机制。当主题对象的状态发生变化时,所有依赖于它的观察者对象都需要得到通知并自动更新。如果直接在主题对象和观察者对象之间建立硬编码的依赖关系,将会导致系统难以扩展和维护。观察者模式通过解耦主题对象和观察者对象之间的依赖关系,使得它们可以独立地变化和发展。

解决方案

观察者模式的核心思想是定义一种一对多的依赖关系,使得一个主题(通常称为被观察者)可以同时维护多个观察者,并在其状态改变时自动通知所有观察者。观察者模式通过以下几个步骤来解决问题:

  1. 定义主题接口:定义一个主题接口,它包含一个用于注册观察者对象的方法和一个用于通知所有观察者对象状态变化的方法。
  2. 实现主题类:实现主题接口,管理观察者对象的集合,并在状态发生变化时遍历集合,调用每个观察者的更新方法。
  3. 定义观察者接口:定义一个观察者接口,它包含一个更新方法,用于在接收到主题对象的通知时执行相应的操作。
  4. 实现观察者类:实现观察者接口,定义接收到通知后需要执行的具体操作。
#include <iostream>  
#include <vector>  
#include <algorithm>  
  
// 观察者接口  
class Observer {  
public:  
    virtual void Update(const std::string& message) = 0;  
    virtual ~Observer() {}  
};  
  
// 具体的观察者类  
class ConcreteObserver : public Observer {  
private:  
    std::string name;  
public:  
    ConcreteObserver(const std::string& name) : name(name) {}  
    void Update(const std::string& message) override {  
        std::cout << name << " received message: " << message << std::endl;  
    }  
};  
  
// 主题接口  
class Subject {  
public:  
    virtual void Attach(Observer* observer) = 0;  
    virtual void Detach(Observer* observer) = 0;  
    virtual void Notify(const std::string& message) = 0;  
    virtual ~Subject() {}  
};  
  
// 具体的主题类  
class ConcreteSubject : public Subject {  
private:  
    std::vector<Observer*> observers;  
  
public:  
    void Attach(Observer* observer) override {  
        observers.push_back(observer);  
    }  
  
    void Detach(Observer* observer) override {  
        auto it = std::find(observers.begin(), observers.end(), observer);  
        if (it != observers.end()) {  
            observers.erase(it);  
        }  
    }  
  
    void Notify(const std::string& message) override {  
        for (auto observer : observers) {  
            observer->Update(message);  
        }  
    }  
};  
  
int main() {  
    ConcreteSubject* subject = new ConcreteSubject();  
    Observer* observer1 = new ConcreteObserver("Observer1");  
    Observer* observer2 = new ConcreteObserver("Observer2");  
  
    subject->Attach(observer1);  
    subject->Attach(observer2);  
    subject->Notify("Hello, Observers!");  
  
    // 可以在需要时取消订阅  
    // subject->Detach(observer1);  
  
    delete observer1;  
    delete observer2;  
    delete subject;  
    return 0;  
}

应用场景

  1. 事件监听:如GUI组件之间的交互,当某个组件的状态发生变化时,需要通知其他组件进行更新。
  2. 消息发布/订阅系统:在消息中间件中,消息的发布者(主题)和订阅者(观察者)之间通过观察者模式进行解耦。
  3. 状态监控:在监控系统中,当被监控对象的状态发生变化时,需要通知相关的观察者进行响应。

优缺点

优点:

  • 解耦:观察者和主题之间的解耦使得它们可以独立地变化和发展。
  • 灵活性:可以动态地添加或删除观察者。
  • 复用性:主题和观察者接口是通用的,可以用于不同的场景。

缺点:

  • 性能问题:如果观察者数量过多,且状态更新频繁,可能会导致性能问题。
  • 消息顺序:如果多个观察者之间有依赖关系,且它们的更新顺序很重要,那么观察者模式可能无法满足这种需求。
  • 错误处理: 观察者模式通常没有内置的错误处理机制。如果某个观察者在更新过程中抛出异常,这可能会中断通知过程,甚至影响其他观察者的正常更新。为了解决这个问题,可以在观察者接口中定义错误处理策略,或者在主题类中捕获并处理来自观察者的异常。
  • 依赖管理:当观察者对象由主题对象创建时,可能会引入不必要的依赖关系。这违反了依赖倒置原则,即高层模块不应该依赖低层模块的具体实现。为了避免这种情况,可以使用依赖注入或工厂模式来创建观察者对象。

16. 迭代器模式(Iterator)

定义

迭代器模式提供了一种顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示的方法。迭代器模式将遍历聚合对象内部元素的责任封装在迭代器内部,使得外部代码可以通过迭代器来统一遍历不同的聚合对象。

问题

在软件开发中,经常需要遍历容器(如列表、集合等)中的元素。如果直接在容器类中实现遍历逻辑,将会导致容器类与遍历逻辑紧密耦合,增加系统的复杂性和维护难度。此外,不同的容器可能需要不同的遍历方式,如果直接在容器类中实现遍历,将难以实现遍历逻辑的复用。

解决方案

迭代器模式通过引入一个迭代器对象来封装遍历逻辑,解决了上面的问题。迭代器模式通过以下几个步骤来解决问题:

  1. 定义迭代器接口:定义一个迭代器接口,它包含遍历聚合对象所需的方法,如hasNext()(检查是否还有更多元素)、next()(返回下一个元素)等。
  2. 实现迭代器类:针对具体的聚合对象,实现迭代器接口,封装遍历逻辑。
  3. 在聚合对象中添加获取迭代器的方法:在聚合对象中添加一个方法,用于返回该对象的迭代器实例。
#include <iostream>  
#include <vector>  
  
// 迭代器接口  
class Iterator {  
public:  
    virtual bool HasNext() const = 0;  
    virtual int Next() = 0;  
    virtual ~Iterator() {}  
};  
  
// 具体的聚合对象  
class Collection {  
public:  
    virtual Iterator* CreateIterator() const = 0;  
    virtual ~Collection() {}  
};  
  
// 具体的迭代器实现  
class VectorIterator : public Iterator {  
private:  
    const std::vector<int>* vec;  
    size_t currentIndex;  
public:  
    VectorIterator(const std::vector<int>* vec) : vec(vec), currentIndex(0) {}  
  
    bool HasNext() const override {  
        return currentIndex < vec->size();  
    }  
  
    int Next() override {  
        return vec->at(currentIndex++);  
    }  
};  
  
// 具体的聚合对象实现  
class VectorCollection : public Collection {  
private:  
    std::vector<int> data;  
public:  
    void Add(int element) {  
        data.push_back(element);  
    }  
    Iterator* CreateIterator() const override {  
        return new VectorIterator(&data);  
    }  
};  
  
int main() {  
    VectorCollection collection;  
    collection.Add(1);  
    collection.Add(2);  
    collection.Add(3);  
  
    Iterator* iterator = collection.CreateIterator();  
    while (iterator->HasNext()) {  
        std::cout << iterator->Next() << " ";  
    }  
    std::cout << std::endl;  
  
    delete iterator; // 注意:在实际应用中,可能需要使用智能指针来管理资源  
    return 0;  
}

p.s. STL中也实现了迭代器类,就是这个迭代器模式的最好案例。

应用场景

  1. 访问一个聚合对象的内容而无需暴露它的内部表示。
  2. 支持对聚合对象的多种遍历。
  3. 为聚合对象提供统一的遍历接口。

优缺点

优点:

  • 提高代码的封装性:迭代器模式将遍历逻辑封装在迭代器内部,使得聚合对象可以专注于存储和管理元素,而不需要关心遍历逻辑。
  • 提高代码的复用性:不同的聚合对象可以使用相同的迭代器接口,使得遍历逻辑可以在不同的聚合对象之间复用。
  • 简化客户端代码:客户端代码可以通过统一的迭代器接口来遍历不同的聚合对象,简化了客户端代码。

缺点:

  • 增加系统的复杂性:在简单场景下,直接使用循环遍历可能更为简单直接。引入迭代器模式会增加系统的类和接口的数量,增加系统的复杂性。
  • 可能增加性能开销:在某些情况下,迭代器模式可能会引入额外的性能开销,如动态内存分配和虚函数调用等。然而,在大多数情况下,这种开销是可以接受的。

To be continued.

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值