设计模式(13)享元模式

享元模式简介

工程师在撸代码的时候,会通过键盘的输入,键入相关的代码和指令,通过敲击不同的按键进行组合,形成了各种各样的代码,以便实现各种各样的功能。其中,值得注意的是,无论实现什么样的功能,具体的代码也是由键盘上有限的按键输入进来的。

整个开发项目中,会存在大量的相同或者类似的重复使用的对象,针对这一场景,享元模式应运而生。

享元模式通过共享技术实现相同或相似的细粒度对象的复用,提供了一个享元池存储已经创建好的对象,并通过享元工厂类将享元对象提供给客户端使用。

享元模式:运用共享技术有效地支持大量细粒度对象的复用。

享元模式要求被共享的对象必须是细粒度对象。如上面提到的输入按键的例子,26个字母可能随时被客户重复使用。尽管每个字母可能出现的位置不一样,但在物理上它们共享同一个对象(同一个实例)。利用享元模式,可以创建一个存储26个字母对象的享元池,需要时从享元池中取出。

享元对象能够做到共享的关键在于区分了内部状态和外部状态:

  • 内部状态:存储在享元对象内部,不会随着环境的改变而改变的,内部状态可以共享。比如围棋中棋子的形状、大小,不会随着外部变化而变化;比如字母A,无论谁使用,都是A,不会变化;
  • 外部状态:随环境变化而变化、不可以共享的状态,如棋子的位置,如每个字母的位置。外部状态一般由客户端保存,在使用时再传入到享元对象内部。不同的外部状态之间是相互独立的,棋子A和棋子B的位置可以不同,并且不会相互影响。

享元模式结构

享元模式常常结合工厂模式一起使用,其结构包含抽象享元类、具体享元类、非共享具体享元类和享元工厂类:

  • Flyweight(抽象享元类):是一个抽象类,声明了具体享元类公共的方法,这些方法可以向外部提供享元对象的内部状态数据,也可以通过这些方法设置外部状态;
  • ConcreteFlyweight(具体享元类):具体实现抽象享元类声明的方法,具体享元类中为内部状态提供存储空间。具体享元类常常结合单例模式来设计实现,保证每个享元类对象只被创建一次,为每个具体享元类提供唯一的享元对象。
  • UnsharedConcreteFlyweight(非共享具体享元类):并不是所有抽象享元类的子类都需要被共享,可以将这些类设计为非共享具体享元类;
  • FlyweightFactory(享元工厂类):用于创建并管理享元对象,针对抽象享元类编程,将各种具体享元类对象存储在一个享元池中,享元池一般设计为一个存储键值对的集合(或者其他类型的集合),可结合工厂模式设计。客户需要某个享元对象时,如果享元池中已有该对象实例,则返回该实例,否则创建一个新的实例,给客户返回新的实例,并将新实例保存在享元池中。

在这里插入图片描述

享元模式代码实例

#include <mutex>
#include <vector>

using namespace std;

// 抽象享元类
class NetDevice
{
    
    
public:
  NetDevice() {
    
    }
  virtual string getName() = 0;

  void print(int portNum)
  {
    
    
    cout << " NetDevice : " << getName().c_str() << " PortNum : " << portNum << endl;
  }
};

// 具体享元类:集线器
class Hub : public NetDevice
{
    
    
public:
  Hub() {
    
    }

  string getName()
  {
    
    
    return "集线器";
  }
};

// 具体享元类:交换机
class Switch : public NetDevice
{
    
    
public:
  Switch() {
    
    }

  string getName()
  {
    
    
    return "交换机";
  }
};

// 享元工厂类
class NetDeviceFactory
{
    
    
public:
  NetDevice *getNetDevice(char ch)
  {
    
    
    if (ch == 'S')
    {
    
    
      return devicePool[1];
    }
    else if (ch == 'H')
    {
    
    
      return devicePool[0];
    }
    else
    {
    
    
      cout << "wrong input!" << endl;
    }

    return NULL;
  }

  // 单例模式:返回享元工厂类的唯一实例
  static NetDeviceFactory *getFactory()
  {
    
    
    if (instance == NULL)
    {
    
    
      m_mutex.lock();
      if (instance == NULL)
      {
    
    
        instance = new NetDeviceFactory();
      }
      m_mutex.unlock();
    }
    return instance;
  }

private:
  NetDeviceFactory()
  {
    
    
    Hub *hub = new Hub();
    Switch *switcher = new Switch();

    devicePool.push_back(hub);
    devicePool.push_back(switcher);
  }

  static NetDeviceFactory *instance;
  static std::mutex m_mutex;

  // 共享池:用一个vector来表示
  vector<NetDevice *> devicePool;
};

NetDeviceFactory *NetDeviceFactory::instance = NULL;
std::mutex NetDeviceFactory::m_mutex;
#include <iostream>
#include "FlyweightPattern.h"

using namespace std;

int main()
{
    
    
	NetDeviceFactory *factory = NetDeviceFactory::getFactory();

	NetDevice *device1, *device2, *device3, *device4;

	// 客户端2获取一个hub
	device1 = factory->getNetDevice('H');
	device1->print(1);
	// 客户端2获取一个hub
	device2 = factory->getNetDevice('H');
	device2->print(2);
	cout << " 判断两个hub是否是同一个:" << endl;
    cout << " Device 1: " << device1 << endl << " Device 2: " << device2 << endl;

    cout << endl;

	// 客户端3获取一个switch
	device3 = factory->getNetDevice('S');
	device3->print(1);
	// 客户端4获取一个hub
	device4 = factory->getNetDevice('S');
	device4->print(2);
    cout << " 判断两个switch是否是同一个:" << endl;
    cout << " Device 3: " << device3 << endl << " Device 4: " << device4 << endl;

	delete factory;
	delete device1;
	// delete device2;
	delete device3;
	// delete device4;
	
	return 0;
}

从上述代码的输出中,可以看出,两个集线器、两个交换机的地址都是相同的,可以看出上述代码实现了同一设备的共享。

进一步的,尽管不同的终端计算机可能会共享同一个集线器(交换机),但是每个计算机接入的端口(port)是不一样的,端口就是每个享元对象的外部状态。 在享元模式的使用过程中,内部状态可以作为具体享元类的成员对象,而外部状态可以通过外部注入的方式添加到具体享元类中。见代码,可以看出客户端可以通过函数传参的形式将“端口号”注入到具体的享元类中。

享元模式总结

优点:

  • 享元模式通过享元池存储已经创建好的享元对象,实现相同或相似的细粒度对象的复用,大大减少了系统中的对象数量,节约了内存空间,提升了系统性能;
  • 享元模式通过内部状态和外部状态的区分,外部状态相互独立,客户端可以根据需求任意使用。

缺点:

  • 享元模式需要增加逻辑来取分出内部状态和外部状态,增加了编程的复杂度;

适用环境:

  • 当一个系统中有大量重复使用的相同或相似对象时,使用享元模式可以节约系统资源;
  • 对象的大部分状态都可以外部化,可以将这些状态传入对象中。

Guess you like

Origin blog.csdn.net/qq_24649627/article/details/115377833