一、
享元模式(Flyweight):对象结构型模式运用共享技术有效地支持大量细粒度的对象。
享元模式可以分成单纯享元模式和复合享元模式两种形式。
二、事例:
在文档编辑器例子中如果一个字符对应一个对象,那么一篇文档所要容纳的对象将是非常的庞大耗费大量的内存。
而实际组成文档的字符是有限的,是由这些字符不同的组合和排列得到的。
所以需要共享,将基本的字符进行共享,将使得字符对象变得有限。
Flyweight只存储相应的字符代码
这里的关键概念是内部状态和外部状态之间的区别。
内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。
如字符代码,字符大小……
外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight。
如字符位置,字符颜色……
三、结构:
单纯享元模式
1) 抽象享元类(Flyweight):描述一个接口,通过这个接口flyweight可以接受并作用于外部状态。
2) 具体享元类(ConcreteFlyweight):实现Flyweight接口 ,并为内部状态( 如果有的话 )增加存储空间。
ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于
ConcreteFlyweight对象的场景。
3) 非共享具体享元类(UnsharedConcreteFlyweight):并非所有的Flyweight子类都需要被共享。
Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,
UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点。
4) 享元工厂类(FlyweightFactory):创建并管理flyweight对象, 确保合理地共享flyweight。本角色必须
保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象flyweight的时候,享元
工厂角色(Flyweight Factory对象)会检查系统中是否已经有一个符合要求的享元对象。如果已经有
了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享
元工厂角色就应当创建一个合适的享元对象。
5)客户(Client):维持一个对flyweight的引用。计算或存储一个(多个)flyweight的外部状态。 Flyweight执行时所需的状态必定是内部的或外部的。内部状态存储于ConcreteFlyweight对象之中;而外部对象则由C l i e n t对象存储或计算。当用户调用Flyweight对象的操作时,将该状态传递给它。用户不应直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight对象,
这可以保证对它们适当地进行共享
复合享元模式:
单纯享元模式中,所有的享元对象都可以直接共享。下面考虑一个较为复杂的情况,即将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。
复合享元模式的类图如下图所示:
享元模式所涉及的角色有抽象享元角色、具体享元角色、复合享元角色、享员工厂角色,以及客户端角色等。
抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。
享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。
【单纯享元模式】
在单纯的享元模式中,所有的享元对象都是可以共享的。
/**
* 单纯享元模式
* @author guisu
*/
/**
* 抽象享元角色
*/
abstract class Flyweight {
/**
* 示意性方法
* @param string $state 外部状态
*/
abstract public function operation($state);
}
/**
* 具体享元角色
*/
class ConcreteFlyweight extends Flyweight {
private $_intrinsicState = null;
/**
* 构造方法
* @param string $state 内部状态
*/
public function __construct($state) {
$this->_intrinsicState = $state;
}
public function operation($state) {
echo 'ConcreteFlyweight operation, Intrinsic State = ' . $this->_intrinsicState
. ' Extrinsic State = ' . $state . '<br />';
}
}
/**
* 享元工厂角色
*/
class FlyweightFactory {
private $_flyweights;
public function __construct() {
$this->_flyweights = array();
}
public function getFlyweigth($state) {
if (isset($this->_flyweights[$state])) {
return $this->_flyweights[$state];
} else {
return $this->_flyweights[$state] = new ConcreteFlyweight($state);
}
}
}
class Client{
static function main (){
$flyweightFactory = new FlyweightFactory();
$flyweight = $flyweightFactory->getFlyweigth('state A');
$flyweight->operation('other state A');
$flyweight = $flyweightFactory->getFlyweigth('state B');
$flyweight->operation('other state B');
}
}
【复合享元模式】
复合享元模式对象是由一些单纯享元使用合成模式加以复合而成
复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。/**
* 复合享元模式
*
*/
/**
* 抽象享元角色
*/
abstract class Flyweight {
/**
* 示意性方法
* @param string $state 外部状态
*/
abstract public function operation($state);
}
/**
* 具体享元角色
*/
class ConcreteFlyweight extends Flyweight {
private $_intrinsicState = null;
/**
* 构造方法
* @param string $state 内部状态
*/
public function __construct($state) {
$this->_intrinsicState = $state;
}
public function operation($state) {
echo 'ConcreteFlyweight operation, Intrinsic State = ' . $this->_intrinsicState
. ' Extrinsic State = ' . $state . '<br />';
}
}
/**
* 不共享的具体享元,客户端直接调用
*/
class UnsharedConcreteFlyweight extends Flyweight {
private $_flyweights;
/**
* 构造方法
* @param string $state 内部状态
*/
public function __construct() {
$this->_flyweights = array();
}
public function operation($state) {
foreach ($this->_flyweights as $flyweight) {
$flyweight->operation($state);
}
}
public function add($state, Flyweight $flyweight) {
$this->_flyweights[$state] = $flyweight;
}
}
/**
* 享元工厂角色
*/
class FlyweightFactory {
private $_flyweights;
public function __construct() {
$this->_flyweights = array();
}
public function getFlyweigth($state) {
if (is_array($state)) { // 复合模式
$uFlyweight = new UnsharedConcreteFlyweight();
foreach ($state as $row) {
$uFlyweight->add($row, $this->getFlyweigth($row));
}
return $uFlyweight;
} else if (is_string($state)) {
if (isset($this->_flyweights[$state])) {
return $this->_flyweights[$state];
} else {
return $this->_flyweights[$state] = new ConcreteFlyweight($state);
}
} else {
return null;
}
}
}
class Client{
static function main (){
$flyweightFactory = new FlyweightFactory();
$flyweight = $flyweightFactory->getFlyweigth('state A');
$flyweight->operation('other state A');
$flyweight = $flyweightFactory->getFlyweigth('state B');
$flyweight->operation('other state B');
/* 复合对象*/
$uflyweight = $flyweightFactory->getFlyweigth(array('state A', 'state B'));
$uflyweight->operation('other state A');
}
}
四、一个咖啡摊的例子
在这个咖啡摊(Coffee Stall)所使用的系统里,有一系列的咖啡"风味(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++;
}
}
五、 咖啡屋的例子
在前面的咖啡摊项目里,由于没有供客人坐的桌子,所有的咖啡均没有环境的影响。换言之,咖啡仅有内蕴状态,也就是咖啡的种类,而没有外蕴状态。
下面考虑一个规模稍稍大一点的咖啡屋(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));
}
}
六、
享元模式的优点:
1)享元模式的优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份。
2)享元模式的外部状态相对独立,而且不会影响其内部状态,从而使得享元对象可以在不同的环境中被共享。
享元模式的缺点:
1)享元模式使得系统更加复杂,需要分离出内部状态和外部状态,这使得程序的逻辑复杂化。而且,需要维护一个存储享元对象的享元池,这样也会耗费资源。
2)为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
七、总结
1) 享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。
2) 享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
3) 享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态(Internal State)和外部状态(External State)。
(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享。
(2) 外部状态是随环境改变而改变的、不可以共享的状态。享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候 再传入到享元对 象内部。一个外部状态与另一个外部状态之间是相互独立的。