享元模式

一、概念

当系统存在大量相同或者相似的对象时,可以使用享元模式。享元模式通过共享技术实现相同或者相似的细粒度对象的复用,从而节约了内存空间,提高了系统性能。在享元模式中提供了一个享元池用于存储已经创建好的享元对象,并通过享元工厂类享元对象提供给客户端使用。

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

享元模式与对象池模式的区别:对象池的概念是为了避免频繁地进行对象创建和释放导致内存碎片,可以预先申请一片连续地区域空间,每次创建对象时,从对象池取出空闲对象来使用,使用完成后再放回对象池以供后续使用。

对象池复用为重复使用,主要目的是节省时间与性能消耗。而享元模式的复用可以理解为共享使用,整个声明周期都是被所有使用者共享的,主要目的是节省空间

二、模式结构

享元模式(又称轻量级模式)通常结合工厂模式一起使用,要求能够被共享的对象必须是细粒度对象,是一种对象结构型模式。

  • Flyweight(抽象享元类):抽象享元类通常是一个接口或者是抽象类,其声明了具体享元类公共的方法,这些方法向外界提供享元对象的内部数据(内部状态),同时也通过这些方法来设置外部数据(外部状态)

  • ConcreteFlyweight(具体享元类):具体享元类实现了抽象享元类,其实例被称为享元对象,为内部状态提供存储空间。

  • UnsharedConcreteFlyweight(非共享具体享元类):继承于抽象享元类,但是不能被共享。

  • FlyweightFactory(享元工厂类):用于创建并管理享元类对象,将各种类型的具体享元类对象存储在享元池中。

    image-20240706222025606

  • 单纯共享模式:在单元共享模式中所有的具体享元类都是可以共享的,不存在非共享具体享元类。

    image-20240706223613510

  • 复合享元模式:将一些单纯享元对象使用组合模式还可以形成复合享元对象,复合享元模式虽然本身不可共享,但是可以将多个内部状态不同的享元对象设置成相同的外部状态。

    image-20240706224459989

三、具体实现

设计一款围棋游戏,棋盘中存在需要除坐标不同,其他都相同的黑子、白子,如果将所有的棋子都作为一个独立对象存储在内存中,将导致所需内存空间较大,这个时候也使用享元模式来解决该问题。

  • Coordinates:坐标类,用来表示享元模式的外部状态
public class Coordinates
{
    private int x;
    private int y;

    public Coordinates(int x, int y)
    {
        this.x = x;
        this.y = y;
    }

    public int getX()
    {
        return x;
    }
    public int getY()
    {
        return y;
    }
    public void setPos(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}
  • IgoChessman(抽象享元类):抽象棋子类
public abstract class IgoChessman
{
    public abstract string GetColor();

    public void Display(Coordinates coordinates)
    {
        Console.WriteLine($"棋子颜色: {GetColor()}, 棋子位置: {coordinates.getX()}, {coordinates.getY()}");
    }
}
  • BlackIgoChessman、WhiteIgoCheesman(具体享元类):黑白棋子类
public class BlackIgoChessman : IgoChessman
{
    public override string GetColor()
    {
        return "Black";
    }
}

public class WhiteIgoCheesman : IgoChessman
{
    public override string GetColor()
    {
        return "White";
    }
}
  • IgoChessmanFactory(享元工厂类):创造黑白棋子
public class IgoChessmanFactory
{
    private static IgoChessmanFactory _instance;
    private Dictionary<string, IgoChessman> _igoChessDictionary = new Dictionary<string, IgoChessman>();

    public static IgoChessmanFactory GetInstance()
    {
        if (_instance == null) _instance = new IgoChessmanFactory();
        return _instance;
    }
    public IgoChessman GetIgoChessman(string name)
    {
        if (!_igoChessDictionary.TryGetValue(name, out IgoChessman chessman))
        {
            if (name == "Black") chessman = new BlackIgoChessman();
            else if (name == "White") chessman = new WhiteIgoCheesman();
            _igoChessDictionary.Add(name, chessman);
        }
        return chessman;
    }
}
  • Client:客户端
IgoChessman black1, black2, black3, white1, white2;
IgoChessmanFactory igoChessmanFactory = IgoChessmanFactory.GetInstance();
Coordinates coordinates = new Coordinates(0, 0);

black1 = igoChessmanFactory.GetIgoChessman("Black");
black2 = igoChessmanFactory.GetIgoChessman("Black");
black3 = igoChessmanFactory.GetIgoChessman("Black");
if (black1 == black2 && black2 == black3) Console.WriteLine("Same Black Chess");
white1 = igoChessmanFactory.GetIgoChessman("White");
white2 = igoChessmanFactory.GetIgoChessman("White");
if (white1 == white2) Console.WriteLine("Same White Chess");
black1.Display(coordinates);
coordinates.setPos(1, 0);
black2.Display(coordinates);
coordinates.setPos(1, 1);
black3.Display(coordinates);
coordinates.setPos(1, 2);
white1.Display(coordinates);
coordinates.setPos(1, 3);
white2.Display(coordinates);

四、优缺点

  • 优点:
    1. 降低内存消耗:通过共享内部状态,相同对象只需要保存一份,从而减少了系统对象的数量,降低内存占用。
    2. 提高性能:由于对象数量减少,性能也会相应提升。
  • 缺点:
    1. 增加程序复杂性:为了使对象可以共享,需要将不能共享的状态外部化,使逻辑变得复杂。
    2. 运行时间变长:读取享元模式的外部状态可能会增加运行时间。

五、适用场景

  1. 当系统有大量相同或者相似的对象,造成内存浪费时,可以使用享元模式。
  2. 对象的大部分状态都可以外部化,可以将这些外部状态传入对象。