当前位置: 首页 > article >正文

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

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


http://www.kler.cn/a/460089.html

相关文章:

  • 酒店管理系统|Java|SSM|VUE| 前后端分离
  • Git的.gitignore文件详解与常见用法
  • [算法] [leetcode-509] 斐波那契数
  • JavaScript的diff库详解(示例:vue项目实现两段字符串比对标黄功能)
  • 活动预告 |【Part2】Microsoft 安全在线技术公开课:安全性、合规性和身份基础知识
  • C语言格式输出
  • Python软体中使用 Kafka 实现高吞吐量的任务队列
  • 结合华为云实现人证核验
  • C++ 设计模式:抽象工厂(Abstract Factory)
  • pyqt5 设计pdf 和word文件互相转换小程序
  • 二、SQL语言,《数据库系统概念》,原书第7版
  • 【Redis】万字整理 Redis 非关系型数据库的安装与操作
  • Android笔试面试题AI答之Android基础(4)
  • elementui的默认样式修改
  • 备战春招 | 数字IC FPGA笔试题
  • 【从零开始入门unity游戏开发之——C#篇32】C#其他不常用的泛型数据结构类、顺序存储和链式存储
  • Python 爬虫中的反爬策略及详细应对方法
  • 【LLM】Langflow 的简单使用
  • SQL实现新年倒计时功能
  • golang标准库SSH操作示例
  • PHP语言laravel框架中基于Redis的异步队列使用实践与原理
  • 【每日学点鸿蒙知识】中间产物版本号问题、Toast应用外不生效、Dialog键盘问题、WebView cookie、本地缓存
  • 如何判断服务器是否被网络攻击?
  • 【C++】九九乘法表编程题详解与多角度对比分析
  • 整合版canal ha搭建--基于1.1.4版本
  • CSS系列(39)-- Shapes详解