设计模式——享元模式

初识

享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。
举个最简单的例子,网络联机下棋的时候,一台服务器连接了多个客户端(玩家),如果我们每个棋子都要创建对象,那一盘棋可能就有上百个对象产生,玩家多点的话,因为内存空间有限,一台服务器就难以支持了,所以这里要使用享元模式,将棋子对象减少到几个实例。


结构

设计模式——享元模式

角色

  • FlyweightFactory:一个享元工厂,用来创建并管理Flyweight对象,它主要用来确保合理的共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话)
  • Flyweight:所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态。
  • ConcreteFlyweight:继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间。
  • UnsharedConcreteFlyweight:指那些不需要共享的Flyweight子类,因为Flyweight接口共享成为可能,但它不强制共享。

实现

Flyweight类:

abstract class Flyweight
{
    public abstract void Operation(int extrinsicstate);
    
}

ConcreteFlyweight类:

class ConcreteFlyweight:Flyweight
{
   public override void Operation(int extrinsicstate)
   {
       Console.WriteLine("具体Flyweight:"+extrinsicstate);
   }
}

UnsharedConcreteFlyweight类:

class UnsharedConcreteFlyweight:Flyweight
{
    public override void Operation(int extrinsicstate)
    {
        Console.WriteLine("不共享的具体Flyweight:"+extrinsicstate);
    }
}

FlyweightFactory类:

class FlyweightFactory
{
   private Hashtable flyweights = new Hashtable();

   public FlyweightFactory()
   {
       flyweights.Add("X", new ConcreteFlyweight());
       flyweights.Add("Y", new ConcreteFlyweight());
       flyweights.Add("Z", new ConcreteFlyweight());
   }

   public Flyweight GetFlyweight(string key)
   {
       return ((Flyweight)flyweights[key]);
   }
}

Client客户端:

static void Main(string[] args)
{
    int extrinsicstate = 22;

    FlyweightFactory f = new FlyweightFactory();

    Flyweight fx = f.GetFlyweight("X");
    fx.Operation(--extrinsicstate);

    Flyweight fy = f.GetFlyweight("Y");
    fy.Operation(--extrinsicstate);

    Flyweight fz = f.GetFlyweight("Z");
    fz.Operation(--extrinsicstate);

    Flyweight uf = new UnsharedConcreteFlyweight();

    uf.Operation(--extrinsicstate);

    Console.Read();
}

内部状态与外部状态

内部状态: 在享元对象内部并且不会随环境改变而改变的共享部分,可以成为是享元对象的内部状态。

外部状态: 随环境改变而改变的,不可以共享的状态就是外部状态。

举例:

享元模式通过共享技术实现相同或相似对象的重用,示意图如下(我们可以共用一个 Hello world 对象,其中字符串 “Hello world” 为内部状态,可共享;字体颜色为外部状态,不可共享,由客户端设定):

设计模式——享元模式
那么为什么这里要用享元模式呢?可以想象一下,上面提到的棋类游戏的例子,比如围棋,理论上有361个空位可以放棋子,常规情况下每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。


优点

  • 享元模式的优点在于它能够极大的减少系统中对象的个数。
  • 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。

缺点

  • 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

适用场景

  • 如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
  • 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

模式分析

享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。

享元对象能做到共享的关键是区分内部状态与外部状态。