`
liboxlu
  • 浏览: 65076 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

深入讨论PHP5对象复制技术

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

PHP代码

    * /**
    * * 电视机类
    * */
    * class Television  
    * { 
    *     /**
    *      * 屏幕高度
    *      */
    *     protected
      $_screenLength = 300; 
    *      
    *     /**
    *      * 屏幕宽度
    *      */
    *     protected
      $_screenHight  = 200; 
    *      
    *     /**
    *      * 电视机外观颜色
    *      */
    *     protected
      $_color        = 'black'; 
    *      
    *     /**
    *      * 返回电视外观颜色
    *      */
    *     public
      function getColor() 
    *     { 
    *         return
      $this->_color; 
    *     } 
    *      
    *     /**
    *      * 设置电视机外观颜色
    *      */
    *     public
      function setColor($color) 
    *     { 
    *         $this->_color = (string)$color; 
    *         return
      $this; 
    *     } 
    * } 
    *  
    * $tv1 = new Television(); 
    * $tv2 = $tv1; 


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

PHP代码

    * echo
      'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
    * echo
      '<br>'; 
    * echo
      'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
    * echo
      '<br>'; 
    *  
    * //把tv2涂成白色
    * $tv2->setColor('white'); 
    *  
    * echo
      'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
    * echo
      '<br>'; 
    * 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语言结构来复制对象,代码如下:
[size=+0]PHP代码

    * [size=+0]$tv1 = new Television(); 
    * $tv2 = clone $tv1; 
    *

    * echo
      'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black
    * echo
      '<br>'; 
    * echo
      'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是black
    * echo
      '<br>'; 
    *

    * //把tv2换成涂成白色
    * $tv2->setColor('white'); 
    *

    * echo
      'color of tv2 is: ' . $tv2->getColor();//tv2的颜色是white
    * echo
      '<br>'; 
    * 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代码

    * /**
    * * 电视机类
    * */
    * class Television  
    * { 
    *      
    *     /**
    *      * 电视机编号
    *      */
    *     protected
      $_identity    = 0; 
    *      
    *     /**
    *      * 屏幕高度
    *      */
    *     protected
      $_screenLength = 300; 
    *      
    *     /**
    *      * 屏幕宽度
    *      */
    *     protected
      $_screenHight  = 200; 
    *      
    *     /**
    *      * 电视机外观颜色
    *      */
    *     protected
      $_color        = 'black'; 
    *      
    *     /**
    *      * 返回电视外观颜色
    *      */
    *     public
      function getColor() 
    *     { 
    *         return
      $this->_color; 
    *     } 
    *      
    *     /**
    *      * 设置电视机外观颜色
    *      */
    *     public
      function setColor($color) 
    *     { 
    *         $this->_color = (string)$color; 
    *         return
      $this; 
    *     } 
    *  
    *    /**
    *      * 返回电视机编号
    *      */
    *     public
      function getIdentity() 
    *     { 
    *         return
      $this->_identity;     
    *     } 
    *      
    *     /**
    *      * 设置电视机编号
    *      */
    *     public
      function setIdentity($id) 
    *     { 
    *         $this->_identity = (int)$id; 
    *         return
      $this; 
    *     } 
    *      
    *     public
      function __clone() 
    *     { 
    *         $this->setIdentity(0);  
    *     } 
    * } 


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

PHP代码

    * $tv1 = new Television(); 
    * $tv1->setIdentity('111111'); 
    * echo
      'id of tv1 is ' . $tv1->getIdentity();//111111 
    * echo
      '<br>'; 
    *  
    * $tv2 = clone $tv1; 
    * 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代码

    * /**
    * * 电视机类
    * */
    * class Television  
    * { 
    *      
    *     /**
    *      * 电视机编号
    *      */
    *     protected
      $_identity    = 0; 
    *      
    *     /**
    *      * 屏幕高度
    *      */
    *     protected
      $_screenLength = 300; 
    *      
    *     /**
    *      * 屏幕宽度
    *      */
    *     protected
      $_screenHight  = 200; 
    *      
    *     /**
    *      * 电视机外观颜色
    *      */
    *     protected
      $_color        = 'black'; 
    *      
    *     /**
    *      * 遥控器对象
    *      */
    *     protected
      $_control      = null; 
    *      
    *     /**
    *      * 构造函数中加载遥控器对象
    *      */
    *     public
      function __construct() 
    *     { 
    *         $this->setControl(new Telecontrol()); 
    *     } 
    *  
    *     /**
    *      * 设置遥控器对象
    *      */
    *     public
      function setControl(Telecontrol $control) 
    *     { 
    *         $this->_control = $control; 
    *         return
      $this; 
    *     } 
    *      
    *     /**
    *      * 返回遥控器对象
    *      */
    *     public
      function getControl() 
    *     { 
    *         return
      $this->_control; 
    *     }     
    *      
    *     /**
    *      * 返回电视外观颜色
    *      */
    *     public
      function getColor() 
    *     { 
    *         return
      $this->_color; 
    *     } 
    *      
    *     /**
    *      * 设置电视机外观颜色
    *      */
    *     public
      function setColor($color) 
    *     { 
    *         $this->_color = (string)$color; 
    *         return
      $this; 
    *     } 
    *  
    *    /**
    *      * 返回电视机编号
    *      */
    *     public
      function getIdentity() 
    *     { 
    *         return
      $this->_identity;     
    *     } 
    *      
    *     /**
    *      * 设置电视机编号
    *      */
    *     public
      function setIdentity($id) 
    *     { 
    *         $this->_identity = (int)$id; 
    *         return
      $this; 
    *     } 
    *      
    *     public
      function __clone() 
    *     { 
    *         $this->setIdentity(0);  
    *     } 
    * } 
    *  
    *  
    * /**
    * * 遥控器类
    * */
    * class Telecontrol  
    * { 
    *  
    * } 


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

PHP代码

    * $tv1 = new Television(); 
    * $tv2 = clone $tv1; 
    *  
    * $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
    * $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
    * echo
      $tv1;    //tv1的object id 为 #1
    * echo
      '<br>'; 
    * echo
      $contr1; //contr1的object id 为#2
    * echo
      '<br>';  
    * echo
      $tv2;    //tv2的object id 为 #3
    * echo
      '<br>'; 
    * 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()魔术方法:

[size=+0][size=+0]PHP代码

    * [size=+0][size=+0]public
      function __clone() 
    * { 
    *     $this->setIdentity(0); 
    *     //重新设置一个遥控器对象
    *     $this->setControl(new Telecontrol()); 
    * } 


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

但是这样的方式大概并不算太好,如果被复制对象中有多个到其它对象的引用,我们必须在__clone方法中逐个的重新设置,更糟糕的是如果被复制对象的类由第三方提供,我们无法修改代码,那复制操作基本就无法顺利完成了。
我们使用clone来复制对象,这种复制叫做“浅复制”:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。也就是说,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。相对于“浅复制”,当然也有一个“深复制”:被复制的对象的所有的变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。也就是说,深复制把要复制的对象所引用的对象都复制了一遍。深复制需要决定深入到多少层,这是一个不容易确定的问题,此外可能会出现循环引用的问题,这些都必须小心处理。我们的方案2将是一个深复制的解决方案。

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

    * [size=+0][size=+0]$tv1 = new Television(); 
    * $tv2 = unserialize(serialize($tv1));//序列化然后反序列化
    *

    * $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1
    * $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2
    *

    * echo
      $tv1;    //tv1的object id 为 #1
    * echo
      '<br>'; 
    * echo
      $contr1; //contr1的object id 为#2
    * echo
      '<br>';  
    * echo
      $tv2;    //tv2的object id 为 #4
    * echo
      '<br>'; 
    * echo
      $contr2; //contr2的object id 为#5


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



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

相关推荐

    深入PHP面向对象、模式与实践 (第2版)》 part2

    首先介绍了php的oo特性,包括类声明、对象实例化、继承、方法与属性封装以及静态方法与属性、抽象类、接口、异常处理、对象克隆等高级主题。然后介绍了设计模式,阐述了模式的概念,展示了如何在php中实现几个关键的...

    深入PHP面向对象、模式与实践 (第2版) part1

    首先介绍了php的oo特性,包括类声明、对象实例化、继承、方法与属性封装以及静态方法与属性、抽象类、接口、异常处理、对象克隆等高级主题。然后介绍了设计模式,阐述了模式的概念,展示了如何在php中实现几个关键的...

    (第25讲) PHP面向对象开发的学习(九)

    在本讲中,我们将深入探讨PHP面向对象编程的四个关键概念:对象描述的配置、对象方法的异常处理、克隆对象的应用以及自动载入对象的方法。这些知识点是PHP开发中的重要组成部分,对于提升代码的组织结构和可维护性至...

    PHP对象相关知识总结

    本篇文章将深入探讨PHP对象的几个核心概念,包括对象的创建、传递、复制以及`__clone()`方法的使用。 首先,我们来看对象的传递。在PHP中,对象是通过引用传递的,但这里的"引用"并不完全等同于其他编程语言中的...

    PHP网络编程技术详解 完整版 pdf + 源码

    《PHP网络编程技术详解》是一本深入探讨PHP在网络编程中的应用的专业书籍,配合源码学习,可以帮助读者全面掌握PHP在网站开发中的各项技能。本文将根据书名和描述,结合PHP的相关知识点,展开详细的讨论。 PHP...

    PHPWeb开发技术指南——pdf格式

    9.16 复制变量内容:复制构造函数 256 9.17 返回值 257 9.18 打印信息 258 9.18.1 phpinfo()中包含输出 259 9.18.2 执行信息 260 9.19 启动和关闭函数 261 9.20 调用用户函数 261 9.21 下一步该做些什么 265 9.22 ...

    Foundation PHP5 for Flash(PDF)

    - **面向对象编程**:深入讲解PHP5的面向对象特性,如类、对象、继承、多态等概念。 2. **Flash简介**: - **历史背景**:概述Flash的发展历程和技术特点。 - **工作原理**:解释Flash的工作机制及其在Web开发中...

    Zend PHP 5 Certification Study Guide Oct 2006

    综上所述,《Zend PHP 5 Certification Study Guide Oct 2006》是一本非常全面且实用的学习指南,不仅覆盖了 PHP 5 的基础知识和技术要点,还涉及了 PHP 与其他技术(如 C/C++、Java、Ajax)的集成方法,以及在特定...

    php 考证教材 (php certification 2006)

    - **覆盖全面**:涵盖了PHP编程的核心概念和技术要点,有助于读者构建扎实的PHP基础。 - **与时俱进**:尽管是2006年的版本,但仍包含了许多至今仍广泛使用的PHP特性和最佳实践。 #### 六、总结 《PHP|Architect’s...

    php面试中关于面向对象的相关问题

    对象克隆指的是复制对象的过程,PHP中对象是通过引用传递的,使用clone关键字可以创建一个新对象,该对象是原始对象的一个精确副本。 关于this、self和parent的区别,this关键字用于引用当前对象的实例,通常在类的...

    PHP and MYSQL Web Development.5th.Edition chart29

    本章节继续深入探讨如何结合Laravel框架与PHP中的IMAP功能来创建一个简单的基于Web的邮件客户端。在上一章中,我们介绍了Laravel框架的基础概念和组件,并在此基础上进一步讨论了这些概念的实际应用。 #### 主要...

    韩顺平PHP(全149讲)课堂笔记

    1. **PHP性能优化**:讨论PHP性能瓶颈及优化策略,如缓存技术、代码优化等。 2. **PHP框架**:如Laravel、Symfony等,讲解其架构原理和常用功能,快速构建Web应用。 3. **PHP安全**:涵盖XSS、SQL注入等常见Web安全...

    PHP程序开发范例宝典附带光盘第7-12章

    在本资源中,“PHP程序开发范例宝典附带光盘第7-12章”是一本关于PHP编程的实践指南,旨在通过丰富的实例帮助读者深入理解和掌握PHP编程技术。这本书的第7到12章涵盖了PHP开发中的关键概念和实践技巧,包括但不限于...

    PHP开发典型模块大全 代码 part1

    6. **面向对象编程**:PHP 5及以上版本支持面向对象编程,这部分可能包含类和对象的概念,属性和方法的定义,继承、封装和多态性等OOP原则。 7. **模板引擎与视图**:PHP常用于MVC架构,这部分可能介绍了如何使用...

    Tomcat PHP支持的jar包

    标题 "Tomcat PHP支持的jar包" 暗示了我们正在讨论如何在Apache Tomcat服务器上集成PHP环境,使得Tomcat能够处理PHP脚本。这通常涉及到将PHP与Java进行桥接,以便在Java应用服务器上运行PHP代码。下面我们将深入探讨...

    PHP自学教程PDF

    9. **面向对象编程**:深入解析类、对象、属性、方法、继承、封装和多态等面向对象的概念。 10. **模板引擎和框架**:简述PHP模板引擎如Smarty的工作原理,以及一些流行PHP框架(如Laravel、Symfony、CodeIgniter)...

    php中文帮助文档-mysql中文帮助文档-php-mysql-DBHlper

    文档中还包括了对索引、分区、复制和备份策略的讨论,以提高数据库的性能和可用性。对于想要深入理解数据库管理和优化的开发者来说,这是一份非常重要的参考资料。 【PHP-MYSQL-DBHlper】 在PHP与MySQL的交互中,...

Global site tag (gtag.js) - Google Analytics