23种设计模式——享元模式对象性能

Posted J-A

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了23种设计模式——享元模式对象性能相关的知识,希望对你有一定的参考价值。

文章目录

亦称: 缓存、Cache、Flyweight

意图

享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。

什么时候使用享元

仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。

应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

  • 程序需要生成数量巨大的相似对象
  • 这将耗尽目标设备的所有内存
  • 对象中包含可抽取且能在多个对象间共享的重复状态。

日常生活场景中的应用:

我们去驾考的时候,如果给每个考试的人都准备一辆车,那考场就挤爆了,考点都堆不下考试车,因此驾考现场一般会有几辆车给要考试的人依次使用。如果考生人数少,就分别少准备几个自动档和手动档的驾考车,考生多的话就多准备几辆。如果考手动档的考生比较多,就多准备几辆手动档的驾考车。

我们去考四六级的时候(为什么这么多考试?😅),如果给每个考生都准备一个考场,怕是没那么多考场也没有这么多监考老师,因此现实中的大多数情况都是几十个考生共用一个考场。四级考试和六级考试一般同时进行,如果考生考的是四级,那么就安排四级考场,听四级的听力和试卷,六级同理。

生活中类似的场景还有很多,比如咖啡厅的咖啡口味,餐厅的菜品种类,拳击比赛的重量级等等。

在类似场景中,这些例子有以下特点:

  1. 目标对象具有一些共同的状态,比如驾考考生考的是自动档还是手动档,四六级考生考的是四级还是六级;
  2. 这些共同的状态所对应的对象,可以被共享出来;

享元模式的实现

为了能更方便地访问各种享元, 你可以创建一个工厂方法来管理已有享元对象的缓存池。 工厂方法从客户端处接收目标享元对象的内在状态作为参数, 如果它能在缓存池中找到所需享元, 则将其返回给客户端; 如果没有找到, 它就会新建一个享元, 并将其添加到缓存池中。

你可以选择在程序的不同地方放入该函数。 最简单的选择就是将其放置在享元容器中。 除此之外, 你还可以新建一个工厂类, 或者创建一个静态的工厂方法并将其放入实际的享元类中。

内部状态和外部状态

享元对象之所以能做到共享,关键是区分了内部状态和外部状态:

  • 内部状态(Intrinsic State):存储在享元对象内部,并且不会随环境改变而改变。因此,内部状态可以共享。
  • 外部状态(Extrinsic State):随环境改变而改变的、不可以共享的状态。享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用时再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的。

由于区分了内部状态和外部状态,因此可以将具有相同内部状态的对象存储在享元池中来实现共享。当需要时,将对象从享元池中取出以实现对象的复用。通过向取出的对象注入不同的外部状态,可以得到一系列相似的对象,而这些对象在内存中实际上只存储一份。

struct SharedState

	std::string brand_;
	std::string model_;
	std::string color_;

	SharedState(const std::string &brand, const std::string &model, const std::string &color)
		: brand_(brand), model_(model), color_(color)
	
	

	friend std::ostream &operator<<(std::ostream &os, const SharedState &ss)
	
		return os << "[ " << ss.brand_ << " , " << ss.model_ << " , " << ss.color_ << " ]";
	
;

struct UniqueState

	std::string owner_;
	std::string plates_;

	UniqueState(const std::string &owner, const std::string &plates)
		: owner_(owner), plates_(plates)
	
	

	friend std::ostream &operator<<(std::ostream &os, const UniqueState &us)
	
		return os << "[ " << us.owner_ << " , " << us.plates_ << " ]";
	
;

/**
 * The Flyweight stores a common portion of the state (also called intrinsic
 * state) that belongs to multiple real business entities. The Flyweight accepts
 * the rest of the state (extrinsic state, unique for each entity) via its
 * method parameters.
 */
class Flyweight

private:
	SharedState *shared_state_;

public:
	Flyweight(const SharedState *shared_state) : shared_state_(new SharedState(*shared_state))
	
	
	Flyweight(const Flyweight &other) : shared_state_(new SharedState(*other.shared_state_))
	
	
	~Flyweight()
	
		delete shared_state_;
	
	SharedState *shared_state() const
	
		return shared_state_;
	
	void Operation(const UniqueState &unique_state) const
	
		std::cout << "Flyweight: Displaying shared (" << *shared_state_ << ") and unique (" << unique_state << ") state.\\n";
	
;
/**
 * The Flyweight Factory creates and manages the Flyweight objects. It ensures
 * that flyweights are shared correctly. When the client requests a flyweight,
 * the factory either returns an existing instance or creates a new one, if it
 * doesn't exist yet.
 */
class FlyweightFactory

	/**
	 * @var Flyweight[]
	 */
private:
	std::unordered_map<std::string, Flyweight> flyweights_;
	/**
	 * Returns a Flyweight's string hash for a given state.
	 */
	std::string GetKey(const SharedState &ss) const
	
		return ss.brand_ + "_" + ss.model_ + "_" + ss.color_;
	

public:
	FlyweightFactory(std::initializer_list<SharedState> share_states)
	
		for (const SharedState &ss : share_states)
		
			this->flyweights_.insert(std::make_pair<std::string, Flyweight>(this->GetKey(ss), Flyweight(&ss)));
		
	

	/**
	 * Returns an existing Flyweight with a given state or creates a new one.
	 */
	Flyweight GetFlyweight(const SharedState &shared_state)
	
		std::string key = this->GetKey(shared_state);
		if (this->flyweights_.find(key) == this->flyweights_.end())
		
			std::cout << "FlyweightFactory: Can't find a flyweight, creating new one.\\n";
			this->flyweights_.insert(std::make_pair(key, Flyweight(&shared_state)));
		
		else
		
			std::cout << "FlyweightFactory: Reusing existing flyweight.\\n";
		
		return this->flyweights_.at(key);
	
	void ListFlyweights() const
	
		size_t count = this->flyweights_.size();
		std::cout << "\\nFlyweightFactory: I have " << count << " flyweights:\\n";
		for (std::pair<std::string, Flyweight> pair : this->flyweights_)
		
			std::cout << pair.first << "\\n";
		
	
;

享元模式的优缺点

优点缺点
由于减少了系统中的对象数量,提高了程序运行效率和性能,精简了内存占用,加快运行速度;引入了共享对象,使对象结构变得复杂;
外部状态相对独立,不会影响到内部状态,所以享元对象能够在不同的环境被共享;共享对象的创建、销毁等需要维护,带来额外的复杂度(如果需要把共享对象维护起来的话);

与其他模式的关系

  • 你可以使用享元模式实现组合模式树的共享叶节点以节省内存。
  • 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统。
  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同。
    1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
    2. 单例对象可以是可变的。 享元对象是不可变的。

以上是关于23种设计模式——享元模式对象性能的主要内容,如果未能解决你的问题,请参考以下文章

23种设计模式之享元模式(FlyWeight)

23种设计模式之享元模式

23种设计模式(22):享元模式

23种设计模式之享元模式代码实例

Unity3D与23种设计模式享元模式(Flyweight)

Java设计模式-享元模式