设计模式--Flyweight享元模式

  Flyweight模式也叫享元模式,是由GoF提出的23种设计模式中的一种。Flyweight模式是构造型模式之一,它通过与其他类似对象共享数据来减小内存占用,所以叫享元。
  此模式解决的是由于大量的细粒度对象所造成的内存开销的问题,它在实际的开发中并不常用,但是作为底层的提升性能的一种手段却很有效。
  享元模式涉及的主要角色:
1、Flyweight目标类:需要与其他类似对象共享数据来减小内存占用的某一类事物的抽象
2、FlyweightFactory:对Flyweight实例提供管理的类, 调用方在生成Flyweight实例时,不直接通过new的方式而是通过FlyweightFactory.getFlyweight()取得Flyweight的一个实例。
  而FlyweightFactory 对象对Flyweight实例进行管理,调用时当Flyweight的相应实例还未生成时,则:
1,生成Flyweight实例
2,将生成的Flyweight实例保存到HashTable表中(HashTable表)
3,返回该Flyweight实例
  调用时当Flyweight的相应实例已经生成时,则:
1,从HashTable表中取出该Flyweight实例
2,返回该Flyweight实例
通过以上处理,一方面,FlyweightFactory可以在HashTable表里只保存最少限度的Flyweight实例;另一方面,调用方可以不用理会FlyweightFactory的内部实现细节而可以取得Flyweight实例。

  下面我们以一家鼠标配件生产厂家为例来看看享元模式的实现。这家鼠标生产厂家常生产的鼠标有A,B,C,D四种系列。批发商可以要求厂家生产这四种系列, 也可以加入他们指定的其它系列,不同的系列对应不同的产品模具,根据实际,我们知道,生产厂家是不可能对生产的"每一个"产品都事先制造出一个对应的“模 具”,而是针对"每一系列"的产品生产一个对应的"模具"。因为这样才不会造成资源的浪费。当然,如果出现了一种新的系列,哪怕批发商只需要1个产品,厂 家也得事先制造出此系列对应的"模具"出来。
  程序如下: 
            设计模式--Flyweight享元模式 
实现代码如下:
1、 Flyweight目标类:MouseModel



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FlyweightMouse
{
    
//MouseModel就是享元角色。在构造方法中传入modelName,然后它从指定路径加载模型数据,并且把数据放入字段中。

    
class MouseModel
    {
        
#region ModelName属性
        
private string _modelName;
        
public string ModelName
        {
            
get { return _modelName; }
            
set { _modelName = value; }
        }
        
#endregion

        
#region 构造函数
        
public MouseModel(string modelName)
        {
            ModelName 
= modelName;
        }
        
#endregion

        
#region PrintModelName方法
        
public void PrintModelName()
        {
            Console.WriteLine(
"此鼠标产品系列名是{0}",ModelName );
        }
        
#endregion
    }
}

2、FlyweightFactory:MouseModelFactory


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace FlyweightMouse
{
    
//这里的MouseModelFactory就是享元工厂角色。它的作用是创建和管理享元对象。
    
//可以看到,每加载一个鼠标生产模型都会在Hashtable中记录一下,之后如果客户端还是需要这个模型的话就直接把已有的模型对象返回给客户端,而不是重新在内存中加载一份模型数据。

    
class MouseModelFactory
    {
        
private Hashtable modelList = new Hashtable();

        
private static MouseModelFactory modelInstance;

        
// ModelFactory本身应用了Singleton,因为如果实例化多个享元工厂是的话就起不到统一管理和分配享元对象的目的了。
        public static MouseModelFactory GetModelInstance()
        {
            
if(modelInstance==null)
            {
                modelInstance 
= new MouseModelFactory();
            }
            
return modelInstance;
        }

        
public MouseModel GetModel(string modeName)
        {
            MouseModel model 
= modelList[modeName] as MouseModel;
            
if (model == null)
            {
                modelList.Add(modeName, 
new MouseModel(modeName));
                model 
= modelList[modeName] as MouseModel;
            }
            
else
            {
               
// Console.WriteLine(model.ModelName +"在HashTable表中已创建");
            }
            
return model;
        }
    }
}

3、客户端代码: 


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace FlyweightMouse
{
    
class Program
    
{
        
static void Main(string[] args)
        
{

           

           

            Random rnd 
= new Random();
            ArrayList modelNameList 
= new ArrayList() "A""B""C""D" }//鼠标生产厂家提供的常用的鼠标产品系列

            
#region 使用与不使用享元模式的内存占用情况比较
            Console.WriteLine(GC.GetTotalMemory(
true)); //GC.GetTotalMemory 返回堆中已经被分配的内存总量。由于托管堆的工作方式,这个数字并不十分精确,
                                                        
//但是如果你以true作为参数的话,还是会得到一个比较接近的近似值。这个方法在计算之前会先执行一遍回收操作。
            #region 使用享元模式

            ArrayList a 
= new ArrayList();

            
for (int i = 0; i < 10000; i++)
            
{
                
string modeName = modelNameList[rnd.Next(4)].ToString();

                MouseModel model 
= MouseModelFactory.GetModelInstance().GetModel(modeName);

                a.Add(model);

            }

            Console.WriteLine(GC.GetTotalMemory(
true));
            
#endregion


            
#region 不使用享元模式
            ArrayList b 
= new ArrayList();
            
for (int i = 0; i < 10000; i++)
            
{
                
string modeName = modelNameList[rnd.Next(4)].ToString();
                MouseModel model 
= new MouseModel(modeName);  //直接new指定数量的MouseModel
                b.Add(model);

            }

            Console.WriteLine(GC.GetTotalMemory(
true));
            
#endregion


            
#endregion



            
#region 对享元进行管理的示例
            MouseModelFactory mf 
= new MouseModelFactory();

            MouseModel mouse1 
= mf.GetModel("E");
            MouseModel mouse2 
= mf.GetModel("F");
            MouseModel mouse3 
= mf.GetModel("G");
            MouseModel mouse4 
= mf.GetModel("H");
            MouseModel mouse5 
= mf.GetModel("M");
            MouseModel mouse6 
= mf.GetModel("F");


            mouse1.PrintModelName();
            mouse2.PrintModelName();
            mouse3.PrintModelName();
            mouse4.PrintModelName();
            mouse5.PrintModelName();
            mouse6.PrintModelName();

            Console.WriteLine(
"mouse2模型与mouse6模型是否是同一个对象模型? 答案: " + mouse2.Equals(mouse6)); //看看是否同一个对象
            #endregion


            Console.ReadLine();

        }

    }

}

代码说明

1 、 代码中的MouseModelFactory就是享元工厂角色。它的作用是创建和管理享元对象。可以看到,每加载一个模型都会在Hashtable中记录 一下,之后如果客户端还是需要这个模型的话就直接把已有的模型对象返回给客户端,而不是重新在内存中加载一份模型数据。
2 、MouseModelFactory本身应用了Singleton,因为如果实例化多个享元工厂是的话就起不到统一管理和分配享元对象的目的了。
3 、MouseModel 就是享元角色。在构造方法中传入modelName,然后它从指定路径加载模型数据,并且把数据放入字段中。

程序运行效果如下:

使用了享元:

未使用享元:
 
从运行结果中可以看到,如果没有应用享元模式,那么在内存中就会有10000套模型对象,资源的消耗就会明显增加。
HashTable效果:

设计模式--Flyweight享元模式

总结:

享元模式的效果及实现要点
1.面向对象很好的解决了抽象性的问题,但是作为一个运行在机器中的程序实体,我们需要考虑对象的代价问题。Flyweight设计模式主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。

2.Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。享元工厂通过维护一张享元实例表来实现管理。

3.享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:享元模式使得系统更加复杂。为了使对象可以共 享,需要将一些状态外部化,这使得程序的逻辑复杂化。另外它将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。享元不可共享的状态需要在外部 维护,我们还可以按照需求可以对享元角色进行抽象。 
注意事项
1、元模式通常针对细粒度的对象,如果这些对象比较拥有非常多的独立状态(不可共享的状态),或者对象并不是细粒度的,那么就不适合运用享元模式。维持大量的外蕴状态不但会使逻辑复杂而且并不能节约资源。
2、享元工厂中维护了享元实例的列表,同样也需要占用资源,如果享元占用的资源比较小或者享元的实例不是非常多的话(和列表元素数量差不多),那么就不适合使用享元,关键还是在于权衡得失。 
适用性
当以下所有的条件都满足时,可以考虑使用享元模式:
1、   一个系统有大量的对象。
2、   这些对象耗费大量的内存。
3、   这些对象的状态中的大部分都可以外部化。
4、   这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
5、   软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。

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

前往:设计模式学习笔记清单

你可能感兴趣的:(设计模式,Flyweight模式)