观察者模式 Observer pattern

122 阅读4分钟

观察者模式 Observer pattern

定义

定义对象的一种一对多(变化)的依赖关系, 以便当一个对象(Subject)的状态发生改变时, 所有依赖于它的对象都得到通知并自动更新

设计模式作者说的话我反正是经常看不懂不过没事这个往下看还挺好理解

案例1

发现不管看啥观察者模式的案例必有这个气象台, 所以我们还是讲气象台

气象站发布气象资料给数据中心, 数据中心经过处理, 将气象信息更新到两个不同的终端(A和B)

分析

稳定点: 气象站发布气象资料给数据中心这个流程是肯定没啥变化的, 有变化他也得是没变化, 不要杠

变化点: 数据中心处理数据的流程可能汇编, 更新到不同的终端的数量会变化, 显示的信息也可能是变化的

用设计模式之前

class DisplayA {
public:
    void Show(float temperature);
};

class DisplayB {
public:
    void Show(float temperature);
};

class DisplayC {
public:
    void Show(float temperature);
};

class WeatherData {};

class DataCenter {
public:
    float CalcTemperature() {
        WeatherData* data = GetWeatherData();
        // ...
        float temper /* = */;
        return temper;
    }

private:
    WeatherData* GetWeatherData(); // 不同的方式
};

int main() {
    DataCenter* center = new DataCenter;
    DisplayA* da = new DisplayA;
    DisplayB* db = new DisplayB;
    DisplayC* dc = new DisplayC;

    float temper = center->CalcTemperature();
    da->Show(temper);
    db->Show(temper);
    dc->Show(temper);

    return 0;

}

如果不断的增加接受终端, 在main()里就不断的添加代码, 会导致main不断地膨胀和臃肿

要面向接口编程

使用设计模式之后

变成了数据中心主动去推送给终端, 终端去监听数据

// 接口
class IDisplay {
public:
    virtual void Show(float temperature) = 0;
    virtual ~IDisplay() {}
};

class DisplayA : public IDisplay {
public:
    virtual void Show(float temperature);
private:
    void Suggestion(); // 这样子来不同的终端可以做自己的定义
};

class DisplayB : public IDisplay {
public:
    virtual void Show(float temperature);
};

class WeatherData {};

class DataCenter {
public:
    void Attach(IDisplay* ob);
    void Detach(IDisplay* ob);
    void Notify() {
        float temper = CalcTemperature();
        for (auto iter = obs.begin(); iter != obs.end(); iter++) {
            (*iter)->Show(temper);
        }
    }
// 接口隔离
private:
    virtual WeatherData* GetWeatherData();
    virtual float CalcTemperature() {
        WeatherData* data = GetWeatherData();
        // ...
        float temper /* = */;
        return temper;
    }

    std::vector<IDisplay*> obs;
};

int main() {
    DataCenter* center = new DataCenter;
    IDisplay* da = new DisplayA;
    IDisplay* db = new DisplayB;

    center->Attach(da);
    center->Attach(db);

    center->Notify();


    center->Detach(db);
    center->Notify();
    
    return 0;
}

这个是能很清楚的看出使用了之后的优点

案例2

背景

假如你有两种类型的对象: 顾客和 商店. 顾客对某个特定品牌的产品非常感兴趣(例如最新型号的 iPhone 手机),  而该产品很快将会在商店里出售

顾客可以每天来商店看看产品是否到货; 但如果商品尚未到货时, 绝大多数来到商店的顾客都会空手而归

这个图是网上找的啊, 如果侵权了我就删了 image.png

另一方面, 每次新产品到货时, 商店可以向所有顾客发送邮件(可能会被视为垃圾邮件)

这样, 部分顾客就无需反复前往商店了, 但也可能会惹恼对新产品没有兴趣的其他顾客

我们似乎遇到了一个矛盾: 要么让顾客浪费时间检查产品是否到货, 要么让商店浪费资源去通知没有需求的顾客。

解决方案

拥有一些值得关注的状态的对象通常被称为目标, 由于它要将自身的状态改变通知给其他对象, 我们也将其称为发布者

所有希望关注发布者状态变化的其他对象被称为订阅者 

观察者模式建议你为发布者类添加订阅机制, 让每个对象都能订阅或取消订阅发布者事件流

image.png

要点

  • 观察者模式使得我们可以独立改变目标与观察者, 从而使二者之间的关系松耦合
  • 观察者自己决定是否订阅通知, 目标对象并不关注谁订阅
  • 观察者不依赖通知顺序, 目标对象也不知道通知顺序
  • 常用于基于事件的ui框架中, 也是MVC的组成部分
  • 常用在分布式系统中, actor框架

分布式锁中的公平锁和非公平锁和发布订阅也很类似

公平锁(自旋锁)用的就是发布订阅, 非公平锁(互斥锁)就是不断请求这个锁是否被释放

自旋锁(空转cpu), 不断地去检测是否可用

lock(mutex);
// 临界资源
unlock(mutex);