Observer观察者模式

设计模式 2019-03-10 5636 字 2 浏览 点赞

前言

关于观察者模式我有以下几个问题想问:

  • 什么是观察者模式?
  • 为什么需要观察者模式(也就是什么情况下使用观察者模式)?
  • 观察者模式的优点是什么?
  • 观察者模式的缺点是什么?

模式定义

在《设计模式》一书中,对观察者模式做了如下释义:

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

观察者模式又叫发布-订阅模式,从这个角度看,会更容易理解上边的释义。我们希望发布者发布消息时,订阅人会被告知有消息需要更新,就像微博里边的关注功能,总会有红色的小点提醒你。

非观察模式

可以模拟微博大V和粉丝的场景。

现在需要完成两个基类,第一个是微博大V,我将其命名为BigV吧。从实际上看,大V先得有自己的微博名(name),还可以发表微博以示今天的心情(moodToday)。由于微博名在注册时候就需要设置(暂时不管修改的情况),因将名字赋值的任务交给构造函数。另一方面设置心情用setMoodToday()实现,查看心情用getMoodToday()。一个粗制滥造的大V成型了,像下面那样:

class BigV {
private:
    string name;
    string moodToday;
public:
    BigV(string name): name(name) {}
    void setMoodToday(const string words) { moodToday = words; }
    string getMoodToday() const { return moodToday; }
    string getName() const { return name; }
};

第二个基类是Fens。作为粉丝,他可以粉许多人,换句话说,一个粉丝可以关注n个大V,所以这里用vector<BigV*>存放大V们。focusOn()是粉丝的关注按钮(为使结构简单,不考虑取关),updateInfo()用来更新大V们的微博。因此得到基类如下:

class Fens {
private:
    string name;
    vector<BigV*> bigvs;
public:
    Fens(string name): name(name) {}
    void focusOn(BigV* bigv) { bigvs.push_back(bigv); }  /* 关注 */
    void updateInfo() {  /* 更新关注列表,获取所有大V的最新消息 */
        for(vector<BigV*>::iterator i = bigvs.begin();
            i != bigvs.end();
            i ++)
        {
            cout << name << "更新了 “" << (*i)->getMoodToday() 
                 << "” 的消息" << endl;
        }
    }
};

虽说网络上DISS杨幂已经成为政治正确,但无奈,我的喜好自己定。所以这里是杨幂的微博,以及她的三个粉丝。她将发表微博,而粉丝们取获取最新的微博消息:

class YangMi: public BigV {  /* 大V杨幂 */
public:
    YangMi(): BigV("杨幂") {}
};

/* 三个粉丝 */
class Bobby: public Fens {
public:
    Bobby(): Fens("bobby") {}
};

class Lily: public Fens {
public:
    Lily(): Fens("lily") {}
};

class Green: public Fens {
public:
    Green(): Fens("green") {}
};

int main() {
    /* 创建账号 */
    YangMi yangmi;
    Bobby bobby; Lily lily; Green green;

    /* 关注杨幂微博 */
    bobby.focusOn(&yangmi);
    lily.focusOn(&yangmi);
    green.focusOn(&yangmi);

    /* 杨幂更新微博 */
    yangmi.setMoodToday("这世界有太多‘他们说’定义的标签,但我只听自己说~");

    /* 粉丝主动刷新关注列表,获取杨幂的最新消息 */
    bobby.updateInfo();
    lily.updateInfo();
    green.updateInfo();

    return 0;
}
// 输出:
bobby更新了 “杨幂:这世界有太多‘他们说’定义的标签,但我只听自己说~” 的消息
lily更新了 “杨幂:这世界有太多‘他们说’定义的标签,但我只听自己说~” 的消息
green更新了 “杨幂:这世界有太多‘他们说’定义的标签,但我只听自己说~” 的消息

看输出好像没错,任务也完成,目标也达到,bobby、lily、green都知道了自己喜欢的明星的最新微博。但他们是主动获悉的,所以各自调用了updateInfo()。但是,他们怎么知道杨幂有没有更新微博呢?

在这个模型下,粉丝当然不知道,全靠猜。而观察者模式可以解决此类问题。

观察者模式

我们希望大V更新微博后可以通知粉丝:“有新消息了”。但并非所有大V都愿意——因为关注她的人并非都是真粉,有的就是来骂街——所以要不要告诉关注者们这里有新消息,选择权应该在具体的大V手上,但BigV应该提供接口:

class BigV {
protected:
    string name;
    string moodToday;
public:
    BigV(string name): name(name) {}
    virtual void setMoodToday(const string words) { moodToday = words; }
    string getMoodToday() const { return moodToday; }
    string getName() const { return name; }

    /* 提供接口 */
    virtual void addFens(Fens* fens) = 0;  
    virtual void notifyFens() = 0;
};

具体的大V——这里是YangMi,需要完成addFens()notifyFens()的定义。首先需要一个“粉丝池”,用来存放她的关注者们,微博更新后就去遍历这个池子,通知里面的粉丝微博更新了。addFens()的任务是把粉丝添加到这个池子中,而notifyFens()则是执行通知的动作。最后,重新定义setMoodToday()方法是在行使“要不要通知粉丝”的权力。完整YangMi如下:

class YangMi: public BigV {
protected:
    vector<Fens*> fenss;  /* 粉丝池 */
public:
    YangMi(): BigV("杨幂") {}

    virtual void setMoodToday(const string words)
        { BigV::setMoodToday(words); notifyFens(); }  /* 更新微博后,调用notifyFens通知粉丝 */
    virtual void addFens(Fens* fens) { fenss.push_back(fens); }  /* 添加粉丝到粉丝池中 */
    virtual void notifyFens();
};

void YangMi::notifyFens()
{
    /* 遍历粉丝池 */
    for (vector<Fens*>::iterator i = fenss.begin();
         i != fenss.end();
         i ++)
    {
        (*i)->updateInfo();  /* 更新消息 */
    }
}

Fens的改动小许多,只需要在关注大V的时候把他扔进粉丝池就好:

class Fens {
protected:
    string name;
    vector<BigV*> bigvs;
public:
    ...
    void focusOn(BigV* bigv) {
        bigvs.push_back(bigv);
        bigv->addFens(this);  /* 将粉丝添加到大V的粉丝池里 */
    }
    ...

主函数调用:

int main() {
    YangMi yangmi;
    Bobby bobby; Lily lily; Green green;

    bobby.focusOn(&yangmi);
    lily.focusOn(&yangmi);
    green.focusOn(&yangmi);

    yangmi.setMoodToday("这世界有太多‘他们说’定义的标签,但我只听自己说~");

    return 0;
}
// 输出:
boby更新了 “杨幂:这世界有太多‘他们说’定义的标签,但我只听自己说~” 的消息
lily更新了 “杨幂:这世界有太多‘他们说’定义的标签,但我只听自己说~” 的消息
green更新了 “杨幂:这世界有太多‘他们说’定义的标签,但我只听自己说~” 的消息

关系梳理

非观察模式:

观察模式:


非观察者模式中,YangMi和Fens看起来没什么关系;但观察者模式中,FensA、FensB等关联了YangMi。

模式结构

观察者模式包含如下角色:

  • Subject: 目标(对应BigV
  • ConcreteSubject: 具体目标(对应YangMi
  • Observer: 观察者(对应Fens
  • ConcreteObserver: 具体观察者(对应FensA、FensB……

总结

观察者模式优点:

  • 观察者模式可以实现表示层(观察者)和数据逻辑层(目标)的分离,并定义了稳定的消息更新传递机制,抽象了更新接口(被目标的notify代替),使得可以有各种各样不同的表示层作为具体观察者角色;
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合;
  • 观察者模式支持广播通信;
  • 观察者模式符合“开闭原则”的要求。

观察者模式缺点:

  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
  • 如果在观察者和观察目标之间有循环依赖的话,可能导致系统崩溃;
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

感谢



本文由 Guan 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论