我的项目遇到了一些设计问题,我希望得到一些帮助。我想出了一个例子,我想概述了我遇到的问题。我是软件设计的新手,如果我完全错过了什么,请原谅我。
在这个例子中说我有:
struct Book {
std::string author_first_name;
std::string author_last_name;
int year_published;
double price_in_dollars;
};
class BookCase {
std::vector<Book> all_books;
// Rest of class implementation
}
我从一个文件或多个文件中读取所有书籍并将它们存储在BookCase中。然后我想在BookCase上做一堆操作,在运行时定义,这就是我被困住的地方。
假设用户想要按作者对书籍进行排序然后导出,下次按名字排序或按价格排序,或者想要添加新操作可能会删除在X年之前发布的书籍。
我的问题是:
放置这些操作的最佳位置在哪里。我可以轻松地将这些操作添加到BookCase:
class BookCase {
void SortByLastNameAscending();
void SortByLastNameDescending();
void SortByPriceAscending();
// etc...};
我知道这是错误的,这会使类膨胀,并且每次添加新操作时都必须更改类。我可以拥有像类甚至名称空间“BookCaseProcessor”的东西,它具有所有操作并在那里添加新的操作。是否有更优雅的方式来处理这样的事情,也许是我缺少的设计原则。
如何以一种很好的方式将操作串在一起。假设用户想要RemoveBooksBeforeYear(),RemoveBooksThatCostLessThan(),SortByLastNameDescending()。下次他们跑步时,他们只想做一件事,或者10件事。现在,我能想到的唯一解决方案就是一堆if语句。
答案 0 :(得分:1)
我认为最简单的方法是使用std :: function。
假设你实现了各种各样的操作:
void SortByLastNameAscending(BookCase&) {...}
void SortByLastNameDescending(BookCase&) {...}
void SortByPriceAscending(BookCase&) {...}
您可以填充要执行的简单操作列表:
std::vector<std::function<void(BookCase&)>> pipeline;
//populate the pipeline
if(user_choice == "sort") {
pipeline.emplace_back(SortByLastNameAscending);
}
按顺序执行:
BookCase data;
for(auto& op : pipeline) {
op(data);
}
答案 1 :(得分:1)
软件设计问题很少是黑白的,而且往往是主观的。我会尽力提出一些解释。
我认为将支持操作放入BookCase
本身并没有错。提供课堂合同没有比班级更清楚的地方!任何Processor
样式的添加只会使界面不太清晰。您很快就会发现有必要让这个Processor
成为班上的朋友,而且您会发现它比仅仅在BookCase
中使用排序方法更糟糕。
我可能不会创建多个SortBy<Field>...
方法,但相反,会将其模板化或使用标签分发,但它可能更多的是品味偏好。但是,我肯定不会对不同版本的升序与降序进行劝阻。这没有任何意义,使用“升序/降序”&#39;作为sort函数的标记参数。
答案 2 :(得分:0)
我认为混淆来自于您的班级被命名为BookCase
。这不是班级的内容。书柜,更一般地说,图书馆应该有一个明确的顺序。
您实际处理的是BookSelection
。 (根据您的要求,您可能仍需要保留BookCase
;或者文件可以是书架。)
此外,由于重点是排序和修改,list
可能比vector
更有意义。