Flyweight模式详解--设计模式(9)

Flyweight模式产生原因:

        在面向对象系统的设计何实现中,创建对象是最为常见的操作。这里面就有一个问题:如果一个应用程序使用了太多的对象,就会造成很大的存储开销。特别是对于大量轻量级(细粒度)的对象,比如在文档编辑器的设计过程中,我们如果为没有字母创建一个对象的话,系统可能会因为大量的对象而造成存储开销的浪费。例如一个字母“a”在文档中出现了100000次,而实际上我们可以让这一万个字母“a”共享一个对象,当然因为在不同的位置可能字母“a”有不同的显示效果(例如字体和大小等设置不同),在这种情况我们可以为将对象的状态分为“外部状态”和“内部状态”,将可以被共享(不会变化)的状态作为内部状态存储在对象中,而外部对象(例如上面提到的字体、大小等)我们可以在适当的时候将外部对象最为参数传递给对象(例如在显示的时候,将字体、大小等信息传递给对象)。

Flyweight享元模式作用:

      Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度。应用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,那么我们可以将这些字符串储存在Flyweight池(pool)中。就是先创建一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体模型,很显然,在这里需要产生不同的新对象,所以Flyweight模式中常出现Factory模式。Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个Flyweightpool(模式池)来存放内部状态的对象。

Flyweight享元模式的使用场景:

        当以下所有的条件都满足时,可以考虑使用享元模式:

(1).一个系统有大量的对象。

(2).这些对象耗费大量的内存。

(3).这些对象的状态中的大部分都可以外部化。

(4).这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。

(5).软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

       满足以上的这些条件的系统可以使用享元对象。

       最后,使用享元模式需要维护一个记录了系统已有的所有享元的表,而这需要耗费资源。因此,应当在有足够多的享元实例可供共享时才值得使用享元模式。

单纯Flyweight享元模式模式典型的UML结构图如图1所示:

                Flyweight模式详解--设计模式(9)_第1张图片

单纯Flyweight享元模式抽象基类及接口:

        抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入。

       具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。

       享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个复合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应 当创建一个合适的享元对象。

       客户端(Client)角色:本角色需要维护一个对所有享元对象的引用。本角色需要自行存储所有享元对象的外蕴状态。

单纯Flyweight享元模式典型的示例代码如下:

<span style="font-size:14px;">using System;
using System.Collections;

// 享元工厂类
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]);
	}
}
// 轻量级选手
abstract class Flyweight
{
	// 方法
	abstract public void Operation(int extrinsicstate);
}
// 具体享元类
class ConcreteFlyweight : Flyweight
{
	private string intrinsicstate = "A";
	// 方法
	override public void Operation(int extrinsicstate)
	{
		Console.WriteLine("ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}",
			intrinsicstate, extrinsicstate);
	}
}

public class Client
{
	public 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);
	}
}
</span>
复合Flyweight享元模式典型的UML结构图如图2所示:
             Flyweight模式详解--设计模式(9)_第2张图片

复合Flyweight享元模式抽象基类及接口:

       抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。

       具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。

       复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。

        享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。

客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。

示例1:一个咖啡的例子

       在这个咖啡摊(CoffeeStall)所使用的系统里,有一系列的咖啡"风味(Flavor)"。客人到摊位上购买咖啡,所有的咖啡均放在台子上,客人自己拿到咖啡后就离开摊位。咖啡有内蕴状态,也就是咖啡的风味;咖啡没有环境因素,也就是说没有外蕴状态。如果系统为每一杯咖啡都创建一个独立的对象的话,那么就需要创建出很多的细小对象来。这样就不如把咖啡按照种类(即"风味")划分,每一种风味的咖啡只创建一个对象,并实行共享。

       使用咖啡摊主的语言来讲,所有的咖啡都可按"风味"划分成如Capucino、Espresso等,每一种风味的咖啡不论卖出多少杯,都是全同、不可分辨的。所谓共享,就是咖啡风味的共享,制造方法的共享等。因此,享元模式对咖啡摊来说,就意味着不需要为每一份单独调制。摊主可以在需要时,一次性地调制出足够一天出售的某一种风味的咖啡。

      很显然,这里适合使用单纯享元模式。系统的设计如下:  

<span style="font-size:14px;">using System;
using System.Collections;

public abstract class Order
{
  // 将咖啡卖给客人
  public abstract void Serve();
  // 返回咖啡的名字
  public abstract string GetFlavor();
}

public class Flavor : Order
{
  private string flavor;

  // 构造函数,内蕴状态以参数方式传入
  public Flavor(string flavor)
  {
    this.flavor = flavor;
  }

  // 返回咖啡的名字
  public override string GetFlavor()
  {
    return this.flavor;
  }

  // 将咖啡卖给客人
  public override void Serve()
  {
    Console.WriteLine("Serving 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;
  }
}

public class Client
{
  private static FlavorFactory flavorFactory;
  private static int ordersMade = 0;

  public static void Main( string[] args )
  {
    flavorFactory = new FlavorFactory();

    TakeOrder("Black Coffee");
    TakeOrder("Capucino");
    TakeOrder("Espresso");
    TakeOrder("Capucino");
    TakeOrder("Espresso");
    TakeOrder("Black Coffee");
    TakeOrder("Espresso");
    TakeOrder("Espresso");
    TakeOrder("Black Coffee");
    TakeOrder("Capucino");
    TakeOrder("Capucino");
    TakeOrder("Black Coffee");

    Console.WriteLine("\nTotal Orders made: " + ordersMade);

    Console.WriteLine("\nTotal Flavor objects made: " + 
      flavorFactory.GetTotalFlavorsMade());
  }

  private static void TakeOrder(string aFlavor)
  {
    Order o = flavorFactory.GetOrder(aFlavor);
    // 将咖啡卖给客人
    o.Serve();

    ordersMade++;
  }
}</span>

示例2:咖啡店的例子

      在前面的咖啡摊项目里,由于没有供客人坐的桌子,所有的咖啡均没有环境的影响。换言之,咖啡仅有内蕴状态,也就是咖啡的种类,而没有外蕴状态。下面考虑一个规模稍稍大一点的咖啡屋(Coffee Shop)项目。屋子里有很多的桌子供客人坐,系统除了需要提供咖啡的"风味"之外,还需要跟踪咖啡被送到哪一个桌位上,因此,咖啡就有了桌子作为外蕴状态。

       由于外蕴状态的存在,没有外蕴状态的单纯享元模式不再符合要求。系统的设计可以利用有外蕴状态的单纯享元模式。系统的代码如下:

<span style="font-size:14px;">using System;
using System.Collections;

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 string GetFlavor()
  {
    return this.flavor;
  }

  // 将咖啡卖给客人
  public override void Serve(Table table)
  {
    Console.WriteLine("Serving table {0} with flavor {1}", table.Number, 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; }
  }
}

public class Client
{
  private static FlavorFactory flavorFactory;
  private static int ordersMade = 0;

  public static void Main( string[] args )
  {
    flavorFactory = new FlavorFactory();

    TakeOrder("Black Coffee");
    TakeOrder("Capucino");
    TakeOrder("Espresso");
    TakeOrder("Capucino");
    TakeOrder("Espresso");
    TakeOrder("Black Coffee");
    TakeOrder("Espresso");
    TakeOrder("Espresso");
    TakeOrder("Black Coffee");
    TakeOrder("Capucino");
    TakeOrder("Capucino");
    TakeOrder("Black Coffee");

    Console.WriteLine("\nTotal Orders made: " + ordersMade);

    Console.WriteLine("\nTotal Flavor objects made: " + 
      flavorFactory.GetTotalFlavorsMade());
  }

  private static void TakeOrder(string aFlavor)
  {
    Order o = flavorFactory.GetOrder(aFlavor);
    
    // 将咖啡卖给客人
    o.Serve(new Table(++ordersMade));
  }
}</span>

Flyweight享元模式使用总结:

(1).Flyweight享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:

(2).Flyweight享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。

(3).Flyweight享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。




你可能感兴趣的:(C++)