享元模式
享元模式又称:缓存、Cache、Flyweight,享元是一种结构型设计模式,它摒弃了在每个对象中保存所有数据的方式,通过共享多个对象所共有的相同状态,让你能在有限的内存容量中载入更多对象。
问题
我们需要开发一款简单的游戏:玩家们在地图上移动并相互射击。实现一个真实的 弹类 系统,并将其作为游戏的特色。大量的子弹、导弹和爆炸弹片会在整个地图上穿行,为玩家提供紧张刺激的游戏体验。
开发完成后,我们推送提交了最新版本的程序,并在编译游戏后将其发送给了其他朋友进行测试。该游戏在我们的电脑上完美运行,但是其他的朋友却无法长时间进行游戏:游戏总是会在他的电脑上运行几分钟后崩溃。
研究了几个小时的调试消息记录后,发现导致游戏崩溃的原因是内存容量不足。朋友的设备性能远比不上我的电脑,因此游戏运行在他的电脑上时很快就会出现问题。
真正的问题弹类系统有关。每个 弹类(一颗子弹、一枚导弹或一块弹片)都由包含完整数据的独立对象来表示。当玩家在游戏中进入高潮后的某一时刻,游戏将无法在剩余内存中载入新建弹类,于是程序就崩溃了。
解决方法
仔细观察 弹类 Bullet 类,你可能会注意到 颜色(color)和 弹类图(background)这两个成员变量所消耗的内存要比其他变量多得多。对于所有的弹类来说,这两个成员变量所存储的数据几乎完全一样。
每个弹类的坐标则是不同的。因为这些成员变量的数值会不断变化。这些数据代表弹类在存续期间不断变化的情景,但每个弹类的颜色和描述图则会保持不变。所以可以共基础弹类。
内在状态与外部状态
对象的 常量 数据通常被称为 内在状态,其位于对象中,其他对象只 能读取但不能修改其数值。而对象的其他状态常常能被其他对象“从外部”改变,因此被称为 外在状态
所以我们可以把 内部状态共享,外在状态单独实例化。
结构
Flyweight: 享元抽象类 示例中指:弹类; ConcreteFlyweight:实现 Flyweight 接口的可以共享的具体享元类 示例中指:弹片; UnsharedConcreteFlyweight: 非共享具体享元类 示例中指:导弹: FlyweightFactory: 用于创建和管理具体的享元对象的工厂 示例中指:弹类工厂;
代码示例
弹类抽象类
代码语言:javascript复制/**
* 弹类抽象基类
* Class Bullet
* @package cxbdashengDesignPatternsFlyweight
*/
abstract class Bullet
{
protected string $color;
protected string $background;
protected string $no;
/**
* 不同类型的弹类初始化
* Bullet constructor.
* @param string $color
* @param string $background
*/
public function __construct(string $color, string $background)
{
$this->color = $color;
$this->background = $background;
$this->no = md5($color . $background);
}
abstract public function run($coords);
}
具体弹类
导弹
代码语言:javascript复制/**
* 导弹
* Class Missile
* @package cxbdashengDesignPatternsFlyweight
*/
class Missile extends Bullet
{
public function run($coords)
{
echo '导弹位置在:' . $coords;
}
}
弹片
代码语言:javascript复制/**
* 弹片
* Class Shrapnel
* @package cxbdashengDesignPatternsFlyweight
*/
class Shrapnel extends Bullet
{
public function run($coords)
{
echo '弹片位置在:' . $coords;
}
}
弹类工厂
代码语言:javascript复制class BulletFactory
{
protected $bullets = [];
/**
* 获取弹类
* @param string $color
* @param string $background
* @param $bullet
* @return mixed
*/
public function getBullet(string $color, string $background, $bullet)
{
$no = md5($color . $background);
if (isset($this->bullets[$no]) && $this->bullets[$no] instanceof $bullet) {
return $this->bullets[$no];
}
$obj = new $bullet($color, $background);
$this->bullets[$no] = $obj;
return $this->bullets[$no];
}
}
客户端使用
代码语言:javascript复制/**
* 弹类工厂
*/
$factory = new FlyweightFactory();
// 弹片 A
$shrapnelA = $factory->getBullet('black', 'black.jpg', Shrapnel::class);
echo $shrapnelA->run(1) . PHP_EOL;
// 弹片 B
$shrapnelB = $factory->getBullet('black', 'black.jpg', Shrapnel::class);
echo $shrapnelB->run(2) . PHP_EOL;
// 导弹...
$missile=new Missile('red', 'missile.jpg');
echo $missile->run(1);
输出
代码语言:javascript复制弹片位置在:1
弹片位置在:2
导弹位置在:1
UML
优缺点
优点
- 如果程序中有很多相似对象,那么你将可以节省大量内存。
缺点
- 可能需要牺牲执行速度来换取内存,因为他人每次调用享元方法时都需要重新计算部分情景数据。
- 代码会变得更加复杂。团队中的新成员总是会问:“为什么要像这样拆分一个实体的状态?”。
总结
享元模式就是共享对象,在某些对象需要重复创建,且最终只需要得到单一结果的情况下使用,仅在程序必须支持大量对象且没有足够的内存容量时使用享 元模式。因为此种模式是利用先前创建的已有对象,通过某种规则去判断当前所需对象是否可以利用原有对象做相应修改后得到想要的效果。