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享元模式抽象基类及接口:

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

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

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

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

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

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);
	}
}

复合Flyweight享元模式典型的UML结构图如图2所示:

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

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

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

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

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

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

示例1:一个咖啡的例子

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

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

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

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++;
  }
}

示例2:咖啡店的例子

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

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

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));
  }
}

Flyweight享元模式使用总结:

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

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

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

时间: 2024-10-06 23:33:45

Flyweight模式详解--设计模式(9)的相关文章

Proxy模式详解--设计模式(11)

Proxy模式的产生原因:        在某些情况下,一个对象不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 当由于某些特定的需要调用的对象在另外一台机器上,需要跨越网络才能访问,在没有WebService的情况下我们需要直接coding去处理网络连接.处理打包.解包等等非常复杂的步骤,而WebService的出现帮我们解决了其中的一些问题简化客户端的处理,我们只需在客户端建立一个远程对象的代理,客户端就象调用本地对象一样调用该代理,再由代理去跟实际对象联

Interpreter 模式详解--设计模式(22)

Interpreter 模式的来源: Interpreter(解释器)模式是一种特殊的设计模式,它建立一个解释器(Interpreter),对于特定的计算机程序设计语言,用来解释预先定义的文法.简单地说,Interpreter模式是一种简单的语法解释器构架.解释器模式属于行为模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. Interpreter 模式作用:     正如其名,此模式大多用来解释一些(自定义的)独特语法,例如某些游戏开发引擎中

Observer模式详解--设计模式(15)

Observer模式来源: Observer模式应该可以说是应用最多.影响最广的模式之一. 因为Observer的一个实例Model/View/Control(MVC)结构在系统开发架构设计中有着很重要的地位和意义,MVC实现了业务逻辑和表示层的解耦.在MFC中,Doc/View(文档视图结构)提供了实现MVC的框架结构(有一个从设计模式(Observer模式)的角度分析分析Doc/View的文章正在进一步的撰写当中,遗憾的是时间:)).在Java阵容中,Struts则提供和MFC中Doc/Vi

Strategy模式详解--设计模式(13)

Strategy模式来源:        在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能.如查找.排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法:当然也可以将这些查找算法封装在一个统一的方法中,通过if-else-或者case等条件判断语句来进行选择.这两种实现方法我们都可以称之为硬编

C++Builder建造者模式详解--设计模式(4)

生活中有着很多的Builder的例子,个人觉得大学生活就是一个Builder模式的最好体验:要完成大学教育,一般将大学教育过程分成4个学期进行,因此没有学习可以看作是构建完整大学教育的一个部分构建过程,每个人经过这4年的(4个阶段)构建过程得到的最后的结果不一样,因为可能在四个阶段的构建中引入了很多的参数(每个人的机会和际遇不完全相同). Builder模式要解决的也正是这样的问题:当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),我们要要复杂对象的创建过程和这个对象的表示(展示

Memento模式详解--设计模式(16)

Memento模式来源:      我们在进行软件系统的设计时候是要给用户后悔的权利(实际上可能也是用户要求的权利:)),我们对一些关键性的操作肯定需要提供诸如撤销(Undo)的操作.那这个后悔药就是Memento模式提供的. Memento模式作用:       在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态. Memento模式UML结构图如图1所示:                       Memento模式构成:

Template模式详解--设计模式(12)

Template模式来源:    在面向对象系统的分析与设计过程中经常会遇到这样一种情况:对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的.Template提供了这种情况的一个实现框架. Template模式作用: Template模式又叫模板方法模式,在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中.模板方法使得子类可以在不改变算法结构的情冴下,重新定义算法中的某些步骤. 我们使用冲泡咖啡和冲泡茶的例子 加工流程: 咖啡冲

State模式详解--设计模式(14)

State模式来源:         每个人.事物在不同的状态下会有不同表现(动作),而一个状态又会在不同的表现下转移到下一个不同的状态(State).最简单的一个生活中的例子就是:地铁入口处,如果你放入正确的地铁票,门就会打开让你通过.在出口处也是验票,如果正确你就可以ok,否则就不让你通过(如果你动作野蛮,或许会有报警(Alarm)). 有限状态自动机(FSM)也是一个典型的状态不同,对输入有不同的响应(状态转移).通常我们在实现这类系统会使用到很多的Switch/Case语句,Case某种

Iterator模式详解--设计模式(21)

Iterator模式来源: 迭代器(Iterator)模式,又叫做游标(Cursor)模式.GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节.从定义可见,迭代器模式是为容器而生. Iterator模式作用: (1).它支持以不同的方式遍历一个聚合复杂的聚合可用多种方式进行遍历,如二叉树的遍历,可以采用前序.中序或后序遍历.迭代器模式使得改变遍历算法变得很容易: 仅需用一个不同的迭代器的实例代替原先的实例即可,你也可以自己定义迭代器的子