老生常谈:享元模式
享元模式:
以共享的方式高效地支持大量的细粒度对象。
享元对象的状态:
2:外蕴状态(External State)。外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。
享元模式的应用条件:
1: 一个系统有大量的对象。
2:这些对象耗费大量的内存。
3:这些对象的状态中的大部分都可以外部化。
4:这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5:软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
.NET中的String类型就是运用了享元模式。.NET中如果第一次创建了一个字符串对象s1,下次再创建相同的字符串s2时只是把它的引用指向s1所引用的具体对象,这就实现了相同字符串在内存中的共享。下面的程序来演示s1和s2的引
用是否一致: 输出的结果为True。
string s2 = "测试字符串一";
Console.WriteLine(Object.ReferenceEquals(s1, s2));
注意:如果再有一个字符串s3,它的初始值为“测试字符串”,再对它进行操作s3 = s3 + “一”,这时虽然s1和s3的值相同,但是它们的引用是不同的。
享元模式的分类:
1:单纯享元模式;
2:复合享元模式。
第一:单纯享元模式。
1: 单纯享元模式结构图:
2:单纯享元模式构成说明:
1>:抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入。
/// "Flyweight"
/// </summary>
abstract class Flyweight
{
// Methods
/// <summary>
/// 抽象享元对象的商业方法
/// </summary>
/// <param name="extrinsicstate">外蕴状态</param>
abstract public void Operation(int extrinsicstate);
}
2>:具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。
/// "ConcreteFlyweight"
/// </summary>
class ConcreteFlyweight : Flyweight
{
private string intrinsicstate = "A";
// Methods
override public void Operation(int extrinsicstate)
{
Console.WriteLine("ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}",
intrinsicstate, extrinsicstate);
}
}
3>:享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
注意:客户端不可以直接实例化享元类,必须通过享元工厂类来创建,因为享元工厂类在系统中只能有一个,所以可以结合单例模式来改善。当客户端需要单纯享元对象时,需要调用享元工厂的Singleton()方法,此时工厂会取得所有的单纯享元对象,然后传入所需的单纯享元对象的内蕴状态,工厂方法负责产生所需要的享元对象。
/// "FlyweightFactory"
/// </summary>
class FlyweightFactory
{
// Fields
private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
private static readonly FlyweightFactory instance = new FlyweightFactory();
/// <summary>
/// Constructors
/// </summary>
private FlyweightFactory()
{
}
// Methods
/// <summary>
/// 从享元工厂中生产出一个具体的享元对象
/// </summary>
/// <param name="key">内蕴状态</param>
/// <returns></returns>
public Flyweight GetFlyweight(string key)
{
return ((Flyweight)flyweights[key]);
}
/// <summary>
/// 享元工厂单例方法
/// </summary>
/// <returns></returns>
public static FlyweightFactory Singleton()
{
return FlyweightFactory.instance;
}
/// <summary>
/// 向享元工厂对象增加一个享元对象
/// </summary>
/// <param name="sKey">内蕴状态</param>
/// <param name="_Flyweight">具体享元对象</param>
public void AddFlyweight(string sKey, Flyweight _Flyweight)
{
flyweights.Add(sKey , _Flyweight);
}
public Flyweight factory(string sKey)
{
if (flyweights.ContainsKey(sKey))
{
return this.GetFlyweight(sKey);
}
else
{
this.AddFlyweight(sKey, new ConcreteFlyweight());
return this.GetFlyweight(sKey);
}
}
}
4>:客户端(Client)角色:需要维护一个对所有享元对象的引用;需要自行存储所有享元对象外蕴状态。
int extrinsicstate = 22;
//享元工厂对象使用单例
FlyweightFactory f = FlyweightFactory.Singleton () ;
//调用过程
//向享元工厂对象请求一个内蕴状态为"X"的单纯享元对象
Flyweight fx = f.factory("X");
//调用X的商业方法,X的外蕴状态值为21
fx.Operation(--extrinsicstate);
Flyweight fy = f.factory("Y");
fy.Operation(--extrinsicstate);
Flyweight fz = f.factory("Z");
fz.Operation(--extrinsicstate);
第二:复合享元模式。
复合享元模式结构图:
结构对象说明:
1:抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
2:具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
3:复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。
4:享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
5:客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。
享元模式的优点:
大幅度地降低内存中对象的数量。
享元模式的缺点:
1:享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
2:享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
总结:
享元模式一般是解决系统性能问题的,所以经常用于底层开发,在项目开发中并不常用。
注:
本文参考:《Java与模式》
http://www.cnblogs.com/zhenyulu/articles/55793.html