概述
- 享元模式 -> 共享元数据, 对象结构型模式
- 运用共享技术有效地支持大量细粒度的对象, 尽可能减少内存使用量
适用于
大量对象只是重复, 导致令人无法接受的消耗大量内存
经典案例
常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元
Java中的例子是字符串pool, 以及小于128的Integer
String s1 = "abc";
String s2 = "abc";
String s3 = new String("abc");
String s4 = new String("abc");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s3 == s4); // false
System.out.println(s3.intern() == s1); // true
System.out.println(s3.intern() == s3); // false
System.out.println(s3.intern() == s4.intern()); // true
四人帮GOF在书中举的例子: 字处理软件, 有大量的数字字母是重复的, 重复的对象就不要重复的new了, 否则会占用大量内存, 并且会频繁触发GC导致性能下降
使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式
- 抽象享元类 ( Flyweight ): 描述一个接口,通过这个接口flyweight可以接受并作用于外部状态
- 具体享元类 ( ConcreteFlyweight ): 实现Flyweight接口 ,并为内部状态( 如果有的话 )增加存储空间 ConcreteFlyweight对象必须是可共享的。它所存储的状态必须是内部的;即,它必须独立于ConcreteFlyweight对象的场景
- 非共享具体享元类 ( UnsharedConcreteFlyweight ): 并非所有的Flyweight子类都需要被共享。Flyweight接口使共享成为可能,但它并不强制共享。在Flyweight对象结构的某些层次,UnsharedConcreteFlyweight对象通常将ConcreteFlyweight对象作为子节点
- 享元工厂类(FlyweightFactory): 创建并管理flyweight对象, 确保合理地共享flyweight。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象 flyweight的时候,享元工厂角色(Flyweight Factory对象)会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象
- 客户( Client ): 维持一个对flyweight的引用。计算或存储一个(多个)flyweight的外部状态。
优点
- 极大减少内存中对象的数量
缺点
- 使系统更加复杂, 需要分离出内部状态和外部状态, 这使得程序的逻辑复杂化
- 为了使对象可以共享,享元模式 需要将享元对象的状态外部化,而读取外部状态使得运行时间变长
实现
享元模式可以分成单纯享元模式和复合享元模式两种形式
单纯享元模式 所有的享元对象都是可以共享的
<?php
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 = array();
public function getFlyweigth($state) {
if (isset($this->_flyweights[$state])) { // 如果重复了, 就直接用池子里的
return $this->_flyweights[$state];
} else { // 如果没有, 就new一个
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');
}
}
?>
复合享元模式
- 复合享元模式对象是由一些单纯享元使用合成模式加以复合而成
- 复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合
<?php
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 = array();
public function add($state, Flyweight $flyweight) {
$this->_flyweights[$state] = $flyweight;
}
public function operation($state) {
foreach ($this->_flyweights as $flyweight) {
$flyweight->operation($state);
}
}
}
class FlyweightFactory { // 享元工厂角色
private $_flyweights = array();
public function getFlyweigth($state) {
if (is_array($state)) { // 复合模式, 输入参数是一个list
$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 { // 如果没有, 就new一个
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');
}
}
?>
与其他模式的联系
- 客户端要引用享元对象,是通过工厂对象创建或者获得的,客户端每次引用一个享元对象,都是可以通过同一个工厂对象来引用所需要的享元对象。因此,可以将享元工厂设计成单例模式,这样就可以保证客户端只引用一个工厂实例。
Composite模式: Flyweight模式通常和Composite 模式结合起来,用共享叶结点的有向无环图实现一个逻辑上的层次结构。复合享元模式实际上是单纯享元模式与合成模式的组合。单纯享元对象可以作为Leaf对象来讲,是可以共享的,而复合享元对象可以作为树枝对象, 因此在复合享元角色中可以添加聚集管理方法。通常,最好用Flyweight实现State和Strategy对象。
总结
- 享元模式是一个 考虑系统性能 的设计模式,通过使用享元模式可以 节约内存空间,提高系统的性能
- 享元模式的核心在于 享元工厂类, 享元工厂类的作用在于提供一个用于存储享元对象的享元池 ,用户需要对象时,首先从享元池中获取, 如果享 元池中不存在, 则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象
- 享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分 内部状态 (Internal State) 和 外部状态 (External State) 。
(1) 内部状态是存储在享元对象内部并且不会随环境改变而改变的状态 ,因此内部状态可以共享。
(2) 外部状态是随环境改变而改变的、不可以共享的状态。 享元对象的外部状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候 再传入到享元对象内部。一个外部状态与另一个外部状态之间是相互独立的