面向对象语言的原则就是一切都是对象,但是如果真正使用起来,有时对象数可能显得
很庞大,比如,字处理软件,如果以每个文字都作为一个对象,几千个字,对象数就是几千,
无疑耗费内存,那么我们还是要"求同存异",找出这些对象群的共同点,设计一个元类,封
装可以被共享的类,另外,还有一些特性是取决于应用(context),是不可共享的,这也
Flyweight 中两个重要概念内部状态 intrinsic 和外部状态 extrinsic 之分。
说白点,就是先捏一个的原始模型,然后随着不同场合和环境,再产生各具特征的具体
模型,很显然,在这里需要产生不同的新对象,所以 Flyweight 模式中常出现 Factory 模
式.Flyweight 的内部状态是用来共享的,Flyweight factory 负责维护一个
Flyweight pool(模式池)来存放内部状态的对象.
Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应
用场合很多:比如你要从一个数据库中读取一系列字符串,这些字符串中有许多是重复的,
那么我们可以将这些字符串储存在Flyweight池(pool)中.
享元模式(Flyweight):运用共享的技术有效地支持大量细粒度的对象。
抽象享元角色(Flyweight):此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口或抽象类。那些需要外部状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内部状态的话,必须负责为内部状态提供存储空间。享元对象的内部状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。这个角色一般很少使用。
享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
客户端(Client)角色:本角色还需要自行存储所有享元对象的外部状态。
内部状态与外部状态:在享元对象内部并且不会随着环境改变而改变的共享部分,可以称之为享元对象的内部状态,反之随着环境改变而改变的,不可共享的状态称之为外部状态。
现在让我们通过一个面向对象的文本编辑器设计来说明享元模式的应用。假设我们要设计一个文本编辑器,而且它必须创建字符对象来表示文档中的每个字符,现在让我们考虑字符对象保持什么信息呢?如:字体、字体大小和位置等等信息。
一个文档通常包含许多字符对象,它们需要大容量的内存。值得我们注意的是一般字符都是由数字、字母和其他字符组成的(它们是固定的,可知的),这些字符对象可以共享字体和字体大小等信息,现在它们专有属性只剩下位置了,每个字符对象只需保持它们在文档中的位置就OK了,通过分析我们已经降低了编辑器的内存需求。
图2 享元模式(Flyweight)共享对象
/// <summary>
/// The 'Flyweight' class./// </summary>public class Character{ // intrinsic state protected char _symbol; protected int _size; protected string _font; // extrinsic state protected Position _position; public void Display(Position position)
{ Console.WriteLine( String.Format("Symbol: {0} Size: {1} Font: {2} Position: {3} {4}",
_symbol, _size, _font, position._x, position._y));
}
}
现在我们定义了一个字符享元类,其中符合、字体和字体大小都是内部状态,而位置则是外部状态。
/// <summary>
/// A 'ConcreteFlyweight' class/// </summary>public class CharacterA : Character{ public CharacterA()
{
_symbol = 'A';
_size = 10;
_font = "宋体"; //_position = new Position(0, 1); }
}
接着我们定义具体字符A的享元类,并且对内部状态符号、字体和字体大小进行初始化,而且其他字符B到Z享元类都类似。
图3具体享元模式(ConcreteFlyweight)设计
/// <summary>
/// The 'FlyweightFactory' class/// </summary>public class CharacterFactory{ // Keeps the character object by specifying key/value. private Dictionary<char, Character> _characters = new Dictionary<char, Character>(); public Character this[char key]
{ get { Character character = null; // Checked the character whether existed or not,
// if the character existed, then directly returns,
// otherwise, instantiates a character object. if (_characters.ContainsKey(key))
{
character = _characters[key];
} else { string name = this.GetType().Namespace + "." + "Character" + key.ToString();
character = Activator.CreateInstance( Type.GetType(name)) as Character;
_characters.Add(key, character);
} return character;
}
}
}
现在我们定义了一间字符工厂,通过一个Dictionary<Tkey, Tvalue>来保存字符对象,使用字符值来查找字符对象是否已经创建了,如果查找的字符对象已经存在,那么直接返回该对象,反之就创建字符对象实例。
/// <summary>
/// The client./// </summary>
/// <param name="args">The args.</param>[STAThread]static void Main(string[] args)
{ Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new FrmFlyweight()); string text = "ABZABBZZ"; char[] letters = text.ToCharArray(); var characterFactory = new CharacterFactory(); // Creates random position ranges 0 to 100. var rd = new Random(); foreach (char c in letters)
{ Character character = characterFactory[c]; var p = new Position(rd.Next(0, 100), rd.Next(0, 100));
character.Display(p);
} Console.ReadKey();
}
图4享元模式(ConcreteFlyweight)测试结果
接着让我们实现一个享元模式的绘图程序,假设我们的程序要画各种各样的圆,而且圆的属性有形状,位置和颜色,其中形状和颜色是内部状态,而位置是外部状态。
设计分析:
1.提供一个抽象类Shape,让具体的形状如:Circle继承于它
2.定义一个位置结构图记录每个图形的位置
3.设计一间享元图形工厂用来创建图形对象
以上就是我们的享元模式的绘图程序的设计,接下来让我们实现享元模式的绘图程序吧!
/// <summary>
/// Shape can be inherited by Circle, Retangle or triangle and so forth./// Includes a color property and Draw methods./// </summary>public abstract class Shape{ public Color Color { get; set; } public abstract void Draw(Graphics graphics, Position position);
}
上述示意代码定义了一个抽象类Shape,我们的具体图形都必须继承于该类。
/// <summary>
/// Circle implements Shape./// </summary>public class Circle : Shape{ public Circle(Color color)
{
Color = color;
} /// <summary>
/// Draws circle with the specified graphics and position. /// </summary>
/// <param name="graphics">The graphics.</param>
/// <param name="position">The position of circle.</param> public override void Draw(Graphics graphics, Position position)
{ var pen = new Pen(Color);
graphics.DrawEllipse(pen, position.X - position.R,
position.Y - position.R,
position.R, position.R);
}
}
接着我们定义具体图形类Circle,它实现Draw()方法通过Graphics调用DrawEllipse()方法来实现画圆。
/// <summary>
/// Generate the position of concrete shape./// </summary>public struct Position{ private int _x; private int _y; private int _r; public Position GetPosition(Form form)
{ var rd = new Random();
_x = rd.Next(0, form.Width);
_y = rd.Next(0, form.Height); float r = _x < _y ? _x : _y;
_r = rd.Next(0, (int)r); return this;
} public Position(Graphics graphics, int x, int y, int r)
{ if (x > graphics.DpiX) throw new ArgumentOutOfRangeException("x"); if (y > graphics.DpiY) throw new ArgumentOutOfRangeException("y"); if (r > graphics.DpiY && r > graphics.DpiX) throw new ArgumentOutOfRangeException("r");
_x = x;
_y = y;
_r = r;
} public int X
{ get { return _x; }
} public int Y
{ get { return _y; }
} public int R
{ get { return _r; }
}
}
接着我们定义享元工厂负责创建图形对象,如果图形对象不存在就创建该对象,反正直接返回该图形对象。
/// <summary>
/// The flyweight factory/// Generates the instance of shape if object not exists,/// otherwish returns the object directly./// </summary>public class ShapeFactory{ // Saves the shape object in Dictionary<Color, Shape> private static readonly Dictionary<Color, Shape> Shapes = new Dictionary<Color, Shape>(); // Gets the object in Dictionray. public Shape this[Color key]
{ get { Shape shape = null; // if the object exists return directly.
// otherwish generates anew one. if (Shapes.ContainsKey(key))
{
shape = Shapes[key];
} else {
shape = new Circle(key);
Shapes.Add(key, shape);
} return shape;
}
}
}
现在我们已经完成了享元图形类,由于图形的外部状态包括位置和颜色,前面我们通过随机函数生成随机位置,我们要设计一个拾色板来提供用户选择自定义颜色。
图5拾色板设计
由于时间的关系我们已经把拾色板的界面设置,接下来让我们实现拾色板的具体功能。
首先我们新建一个用户自定义控件命名为ColorPanel,接着我们要处理用户点击选择颜色的事件
// Sets the default color.private Color _color = Color.Black;public delegate void ColorChangedHandler(object sender, ColorChangedEventArgs e);public event ColorChangedHandler ColorChanged;/// <summary>
/// Raises the <see cref="E:ColorChanged"/> event./// </summary>
/// <param name="e">The color changed event arguments.</param>protected virtual void OnColorChanged(ColorChangedEventArgs e)
{ if (null != ColorChanged)
ColorChanged(this, e);
}
上述示意代码定义了一个委托ColorChangedHandler,当颜色值发现改变时相应具体处理方法和一个事件ColorChangedHandler,其实事件是对委托的封装,犹如字段和属性的关系,具体委托和事件的介绍请参看这里和这里。
图6自定义事件
我们先介绍一下EventArgs这个的类型。其实这个类并没有太多的功能,它主要是作为一个基类让其他类去实现具体的功能和定义,当我们自定义事件参数时都必须继承于该类。
现在回到我们自定义事件参数ColorChangedEventArgs,其中包含初始化颜色值的方法和获取颜色值的属性。
/// <summary>
/// The color changed event arguments./// </summary>public class ColorChangedEventArgs : EventArgs{ private readonly Color _color; /// <summary>
/// Initializes a new instance of the <see cref="ColorChangedEventArgs"/> class. /// </summary>
/// <param name="color">The color.</param> public ColorChangedEventArgs(Color color)
{
_color = color;
} /// <summary>
/// Gets the color. /// </summary> public Color Color
{ get { return _color; }
}
}
现在我们终于完成了拾色板的基本功能了,接着只需把拾色板控件添加到我们的应用程序中就OK了。
图6享元模式绘图程序界面
由于时间的关系我们已经把程序的界面设计好了,接下来让我们实现一系列的事件处理方法。
/// <summary>
/// Handles the Click event of the btnDrawCircle control./// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/>
/// instance containing the event data.</param>private void btnDrawCircle_Click(object sender, EventArgs e)
{ Graphics graphics = Graphics.FromImage(_drawArea);
// Gets shape object with specified color in flyweight object. Shape shape = _factory[colorPanel1.Color];
shape.Draw(graphics, _position.GetPosition(this)); this.Invalidate();
}/// <summary>
/// Handles the Click event of the btnClear control./// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/>
/// instance containing the event data.</param>private void btnClear_Click(object sender, EventArgs e)
{ Graphics graphics = Graphics.FromImage(_drawArea);
graphics.Clear(Color.SkyBlue);
graphics.Dispose(); this.Invalidate();
}
上面我们定义了处理绘图点击方法和清除图形的方法,当用户选择颜色值时,我们的程序到享元工厂中获取该对象实例,这个对象可能是新建的,也可能是已经存在的。
图7绘图程序效果
解释一下概念:也就是说在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象。比如说一个文本系统,每个字母定一个对象,那么大小写字母一共就是52个,那么就要定义52个对象。如果有一个1M的文本,那么字母是何其的多,如果每个字母都定义一个对象那么内存早就爆了。那么如果要是每个字母都共享一个对象,那么就大大节约了资源。
在Flyweight模式中,由于要产生各种各样的对象,所以在Flyweight(享元)模式中常出现Factory模式。Flyweight的内部状态是用来共享的,Flyweight factory负责维护一个对象存储池(Flyweight Pool)来存放内部状态的对象。Flyweight模式是一个提高程序效率和性能的模式,会大大加快程序的运行速度.应用场合很多,下面举个例子:
先定义一个抽象的Flyweight类:
[java] view plain copy print?
package Flyweight;
public abstract class Flyweight{
public abstract void operation();
}
实现一个具体类:
[java] view plain copy print?
package Flyweight;
public class ConcreteFlyweight extends Flyweight{
private String string;
public ConcreteFlyweight(String str){
string = str;
}
public void operation()
{
System.out.println("Concrete---Flyweight : " + string);
}
}
实现一个工厂方法类:
[java] view plain copy print?
package Flyweight;
import java.util.Hashtable;
public class FlyweightFactory{
private Hashtable flyweights = new Hashtable();//----------------------------1
public FlyweightFactory(){}
public Flyweight getFlyWeight(Object obj){
Flyweight flyweight = (Flyweight) flyweights.get(obj);//----------------2
if(flyweight == null){//---------------------------------------------------3
//产生新的ConcreteFlyweight
flyweight = new ConcreteFlyweight((String)obj);
flyweights.put(obj, flyweight);//--------------------------------------5
}
return flyweight;//---------------------------------------------------------6
}
public int getFlyweightSize(){
return flyweights.size();
}
}
这个工厂方法类非常关键,这里详细解释一下:
在1处定义了一个Hashtable用来存储各个对象;在2处选出要实例化的对象,在6处将该对象返回,如果在Hashtable中没有要选择的对象,此时变量flyweight为null,产生一个新的flyweight存储在Hashtable中,并将该对象返回。
最后看看Flyweight的调用:
[java] view plain copy print?
package Flyweight;
import java.util.Hashtable;
public class FlyweightPattern{
FlyweightFactory factory = new FlyweightFactory();
Flyweight fly1;
Flyweight fly2;
Flyweight fly3;
Flyweight fly4;
Flyweight fly5;
Flyweight fly6;
/** *//** Creates a new instance of FlyweightPattern */
public FlyweightPattern(){
fly1 = factory.getFlyWeight("Google");
fly2 = factory.getFlyWeight("Qutr");
fly3 = factory.getFlyWeight("Google");
fly4 = factory.getFlyWeight("Google");
fly5 = factory.getFlyWeight("Google");
fly6 = factory.getFlyWeight("Google");
}
public void showFlyweight(){
fly1.operation();
fly2.operation();
fly3.operation();
fly4.operation();
fly5.operation();
fly6.operation();
int objSize = factory.getFlyweightSize();
System.out.println("objSize = " + objSize);
}
public static void main(String[] args){
System.out.println("The FlyWeight Pattern!");
FlyweightPattern fp = new FlyweightPattern();
fp.showFlyweight();
}
}
下面是运行结果:
[java] view plain copy print?
Concrete---Flyweight : Google
Concrete---Flyweight : Qutr
Concrete---Flyweight : Google
Concrete---Flyweight : Google
Concrete---Flyweight : Google
Concrete---Flyweight : Google
objSize = 2
我们定义了6个对象,其中有5个是相同的,按照Flyweight模式的定义“Google”应该共享一个对象,在实际的对象数中我们可以看出实际的对象却是只有2个。
总结:
Flyweight(享元)模式是如此的重要,因为它能帮你在一个复杂的系统中大量的节省内存空间。在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String a="abc",其中"abc"就是一个字符串常量。
熟悉java的应该知道下面这个例子:
[java] view plain copy print?
String a = "hello";
String b = "hello";
if(a == b)
System.out.println("OK");
else
System.out.println("Error");
输出结果是:OK。可以看出if条件比较的是两a和b的地址,也可以说是内存空间
核心总结,可以共享的对象,也就是说返回的同一类型的对象其实是同一实例,当客户端要求生成一个对象时,工厂会检测是否存在此对象的实例,如果存在那么直接返回此对象实例,如果不存在就创建一个并保存起来,这点有些单例模式的意思。通常工厂类会有一个集合类型的成员变量来用以保存对象,如hashtable,vector等。在java中,数据库连接池,线程池等即是用享元模式的应用。