前置文章: 设计模式的原则
其他设计模式:用心理解设计模式专栏
设计模式相关代码已统一放至 我的 Github
一、定义
结构型模式之一。
Use sharing to support large numbers of fine-grained objects efficiently.
(使用共享对象可以有效地支持大量的细粒度的对象。)
二、结构解析
蝇量模式的一般结构有三种角色:抽象蝇量、具体蝇量(可分为可共享内部状态的具体蝇量和无可共享内部状态的具体蝇量)、蝇量工厂。
抽象蝇量(Flyweight):定义了对蝇量对象外部状态的操作接口。使用该接口,可在Client中修改蝇量对象的外部状态。
可共享内部状态的具体蝇量(ConcreteFlyweight):维护可以共享的内部状态(因为内部状态不可变,所以一般在构造时初始化);实现操作外部状态的接口。
无可共享内部状态的具体蝇量(UnsharedConcreteFlyweight):无内部状态,所以只实现操作外部状态的接口。
蝇量工厂(FlyweightFactory):负责管理蝇量对象,形成一个对象池,提供取出对象的方法,取出时若池中对象足够,就直接返回,若对象不足则创建后返回。同时,也应提供对象放回的方法,以达到复用的目的,放回时要对外部状态进行重置,避免下次取出一个脏对象(或,不忘记在取出对象后对外部状态进行改变)。一个蝇量工厂应只管理一种蝇量对象,所以,当有多种蝇量对象时,应结合工厂模式。
三、评价
蝇量模式,又叫享元模式(共享元素/共享内部状态)。它是一种对性能进行优化的设计模式,通过复用蝇量对象(大量类似的对象),降低系统的内存压力(复用减少对象总数)和CPU压力(复用避免了对象的实例化,有些对象的实例化是耗时操作)。
“内部状态” 指,共享的属性, 内部状态在指定后,就无法在外部进行更改了(一般在声明或对象构造时即赋值,且不提供public的set方法)。
“外部状态” 指,不共享的部分,外部状态将在Client中,根据环境赋予不同的值。
举个例子: 有一种“球”,大小是固定的,颜色是不固定的,那“大小”即为球对象的内部状态,“颜色”即为球对象的外部状态。在蝇量模式中,球工厂在创建球时,即以共同大小创建球,然后交给Client,在Clinet中根据需求,对球赋予不同的颜色。
“复用” 和 “共享” 是两个相互独立的概念。 “复用” 指,对象可以进行回收,反复使用,避免创建。 “共享” 指,对象共享内部状态。
所以,需要注意的是,无可共享内部状态的对象,也是可以进行复用的(只复用不享元)。因此,准确来说,该模式就应该只叫做蝇量模式,而不仅仅是享元模式。
四、类和结构体的选择
结构体是值类型,内存在栈中分配。栈的分配和释放代价比堆低的多,所以如果能使用结构体,就不用再考虑使用“类+对象池”优化。
但使用结构体也有缺点,因为值类型是深拷贝,引用类型是浅拷贝,因此类对象在拷贝时比结构体对象更节省空间。
需要注意的是,结构体作为值类型,应在赋值后不再改变。
最终选用类还是结构体,还得综合考虑,如下:
------------------------------------------------------------------------------
1、对于较大的对象应使用类,因为堆栈的空间有限。
2、对于轻量且生命周期短的对象,可以直接使用结构体,如Unity中的 Vector2/Vector3/Vector4、Color等,就是结构体。
3、拥有继承关系时,应使用类。
4、如使用结构体不能避免拆装箱,就改选用类。
5、如属性有更改需求,应使用类(结构体是值类型,应在赋值后不再改变)。
6、不是自定义的类(如系统类)时,直接不用考虑改结构体了,只能结合对象池优化。
五、实现
为了说明 “无可共享内部状态的具体蝇量” 也进行可以复用, 这里结合 工厂方法模式 (并且使用到了 模板方法模式),派生了 SharedFlyweightFactory 和 UnsharedFlyweightFactory 两个蝇量工厂。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Flyweight
{
//抽象蝇量
public abstract class Flyweight
{
public abstract void Operation(string externalState);
}
//具体蝇量, 有可共享的内部状态
public class ConcreteFlyweight : Flyweight
{
private readonly string internalState; //声明为readonly,不可改变。
public string externalState;
//内部状态在构造时赋值
public ConcreteFlyweight(string internalState)
{
this.internalState = internalState;
}
//外部状态可在Client中操作
public override void Operation(string externalState)
{
this.externalState = externalState;
}
}
//具体蝇量, 无可共享的内部状态
public class UnsharedConcreteFlyweight: Flyweight
{
public string externalState;
public UnsharedConcreteFlyweight(){ }
//外部状态可在Client中操作
public override void Operation(string externalState)
{
this.externalState = externalState;
}
}
//这里想要说明 复用和共享是两个相互独立的概念,UnsharedConcreteFlyweight 也进行可以复用。
//因为,一个蝇量工厂应该只对应一种蝇量对象(方便扩展)。
//所以,这里抽象出一个蝇量工厂的基类,再分别派生两个蝇量工厂类 SharedFlyweightFactory 和 UnsharedFlyweightFactory。
public abstract class FlyweightFactory
{
public FlyweightFactory(int initCount)
{
for (int i = 0; i < initCount; i++)
{
CreateFlyweight();
}
}
public List flyweights = new List();
//从蝇量工厂中拿出蝇量对象
public Flyweight GetFlyweight()
{
if (flyweights.Count > 0)
{
Flyweight flyweight = flyweights[0];
flyweights.RemoveAt(0);
return flyweight;
}
else
{
CreateFlyweight();
return GetFlyweight();
}
}
//将不使用的蝇量对象放回蝇量工厂
public void SetFlyweight(Flyweight flyweight)
{
//重置外部状态
flyweight.Operation(null);
flyweights.Add(flyweight);
}
public abstract void CreateFlyweight();
}
//有可共享的内部状态的蝇量工厂
public class SharedFlyweightFactory : FlyweightFactory
{
public SharedFlyweightFactory(int initCount): base(initCount) {}
//创建蝇量对象
public override void CreateFlyweight()
{
flyweights.Add(new ConcreteFlyweight("这个字符串代表不变、共享的“一些”内部状态"));
}
}
//无可共享的内部状态的蝇量工厂
public class UnsharedFlyweightFactory : FlyweightFactory
{
public UnsharedFlyweightFactory(int initCount) : base(initCount) { }
//创建蝇量对象
public override void CreateFlyweight()
{
flyweights.Add(new UnsharedConcreteFlyweight());
}
}
//客户
public class Client
{
static public void Main()
{
//创建可共享内部状态的蝇量工厂,以一定数量初始化。
FlyweightFactory sff = new SharedFlyweightFactory(2);
//获取蝇量对象
Flyweight sf1 = sff.GetFlyweight(); //蝇量工厂初始化时创建的蝇量对象。
Flyweight sf2 = sff.GetFlyweight(); //蝇量工厂初始化时创建的蝇量对象。
Flyweight sf3 = sff.GetFlyweight(); //蝇量工厂中蝇量对象不足时,新建的蝇量对象。
//在Client中对蝇量对象的外部状态进行操作。
sf1.Operation("这个字符串代表变化、不共享的“一些”外部状态,123123123");
sf2.Operation("这个字符串代表变化、不共享的“一些”外部状态,456456456");
sf3.Operation("这个字符串代表变化、不共享的“一些”外部状态,789789789");
//将不使用的sf3放回蝇量工厂,以便之后进行复用。
sff.SetFlyweight(sf3);
//获取蝇量对象
Flyweight sf4 = sff.GetFlyweight(); //获取到了上一步放回的sf3, 达到复用的目的。
//------------------------------------------NRatel割------------------------------------------------
//创建不可共享内部状态的蝇量工厂,以一定数量初始化。
FlyweightFactory usff = new UnsharedFlyweightFactory(0);
//获取蝇量对象
Flyweight usf1 = sff.GetFlyweight(); //蝇量工厂中蝇量对象不足时,新建的蝇量对象。
//在Client中对蝇量对象的外部状态进行操作。
usf1.Operation("这个字符串代表变化、不共享的“一些”外部状态,789789789");
//将不使用的usf1放回蝇量工厂,以便之后进行复用。
usff.SetFlyweight(usf1);
//获取蝇量对象
Flyweight usf2 = sff.GetFlyweight(); //获取到了上一步放回的usf1, 达到复用的目的。
}
}
}