C++通透讲解设计模式:开闭原则(1)

开闭原则

开闭原则在大部分书籍中是这样描述的:

软件实体对扩展开放,对修改关闭

这句话有一些模糊不清,接下来我将用比较通俗的方式讲解它

"开"和"闭"的故事

接下来,我们用分类书为比喻,帮助你理解这一思想。

首先我们先来规定一下书的一些具有的属性,这里列出三种:大小、类别、颜色。

cpp 复制代码
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;
};

起初,我们的需求很简单------找到并筛选出所有的科学类书籍,所以你很轻松的写了一个筛选器,用代码来看,可能是这样的:

cpp 复制代码
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;
	}
};

我们设计了一个书类和一个书架类,书架中可以存放书的内容。

嗯,非常好,感觉筛选器的功能很好用,我们的需求就达到了。

现在,新的需求来了------找到并筛选出所有的红色封面书籍。或许你可能会继续上面的思路写:

cpp 复制代码
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)

代码如下:

cpp 复制代码
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;
};

规范是用来判断类型是否符合要求的,而筛选器则是用来执行"根据规范筛选结果"这个过程的。

如果还是想要筛选颜色,我们就可以添加一个颜色规范和一个筛选器:

首先是筛选器:

cpp 复制代码
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;
	}
}

这个筛选器可以接受所有基类为规范类型的参数。

然后就是颜色筛选的规范:

cpp 复制代码
class BookColorSpecification : public Specification<Book> {
public:
	BookColor color;
	BookColorSpecification(BookColor color) : color(color) {}
	// 这里定义了颜色是否符合要求的判断方法
	bool is_satisfied(Book *item) override {
		return item->color == color;
	}
};

尝试用一下:

cpp 复制代码
#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;
}

输出结果:

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

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

但是这就结束了吗?

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

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

cpp 复制代码
	BookFilter bf;
	BookColorSpecification bcs(BookColor::RED);
	BookTypeSpecification bts(BookType::HISTORY);

	auto red_books = bf.filter(books, bcs && bts);

具体怎么实现我会写在下一篇文章中。

相关推荐
成工小白17 分钟前
【C++ 】智能指针:内存管理的 “自动导航仪”
开发语言·c++·智能指针
sc写算法19 分钟前
基于nlohmann/json 实现 从C++对象转换成JSON数据格式
开发语言·c++·json
SunkingYang23 分钟前
C++中如何遍历map?
c++·stl·map·遍历·方法
Andrew_Xzw24 分钟前
数据结构与算法(快速基础C++版)
开发语言·数据结构·c++·python·深度学习·算法
库库的里昂25 分钟前
【C++从练气到飞升】03---构造函数和析构函数
开发语言·c++
momo卡26 分钟前
MinGW-w64的安装详细步骤(c_c++的编译器gcc、g++的windows版,win10、win11真实可用)
c语言·c++·windows
凤年徐3 小时前
【数据结构初阶】单链表
c语言·开发语言·数据结构·c++·经验分享·笔记·链表
oioihoii3 小时前
C++11 右值引用:从入门到精通
开发语言·c++
木子.李3477 小时前
排序算法总结(C++)
c++·算法·排序算法
季鸢8 小时前
Java设计模式之状态模式详解
java·设计模式·状态模式