自学内容网 自学内容网

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;
}

输出结果:

output

看起来我们已经实现我们的需求了,下次需要添加新的筛选需求时,我们可以尝试添加一个新的规范而不需要添加一个新的筛选器。

这样,在编译时已有的模块(函数和类)不会被重复编译,也不会产生破坏已有模块功能的可能。

但是这就结束了吗?

如果我们想要同时筛选颜色和类别呢?

我先提供一种理想的实现方式:

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)!