26.2享元模式
享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。
享元模式(Flyweight)结构图
代码示例
Flyweight 类,它是所有具体享元类的超类或接口,通过这个接口,Flyweight 可以接受并作用于外部状态。
abstract class Flyweight
{
abstract function Operation($extrinsic_state);
}
ConcreteFlyweight 是继承 Flyweight 超类或实现 Flyweight 接口,并为内部状态增加存储空间。
class ConcreteFlyweight extends Flyweight
{
public function Operation($extrinsic_state)
{
echo '具体 Flyweight:' . $extrinsic_state . PHP_EOL;
}
}
UnsharedConcreteFlyweight 是指那些不需要共享的 Flyweight 子类。因为 Flyweight 接口共享成为可能,但它并不强制共享。
class UnsharedConcreteFlyweight extends Flyweight
{
public function Operation($extrinsic_state)
{
echo '不共享的具体 Flyweight:' . $extrinsic_state . PHP_EOL;
}
}
FlyweightFactory,是一个享元工厂,用来创建并管理 Flyweight 对象。它主要是用来确保合理地共享 Flyweight,当用户请求一个 Flyweight 时,FlyweightFactory 对象提供一个已创建的实例或者创建一个(如果不存在的话)。
class FlyweightFactory
{
private $flyweights = array();
//初始化工厂时,先生成三个实例
public function __construct()
{
$this->flyweights['X'] = new ConcreteFlyweight();
$this->flyweights['Y'] = new ConcreteFlyweight();
$this->flyweights['Z'] = new ConcreteFlyweight();
}
//根据客户端请求,获得已生成的实例
public function GetFlyweight($key)
{
return $this->flyweights[$key];
}
}
客户端代码
public function flyweightDemo()
{
//代码外部状态
$extrinsic_state = 22;
$f = new FlyweightFactory();
$fx = $f->GetFlyweight('X');
$fx->Operation(--$extrinsic_state);
$fy = $f->GetFlyweight('Y');
$fy->Operation(--$extrinsic_state);
$fz = $f->GetFlyweight('Z');
$fz->Operation(--$extrinsic_state);
$uf = new UnsharedConcreteFlyweight();
$uf->Operation(--$extrinsic_state);
}
结果表示
具体 Flyweight:21
具体 Flyweight:20
具体 Flyweight:19
不共享的具体 Flyweight:18
FlyweightFactory 根据客户需求返回早已生成好的对象,但实际上时不一定需要的,完全可以初始化时什么也不做,到需要时,再去判断对象是否为 null 来决定是否实例化。
UnsharedConcreteFlyweight 的存在,是因为尽管大部分时间都需要共享对象来降低内存的损耗,但个别时候也有可能不需要共享的,那么此时的 UnsharedConcreteFlyweight 子类就有存在的必要了,它可以解决那些不需要共享对象的问题。
26.3网站共享代码
代码实现
网站抽象类
abstract class Website
{
abstract function Used();
}
具体网站类
class ConcreteWebsite extends Website
{
private $name = '';
public function __construct($name)
{
$this->name = $name;
}
public function Used()
{
echo '网站分类:' . $this->name . PHP_EOL;
}
}
网站工厂类
//网站工厂
class WebSiteFactory
{
private $flyweights = array();
//获得网站分类
public function GetWebSiteCategory($key)
{
if (!isset($this->flyweights[$key])) {
$this->flyweights[$key] = new ConcreteWebsite($key);
}
return $this->flyweights[$key];
}
//获得网站分类总数
public function GetWebSiteCount()
{
return count($this->flyweights);
}
}
客户端代码
public function flyweightImpFirstED()
{
$f = new WebSiteFactory();
//实例化“产品展示”的“网站”对象
$fx = $f->GetWebSiteCategory('产品展示');
$fx->Used();
//共享上方生成的对象,不再实例化
$fy = $f->GetWebSiteCategory('产品展示');
$fy->Used();
$fz = $f->GetWebSiteCategory('产品展示');
$fz->Used();
$fl = $f->GetWebSiteCategory('博客');
$fl->Used();
$fm = $f->GetWebSiteCategory('博客');
$fm->Used();
$fn = $f->GetWebSiteCategory('博客');
$fn->Used();
//统计实例化个数,结果为2
echo '网站分类总数为:' . $f->GetWebSiteCount();
}
结果显示
网站分类:产品展示
网站分类:产品展示
网站分类:产品展示
网站分类:博客
网站分类:博客
网站分类:博客
网站分类总数为:2
这样算是基本实现了享元模式的共享对象的目的,也就是说,不管建几个网站,只要是‘产品展示’,都是一样的,只要是‘博客’,也是完全相同的,但这样是有问题的,不同企业的网站数据是不同的,所以至少它们都应该有不同的账号。
26.4内部状态于外部状态
在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态;
而随环境改变而改变、不可以共享的状态就是外部状态了。
事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。
也就是说,享元模式 Flyweight 执行时所需的状态是有内部的也可能有外部的,内部状态存储于 ConcreteFlyweight 对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用 Flyweight 对象的操作时,将该状态传递给它。
代码结构图
代码实现
用户类,用于网站的客户账号,是“网站”类的外部状态
//用户
class User
{
private $name = '';
public function __construct($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
网站抽象类
abstract class WebSite
{
//“使用”方法需要传递“用户”对象
abstract function Used($user);
}
具体网站类
class ConcreteWebSite extends WebSite
{
private $name = '';
public function __construct($name)
{
$this->name = $name;
}
//实现“User”方法
public function Used($user)
{
echo '网站分类:' . $this->name . ' 用户:' . $user->getName() . PHP_EOL;
}
}
网站工厂类
//网站工厂
class WebSiteFactory
{
private $flyweights = array();
//获得网站分类
public function GetWebSiteCategory($key)
{
if (!isset($this->flyweights[$key])) {
$this->flyweights[$key] = new ConcreteWebSite($key);
}
return $this->flyweights[$key];
}
//获得网站分类总数
public function GetWebSiteCount()
{
return count($this->flyweights);
}
}
客户端代码
public function flyweightImp()
{
$f = new WebSiteFactoryImp();
$fx = $f->GetWebSiteCategory('产品展示');
$fx->Used(new User('小菜'));
$fy = $f->GetWebSiteCategory('产品展示');
$fy->Used(new User('大鸟'));
$fz = $f->GetWebSiteCategory('产品展示');
$fz->Used(new User('娇娇'));
$fl = $f->GetWebSiteCategory('博客');
$fl->Used(new User('老顽童'));
$fm = $f->GetWebSiteCategory('博客');
$fm->Used(new User('桃谷六仙'));
$fn = $f->GetWebSiteCategory('博客');
$fn->Used(new User('南海鳄神'));
echo '得到网站分类总数为:' . $f->GetWebSiteCount();
}
显示结果 尽管给六个不同用户使用网站,但实际上只有两个网站实例。
网站分类:产品展示 用户:小菜
网站分类:产品展示 用户:大鸟
网站分类:产品展示 用户:娇娇
网站分类:博客 用户:老顽童
网站分类:博客 用户:桃谷六仙
网站分类:博客 用户:南海鳄神
得到网站分类总数为:2
26.5享元模式应用
享元模式的应用场景:
- 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;
- 还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
虽说享元模式更多的时候是一种底层的设计模式,但现实中也是有应用的。比如说休闲游戏开发中,像围棋、五子棋、跳棋等,它们都有大量的棋子对象,对于围棋和五子棋只有黑白两色、跳棋颜色略多一些,但也是不太变化的,所以颜色应该是棋子的内部状态,而各棋子之间的差别主要就是位置的不同,所以方位坐标应该是棋子的外部状态,享元模式可以将棋子对象减少到有限个实例。
在某些情况下,对象的数量可能会太多,从而导致了运行时的资源与性能损耗。如何去避免大量细粒度的对象,同时又不影响客户程序,是一个值得去思考的问题,享元模式,可以运用共享技术有效地支持大量细粒度的对象。不过,使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要消耗资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。