设计模式历险记之享元模式

享元模式:运用共享技术有效的支持大量细粒度的对象。

我们先来看看它的结构图:

从上面的结构图我们可以知道,享元模式是由享元工厂、享元抽象(不是必须的)、享元实体和不需要享元的实体所组成(不是必须的),这里面这个享元工厂是这个设计模式的核心,它主要是负责来共享需要共享的享元实体的。

例子:

假设我们现在正在开发一款中国象棋的游戏,我们现在需要初始化每个象棋,都知道象棋里面红黑共有10个兵,下面我们就以初始化其中的五个兵来说明。

我们先用普通的面向对象的思路来初始化一下:

<?php
//抽象象棋类
abstract class chess{
    //当前位置X轴坐标
    protected $locationByX;
    //当前位置Y轴坐标
    protected $locationByY;

    //象棋名称
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function setlocation($locationByX, $locationByY)
    {
        $this->locationByX = $locationByX;
        $this->locationByY = $locationByY;
    }

    public abstract function move($direction);
}

//兵类
class soldiers extends chess{
    public function move($direction)
    {
        switch($direction){
            case '上':
                $this->locationByX + 1;
                break;

            case '下':
                $this->locationByX - 1;
                break;

            case '左':
                $this->locationByY + 1;
                break;

            case '右':
            default:
                $this->locationByY - 1;
                break;
        }
    }
}

//实例化士兵
$redSoldiersOne = new soldiers('soldiers');
$redSoldiersOne->setlocation(1,5);


$redSoldiersTwo = new soldiers('soldiers');
$redSoldiersTwo->setlocation(3,5);


$redSoldiersThree = new soldiers('soldiers');
$redSoldiersThree->setlocation(5,5);


$redSoldiersFour = new soldiers('soldiers');
$redSoldiersFour->setlocation(7,5);


$redSoldiersFive = new soldiers('soldiers');
$redSoldiersFive->setlocation(9,5);

var_dump($redSoldiersOne,$redSoldiersTwo,$redSoldiersThree,$redSoldiersFour,$redSoldiersFive);

我们看到,每个兵都被我们实例化成了一个对象,这样做是没有问题。但是,我们仔细观察每个兵对象,发现每个兵对象除了它所在的位置属性的值不一样外,其它都是一样的,这个时候我们是不是可以将位置属性值抽出来,保存在外部,然后初始化一个共享的兵类。通过这个共享的兵类来操作数据(无论是通过实例化多个对象还是共享同一个对象,它们的目的都是操作数据)。

享元模式的实现:

<?php
//抽象象棋类
abstract class chess{
    //当前位置X轴坐标
    protected $locationByX;
    //当前位置Y轴坐标
    protected $locationByY;

    //象棋名称
    protected $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function setlocation($locationByX, $locationByY)
    {
        $this->locationByX = $locationByX;
        $this->locationByY = $locationByY;
    }

    public abstract function move($direction);
}

//兵类
class soldiers extends chess{
    public function move($direction)
    {
        switch($direction){
            case '上':
                $this->locationByX + 1;
                break;

            case '下':
                $this->locationByX - 1;
                break;

            case '左':
                $this->locationByY + 1;
                break;

            case '右':
            default:
                $this->locationByY - 1;
                break;
        }
    }
}

//享元工厂类
class FlyweightFactory{

    private $concreteFlyweight = [];

    public function getFlyweight($name){

        if(empty($this->concreteFlyweight[$name])){
            $this->concreteFlyweight[$name] = new $name($name);
        }

        return $this->concreteFlyweight[$name];
    }
}

//初始化享元工厂
$chessFactory = new FlyweightFactory();

//初始化五个兵的位置
$locationArr = [
    [1,5],
    [3,5],
    [5,5],
    [7,5],
    [9,5],
];

//通过享元工厂获取享元类,并注入不同的数据。
foreach ($locationArr as $key => $location) {
    $redSoldiers = $chessFactory->getFlyweight('soldiers');
    $redSoldiers->setlocation($location[0],$location[1]);

    var_dump($redSoldiers);
}

这里需要引入两个概念:

  • 内部状态:享元类中始终不会变化的部分(上例中的 move 方法)。
  • 外部状态:享元类中可能会变化的部分(上例中象棋位置值),一般将这个属性放到享元类外部,通过参数形式注入进去。

我们用享元模式来重构了代码。将兵的位置属性(外部状态)从类中存储抽出来放到外部,然后通过享元工厂只生产一个共享的处理兵的类,通过这个类的 move(内部状态) 方法来操作所有的兵。当然,享元工厂中也能存放其它的象棋类(车,马,炮等)。

再来想想:
一个游戏平台,上边不可能只有一局象棋游戏在进行,可能有上千局在同时进行,如果给每个棋子都实例化的话,那么对象的数量是庞大的,占用内存是巨大的。
如果我们用享元模式来处理,我们只需要十几个对象(每种象棋只需要一个对象),然后将所有棋局游戏只保存数据,通过共享的对象来操作数据。因为单纯的数据存储比对象存储占用内存要小,这样是不是我们整体内存占用就变小了。

接下来我们仔细看看这个享元工厂类:

//享元工厂类
class FlyweightFactory{

    private $concreteFlyweight = [];

    public function getFlyweight($name){

        if(empty($this->concreteFlyweight[$name])){
            $this->concreteFlyweight[$name] = new $name($name);
        }

        return $this->concreteFlyweight[$name];
    }
}

emmm,是不是感觉 getFlyweight 方法和单例模式里面获取单例 getInstance 方法很像,是的,确实很像,都是通过判断被请求的类是否存在来决定返回新实例化的类或者是已经存在的类。不同的是享元工厂中存在多个共享的类(可以称为共享池),而单例模式中只有它本身。我觉得它们之间的关系类似于工厂方法模式和抽象工厂方法模式。

总结一下吧:

什么是享元模式:

  • 通过将相同的类或者不同类但是通过抽出属性后可以变得相同的类用来共享,这样避免了大量相同类的实例化,减少了内存。

单例模式的优点:

  • 大大的减少了类的数量,减少了消耗的内存。

单例模式的缺点:

  • 编码过程稍微变的复杂,逻辑稍微变得复杂。

作者自己的总结:享元模式共享的类和单例模式获得的单例类一般都是没有状态的类,就算是有状态,也是通过外部传入状态,类本身是没有任何的状态。

Snail's Blog
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.3 -develop 搭建 © 2014-2018 www.snail-c.cn 版权所有 ICP证:蜀ICP备18023253号-1
  • 联系邮箱:459921737@qq.com