陈大剩博客

php设计模式(七):原型模式(Prototype)

  • 陈大剩
  • 2023-04-16 23:40:27
  • 333

原型模式

原型模式又称为:克隆、Clone、Prototype,原型是一种创建型设计模式,使 你能够复制已有对象,而又无需 使代码依赖它们所属的类。

问题

如果有一个 对象 ,并希望生成与其完全相同的一个复制品,该如何实现呢?

首先,必须新建一个属于相同类的 对象 。然后,必须遍历原始对象的 所有成员变量 ,并将成员变量值复制到新对象中。
对象

并非所有对象都能通过这种方式进行 复制 ,因为有些对象可能拥有 私有成员变量 ,它们在对象本身以外是不可见的。

直接复制还有另外一个问题。必须知道 对象所属的类 才能创建复制品, 所以代码必须依赖该类。

解决方法

原型模式会将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口让你能够克隆 对象, 同时又无需将代码和对象所属类耦合。 通常情况下,这样的接口中仅包含一个 克隆 方法。

实现步骤

STEP 1 创建原型接口

创建抽象原型基类接口, 并在其中声明 克隆 方法。

STEP 2 创建具体原型

创建具体的原型类,并申明其 克隆 方法。克隆方法实现 拷贝 功能。

STEP 3 客户端

客户端使用具体的原型类,克隆一个相同的对象。

浅拷贝与深拷贝

说到原型方法就不得不提浅拷贝与深拷贝的问题,面试中也经常会问到此问题。

浅拷贝

被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝。

深拷贝

被拷贝对象的所有的变量都含有与原来对象相同的值,除了那些引用其他对象的变量。那些引用其他对象的变量将指向一个被拷贝的新对象,而不再是原有那些被引用对象。
深拷贝把要拷贝的对象所引用的对象也都拷贝了一次,而这种对被引用到的对象拷贝叫做间接拷贝。
深拷贝要深入到多少层,是一个不确定的问题。
在决定以深拷贝的方式拷贝一个对象的时候,必须决定对间接【与其相关联的对象】拷贝的对象是采取浅拷贝还是深拷贝还是继续采用深拷贝。
因此,在采取深拷贝时,需要决定多深才算深。此外,在深拷贝的过程中,很可能会出现循环引用的问题。

利用序列化来做深拷贝

利用序列化来做深拷贝,把对象写到流里的过程是序列化 Serilization 过程,但在业界又将串行化这一过程形象的称为“冷冻”或“腌咸菜”过程;

而把对象从流中读出来的过程则叫做反序列化 Deserialization 过程,也称为“解冻”或“回鲜”过程。
在PHP中使用 serializeunserialize 函数实现序列化和反序列化

结构

1个 interface 或者 abstract 原型基类

多个或一个具体原型类

代码示例

抽象原型接口类

可手动增加零件的原型类

// Prototype.php
abstract class Prototype
{
    private array $parts = [];

    /**
     * 添加零件
     * @param string $type
     * @param float $price
     * @return void
     */
    public function add(string $type, float $price)
    {
        $this->parts[$type] = $price;
    }

    /**
     * 显示零件
     * @param string $type
     * @return mixed
     */
    public function show(string $type)
    {
        if (!isset($this->parts[$type])) {
            throw new \InvalidArgumentException('没有此部件');
        }
        return $this->parts[$type];
    }

    abstract public function __clone();
}

具体原型类

//Display.php
/**
 * 显示器
 */
class Display
{
    public $price = 1999.9;
}

// ComputerPrototype.php
class ComputerPrototype extends Prototype
{
    private object $display;

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

    public function setDisplayPrice(float $price)
    {
        $this->display->price = $price;
    }

    public function __clone()
    {
        /**
         * 深拷贝序列化实现
         */
        //$serializeObj = serialize($this);  //  序列化
        //$cloneObj = unserialize($serializeObj);   //  反序列化
        //return $cloneObj;
        /**
         * 深拷贝克隆子类实现
         */
        //$this->display = clone $this->display;
        /**
         * 浅拷贝
         */
        return $this;
    }
}

客户端使用

$computer = new ComputerPrototype(new Display());
$computer->add('mouse', 49.9);
$computer->add('keyboard', 149.9);
var_dump($computer);


$cloneComputer = clone $computer;
$cloneComputer->add('keyboard', 199.9);
$cloneComputer->setDisplayPrice(2999.9);
var_dump($cloneComputer);
var_dump($computer);

UML

UML

优缺点

优点

  • 可以克隆对象, 而无需与它们所属的具体类相耦合。
  • 可以克隆预生成原型, 避免反复运行初始化代码。
  • 可以更方便地生成复杂对象。
  • 可以用继承以外的方式来处理复杂对象的不同配置。

缺点

  • 克隆包含循环引用的复杂对象可能会非常麻烦。
分享到:
0

说点儿什么吧

头像

表情

本站由陈大剩博客程序搭建 | 湘ICP备2023000975号| Copyright © 2017 - 陈大剩博客 | 本站采用创作共用版权:CC BY-NC 4.0

站长统计| 文章总数[109]| 评论总数[9]| 登录用户[22]| 时间点[112]

logo

登入

社交账号登录