[ZT]深入讨论PHP5对象复制技术

此文将由浅入深的讨论PHP5的对象复制技术  原创文章 请尊重版权  有错误或则不当之处还希望能够指出来

对象复制的由来

为什么对象会有“复制”这个概念,这与PHP5中对象的传值方式是密切相关的,让我们看看下面这段简单的代码

PHP代码

  1. /**
  2. * 电视机类
  3. */
  4. class Television
  5. {
  6. // 屏幕高度
  7. protected $_screenLength = 300;
  8. //屏幕宽度
  9. protected $_screenHight  = 200;
  10. //电视机外观颜色
  11. protected $_color = 'black';
  12. /**
  13. * 返回电视外观颜色
  14. */
  15. public function getColor()
  16. {
  17. return $this->_color;
  18. }
  19. /**
  20. * 设置电视机外观颜色
  21. */
  22. public function setColor($color)
  23. {
  24. $this->_color = (string)$color;
  25. return $this;
  26. }
  27. }
  28. $tv1 = new Television();
  29. $tv2 = $tv1;

这段代码定义了一个电视机的类 Television , $tv1为一个电视机的实例,然后我们按照普通的变量赋值方式将$tv1的值赋给$t2。那么现在我们拥有两台电视机$tv1和$tv2了,真的是这样的吗?我们来测试一下。

PHP代码

  1. echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
  2. echo '<br>';
  3. echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
  4. echo '<br>';
  5. //把tv2涂成白色
  6. $tv2->setColor('white');
  7. echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
  8. echo '<br>';
  9. echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是white

首 先我们看到tv1和tv2的颜色都是black,现在我们希望tv2换个颜色,所以我们将它的颜色设置成了white,我们再看看tv2的颜色,确实成为 了white,似乎满足了我们的要求,可是并没有想象中的那么顺利,当我们接着看tv1的颜色的时候,我们发现tv1也由black边成了white。我 们并没有重新设置tv1的颜色,为什么tv1会重black变成white呢?这是因为PHP5中对象的赋值和传值都是以“引用”的方式。PHP5使用了 Zend引擎II,对象被储存于独立的结构Object Store中,而不像其它一般变量那样储存于Zval中(在PHP4中对象和一般变量一样存储于Zval)。在Zval中仅存储对象的指针而不是内容 (value)。当我们复制一个对象或者将一个对象当作参数传递给一个函数时,我们不需要复制数据。仅仅保持相同的对象指针并由另一个zval通知现在这 个特定的对象指向的Object Store。由于对象本身位于Object Store,我们对它所作的任何改变将影响到所有持有该对象指针的zval结构----表现在程序中就是目标对象的任何改变都会影响到源对象。.这使 PHP对象看起来就像总是通过引用(reference)来传递。所以以上的tv2和tv1其实是指向同一个电视机实例,我们对tv1或则tv2所做的操 作其实都是针对这同一个实例。因此我们的“复制”失败了。看来直接变量赋值的方式并不能拷贝对象,为此PHP5提供了一个专门用于复制对象的操作,也就是 clone 。这就是对象复制的由来。

用clone(克隆)来复制对象

我们现在使用PHP5的clone语言结构来复制对象,代码如下:

PHP代码

  1. $tv1 = new Television();
  2. $tv2 = clone $tv1;
  3. echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
  4. echo '<br>';
  5. echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
  6. echo '<br>';
  7. //把tv2换成涂成白色
  8. $tv2->setColor('white');
  9. echo 'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
  10. echo '<br>';
  11. echo 'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black

这段代码的第2行,我们用clone关键字复制tv1,现在我们就拥有了一份真正的tv1的拷贝tv2,我们还是按照之前的方法来检测复制是否成功。我们可以看到,我们将tv2的颜色换成了white,tv1的颜色还是black,这样我们的复制操作就成功了。

__clone魔术方法

现在我们考虑到这样一个情况,每一台电视机应该都有自己的编号,这个编号如同我们的×××号码一样应该是唯一的,所以当我们在复制一台电视机的时候,我们 不希望这个编号也被复制过来,以免造成一些麻烦。我们想到的一个策略是将赋值出来的电视机的编号清空,然后再按照需求来重新分配编号。

那么__clone魔术方法就是专门用来解决这样的问题,__clone魔术方法会在对象被复制( 也就是clone操作)的时候被触发。我们修改了电视机类Television的代码,添加了编号属性和__clone方法,代码如下。

PHP代码

  1. /**
  2. * 电视机类
  3. */
  4. class Television
  5. {
  6. // 电视机编号
  7. protected $_identity = 0;
  8. //屏幕高度
  9. protected $_screenLength = 300;
  10. //屏幕宽度
  11. protected $_screenHight  = 200;
  12. //电视机外观颜色
  13. protected $_color = 'black';
  14. /**
  15. * 返回电视外观颜色
  16. */
  17. public function getColor()
  18. {
  19. return $this->_color;
  20. }
  21. /**
  22. * 设置电视机外观颜色
  23. */
  24. public function setColor($color)
  25. {
  26. $this->_color = (string)$color;
  27. return $this;
  28. }
  29. /**
  30. * 返回电视机编号
  31. */
  32. public function getIdentity()
  33. {
  34. return $this->_identity;
  35. }
  36. /**
  37. * 设置电视机编号
  38. */
  39. public function setIdentity($id)
  40. {
  41. $this->_identity = (int)$id;
  42. return $this;
  43. }
  44. public function __clone()
  45. {
  46. $this->setIdentity(0);
  47. }
  48. }

下面我们来复制这样的一个电视机对象。

PHP代码

  1. $tv1 = new Television();
  2. $tv1->setIdentity('111111');
  3. echo 'id of tv1 is ' . $tv1->getIdentity();//111111
  4. echo '<br>';
  5. $tv2 = clone $tv1;
  6. echo 'id of tv2 is ' . $tv2->getIdentity();//0

我 们生产了一台电视机tv1 , 并且设置它的编号为111111,然后我们用clone将tv1复制得到了tv2,这个时候__clone魔术方法被触发,此方法将直接作用与复制得到的 对象tv2,我们在__clone方法中调用了setIdentity成员方法将tv2的_identity属性清空,以便我们后面对它进行重新编号。由 此我们可以看到__clone魔术方法能让我们非常方便的在clone对象的时候做一些附加的操作。

clone操作的致命缺陷

clone真的能够达到理想的复制效果吗?在某些情况下,你应该会发现,clone操作并没有我们想象中的那么完美。我们将以上的电视机类修改一下,然后做测试。

每台电视机都会附带一个遥控器,所以我们将会有一个遥控器类,遥控器和电视机是一种“聚合”关系(相对与“组合”关系,是一种较弱的依赖关系,因为一般情况电视机就算没有遥控也能正常使用),现在我们的电视机对象应该都持有一个到遥控器对象的引用。下面看看代码

PHP代码

  1. /**
  2. * 电视机类
  3. */
  4. class Television
  5. {
  6. //电视机编号
  7. protected $_identity    = 0;
  8. //屏幕高度
  9. protected $_screenLength = 300;
  10. // 屏幕宽度
  11. protected $_screenHight  = 200;
  12. //电视机外观颜色
  13. protected $_color        = 'black';
  14. //遥控器对象
  15. protected $_control      = null;
  16. /**
  17. * 构造函数中加载遥控器对象
  18. */
  19. public function __construct()
  20. {
  21. $this->setControl(new Telecontrol());
  22. }
  23. /**
  24. * 设置遥控器对象
  25. */
  26. public function setControl(Telecontrol $control)
  27. {
  28. $this->_control = $control;
  29. return $this;
  30. }
  31. /**
  32. * 返回遥控器对象
  33. */
  34. public function getControl()
  35. {
  36. return $this->_control;
  37. }
  38. /**
  39. * 返回电视外观颜色
  40. */
  41. public function getColor()
  42. {
  43. return $this->_color;
  44. }
  45. /**
  46. * 设置电视机外观颜色
  47. */
  48. public function setColor($color)
  49. {
  50. $this->_color = (string)$color;
  51. return $this;
  52. }
  53. /**
  54. * 返回电视机编号
  55. */
  56. public function getIdentity()
  57. {
  58. return $this->_identity;
  59. }
  60. /**
  61. * 设置电视机编号
  62. */
  63. public function setIdentity($id)
  64. {
  65. $this->_identity = (int)$id;
  66. return $this;
  67. }
  68. public function __clone()
  69. {
  70. $this->setIdentity(0);
  71. }
  72. }
  73. /**
  74. * 遥控器类
  75. */
  76. class Telecontrol
  77. {
  78. }

下面复制这样的一个电视机对象并且观察电视机的遥控器对象。

PHP代码

  1. $tv1 = new Television();
  2. $tv2 = clone $tv1;
  3. $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
  4. $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
  5. echo $tv1;    //tv1的object id 为 #1
  6. echo '<br>';
  7. echo $contr1; //contr1的object id 为#2
  8. echo '<br>';
  9. echo $tv2;    //tv2的object id 为 #3
  10. echo '<br>';
  11. echo $contr2; //contr2的object id 为#2

经过复制之后,我们查看对象id,通过clone操作从tv1复制出了tv2,tv1和tv2的对象id分别是1和3,这表示tv1和tv2是引用两个不同 的电视机对象,这符合clone操作的结果。然后我们分别获取了tv1的遥控器对象contr1和tv2的遥控器对象contr2,通过查看它们的对象 id我们发现contr1和contr2的对象id都是2,这表明它们是到同一个对象的引用,也就是说我们虽然从tv1复制出tv2,但是遥控器并没有被 复制,每台电视机都应该配有一个遥控器,而这里tv2和tv1共用一个遥控器,这显然是不合常理的。

由此可见,clone操作有这么一个非常大的缺陷:使用clone操作复制对象时,当被复制的对象有对其它对象的引用的时候,引用的对象将不会被复制。然 而这种情况又非常的普遍,现今 “合成/聚合复用”多被提倡用来代替“继承复用”,“合成”和“聚合”就是让一个对象拥有对另一个对象的引用,从而复用被引用对象的方法。我们在使用 clone的时候应该考虑到这样的情况。那么在clone对象的时候我们应该如何去解决这样的一个缺陷呢?可能你很快就想到了之前提到的__clone魔 术方法,这确实是一种解决方案。

方案1:用__clone魔术方法弥补

前面我们已经介绍了__clone魔术方法的用法,我们可以在__clone方法中将被复制对象中其它对象的引用重新引用到一个新的对象。下面我们看看修改后的__clone()魔术方法:

PHP代码

  1. public function __clone()
  2. {
  3. $this->setIdentity(0);
  4. //重新设置一个遥控器对象
  5. $this->setControl(new Telecontrol());
  6. }

第04行中我们为复制出来的电视机对象重新设置了一个遥控器,我们按照之前的方法查看对象的id可以发现,两台电视机的遥控器拥有不同的对象id,这样我们的问题就解决了。

但是这样的方式大概并不算太好,如果被复制对象中有多个到其它对象的引用,我们必须在__clone方法中逐个的重新设置,更糟糕的是如果被复制对象的类由第三方提供,我们无法修改代码,那复制操作基本就无法顺利完成了。

我 们使用clone来复制对象,这种复制叫做“浅复制”:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对 象。也就是说,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。相对于“浅复制”,当然也有一个“深复制”:被复制的对象的所有的变量都含有与原来 的对象相同的值,除去那些引用其他对象的变量。也就是说,深复制把要复制的对象所引用的对象都复制了一遍。深复制需要决定深入到多少层,这是一个不容易确 定的问题,此外可能会出现循环引用的问题,这些都必须小心处理。我们的方案2将是一个深复制的解决方案。

方案2:利用串行化做深复制

PHP有串行化(serialize)和反串行化(unserialize)函数,我们只需要用serialize()将一个对象写入一个流,然后从流中读回对象,那么对象就被复制了。在JAVA语言里面,这个过程叫做“冷藏”和“解冻”。下面我们将测试一下这个方法:

PHP代码

  1. $tv1 = new Television();
  2. $tv2 = unserialize(serialize($tv1));//序列化然后反序列化
  3. $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
  4. $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
  5. echo $tv1;    //tv1的object id 为 #1
  6. echo '<br>';
  7. echo $contr1; //contr1的object id 为#2
  8. echo '<br>';
  9. echo $tv2;    //tv2的object id 为 #4
  10. echo '<br>';
  11. echo $contr2; //contr2的object id 为#5

我 们可以看到输出结果,tv1和tv2拥有了不同的遥控器。这比方案1要方便很多,序列化是一个递归的过程,我们不需要理会被对象内部引用了多少个对象以及 引用了多少层对象,我们都可以彻底的复制。注意使用此方案时我们无法触发__clone魔术方法来完成一些附加操作,当然我们可以在深复制之后再进行一次 clone操作来触发__clone魔术方法,只是会对效率些小的影响。另外此方案会触发被复制对象和所有被引用对象的__sleep和__wakeup 魔术方法,所以这些情况都需要被考虑。

总结

不同的对象复制方式有着不同的效果,我们应该根据具体应用需求来考虑使用哪种方式以及如何改进复制方式。PHP5的面向对象特性和JAVA比较接近,相信我们可以从JAVA中借鉴很多宝贵的经验。

引自:http://yooq.blog.163.com/blog/static/62280272008111721216410/

转载于:https://blog.51cto.com/stark/699583

深入讨论PHP5对象复制技术相关推荐

  1. php对象持久化,在 Oracle 中完成 PHP5 对象的持久

    在 Oracle 中完成 PHP5 对象的持久 作者:Barry McKay 在数据库驱动的 Web 应用程序中实现 PHP5 对象的持久,朝着完全面向对象的 Web 应用程序开发迈进重要的一步. 2 ...

  2. php5对象复制、clone、浅复制与深复制的区别与介绍

    对象复制的由来  为什么对象会有"复制"这个概念,这与PHP5中对象的传值方式是密切相关的,让我们看看下面这段简单的代码 PHP代码 * /**      * * 电视机类     ...

  3. 都在讨论函数对象,那么,什么是函数对象呢?

    一种将函数作为参数传递的独创方法是注意到对象既包含数据也包含方法,于是我们可以定义一个没有数据只有一个方法的类,并传递该类的一个实例.事实上,一个函数通过将其放在一个对象内部而被传递.这样的对象通常叫 ...

  4. 如何从零起步搭建一个分布式对象存储的架构

    早在几年前,云存储还只是存在于业界大佬们口中的一个概念,其应用场景仅供大公司使用.突飞猛进的网络技术似乎在一瞬间就把这个概念普及到千家万户,现在云存储已经是大家司空见惯的一个网络服务了.比如大家用的百 ...

  5. HotSpot虚拟机对象的创建过程

    1.文中讨论的对象限于普通Java对象,不包括数组和class对象. 2.内存的分配方式由Java堆是否规整来决定,而Java堆是否规整取决于垃圾收集器是否有压缩整理的功能. 3.还需要考虑:对象的创 ...

  6. 文件系统vs对象存储——选型和趋势

    摘要:对象存储和我们经常接触到的硬盘和文件系统等存储形态不同,它提供Key-Value(简称K/V)方式的RESTful数据读写接口,并且常以网络服务的形式提供数据的访问.但经过多年的发展,我们现在通 ...

  7. 【深入Java虚拟机JVM 05】HotSpot对象探秘

    说明:文章所有内容均摘自<深入理解Java虚拟机:JVM高级特性与最佳实践(第二版)> 介绍完Java虚拟机的运行时数据区之后,我们大致知道了虚拟机内存的概况,读者了解了内存中放了些什么后 ...

  8. deferred对象和promise对象(二)---deferred对象

    早上醒来,继续讨论deferred对象和primise对象. deferred对象的的方法: 1.$.Deferred()-----生成一个deferred对象 2.deferred.done()-- ...

  9. python 数组 运算_python数据分析(二) python numpy--数组和矢量运算--数组对象

    Numpy numpy是数值计算最重要的基础包,几乎所有的科学运算的模块底层所用的都是numpy数组. Numpy本身没有提供多么高级的数据分析功能,他所提供的功能主要是: 1.具有矢量算术运算(用数 ...

最新文章

  1. 【分享】Vue 资源典藏(UI组件、开发框架、服务端、辅助工具、应用实例、Demo示例)...
  2. CentOS下查看最后登录的用户信息以及LOG记录
  3. 从oracle10g 10.2.0.1 升级到10.2.0.4碰到的问题及解决
  4. c++ 暂停功能_10月10日丨注意:东部华侨城茶溪谷暂停开放!深圳9月街道环卫榜出炉!前五街道盐田占其三!…今日盐田大小事...
  5. centos安装 crf 和 kenlm
  6. gson的简单使用方法
  7. Lilypad Pondg(POJ-3171)
  8. ExtJs2.0学习系列(5)--Ext.FormPanel之第二式
  9. Maven学习总结(45)——Maven仓库、将本地Jar包安装到仓库、打可执行的Jar总结
  10. [20180614]删除bootstrap$记录无法启动2.txt
  11. C#中的类型转换(转载)
  12. Atitit 如何利用先有索引项进行查询性能优化
  13. 【ACM ICPC 2011–2012, Northeastern European Regional Contest】Interactive Permutation Guessing【交互题】
  14. 数据库系统基础教程一:关系数据库与关系代数
  15. 惊喜! UE4 + ftrack开源了!
  16. Dataframe基础操作巩固——美国大选献金项目
  17. 装服务器系统时无法找到介质,服务器安装介质未找到
  18. 读书笔记:《量化投资实务》
  19. 数据挖掘开发部署,主要分为哪几步?
  20. NLP深入学习——什么是词向量和句向量(Word Embedding and Sentence Embedding)

热门文章

  1. 全球机器换人排行榜!这八个国家遥遥领先
  2. 漫画 | 一台 Linux 服务器最多能支撑多少个 TCP 连接?
  3. 为什么程序员都不写文档?
  4. 干货!我的计算机网络怎么考了 100 分的?
  5. 后浪优秀!21 岁小伙兼职程序员养家,大三存款达 6 位数
  6. Apache Pulsar中的地域复制,第1篇:概念和功能
  7. WPF Effect 造成的字体模糊
  8. Python模块包中__init__.py文件的作用
  9. Azure Service Fabric正式发布
  10. linux下vsftp