C++通透讲解设计模式:开闭原则(1)
开闭原则
开闭原则在大部分书籍中是这样描述的:
软件实体对扩展开放,对修改关闭
这句话有一些模糊不清,接下来我将用比较通俗的方式讲解它
“开”和“闭”的故事
接下来,我们用分类书为比喻,帮助你理解这一思想。
首先我们先来规定一下书的一些具有的属性,这里列出三种:大小、类别、颜色。
enum class BookColor {
RED,
GREEN,
BLUE,
};
enum class BookType {
SCIENCE,
HISTORY,
};
enum class BookSize {
SMALL,
MEDIUM,
LARGE,
};
class Book {
public:
BookColor color;
BookType type;
BookSize size;
std::string name;
std::string auther;
};
起初,我们的需求很简单——找到并筛选出所有的科学类书籍,所以你很轻松的写了一个筛选器
,用代码来看,可能是这样的:
class BookFilter {
using Books = std::vector<Book *>;
public:
Books by_type(const Books books, BookType type) {
Books result;
for (const auto &book : books) {
if (book->type == type) {
result.push_back(book);
}
}
return result;
}
};
我们设计了一个书类和一个书架类,书架中可以存放书的内容。
嗯,非常好,感觉筛选器的功能很好用,我们的需求就达到了。
现在,新的需求来了——找到并筛选出所有的红色封面书籍。或许你可能会继续上面的思路写:
class BookFilter {
using Books = std::vector<Book *>;
public:
Books by_type(const Books books, BookType type) {
Books result;
for (const auto &book : books) {
if (book->type == type) {
result.push_back(book);
}
}
return result;
}
// 在这里补充上
Books by_color(const Books books, BookColor color) {
Books result;
for (const auto &book : books) {
if (book->color == color) {
result.push_back(book);
}
}
return result;
}
};
这样一来,就实现了筛选红色封面书籍的功能。
但是,有没有发现这两段代码很像?当然这还不是最重要的问题,问题是新增加的代码段在
BookFilter
这个类内。这意味着他们之前写过的内容需要再被编译一次。
重复的编译并不符合我们在设计中的期望,一方面,这不仅会导致编译速度变得更慢,另一方面也会导致其他已经实现的功能会受到干扰的风险。
如何实现开闭原则
其实方法不止有一种,但是这通常取决于实现的人。一千万个人心中有一千万个哈利波特。
比较标准的做法是,将分类器进一步细分,拆分成两部分:
- 一个具有判断能力的规范
- 一个根据规范,将输入筛选出来的过程
以上面的书架为例子:
我们将书的筛选器进一步向上抽象(如果你不明白抽象的意思,我建议复习一下面向对象的基本思想)抽象出两个类:
- 规范(Specifiction)
- 筛选器(filter)
代码如下:
template <typename T>
class Specification {
public:
virtual bool is_satisfied(T *item) = 0;
};
template <typename T>
class Filter {
using Items = std::vector<T *>;
public:
virtual Items filter(Items items, Specification<T> &spec) const = 0;
};
规范是用来判断类型是否符合要求的,而筛选器则是用来执行”根据规范筛选结果“这个过程的。
如果还是想要筛选颜色,我们就可以添加一个颜色规范和一个筛选器:
首先是筛选器:
class BookFilter : public Filter<Book> {
public:
using Items = std::vector<Book *>;
Items filter(Items items, Specification<Book> &spec) const override {
Items result;
for (const auto &book : items) {
if (spec.is_satisfied(book)) {
result.push_back(book);
}
}
return result;
}
}
这个筛选器可以接受所有基类为规范类型的参数。
然后就是颜色筛选的规范:
class BookColorSpecification : public Specification<Book> {
public:
BookColor color;
BookColorSpecification(BookColor color) : color(color) {}
// 这里定义了颜色是否符合要求的判断方法
bool is_satisfied(Book *item) override {
return item->color == color;
}
};
尝试用一下:
#define __NAMESPACE_OCPBOOK_BEGIN__ namespace OCPBOOK {
#define __NAMESPACE_OCPBOOK_END__ }
__NAMESPACE_OCPBOOK_BEGIN__
enum class BookColor {
RED,
GREEN,
BLUE,
};
enum class BookType {
SCIENCE,
HISTORY,
};
enum class BookSize {
SMALL,
MEDIUM,
LARGE,
};
// 符合开闭原则的实现, 新增一个规范,只需要新增一个Specification的实现类,不需要修改Filter类
template <typename T>
class Specification {
public:
virtual bool is_satisfied(T *item) = 0;
};
template <typename T>
class Filter {
using Items = std::vector<T *>;
public:
virtual Items filter(Items items, Specification<T> &spec) const = 0;
};
class BookColorSpecification : public Specification<Book> {
public:
BookColor color;
BookColorSpecification(BookColor color) : color(color) {}
bool is_satisfied(Book *item) override {
return item->color == color;
}
};
// 实现一个过滤器,根据规范过滤书
class BookFilter : public Filter<Book> {
public:
using Items = std::vector<Book *>;
Items filter(Items items, Specification<Book> &spec) const override {
Items result;
for (const auto &book : items) {
if (spec.is_satisfied(book)) {
result.push_back(book);
}
}
return result;
}
};
int main() {
Book book1{"Book1", "Author1", BookColor::RED, BookType::SCIENCE, BookSize::SMALL};
Book book2{"Book2", "Author2", BookColor::GREEN, BookType::HISTORY, BookSize::MEDIUM};
Book book3{"Book3", "Author3", BookColor::BLUE, BookType::SCIENCE, BookSize::LARGE};
Book book4{"Book4", "Author4", BookColor::RED, BookType::HISTORY, BookSize::SMALL};
vector<Book *> books = {&book1, &book2, &book3, &book4};
BookFilter bf;
BookColorSpecification bcs(BookColor::RED);
auto red_books = bf.filter(books, bcs);
for (const auto &book : red_books) {
cout << book->name << endl;
}
return 0;
}
__NAMESPACE_OCPBOOK_END__
int main() {
OCPBOOK::main();
return 0;
}
输出结果:
看起来我们已经实现我们的需求了,下次需要添加新的筛选需求时,我们可以尝试添加一个新的规范而不需要添加一个新的筛选器。
这样,在编译时已有的模块(函数和类)不会被重复编译,也不会产生破坏已有模块功能的可能。
但是这就结束了吗?
如果我们想要同时筛选颜色和类别呢?
我先提供一种理想的实现方式:
BookFilter bf;
BookColorSpecification bcs(BookColor::RED);
BookTypeSpecification bts(BookType::HISTORY);
auto red_books = bf.filter(books, bcs && bts);
具体怎么实现我会写在下一篇文章中。
原文地址:https://blog.csdn.net/weixin_74239689/article/details/144704230
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!