享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内蕴状态和外蕴状态,内蕴状态是存储在享元对象内部并且不会随环境改变而改变,因此内蕴状态并可以共享。
外蕴状态是随环境改变而改变的,不可以共享的状态,享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部,外蕴状态与内蕴状态是相互独立的。
单纯享元模式的结构
在单纯享元模式中,所有的享元对象都是可以共享的
抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定需要实现的公共接口,那些需要外蕴状态的操作可以通过调用商业方法以参数形式传入。
具体享元角色:实现抽象享元角色所规定的接口,如果有内蕴状态的话,必须负责为内蕴状态提供存储空间,享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。
享元工厂角色:本角色负责创建和管理享元角色,本角色必须保证享元对象可以被系统适当地共享,当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象,如果系统中没有一个适当地享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
客户端角色:本角色需要维护一个对所有享元对象的引用,本角色需要自行存储所有享元对象的外蕴状态。
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Simple_Factory
{
/// <summary>
/// 单纯享元模式
/// </summary>
abstract class Flyweight
{
abstract public void Operation(int extrinsicstate);
}
class ConcrateFlyweight:Flyweight
{
private string intrinsicstate = "a";
public override void Operation(int extrinsicstate)
{
Console.WriteLine("ConcreateFlyweight:intrinsicstate{0},extrinsicstate{1}",intrinsicstate,extrinsicstate);
}
}
class FlyweightFactory
{
private Hashtable flyweights = new Hashtable();
public FlyweightFactory()
{
flyweights.Add("x", new ConcrateFlyweight());
flyweights.Add("y", new ConcrateFlyweight());
flyweights.Add("z", new ConcrateFlyweight());
}
public Flyweight GetFlyweight(string key)
{
return ((Flyweight)flyweights[key]);
}
}
class Flyweight_pattern
{
public static void Main()
{
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);
}
}
}
复合享元模式中,所有的享元对象都可以直接共享,下面考虑一个较为复杂的情况,即将一些单纯享元使用合成模式加以复合,形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
享元模式所涉及的角色有抽象享元角色,具体享元角色,复合享元角色,享元工厂角色,以及客户端角色
抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口,那些需要外蕴状态的操作可以通过方法的参数传入,抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元角色:实现抽象享元角色所规定的接口,如果有内蕴状态的话,必须负责为内蕴状态提供存储空间,享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
复合享元角色:复合享元角色所代表的对象是不可以共享的,但是一个符合享元对象可以分解成为多个本色是单纯享元对象的组合,复合享元角色又称作不可共享的享元对象。
享元工厂角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享,当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当地享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
客户端角色:本角色还需要自行存储所有享元对象的外蕴状态。
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Simple_Factory
{
/// <summary>
/// 单纯享元模式
/// 模拟卖咖啡问题
/// </summary>
public abstract class Order
{
/// <summary>
/// 将咖啡卖给客人
/// </summary>
public abstract void Serve();
/// <summary>
/// 返回咖啡的名字
/// </summary>
public abstract string GetFlavor();
}
public class Flavor:Order
{
private string flavor;
/// <summary>
/// 构造函数,内蕴状态以参数方式传入
/// </summary>
public Flavor(string flavor)
{
this.flavor = flavor;
}
public override void Serve()
{
Console.WriteLine("Serving flavor"+flavor);
}
public override string GetFlavor()
{
this.flavor = flavor;
}
}
public class FlavorFactory
{
private Hashtable flavors = new Hashtable();
public Order GetOrder(string key)
{
if (!flavors.ContainsKey(key))
flavors.Add(key,new Flavor(key));
return ((Order)flavors[key]);
}
public int GetTotalFlavorsMade()
{
return flavors.Count;
}
}
class SaleCoffee
{
private static FlavorFactory flavorFactory;
private static int ordersMade = 0;
private static void TakeOrder(string aFlavor)
{
Order o = flavorFactory.GetOrder(aFlavor);
//将咖啡卖给客人
o.Serve();
ordersMade++;
}
public static void Main()
{
flavorFactory = new FlavorFactory();
TakeOrder("Black Coffee");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Black Coffee");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Black Coffee");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Black Coffee");
Console.WriteLine("/n Total Orders made:"+ordersMade);
Console.WriteLine("/n Total Flavor objects made:"+flavorFactory.GetTotalFlavorsMade());
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
namespace Simple_Factory
{
/// <summary>
/// 有外蕴状态的单纯享元模式
/// </summary>
public abstract class Order
{
//将咖啡卖给客人
public abstract void Serve(Table table);
//返回咖啡的名字
public abstract string GetFlavor();
}
public class Flavor:Order
{
private string flavor;
//构造函数,内蕴状态以参数方式传入
public Flavor(string flavor)
{
this.flavor = flavor;
}
public override void Serve(Table table)
{
Console.WriteLine("Serving table {0} with flavor {1}",table.Number,flavor);
}
public override string GetFlavor()
{
return this.flavor;
}
}
public class FlavorFactory
{
private Hashtable flavors = new Hashtable();
public Order GetOrder(string key)
{
if (!flavors.ContainsKey(key))
flavors.Add(key,new Flavor(key));
return ((Order)flavors[key]);
}
public int GetTotalFlavorsMade()
{
return flavors.Count;
}
}
public class Table
{
private int number;
public Table(int number)
{
this.number = number;
}
public int Number
{
get
{
return number;
}
}
}
class SaleCoffeeTwo
{
private static FlavorFactory flavorFactory;
private static int ordersMade = 0;
private static void TakeOrder(string aFlavor)
{
Order o = flavorFactory.GetOrder(aFlavor);
//将咖啡卖给客人
o.Serve(new Table(++ordersMade));
}
public static void Main()
{
flavorFactory = new FlavorFactory();
TakeOrder("Black Coffee");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Black Coffee");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Black Coffee");
TakeOrder("Capucino");
TakeOrder("Espresso");
TakeOrder("Capucino");
TakeOrder("Espresso");
Console.WriteLine("/n Total Orders made:"+ordersMade);
Console.WriteLine("/n Total Flavor objects made:"+flavorFactory.GetTotalFlavorsMade());
}
}
}
享元模式应当在什么情况下使用
1,一个系统有大量的对象
2,这些对象耗费大量的内存
3,这些对象的状态中的大部分都可以外部化
4,这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替
5,软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的
最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源,因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。
享元模式的优点和缺点
享元模式的优点在于它大幅度地降低内存中对象的数量,但是,它做到这一点所付出的代价也是很高的:
享元模式使得系统更加复杂,为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。