设计模式历险记之原型模式

原型模式:原型模式又叫做克隆模式,它是用原型实例来制定创建对象的种类,并且通过拷贝这些原型创建新的对象。

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

从上面的结构图可以知道,原型模式由一个客户端类,原型抽象类和原型实体类组成。客户端类关联原型类,在客户端中来生产生产克隆原型对象。原型基类定义了一个 Clone 方法,然后每个实体类实现这个 Clone方法。

应用场景:

现在假如我们正在开发一款游戏,里面有这样一个需求,在一块地图上出现了三个一模一样的怪,下面我们用代码来实现这个需求。

  • 不用原型模式:


<?php

//创建怪物类A
class monsterA{
    private $atk;
    private $defense;

    public function __construct($atk,$defense){
        $this->atk = $atk;
        $this->defense = $defense;
    }
}

//创建怪物类B
class monsterB{
    private $life;

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

//实例化三个相同的怪物类A
$monsterA1 = new monsterA(999,999);
$monsterA2 = new monsterA(999,999);
$monsterA3 = new monsterA(999,999);

//实例化三个相同的怪物类B
$monsterB1 = new monsterB(999);
$monsterB2 = new monsterB(999);
$monsterB3 = new monsterB(999);

看看上面的代码,是不是感觉有点坏味道。

是的,它用 new 关键字实例化了相同的类三次,这样做的后果就是效率低,因为每次 new 都需要去执行构造函数,并且假如我们要修改怪物的攻击力,防御力等属性值,是不是在每个实例化的类中都要改。

那么如果说我们要生成十个、二十个或者更多的 怪应该怎么办?如果继续用 new 关键字来生产的话,不仅代码运行效率滴,而且如果你现在需要更改怪的攻击力和防御力等属性,是不是实例化多少次就需要改多少次,想想,这是不是噩梦般的感受。

下面我们用克隆模式来实现一下。

  • 用克隆模式实现:
<?php

//原型接口
interface Prototype{
    public function clone();
}

//原型怪物A
class monsterA implements Prototype{
    private $atk;
    private $defense;

    public function __construct($atk,$defense){
        $this->atk = $atk;
        $this->defense = $defense;
    }

    public function clone(){
        return clone $this;
    }
}

//原型怪物B
class monsterB implements Prototype{
    private $life;

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

    public function clone(){
        return clone $this;
    }
}

//实例化一次怪物A,然后克隆两次
$monsterA1 = new monsterA(999,999);
$monsterA2 = $monsterA1->clone();
$monsterA3 = $monsterA1->clone();

$monsterB1 = new monsterB(999);
$monsterB2 = $monsterB1->clone();
$monsterB3 = $monsterB1->clone();

emmm。这样是不是感觉好多了,因为不需要去用关键字 new 去实例化多次相同的类,这样的话就不需要去执行多次构造函数,这样的效率是不是高一些,并且如果现在要更改怪的防御力,攻击力等属性,只需要更改一个原型类就好了,而是通过 Clone 方法来克隆出相同的类,

当然,上面的代码还可以再优化一下,就是通过循环和原型类来生产多个类,这样也减少了代码量。

到这儿,你以为就完了吗?没有没有,我们继续将上面的需求改变一下,就是每个怪可以拥有装备,拥有了装备后它的攻击力这些都会加强。好,现在我们继续用原型模式来实现这个需求。

  • 拥有装备的怪(浅复制):
<?php
//抽象装备类
interface equip{

}
//装备剑类
class sword implements equip{
    public $atk = 100;
    public $defense = 100;
}
//抽象怪 
interface Prototype{
    public function clone();
}
//怪A
class monsterA implements Prototype{
    private $atk;
    private $defense;
    public $equip;

    public function __construct($atk,$defense,equip $equip){
        $this->equip = $equip;
        $this->atk = $atk;
        $this->defense = $defense;
    }

    public function clone(){
        return clone $this;
    }
}

$equip = new sword();

$monsterA1 = new monsterA(999,999,$equip);
//克隆生成怪物类
$monsterA2 = $monsterA1->clone();

我们看看上面的代码,怪物类中的装备属性指向了一个对象,我们通过 Clone 方法来克隆了 $monsterA1 生成了 $monsterA2,这个时候,$monsterA1$monsterA2 的装备属性 $equip 其实是指向的同一个装备对象(这个叫做对象的浅复制).

我们将上面的代码添加两行来做个实验:

<?php
//接上面的代码
.
.
.
//改变 $monsterA 的装备攻击力为 200
$monsterA1->equip->atk = 200;

echo $monsterA2->equip->atk;

这个时候我们看一下输出:

我们改了 $monsterA1 的装备 $equip 的攻击力为 200,打印出 $monsterA2 的装备 $equip 攻击力也变成了 200。所以这个实验也验证了上面 $monsterA1$monsterA2 的装备属性是指向的同一个对象,那么我们应该怎样做它们才不会指向同一个属性呢,看下面:

  • 拥有装备的怪(深复制):
<?php
//抽象装备类
interface equip{

}
//装备剑类
class sword implements equip{
    public $atk = 100;
    public $defense = 100;
}
//抽象怪 
interface Prototype{
    public function clone();
}
//怪A
class monsterA implements Prototype{
    private $atk;
    private $defense;
    public $equip;

    public function __construct($atk,$defense,equip $equip){
        $this->equip = $equip;
        $this->atk = $atk;
        $this->defense = $defense;
    }

    public function clone(){
        return clone $this;
    }

    //我们添加__Clone 魔术方法,去将对象属性克隆一份。
    public function __Clone(){
        $this->equip = clone $this->equip;
    }
}

$equip = new sword();

$monsterA1 = new monsterA(999,999,$equip);
$monsterA2 = $monsterA1->clone();

$monsterA1->equip->atk = 200;

echo $monsterA2->equip->atk;

执行一下上面的代码 :

可以看到我们更改了 $monsterA 的装备属性,但是没有影响到 $monsterB,这个时候通过原型对象生成的新对象和原型对象就彻底分开了(这个叫做对象的深复制)。

总结:

所谓原型模式(克隆模式)就是通过克隆来复制已有对象,它应用的场景就是需要同时实例化多个相同的对象场景,需要注意的是如果被克隆的对象中有属性也是对象,那么应该用深复制而不是浅复制。

拓展:

php 中(其它语言基本一样),对象的赋值其实是引用赋值,就是被赋值的对象和赋值对象指向的是相同的对象,这个使用要想使它们指向不同的对象,用 Clone 关键字。

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