首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >面向杂志订阅的C++观测器设计模式

面向杂志订阅的C++观测器设计模式
EN

Code Review用户
提问于 2022-02-12 02:14:11
回答 3查看 232关注 0票数 4

我尝试了一种观察者设计模式(我知道使用命名空间std并不好)。我对设计图案很陌生。所有内容都与类定义一致。如果我做错了什么,或者有什么需要改进的地方,请告诉我。订阅者(观察者)需要存储来自更新功能的信息吗?另外,operator==是否可以比较和查找具有名称的正确订阅者,还是应该使用getname()?

基地:

代码语言:javascript
复制
class Subscriber
{
    string name;
public:
    Subscriber(string name) : name(name) {}
    virtual void update(string title, double price) = 0;
    string getname() const {return name;}
    bool operator==(string name) {
        if (this->name == name)
            return true;
        return false;
    }
    virtual ~Subscriber() {};
};

衍生:

代码语言:javascript
复制
class PaidSubscriber : public Subscriber
{
public:
    PaidSubscriber(string name) : Subscriber(name) {}
    void update(string title, double price) {
        cout << "\nPaid Subscriber: " << getname() << endl;
        cout << "New Issue: " << title << endl;
        if (price > 0)
            cout << "Price: $" << std::fixed << setprecision(2) << price << endl;
        else 
            cout << "Price: FREE";
    };
};

class VIPSubscriber : public Subscriber
{
public:
    VIPSubscriber(string name) : Subscriber(name) {}
    void update(string title, double price) {
        cout << "\nVIP Subscriber: " << getname() << endl;
        cout << "New Issue: " << title << endl;
        if (price-10 > 0)
            cout << "Price: $" << std::fixed << setprecision(2) << price-10 << endl;
        else 
            cout << "Price: FREE";
    };
};

class FreeSubscriber : public Subscriber
{
public:
    FreeSubscriber(string name) : Subscriber(name) {}
    void update(string title, double price) {
        cout << "\nFree Subscriber: " << getname() << endl;
        cout << "New Issue: " << title;
        if (price > 0) {
            cout << " (preview only)" << endl;
            cout << "Upgrade to Paid Subscriber!" << endl;
        }
        else 
            cout << endl;
    };

};

主题:

代码语言:javascript
复制
class Magazine
{
    string currissue;
    double currprice;
    
    listsublist;
public:
    enum SubscriberType {SUB_FREE, SUB_PAID, SUB_VIP};
    Magazine(string currissue, double currprice) : currissue(currissue), currprice(currprice) {}
    void subscribe(string name, SubscriberType subtype) {
        Subscriber *sub; 
        switch(subtype) {
            case SUB_FREE: 
                sub = new FreeSubscriber(name);
                break;
            case SUB_PAID:
                sub = new PaidSubscriber(name);
                break;
            case SUB_VIP:
                sub = new VIPSubscriber(name);
                break;
            default:
                return;
        }
        sublist.push_back(sub);
    }
    void unsubscribe(string name) 
    {
        for (auto it = sublist.begin(); it != sublist.end(); it++) {
            if (**it == name) {
                sublist.erase(it);
                break;
            }
        }
    }
    void changeissue(string name, double price) {
        this->currissue = name;
        this->currprice = price;
    }
    void notify()
    {
        for (auto sub : sublist)
            sub->update(currissue, currprice);
    }
    ~Magazine() {
        for (auto sub : sublist)
            delete sub;
    }
};

主要:

代码语言:javascript
复制
int main()
{
    Magazine nationalgeographic("Lions", 5.34);
    nationalgeographic.subscribe("Ivy Parks", Magazine::SUB_PAID);
    nationalgeographic.subscribe("Mike Gopher", Magazine::SUB_FREE);
    nationalgeographic.subscribe("Stan Shunpike", Magazine::SUB_VIP);
    nationalgeographic.notify();

    nationalgeographic.changeissue("Elephants", 15.23);
    nationalgeographic.notify();
}
EN

回答 3

Code Review用户

回答已采纳

发布于 2022-02-12 11:32:07

回答您的问题

我知道using namespace std不好,请告诉我,如果我做错了什么,或者有什么可以改进的。

好吧,您已经自己说过了:不要用using namespace std,特别是对于要放入头文件的任何内容。

订阅者(观察者)需要存储来自更新功能的信息吗?

这取决于观察者需要做什么。如果您只是想打印一些信息,如您的例子,没有必要存储任何东西。

另外,operator==是否可以比较和查找正确的订阅者和名称,还是应该使用getname()

我建议使用getname()。主要使用operator==来比较两个相同类型的对象。所以我会把它改写成:

代码语言:javascript
复制
bool operator==(const Subscriber& other) const {
    return this->name == other.name;
}

但是,这在您的代码中没有多大用处,所以您也可以考虑删除它。

直接返回布尔

而不是写:

代码语言:javascript
复制
if (foo)
    return true;
else
    return false;

您可以这样写:

代码语言:javascript
复制
return foo;

使用'\n‘代替std::endl

更喜欢使用'\n‘而不是std::endl;后者等价于前者,但也强制输出被刷新,这通常不是必要的,而且会影响性能。

在适当的情况下通过const引用传递字符串std::string可以是在堆上分配内存的大型对象。通过将它们按值传递给其他函数,必须创建一个副本。如果您只需要从字符串中读取,则该副本是不必要的,只会降低性能。为了避免这种情况,可以通过const引用传递它们。例如: class Subscriber { std::string name; public: Subscriber(const std::string& name) : name(name) {} ... }; 如果您可以使用C++17,那么更好的做法是使用std::string_view传递字符串。

避免手动newdelete

手动调用newdelete可以使错误很容易滑到代码中。例如,如果您取消订阅一个Subscriber,您只删除它在sublist中的指针,就会忘记实际删除Subscriber对象。因此,尽可能避免手动newdelete。对于订阅者列表,您可以将其作为std::unique_ptrS的列表:

代码语言:javascript
复制
std::list> sublist;

您的subscribe()函数可以更改为:

代码语言:javascript
复制
void subscribe(const std::string& name, SubscriberType subtype) {
    std::unique_ptr sub; 

    switch (subtype) {
    case SUB_FREE: 
        sub = std::make_unique(name);
        break;
    ...
    }

    sublist.push_back(std::move(sub));
}

虽然有更好的解决办法,我将在下面说明。

notify()中,需要确保使用引用:

代码语言:javascript
复制
for (auto& sub: sublist)
    sub->update(currissue, currprice);

~Magazine()现在可以被完全移除了,当sublist被摧毁时,它会反过来摧毁所有的std::unique_ptrs,而后者又会把它们所持有的物体放在delete上。

简化subscribe()

subscribe()的问题在于,它不仅必须向列表中添加订阅服务器,还需要基于subtype创建一个新的订阅服务器对象。如果您添加了许多不同的订阅者类型,这意味着这个功能也会增长很多。保持这个函数很小是很好的,并且只需要处理向列表中添加一个订阅者。理想的情况是:

代码语言:javascript
复制
void subscribe(std::unique_ptr&& sub) {
    sublist.push_back(std::move(sub));
}

然后,在main()中,您必须编写:

代码语言:javascript
复制
nationalgeographic.subscribe(std::make_unique("Ivy Parks"));

在某种程度上,这只会将问题转移到调用者身上。使调用程序更简单的一种可能方法是仍然让subscribe()创建Subscriber对象,然后使其成为一个template,以避免代码重复:

代码语言:javascript
复制
template
void subscribe(const std::string& name) {
    sublist.push_back(std::make_unique(name));
};

然后打电话的人看起来是:

代码语言:javascript
复制
nationalgeographic.subscribe("Ivy Parks");

合并changeissue()notify()

无论什么时候杂志发行了新的杂志,你总是想通知订阅者。因此,有两个您需要调用的独立函数是没有意义的。相反,我要创建一个函数:

代码语言:javascript
复制
void release_issue(const std::string& issue, double price) {
    for (auto& sub: sublist)
        sub->update(issue, price);
}

这样,您还不再需要成员变量currissuecurrprice,并且可以删除构造函数。你会像这样使用它:

代码语言:javascript
复制
Magazine nationalgeographic;
nationalgeographic.subscribe("Ivy Parks");
...
nationalgeograpic.release_issue("Lions", 5.34);
nationalgeograpic.release_issue("Elephants", 15.23);
票数 5
EN

Code Review用户

发布于 2022-02-12 21:53:37

如前所述,您应该根据订阅者的指针而不是名称添加和删除订阅服务器。也可以使用set来代替om列表。所以在这里,我要做的改变是:

代码语言:javascript
复制
set subscribers;

void subscribe(Subscriber* subscriber) //add Subscriber
{
  subscribers.insert(subscriber);
}

void unsubscribe(Subscriber* subscriber) //remove Subscriber
{
  subscribers.erase(subscriber);
}

我在这里也只使用C风格的指针,但使用某种智能指针可能是个好主意,而不是使用哪个智能指针最适合这里。如果按名称删除订阅服务器,则可能删除错误的订阅服务器。如果您编写此示例以熟悉设计模式,则可以使用另一种模式来创建具体的订阅服务器-- Factory方法模式。

票数 0
EN

Code Review用户

发布于 2022-05-09 18:02:22

请考虑将来是否可能有更多的订户。若要避免或对现有代码的影响最小,可以使用工厂模式创建订阅服务器,如下所示:

代码语言:javascript
复制
class SubscriberFactory {
 public:
   static Subscriber* createSubscriber(SubscriberType subtype, string name) {
     switch(subtype) {
        case SUB_FREE: 
            return new FreeSubscriber(name);
        case SUB_PAID:
            return new PaidSubscriber(name);
        case SUB_VIP:
            return new VIPSubscriber(name);             
        default:
            /* This way you also adhere to RAII. */
            throw std::invalid_argument("Invalid subscriber type");
            return nullptr; 
   }
};

这样,“杂志”::subscribe()将如下所示:

代码语言:javascript
复制
    class Magazine {
       string currissue;
       double currprice;
       listsublist;
      public:
         void subscribe(string name, SubscriberType subtype) {
           Subscriber *sub = SubscriberFactory::createSubscriber(subtype, name);
           sublist.push_back(sub);
         }
    };
票数 0
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/274014

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档