Flyweight(亨元模式) 结构型

一、概述

  1. 目的:该模式主要的设计目的是为了迎合系统大量相似数据的应用而生,减少用于创建和操作相似的细碎对象所花费的成本。大量的对象会消耗高内存,享元模式给出了一个解决方案,即通过共享对象来减少内存负载。

  2. 作用:运用共享技术来有効地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。对于 C++来说就是共用一个内存块,对象指针指向同一个地方)

例如:享元模式可以看成是一个工具箱,而享元对象就是工具箱内的具体的工具,我们在使用工具的时候,不必每回临时的制造工具,而是直接从工具箱里找到工具进行使用,这样就大大节约了制造工具的成本时间和工具占用的空间。

享元模式应用最多就是池技术,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。

二、两种状态

  1. 内蕴状态:是对象本身的属性,在生成对象以后一般不会进行改变,比如工具中的属性:名字、大小、重量等,还有就是我们一般需要一个关键性的属性作为其区别于其他对象的key,如工具的话我们可以把名称作为找到工具的唯一标识。
  2. 外蕴状态:是对象的外部描述,是每个对象的可变部分,比如对工具的使用地点、使用时间、使用人、工作内容的描述,这些属性不属于对象本身,而是根据每回使用情况进行变化的,这就需要制作成接口进行外部调用,而外蕴状态的维护是由调用者维护的,对象内不进行维护。

三、结构

  1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入;
  2. 具体享元角色(Concrete Flyweight):实现抽象享元角色中所规定的接口,必须是可共享的,需要封装享元对象的内部状态;
  3. 非享元角色(Unshared ConcreteFlyweight):是不可共享的外部状态,它以参数的形式注入具体享元的相关方法中;
  4. 享元工厂角色(Flyweigh tFactory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

在这里插入图片描述
享元工厂是享元模式的核心,它需要确保系统可以共享相同的对象。一般情况下,享元工厂会维护一个对象列表,当任何组件尝试获取享元类时,如果请求的享元类已经被创建,则直接返回已有的享元类;若没有,则创建一个新的享元对象,并将它加入到维护队列中。

四、实例

#include<iostream>
#include<string>
#include <map>
#include <vector>
#include <mutex>
using namespace std;


/*
 享元类包含了树类型的部分状态, 这些成员变量保存的数值对于特定树而言是唯一的。
 很多树木之间包含共同的名字、颜色和纹理, 如果在每棵树中都存储这些数据就会浪费大量内存。
 因此我们将这些「内在状态」导出到一个单独的对象中, 然后让众多的单个树对象去引用它。

*/
class TreeType
{
private:
	string name_;
	string color_;
	string texture_;
public:
	TreeType(string n,string c,string t):name_(n),color_(c),texture_(t){}
	void draw(string cavas, double x, double y) {
		// 1. 创建特定类型、颜色和纹理的位图
		// 2. 在画布坐标(x,y)处绘制位图
		return;
	}
};

/*
情景对象包含树类型的「外在状态」, 程序中可以创建数十亿个此类对象
因为它们体积很小: 仅有两个浮点坐标类型和一个引用成员变量
*/

class Tree
{
private:
	double x_;
	double y_;
	TreeType* type_;

public:
	Tree(double x,double y,TreeType *t):x_(x),y_(y),type_(t){}
	void draw(string canvas)
	{
		return type_->draw(canvas, x_, y_);
	}

};

/*
享元工厂: 决定是否复用已有享元或者创建一个新的对象, 同时它也是一个单例模式
*/ 
class TreeFactory {
private:
	TreeFactory(){}
	static TreeFactory* instance_;
	static mutex mutex_;

	 共享池, 其中key格式为name_color_texture
	map<string, TreeType*>tree_types_;
public:
	static TreeFactory* getInstance() {
		if (instance_ == nullptr)
		{
			mutex_.lock();
			if (instance_ == nullptr) {
				instance_ = new TreeFactory();
			}
			mutex_.unlock();
		}
		return instance_;
	}


	TreeType* getTreeType(string name, string color, string texture) {
		string key = name + "_" + color + "_" + texture;
		auto iter = tree_types_.find(key);
		if (iter == tree_types_.end()) {
			// 新的tree type
			TreeType* new_tree_type = new TreeType(name, color, texture);
			tree_types_[key] = new_tree_type;
			return new_tree_type;

		}
		else {
			// // 已存在的tree type
			return iter->second;
		}
	}
};

//Forest包含数量及其庞大的Tree
class Forest {
private:
	vector<Tree> trees_;
public:
	void planTree(double x, double y, string name, string color, string texture) {
		TreeType* type = TreeFactory::getInstance()->getTreeType(name, color, texture);
		Tree tree = Tree(x, y, type);
		trees_.push_back(tree);
	}
	void draw() {
		//将 tree_容器中的每一个元素从前往后枚举出来,并用tree来表示,
		for(auto tree:trees_)
		{
			tree.draw("canvas");
		}
	}
};


int main()
{
	Forest* forest = new Forest();
	//在forest中种植很多棵树
	for (int i = 0; i < 500; i++) {
		for (int j = 0; j < 500; j++) {
			double x = i;
			double y = j;
			//树类型
			forest->planTree(x, y, "榕树", "绿色", "");
			forest->planTree(x, y, "杉树", "红色", "");
			forest->planTree(x, y, "桦树", "白色", "");
		}
	}

	forest->draw();
	delete forest;
	return 0;
}

五、优缺点

优点:

  1. 极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;
  2. 享元对象的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
  3. 项目3

缺点:

  1. 享元模式使得系统更加复杂,需要分离出内部状态和外部状态,从而使得程序的逻辑复杂化

  2. 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

六、应用场景

  1. 当一个系统有大量相同或相似的对象,由于这些对象的大量使用,造成内存的大量耗费;使用享元模式可以节约内存空间,提高系统的性能。

  2. 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。

七、对比

享元模式与单例模式的区别

  1. 享元设计模式是一个类有很多对象,而单例是一个类仅一个对象。
  2. 享元模式是为了节约内存空间,提升程序性能,而单例模式则主要是出于共享状态的目的。

享元模式和工厂模式、单例模式

  1. 在区分出不同种类的外部状态后,创建新对象时需要选择不同种类的共享对象,这时就可以使用工厂模式来提供共享对象。
  2. 在共享对象的维护上,经常会采用单例模式来提供单实例的共享对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值