设计模式(C++)-结构型模式-享元模式

设计模式(C++)-结构型模式-享元模式

一、享元模式概述

享元模式是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度对象的复用,从而减少内存使用和提高性能。

核心思想:如果程序中有大量相似对象,可以将它们共有的部分(内部状态)提取出来共享,而变化的部分(外部状态)在运行时传入。

形象比喻:字母处理器的例子

  • 文档中有成千上万个字母'a'、'b'、'c'等
  • 每个字母都有自己的字体,大小、颜色、位置等
  • 如果为每个字母都创建完整的对象,内存占用巨大
  • 享元模式:创建一个共享的字母对象(内部状态:字符代码),将字体、颜色、位置等作为外部状态输入。

要解决什么问题?

问题背景:

  • 软件系统中有大量相似对象
  • 这些对象造成了很大的内存开销
  • 大部分对象的状态可以分为内部状态和外部状态
    示例:在游戏开发中,有成千上万的树、草、士兵等对象,每个对象都有位置、方向、颜色等属性。如果为买个对象都创建完整的实例,内存会迅速耗尽。

核心概念:内部状态vs外部状态

内部状态:

  • 存储在享元对象内部
  • 不会随环境变化而改变
  • 可以被共享
  • 例如:字符代码、树的模型、士兵的类型

外部状态:

  • 随环境变化而改变
  • 不能被共享
  • 由客户端保存,使用时传入
  • 例如:字符的位置、颜色、大小;树的坐标、朝向

二、享元模式UML类图

场景:森林模拟

实现一个森林模拟系统,森林中有成千上万颗树,但树的类型(内部状态)只有几种

三、代码实现

cpp 复制代码
//flyweight.h
/*
 享元模式(flyWeight pattern)是一种结构型设计模式,它通过共享技术来有效地支持大量细粒度对象的复用,从而减少内存使用和提高性能。
  核心思想:如果程序中有大量相似对象,可以将它们共有的部分(内部状态)提取出来共享,而变化的部分(外部状态)在运行时传入。
  形象比喻:字母处理器的例子
 - 文档中有成千上万个字母'a'、'b'、'c'等
 - 每个字母都有自己的字体,大小、颜色、位置等
 - 如果为每个字母都创建完整的对象,内存占用巨大
 - 享元模式:创建一个共享的字母对象(内部状态:字符代码),将字体、颜色、位置等作为外部状态输入。
*/
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <memory>
using namespace std;
//--------------------享元类:树的类型(内部状态)------------------------------
//FlyWeight
class TreeType {
private:
	string name_;   //内部状态
	string color_;  //内部状态
	string texture_;//内部状态
public:
	TreeType(const string&name, const string& color, const string&texture) :name_(name), color_(color), texture_(texture) {}
	void draw(int x, int y)const;
	string getKey()const;
};
//-------------------享元工厂------------------------------------
class TreeFactory {
private:
	//存储共享的数类型对象
	static unordered_map<string, shared_ptr<TreeType>>treeTypes_;
public:
	static shared_ptr<TreeType> getTreeType(const string&name, const string& color, const string&texture);
	static void showTreeTypes();
};

//--------------------树类(包含外部状态)-------------------------------
class Tree {
private:
	int x_; //外部状态
	int y_; //外部状态
	shared_ptr<TreeType> type_;//共享的内部状态
public:
	Tree(int x, int y, shared_ptr<TreeType> type) :x_(x), y_(y), type_(type) {}
	void draw()const;
};

//------------------森林类(管理大量树)----------------------------------------
class Forest {
private:
	vector<Tree> trees_;
public:
	void plantTree(int x, int y, const string&name, const string& color, const string&texture);
	void draw()const;
};

void testFlyWeight();

//flyweight.cc
#include "flyweight.h"
//--------------------享元类:树的类型(内部状态)------------------------------
void TreeType::draw(int x, int y)const {
	cout << "在位置(" << x << ", " << y << ") 绘制一棵"
		<< name_ << "树,颜色:" << color_
		<< ",纹理:" << texture_ << endl;
}
string TreeType::getKey()const {
	return name_ + "_" + color_ + "_" + texture_;
}
//-------------------享元工厂------------------------------------
// 静态成员初始化
unordered_map<string,shared_ptr<TreeType>> TreeFactory::treeTypes_;
shared_ptr<TreeType> TreeFactory::getTreeType(const string&name, const string& color, const string&texture) {
	std::string key = name + "_" + color + "_" + texture;
	auto it = treeTypes_.find(key);
	if (it != treeTypes_.end()) {
		// 已存在,返回共享对象
		return it->second;
	}
	else {
		// 不存在,创建新对象并存储
		auto treeType = std::make_shared<TreeType>(name, color, texture);
		treeTypes_[key] = treeType;
		return treeType;
	}
}
void TreeFactory::showTreeTypes() {
	std::cout << "\n已存在的树类型(享元对象):" << std::endl;
	for (const auto& pair : treeTypes_) {
		std::cout << "  " << pair.first << std::endl;
	}
	std::cout << "总共:" << treeTypes_.size() << " 种类型" << std::endl;
}
//--------------------树类(包含外部状态)-------------------------------
void Tree::draw()const {
	type_->draw(x_, y_);
}
//------------------森林类(管理大量树)----------------------------------------
void Forest::plantTree(int x, int y, const string&name, const string& color, const string&texture) {
	// 从工厂获取共享的树类型
	auto type = TreeFactory::getTreeType(name, color, texture);
	// 创建树对象,包含外部状态
	trees_.emplace_back(x, y, type);
}
void Forest::draw()const {
	std::cout << "\n森林中有 " << trees_.size() << " 棵树:" << std::endl;
	for (const auto& tree : trees_) {
		tree.draw();
	}
}
void testFlyWeight() {
	cout << "=================FlyWeight End===============" << endl;
	Forest forest;
	// 种植大量树,但只有少数几种类型
	forest.plantTree(1, 1, "橡树", "绿色", "橡木纹理");
	forest.plantTree(2, 3, "松树", "深绿", "松木纹理");
	forest.plantTree(3, 5, "橡树", "绿色", "橡木纹理");  // 共享橡树类型
	forest.plantTree(4, 7, "枫树", "红色", "枫木纹理");
	forest.plantTree(5, 9, "松树", "深绿", "松木纹理");  // 共享松树类型
	forest.plantTree(6, 11, "橡树", "绿色", "橡木纹理"); // 共享橡树类型
	forest.plantTree(7, 13, "橡树", "黄色", "秋日橡木纹理"); // 新类型
	forest.plantTree(8, 15, "松树", "深绿", "松木纹理");  // 共享松树类型

	// 绘制森林
	forest.draw();
	// 显示共享的树类型
	TreeFactory::showTreeTypes();
	cout << "=================FlyWeight End===============" << endl;
}

四、优缺点总结

优点:

  • 大幅减少内存使用:通过共享相似对象,避免重复粗存储相同数据
  • 提高性能:减少了对象的创建和销毁开销
  • 支持大量细粒度对象:可以处理远超内存容量的对象数量
  • 分离内部状态和外部状态:使得程序结构更清晰、职责更分明

缺点:

  • 增加系统复杂性:需要区分内部装和外部状态,增加了设计和实现的复杂度
  • 可能增加运行时间:需要从挖我不传递状态,可能增加方法调用的开销
  • 线程安全问题:共享的享元对象可能需要考虑线程安全问题
  • 不适用于所有场景:如果对象之间差异很大,享元模式反而会增加复杂度。
相关推荐
Hello!!!!!!1 小时前
C++基础(五)——屏幕和文件输入输出
开发语言·c++·算法
ytttr8731 小时前
C++ LZW 文件压缩算法实现
开发语言·c++
geovindu1 小时前
go: Facade Pattern
设计模式·golang·外观模式
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【排序贪心】:加工生产调度
c++·算法·贪心·csp·信奥赛·排序贪心·加工生产调度
小菜鸡桃蛋狗2 小时前
C++——vector
开发语言·c++·算法
少司府2 小时前
C++基础入门:初识模板
开发语言·c++·c·模板·函数模板·类模板·泛型编程
十五年专注C++开发2 小时前
C++中TAS和CAS实现自旋锁
c++·cas·原子操作·tas
噜噜噜噜鲁先森2 小时前
STL——String类
开发语言·c++·算法
谭欣辰2 小时前
详细讲解 C++ 有向无环图(DAG)及拓扑排序
c++·算法·图论