ホームページ  >  記事  >  php教程  >  php5 オブジェクトのコピー、クローン、浅いコピー、および深いコピー

php5 オブジェクトのコピー、クローン、浅いコピー、および深いコピー

大家讲道理
大家讲道理オリジナル
2016-11-08 14:36:311309ブラウズ

オブジェクトのコピーの起源
オブジェクトに「コピー」という概念があるのはなぜですか? これは、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;


このコードは TV クラス Television を定義します。$tv1 は TV のインスタンスです。その後、通常の変数代入方法に従って $tv1 の値を $t2 に代入します。これで、$tv1 と $tv2 という 2 つのテレビができました。これは本当に事実でしょうか?試してみましょう。

PHP コード


* echo 
      'color of tv1 is: ' . $tv1->getColor();//tv1的颜色是black 
    * echo 
      &#39;<br>&#39;;  
    * echo 
      &#39;color of tv2 is: &#39; . $tv2->getColor();//tv2的颜色是black 
    * echo 
      &#39;<br>&#39;;  
    *   
    * //把tv2涂成白色 
    * $tv2->setColor(&#39;white&#39;);  
    *   
    * echo 
      &#39;color of tv2 is: &#39; . $tv2->getColor();//tv2的颜色是white 
    * echo 
      &#39;<br>&#39;;  
    * echo 
      &#39;color of tv1 is: &#39; . $tv1->getColor();//tv1的颜色是white


まず、tv1 と tv2 の色が両方とも黒であることがわかります。そこで、tv2 の色を白に設定します。要件を満たしているように見えましたが、tv1 の色を確認すると、tv1 も黒から黒に変化していることがわかりました。 白。 tv1 の色をリセットしなかったのはなぜですか?なぜなら、PHP5におけるオブジェクトの代入や値の受け渡しはすべて「参照」によって行われるからです。 PHP5 は Zend Engine II を使用し、オブジェクトは独立した構造に格納されます。 他の一般変数のように Zval に保存されるのではなく、保存します (PHP4 では、オブジェクトは一般変数と同様に Zval に保存されます)。 Zval にはオブジェクトのポインタのみを格納し、コンテンツは格納しません (価値)。オブジェクトをコピーする場合、またはオブジェクトをパラメーターとして関数に渡す場合、データをコピーする必要はありません。同じオブジェクト ポインターを保持し、この特定のオブジェクトが別の zval によって現在ポイントされていることをオブジェクトに通知するだけです。 店。オブジェクト自体は Object に配置されているため、 ストア、それに加えた変更は、オブジェクトへのポインターを保持するすべての zval 構造に影響します。プログラムに反映され、ターゲット オブジェクトに対する変更はソース オブジェクトに影響します。 .これにより PHP オブジェクトは常に参照によって渡されるようです。したがって、上記の tv2 と tv1 は実際には同じ TV インスタンスを指しており、tv1 または tv2 で行う操作は実際にはこの同じインスタンスに対するものです。したがって、「コピー」は失敗しました。このため、直接変数を代入する方法ではオブジェクトをコピーできないようです。 PHP5 には、オブジェクトのコピーに特化した操作 (クローン) が用意されています。ここでオブジェクトのコピーが登場します。


クローンを使用してオブジェクトをコピーします
PHP5 のクローン言語構造を使用してオブジェクトをコピーします。コードは次のとおりです:
[size=+0]PHP コード

    * [size=+0]$tv1 = new Television();  
    * $tv2 = clone $tv1;  
    * 
    * echo 
      &#39;color of tv1 is: &#39; . $tv1->getColor();//tv1的颜色是black 
    * echo 
      &#39;<br>&#39;;  
    * echo 
      &#39;color of tv2 is: &#39; . $tv2->getColor();//tv2的颜色是black 
    * echo 
      &#39;<br>&#39;;  
    * 
    * //把tv2换成涂成白色 
    * $tv2->setColor(&#39;white&#39;);  
    * 
    * echo 
      &#39;color of tv2 is: &#39; . $tv2->getColor();//tv2的颜色是white 
    * echo 
      &#39;<br>&#39;;  
    * echo 
      &#39;color of tv1 is: &#39; . $tv1->getColor();//tv1的颜色是black


このコードの 2 行目では、 clone キーワードを使用して tv1 をコピーします。これで、tv1 と tv2 の実際のコピーが作成されました。引き続き、コピーが成功したかどうかを確認します。 tv2 の色を白に変更しましたが、tv1 の色はまだ黒であることがわかり、コピー操作は成功しました。



__クローンマジックメソッド

ここで、各テレビに独自の番号が必要であるという状況を考えます。この番号は、ID 番号と同様に一意である必要があるため、テレビをコピーするときに、トラブルを避けるために番号もコピーされることは望ましくありません。私たちが考え出した戦略の 1 つは、割り当てられている TV 番号をクリアし、必要に応じて番号を再割り当てすることです。
このような問題を解決するために、__clone マジック メソッドが特に使用されます。オブジェクトがコピーされる (つまり、クローン操作) ときに、__clone マジック メソッドがトリガーされます。 TV クラス Television のコードを変更し、number 属性と __clone メソッドを追加しました。コードは次のとおりです。
PHP コード

    * /** 
    * * 电视机类 
    * */ 
    * class Television   
    * {  
    *       
    *     /** 
    *      * 电视机编号 
    *      */ 
    *     protected 
      $_identity    = 0;  
    *       
    *     /** 
    *      * 屏幕高度 
    *      */ 
    *     protected 
      $_screenLength = 300;  
    *       
    *     /** 
    *      * 屏幕宽度 
    *      */ 
    *     protected 
      $_screenHight  = 200;  
    *       
    *     /** 
    *      * 电视机外观颜色 
    *      */ 
    *     protected 
      $_color        = &#39;black&#39;;  
    *       
    *     /** 
    *      * 返回电视外观颜色 
    *      */ 
    *     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);   
    *     }  
    * }


このような TV オブジェクトをコピーしてみましょう。

PHP コード

    * $tv1 = new Television();  
    * $tv1->setIdentity(&#39;111111&#39;);  
    * echo 
      &#39;id of tv1 is &#39; . $tv1->getIdentity();//111111  
    * echo 
      &#39;<br>&#39;;  
    *   
    * $tv2 = clone $tv1;  
    * echo 
      &#39;id of tv2 is &#39; . $tv2->getIdentity();//0


私たちはテレビ tv1 を制作しました、 そしてその番号を 111111 に設定し、clone を使用して tv1 を tv2 にコピーします。このとき、このメソッドはコピーされたオブジェクト tv2 に直接作用します。このメソッドは、後で番号を付け直すことができるように、tv2 の _identity 属性をクリアします。このことから、__clone マジック メソッドを使用すると、オブジェクトのクローンを作成するときにいくつかの追加操作を非常に便利に実行できることがわかります。

クローン操作の致命的な欠陥
クローンは本当に理想的なコピー効果を達成できるのでしょうか?場合によっては、クローン操作が想像したほど完璧ではないことがわかるはずです。上記の TV タイプを変更してテストしてみましょう。
すべてのテレビにはリモコンが付属しているので、リモコン教室を開催します。 リモコンとテレビは「集合」関係です (「組み合わせ」関係に比べて依存関係は弱いです。なぜなら、一般にテレビはリモコンがなくても通常どおり使用できます)。これで、TV オブジェクトはすべてリモコン オブジェクトへの参照を保持するはずです。コード
PHP コード

    * /** 
    * * 电视机类 
    * */ 
    * class Television   
    * {  
    *       
    *     /** 
    *      * 电视机编号 
    *      */ 
    *     protected 
      $_identity    = 0;  
    *       
    *     /** 
    *      * 屏幕高度 
    *      */ 
    *     protected 
      $_screenLength = 300;  
    *       
    *     /** 
    *      * 屏幕宽度 
    *      */ 
    *     protected 
      $_screenHight  = 200;  
    *       
    *     /** 
    *      * 电视机外观颜色 
    *      */ 
    *     protected 
      $_color        = &#39;black&#39;;  
    *       
    *     /** 
    *      * 遥控器对象 
    *      */ 
    *     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   
    * {  
    *   
    * }


を見てみましょう。以下のような TV オブジェクトをコピーし、TV のリモコン オブジェクトを観察してください。

PHPコード

    * $tv1 = new Television();  
    * $tv2 = clone $tv1;  
    *   
    * $contr1 = $tv1->getControl(); //获取tv1的遥控器contr1 
    * $contr2 = $tv2->getControl(); //获取tv2的遥控器contr2 
    * echo 
      $tv1;    //tv1的object id 为 #1 
    * echo 
      &#39;<br>&#39;;  
    * echo 
      $contr1; //contr1的object id 为#2 
    * echo 
      &#39;<br>&#39;;   
    * echo 
      $tv2;    //tv2的object id 为 #3 
    * echo 
      &#39;<br>&#39;;  
    * 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 
      &#39;<br>&#39;;  
    * echo 
      $contr1; //contr1的object id 为#2 
    * echo 
      &#39;<br>&#39;;   
    * echo 
      $tv2;    //tv2的object id 为 #4 
    * echo 
      &#39;<br>&#39;;  
    * echo 
      $contr2; //contr2的object id 为#5

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



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

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。