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

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

相关推荐
HEU_firejef5 小时前
设计模式——工厂模式
java·开发语言·设计模式
DBWYX5 小时前
GoF23种设计模式 简介
设计模式·面向对象
阿正的梦工坊6 小时前
PyTorch到C++再到 CUDA 的调用链(C++ ATen 层) :以torch._amp_update_scale_调用为例
c++·人工智能·pytorch
微凉的衣柜7 小时前
【C++/CMake】从静态库到动态库:一行改动解决 “找不到 -ljsoncpp” 链接报错
开发语言·c++
诸葛悠闲9 小时前
设计模式——外观模式
设计模式·外观模式
bbqz0079 小时前
浅说c/c++ coroutine
c++·协程·移植·epoll·coroutine·libco·网络事件库·wepoll
~糖炒栗子~9 小时前
[Day 11]209.长度最小的子数组
数据结构·c++·算法·leetcode
zym大哥大10 小时前
C++11右值与列表初始化
数据结构·c++
Reoyy12 小时前
Visual Studio 2022安装教程
c++·ide·visual studio
执着的小火车13 小时前
【2024华为OD-E卷-100分-火星文计算】(题目+思路+Java&C++&Python解析)
java·数据结构·c++·算法·华为od·华为