我尝试了一种观察者设计模式(我知道使用命名空间std并不好)。我对设计图案很陌生。所有内容都与类定义一致。如果我做错了什么,或者有什么需要改进的地方,请告诉我。订阅者(观察者)需要存储来自更新功能的信息吗?另外,operator==是否可以比较和查找具有名称的正确订阅者,还是应该使用getname()?
基地:
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() {};
};衍生:
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;
};
};主题:
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;
}
};主要:
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();
}发布于 2022-02-12 11:32:07
我知道
using namespace std不好,请告诉我,如果我做错了什么,或者有什么可以改进的。
好吧,您已经自己说过了:不要用using namespace std,特别是对于要放入头文件的任何内容。
订阅者(观察者)需要存储来自更新功能的信息吗?
这取决于观察者需要做什么。如果您只是想打印一些信息,如您的例子,没有必要存储任何东西。
另外,
operator==是否可以比较和查找正确的订阅者和名称,还是应该使用getname()?
我建议使用getname()。主要使用operator==来比较两个相同类型的对象。所以我会把它改写成:
bool operator==(const Subscriber& other) const {
return this->name == other.name;
}但是,这在您的代码中没有多大用处,所以您也可以考虑删除它。
而不是写:
if (foo)
return true;
else
return false;您可以这样写:
return foo;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传递字符串。new和delete手动调用new和delete可以使错误很容易滑到代码中。例如,如果您取消订阅一个Subscriber,您只删除它在sublist中的指针,就会忘记实际删除Subscriber对象。因此,尽可能避免手动new和delete。对于订阅者列表,您可以将其作为std::unique_ptrS的列表:
std::list> sublist;您的subscribe()函数可以更改为:
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()中,需要确保使用引用:
for (auto& sub: sublist)
sub->update(currissue, currprice);~Magazine()现在可以被完全移除了,当sublist被摧毁时,它会反过来摧毁所有的std::unique_ptrs,而后者又会把它们所持有的物体放在delete上。
subscribe()subscribe()的问题在于,它不仅必须向列表中添加订阅服务器,还需要基于subtype创建一个新的订阅服务器对象。如果您添加了许多不同的订阅者类型,这意味着这个功能也会增长很多。保持这个函数很小是很好的,并且只需要处理向列表中添加一个订阅者。理想的情况是:
void subscribe(std::unique_ptr&& sub) {
sublist.push_back(std::move(sub));
}然后,在main()中,您必须编写:
nationalgeographic.subscribe(std::make_unique("Ivy Parks"));在某种程度上,这只会将问题转移到调用者身上。使调用程序更简单的一种可能方法是仍然让subscribe()创建Subscriber对象,然后使其成为一个template,以避免代码重复:
template
void subscribe(const std::string& name) {
sublist.push_back(std::make_unique(name));
};然后打电话的人看起来是:
nationalgeographic.subscribe("Ivy Parks");changeissue()和notify()无论什么时候杂志发行了新的杂志,你总是想通知订阅者。因此,有两个您需要调用的独立函数是没有意义的。相反,我要创建一个函数:
void release_issue(const std::string& issue, double price) {
for (auto& sub: sublist)
sub->update(issue, price);
}这样,您还不再需要成员变量currissue和currprice,并且可以删除构造函数。你会像这样使用它:
Magazine nationalgeographic;
nationalgeographic.subscribe("Ivy Parks");
...
nationalgeograpic.release_issue("Lions", 5.34);
nationalgeograpic.release_issue("Elephants", 15.23);发布于 2022-02-12 21:53:37
如前所述,您应该根据订阅者的指针而不是名称添加和删除订阅服务器。也可以使用set来代替om列表。所以在这里,我要做的改变是:
set subscribers;
void subscribe(Subscriber* subscriber) //add Subscriber
{
subscribers.insert(subscriber);
}
void unsubscribe(Subscriber* subscriber) //remove Subscriber
{
subscribers.erase(subscriber);
}我在这里也只使用C风格的指针,但使用某种智能指针可能是个好主意,而不是使用哪个智能指针最适合这里。如果按名称删除订阅服务器,则可能删除错误的订阅服务器。如果您编写此示例以熟悉设计模式,则可以使用另一种模式来创建具体的订阅服务器-- Factory方法模式。
发布于 2022-05-09 18:02:22
请考虑将来是否可能有更多的订户。若要避免或对现有代码的影响最小,可以使用工厂模式创建订阅服务器,如下所示:
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()将如下所示:
class Magazine {
string currissue;
double currprice;
listsublist;
public:
void subscribe(string name, SubscriberType subtype) {
Subscriber *sub = SubscriberFactory::createSubscriber(subtype, name);
sublist.push_back(sub);
}
};https://codereview.stackexchange.com/questions/274014
复制相似问题