设计模式---享元模式

158 阅读4分钟

定义

享元模式(Flyweight Pattern)是一种结构型设计模式,它旨在减少创建对象的数量,从而减少内存消耗和提高性能。这个模式通过共享大量细粒度对象来实现。这种模式特别适用于需要大量对象的场景,比如在游戏开发中处理大量相似的对象时。

核心概念

  • 享元(Flyweight) :享元对象是可以共享的对象,它们在系统中是重复使用的。享元模式将享元对象和具体的状态(外部状态)分离,以减少对象的创建和存储。

  • 外部状态(External State) :享元对象不包含的、可以变化的状态,通常是对象的上下文信息。外部状态由客户端提供,而享元对象则只维护内部状态。

  • 内部状态(Internal State) :享元对象自身的状态,这些状态在对象间是共享的。

结构

  • 享元接口(Flyweight Interface) :定义了享元对象的公共接口,用于通过外部状态来处理具体的业务逻辑。

  • 具体享元(Concrete Flyweight) :实现了享元接口,定义了内部状态的具体实现。每个具体享元对象都共享相同的内部状态。

  • 享元工厂(Flyweight Factory) :管理享元对象的创建和共享。享元工厂确保每个享元对象只有一个实例,并为客户端提供共享的享元对象。

  • 客户端(Client) :使用享元工厂获取享元对象,并将外部状态传递给享元对象,以实现具体业务逻辑。

代码示例

假设在开发一个图形编辑器,其中有很多相同类型的图形(如树木),每棵树木的外观是相似的,但它们的位置和大小可能不同。在享元模式中,我们可以将所有相同的树木外观(如颜色、形状)共享为一个享元对象,而将每棵树木的位置和大小作为外部状态传递给享元对象。

#include <iostream>
#include <unordered_map>

// 享元接口
class TreeType
{
public:
	virtual ~TreeType() = default;
	virtual void draw(int x, int y) const = 0;
};


// 具体享元
class ConcreteTreeType : public TreeType
{
private:
	std::string name;

public:
	ConcreteTreeType(const std::string& name) : name(name) {}

	void draw(int x, int y) const override
	{
		std::cout << "Drawing tree type: " << name << " at (" << x << ", " << y << ")" << std::endl;
	}

	// 当前对象的地址
	void printAddress() const
	{
		std::cout << "TreeType address: " << this << std::endl;
	}
};


// 享元工厂
class TreeFactory
{
private:
	std::unordered_map<std::string, std::shared_ptr<TreeType>> treeTypes;

public:
	std::shared_ptr<TreeType> getTreeType(const std::string& name)
	{
		auto it = treeTypes.find(name);
		if (it != treeTypes.end())
			return it->second;

		auto treeType = std::make_shared<ConcreteTreeType>(name);
		treeTypes[name] = treeType;
		return treeType;
	}
};


// 客户端代码
class Tree
{
private:
	std::shared_ptr<TreeType> treeType;
	int x, y;

public:
	Tree(const std::shared_ptr<TreeType>& treeType, int x, int y)
		: treeType(treeType), x(x), y(y) {}

	void draw() const
	{
		treeType->draw(x, y);
	}

	// 打印对象地址
	void printTreeTypeAddress() const
	{
		std::dynamic_pointer_cast<ConcreteTreeType>(treeType)->printAddress();
	}
};


int main()
{
	TreeFactory treeFactory;

	// 创建不同树木类型
	auto oak = treeFactory.getTreeType("Oak");
	auto pine = treeFactory.getTreeType("Pine");

	// 打印树木类型的地址,验证共享
	std::cout << "Address of Oak: ";
	std::dynamic_pointer_cast<ConcreteTreeType>(oak)->printAddress();

	std::cout << "Address of Pine: ";
	std::dynamic_pointer_cast<ConcreteTreeType>(pine)->printAddress();

	// 创建树木对象,并指定位置
	std::vector<Tree> forest;
	forest.emplace_back(oak, 10, 20);
	forest.emplace_back(pine, 30, 40);
	forest.emplace_back(oak, 50, 60);
	forest.emplace_back(pine, 70, 80);

	// 打印所有树木类型的地址,验证共享
	for (const auto& tree : forest) {
		tree.printTreeTypeAddress();
		tree.draw();
	}

	system("pause");
	return 0;
}
  • TreeType: 享元接口,定义了draw方法来绘制树木。

  • ConcreteTreeType: 具体享元,包含具体的树木类型名称,并实现了draw方法。

  • TreeFactory: 享元工厂,管理具体享元对象的创建和共享。它通过getTreeType方法返回享元对象,并且确保相同名称的享元对象是共享的。在 getTreeType 方法中,TreeFactory 首先检查哈希表中是否已经存在请求的享元对象(即是否已经创建过相同名称的树木类型)。如果存在,它直接返回已存在的对象;如果不存在,则创建新的对象,并将其存储到哈希表中,以供未来的请求复用。

  • Tree: 客户端对象,持有对享元对象的引用,并且包含具体的外部状态(树木的位置)。它通过draw方法来绘制树木。

  • main: 客户端代码,使用TreeFactory创建树木类型,然后创建不同位置的树木对象,并绘制它们。