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();//统计实例化个数,结果为2echo '网站分类总数为:' . $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享元模式应用
享元模式的应用场景:
- 如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;
- 还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
虽说享元模式更多的时候是一种底层的设计模式,但现实中也是有应用的。比如说休闲游戏开发中,像围棋、五子棋、跳棋等,它们都有大量的棋子对象,对于围棋和五子棋只有黑白两色、跳棋颜色略多一些,但也是不太变化的,所以颜色应该是棋子的内部状态,而各棋子之间的差别主要就是位置的不同,所以方位坐标应该是棋子的外部状态,享元模式可以将棋子对象减少到有限个实例。
在某些情况下,对象的数量可能会太多,从而导致了运行时的资源与性能损耗。如何去避免大量细粒度的对象,同时又不影响客户程序,是一个值得去思考的问题,享元模式,可以运用共享技术有效地支持大量细粒度的对象。不过,使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要消耗资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。
