程序员人生 网站导航

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

栏目:php教程时间:2016-08-29 09:03:57

Flyweight模式产生缘由:

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

Flyweight享元模式作用:

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

Flyweight享元模式的使用处景:

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

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

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

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

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

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

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

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

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

                

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

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

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

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

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

单纯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)角色:复合享元角色所代表的对象是不可以同享的,但是1个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可同享的享元对象。

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

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

示例1:1个咖啡的例子

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

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

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

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:咖啡店的例子

      在前面的咖啡摊项目里,由于没有供客人坐的桌子,所有的咖啡均没有环境的影响。换言之,咖啡唯一内蕴状态,也就是咖啡的种类,而没有外蕴状态。下面斟酌1个范围稍稍大1点的咖啡屋(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享元模式的优点在于它大幅度地下降内存中对象的数量。但是,它做到这1点所付出的代价也是很高的:

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

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




------分隔线----------------------------
------分隔线----------------------------

最新技术推荐