更多优质内容
请关注公众号

面向对象和设计模式(十四)原型模式——PHP语言实现-张柏沛IT博客

正文内容

面向对象和设计模式(十四)原型模式——PHP语言实现

栏目:其他内容 系列:面向对象与设计模式 发布时间:2023-02-01 17:04 浏览量:1378
本系列文章目录
展开/收起

一、什么是原型模式

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分成员属性都相同),可以利用对已有对象(原型)进行拷贝的方式来创建新对象,以达到节省创建时间的目的,这就是原型模式。


原型模式的使用场景:

如果想要获取一个和原有对象状态一致(数据一致、属性一致)的新对象,但是遇到以下问题的情况下,就可以使用原型模式。

1、对象中的数据需要得到复杂的计算,如排序、计算哈希值等,或者从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取。拷贝对象可以跳过这些过程和开销,直接得到这些数据。

2、对象达到当前状态需要调用很多过程和方法调用,为了避免调用者重复执行这些调用,可以直接拷贝对象一步到位。


那么什么情况下我们会想要获得一个和原有对象一样的新对象呢?

当一个对象A需要提供给对象B和对象C访问(也就是说对象B和对象C都依赖对象A), 而且各个调用者可能需要修改对象A内部的值,如果不想对象B对对象A的变更影响到对象C对对象A的使用,就可以克隆出另一个对象A,让B和C分别依赖两个相互独立的对象A。



二、原型模式如何实现

只要在类中实现一个方法,使得对象调用这个方法后能得到一个和原对象一模一样的新对象,我们就可以说这个类实现了原型模式。


在讨论如何实现之前,需要知道什么是深拷贝和浅拷贝。

我们知道,将一个对象存储到变量中,这个变量其实存储的是对象的引用,或者说存的是对象在内存中的地址。当把这个变量赋值给另一个变量的时候,两个变量存储的都是对象的地址,对象在内存空间中只有一份。同样的道理,如果一个对象A里面既有标量成员,也有对象成员,那么对象A也只是持有对象成员的引用。

假设有一个Row 行对象,里面包含2个字段成员属性,一个是标量成员id,一个是对象成员 Field_A。

对象Row拷贝出一个新对象Row,且两个对象Row的Field_A成员指向的是内存空间的同一个内容(同一个地址),那么说明 Field_A 对象没有被拷贝,依旧只有一份,这种情况就是浅拷贝。


简单的来说,浅拷贝就是值拷贝了表层的对象 Row ,没有拷贝对象Row内部的对象 Field_A。对浅拷贝出来的新对象的变更依旧可能会影响原对象。



如果 拷贝对象Row 的时候,里面的 Field_A 对象也拷贝了一份,那么这种情况就是深拷贝。



浅拷贝的实现方式在各个语言中基本都有方法和函数直接提供,例如python中的copy.copy()、java中Object 类的 clone() 方法、PHP中的clone关键字。


深拷贝则有两种实现方式:

一种是递归的对对象进行浅拷贝,直到要拷贝的最内层对象只包含基本数据类型数据,没有引用对象为止。

另一种最简单粗暴实用,就是直接对对象进行序列化,再对反序列化得到新对象。


原型模式下,基本都是以深拷贝的方式进行对象拷贝,否则对 拷贝对象 的更改依旧会 影响到 原对象,原型模式就失去了意义。


下面以PHP语言为例。


浅拷贝示例

PHP 中可以使用 clone 关键字进行浅拷贝的克隆对象。

<?php
class Inner{
  public $i = 0;

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

  public function incr(){
    $this->i++;
  }

  public function getInt(){
    return $this->i;
  }
}

class Test{
  public $name;
  public $inner;

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

  public function getInner(){
    return $this->inner;
  }
}

$inner = new Inner(10);
$t1 = new Test('zbp',$inner);

$t2 = clone $t1;	// 浅拷贝
$t2->getInner()->incr();

var_dump($t1->getInner()->getInt());	// 11
var_dump($t2->getInner()->getInt());	// 还是11 说明t1和t2中的inner对象是同一个inner对象

一般而言,如果我们在克隆对象之后还希望自动对新对象的属性做一些变更,可以通过重写__clone()方法来做到。


如果在类中设置一个空的,且访问权限为 private 的 __clone() 方法的话,可以起到禁止通过clone关键字克隆的作用。当然,这个方法无法禁止通过 serialize()序列化函数 和 unserialize() 反序列化函数 实现的深拷贝。

private function __clone(){}


深拷贝示例

PHP 中可以使用 serialize()序列化函数 和 unserialize() 反序列化函数实现深拷贝。

$inner = new Inner(10);
$t1 = new Test('zbp',$inner);

$t2 = unserialize(serialize($t1));
$t2->getInner()->incr();

var_dump($t1->getInner()->getInt());    // 10
var_dump($t2->getInner()->getInt());    // 11 说明t1和t2中的inner对象不是同一个inner对象,而是相互独立的两个对象





更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > 面向对象和设计模式(十四)原型模式——PHP语言实现

热门推荐
推荐新闻