PHP5 オブジェクト コピー テクノロジについての詳細な説明
オブジェクトのコピーの起源
なぜオブジェクトに「コピー」という概念があるのか? これは、PHP5 のオブジェクトの値の受け渡し方法と密接に関係しています。
PHP コードを見てみましょう。
* /**
* * テレビ
**/
* クラス テレビ
* {
* /**
* * * 画面の高さ
**/
* protected
$_screenLength = 300 ;
*
* /**
* * * 画面幅
**/
* protected
$_screenHight = 200;
* /**
* * * テレビ出演カラー
**/
* protected
$_color = 'black';
*
* /**
* * * テレビ出演カラーに戻る
**/
* public
function getColor()
* { * * return
$this-> ;_color; {
*$ This-& gt; 🎜> *
$ this を返します。
*}
*}
*
*$ TV1 = New Television ();
* $tv2 = $tv1;
このコードは TV クラス Television を定義し、$tv1 は TV のインスタンスです。通常の変数代入方法 $tv1の値を$t2に代入します。これで、$tv1 と $tv2 という 2 つのテレビができました。これは本当に事実でしょうか?試してみましょう。
PHP コード
* echo
'tv1 の色は: ' . $tv1->getColor();//tv1 の色は黒です
* echo
'
';
* echo
'tv2 の色は次のとおりです: ' . $tv2->getColor();//tv2 の色は黒です
* echo
'< ;br>';
*
* //tv2 を白にペイントします
* $tv2->setColor('white'); ' . ;getColor();//tv2 の色は白です
* echo
'
';
* echo
'tv1 の色は ' $tv1->getColor ();//tv1 の色は白です
まず、tv1 と tv2 の色が両方とも黒であることがわかります。次に、tv2 の色を変更したいので、その色を白に設定します。実際に、一致しているように見えます。要件は満たしていましたが、tv1 の色を確認すると、期待したほどスムーズではありませんでした。 tv1 の色をリセットしなかったのはなぜですか?なぜなら、PHP5におけるオブジェクトの代入や値の受け渡しはすべて「参照」によって行われるからです。 PHP5 は Zend Engine II を使用しており、オブジェクトは他の一般変数のように Zval に保存されるのではなく、別の構造のオブジェクト ストアに保存されます (PHP4 では、オブジェクトは一般変数と同様に Zval に保存されます)。コンテンツ (値) ではなく、オブジェクトへのポインターのみが Zval に格納されます。オブジェクトをコピーする場合、またはオブジェクトをパラメーターとして関数に渡す場合、データをコピーする必要はありません。同じオブジェクト ポインターを保持し、この特定のオブジェクトが別の zval を介してポイントしていることをオブジェクト ストアに通知するだけです。オブジェクト自体はオブジェクト ストアに配置されているため、オブジェクトに加えた変更は、そのオブジェクトへのポインタを保持するすべての zval 構造体に影響します。これは、ターゲット オブジェクトに対する変更がソース オブジェクトに影響するため、プログラムに明示されています。これにより、PHP オブジェクトが常に参照によって渡されるように見えます。したがって、上記の tv2 と tv1 は実際には同じ TV インスタンスを指しており、tv1 または tv2 で行う操作は実際にはこの同じインスタンスに対するものです。したがって、「コピー」は失敗しました。このため、PHP5 ではオブジェクトのコピーに特化した操作 (clone) が提供されています。ここでオブジェクトのコピーが登場します。
オブジェクトをコピーするにはクローンを使用します
オブジェクトをコピーするには PHP5 のクローン言語構造を使用します。コードは次のとおりです:
[size=+0]PHP コード
* [size=+0]$tv1 = new Television();
* $tv2 = クローン $tv1;
*
* echo
'tv1 の色: ' $tv1->getColor();//tv1 の色は黒です
* echo
'
';
* echo
'tv2 の色は次のとおりです: $tv2 ->getColor();//tv2 の色は黒です
* echo
'
'
*
* //tv2 を白に変更します
* $tv2->setColor('white');
*
* echo
'tv2 の色: ' $tv2->getColor();//tv2 の色tv1 の色は白です
* echo
'
';
* echo
'tv1 の色は次のとおりです: ' . 黒
このコードでは、clone キーワードを使用して tv1 をコピーします。これで、tv1 と tv2 の実際のコピーが得られます。コピーが成功したかどうかを検出するために、引き続き前の方法に従います。 tv2 の色を白に変更しましたが、tv1 の色はまだ黒であることがわかり、コピー操作は成功しました。
__clone マジックメソッド
ここで、この状況を考えます。各テレビには独自の番号が必要です。この番号は ID カード番号のようなものです。一意である必要があります。私たちはテレビをコピーしますが、問題を引き起こすことを避けるために、この番号もコピーされたくないのです。私たちが考え出した戦略の 1 つは、割り当てられたテレビ番号をクリアし、必要に応じて番号を再割り当てすることです。
このような問題を解決するために、__clone マジック メソッドが特に使用されます。__clone マジック メソッドは、オブジェクトがコピーされる (つまり、クローン操作) ときにトリガーされます。 TV クラス Television のコードを変更し、number 属性と __clone メソッドを追加しました。コードは次のとおりです。
PHP コード
* /**
* * テレビ
**/
* クラス テレビ
* {
* *
* /**
* * * テレビ番号
**/
* protected
$_identity = 0;
* * /**
* * * 画面の高さ
* **/
* protected
$_screenLength = 300; *
* /**
* * * 画面幅
**/
* protected
$_screenHight = 200;
*
* /**
* * * テレビ出演カラー
**/
* protected
$_color = '黒';
*
* /**
* * * テレビ出演カラーに戻る
**/
* public
function getColor()
* {
* return
$this-& gt;_カラー;
* }
*
* /**
* * * テレビの表示色を設定します
**/
* public
function setColor($color)
* {
* $this->_color = (文字列)$カラー;
* return
$this;
* }
*
* /**
* * * TV 番号を返します
**/
* public
function getIdentity()
* {
* return
$this-> ;_身元;
* }
*
* /**
* * * テレビ番号を設定します
**/
* public
function setIdentity($id)
* {
* $this->_identity = (int)$id;
* return
$this;
* }
*
* public
function __clone()
* {
* $this->setIdentity(0);
* }
* }
次はこのような電子テレビのオブジェクトを作成します。
PHP コード
* $tv1 = 新しいテレビ();
* $tv1->setIdentity('111111');
* echo
'tv1 の ID は ' です。 $tv1->getIdentity();//111111
* echo
'
';
*
* $tv2 = $tv1 のクローン;
* echo
'tv2 の ID は ' です。 $tv2->getIdentity();//0
テレビ セット tv1 を作成し、その番号を 111111 に設定しました。次に、clone を使用して tv1 をコピーし、tv2 を取得します。この時点で、__clone マジック メソッドがトリガーされ、コピーされたオブジェクト tv2 に直接作用します。メソッドでは、setIdentity メンバー メソッドを呼び出して tv2 の _identity 属性をクリアし、後で番号を付け直すことができるようにします。このことから、__clone マジック メソッドを使用すると、オブジェクトのクローンを作成するときにいくつかの追加操作を非常に便利に実行できることがわかります。
クローン操作の致命的な欠陥
クローンは本当に理想的なコピー効果を達成できるのでしょうか?場合によっては、クローン操作が想像したほど完璧ではないことがわかるはずです。上記の TV タイプを変更してテストしてみましょう。
各テレビにはリモコンが付属するので、リモコンのクラスを開きます。リモコンとテレビは「集合」関係にあります (「組み合わせ」関係と比較すると、より弱い依存関係です)。一般に、テレビはリモコンがなくても通常どおり使用できます)、これで TV オブジェクトはすべてリモコン オブジェクトへの参照を保持する必要があります。以下のコードを見てみましょう
PHP コード
* /**
* * テレビ
**/
* class Television
* {
* *
* /**
* * * テレビ番号
** /
* protected
$_identity = 0;
*
* /**
* * * 画面の高さ
* **/
* protected
$_screenLength = 300;
* /**
* * * 画面幅
**/
* protected
$_screenHight = 200;
* /**
* * * テレビ出演カラー
**/
* protected
$_color = ' 黒';
*🎜> *public * return
$this;
* * }
*
* /**
* * * リモコンオブジェクト
**/
* public
function getControl()
* {
* return
$this->_control;
* }
*
* /**
* * * テレビ出演カラーに戻る
**/
* public
function getColor()
* {
* return
$this-> ;_色;
* }
*
* /**
* * * テレビの表示色を設定します
**/
* public
function setColor($color)
* {
* $this->_color = (文字列)$カラー;
* return
$this;
* }
*
* /**
* * * TV 番号を返します
**/
* public
function getIdentity()
* {
* return
$this-> ;_身元;
* }
*
* /**
* * * テレビ番号を設定します
**/
* public
function setIdentity($id)
* {
* $this->_identity = (int)$id;
* return
$this;
* }
*
* public
function __clone()
* {
* $this->setIdentity(0);
* }
* }
*
*
* /**
* * リモコンクラス
**/
* クラス Telecontrol
* {
*
* }
以下では、このようなテレビのオブジェクトを作成し、テレビのリモコン オブジェクトを表示します。
* $tv2 = $tv1 のクローンを作成します。
*
* $contr1 = $tv1->getControl(); //获取tv1の遥コントローラーcontr1
* $contr2 = $tv2->getControl(); //获取tv2の遥コントローラーcontr2
* echo
$tv1; //tv1 のオブジェクト ID は #1
* echo
'
';
* echo
$contr1; //contr1 のオブジェクト ID は #2
* echo
'
';
* echo
$tv2; //tv2 のオブジェクト ID は #3
* echo
'
';
* echo
$contr2; // contr2 のオブジェクト ID は #2
コピー後、オブジェクト ID を確認して tv1 から複製します。 tv2、tv1、tv2 のオブジェクト ID がそれぞれコピーされます。これは、tv1 と tv2 が 2 つの異なる TV オブジェクトを参照することを意味し、クローン操作の結果と一致します。次に、tv1 のリモコン オブジェクト contr1 と tv2 のリモコン オブジェクト contr2 をそれぞれ取得しました。それらのオブジェクト ID を確認すると、contr1 と contr2 のオブジェクト ID は両方とも 2 であり、これらが同じものへの参照であることがわかります。オブジェクト、つまり、tv1 から tv2 をコピーしたが、リモコンはコピーされていないというのは明らかに不合理です。各テレビにはリモコンが装備されているはずであり、ここで tv2 と tv1 はリモコンを共有しています。
クローン操作には非常に大きな欠陥があることがわかります。クローン操作を使用してオブジェクトをコピーする場合、コピーされたオブジェクトに他のオブジェクトへの参照がある場合、参照されたオブジェクトはコピーされません。ただし、この状況は現在、「継承の再利用」に代わる「合成/集約の再利用」が主に提唱されており、「合成」と「集約」は、あるオブジェクトに別のオブジェクトへの参照を持たせることで、それを再利用します。参照されるオブジェクトのメソッド。クローンを使用する場合は、この状況を考慮する必要があります。では、オブジェクトを複製するときにこのような欠陥をどのように解決すればよいでしょうか?おそらく、前述の __clone マジック メソッドをすぐに思いついたかもしれません。これは確かに解決策です。
オプション 1: __clone マジック メソッドを使用してそれを補う
__clone マジック メソッドの使用法をすでに紹介しましたが、コピーされたオブジェクト内の他のオブジェクトの参照を 1 つのオブジェクトに再参照できます。 __clone メソッド内。変更された __clone() マジック メソッドを見てみましょう:
[size=+0][size=+0]PHP コード
* [size=+0][size= + 0]public
function __clone()
* {
* $this->setIdentity(0);
* //リモート コントロール オブジェクトをリセットします
* $this-> ;setControl (new Telecontrol());
* }
04 行目で、コピーされた TV オブジェクトのリモコンをリセットします。前のメソッド ID に従ってオブジェクトを表示します。 2 台のテレビのリモコンのオブジェクト ID は異なるため、問題は解決されました。
しかし、この方法はおそらくあまり良くありません。コピーされたオブジェクトに他のオブジェクトへの参照が複数ある場合、さらに問題となるのは、コピーされたオブジェクトがオブジェクトのクラスである場合です。はサードパーティから提供されており、コードを変更することはできないため、基本的にコピー操作はスムーズに完了しません。
オブジェクトのコピーにはクローンを使用します。この種のコピーは「浅いコピー」と呼ばれます。コピーされたオブジェクトのすべての変数には元のオブジェクトと同じ値が含まれており、他のオブジェクトへの参照はすべて元のオブジェクトを指し続けます。 。つまり、浅いコピーは、参照しているオブジェクトではなく、問題のオブジェクトのみをコピーします。 「浅いコピー」と比較すると、もちろん「深いコピー」もあります。コピーされたオブジェクトのすべての変数には、他のオブジェクトを参照する変数を除き、元のオブジェクトと同じ値が含まれます。つまり、ディープ コピーでは、コピー対象のオブジェクトが参照するすべてのオブジェクトがコピーされます。ディープ コピーでは、どの程度の深さまでコピーするかを決定する必要がありますが、これを決定するのは容易ではありません。さらに、循環参照の問題が発生する可能性があるため、慎重に処理する必要があります。オプション 2 はディープ コピー ソリューションです。
オプション 2: ディープ コピーにシリアル化を使用する
PHP にはシリアル化 (シリアル化) 関数と逆シリアル化 (アンシリアル化) 関数があり、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
を取得します *
$tv1; /tv1 のオブジェクト ID は #1
* echo
'
';
* echo
$contr1; //contr1 のオブジェクト ID は #2
* echo
'
'
* エコー
$tv2; //tv2 のオブジェクト ID は #4
* echo
'
';
* echo
$contr2; //contr2 のオブジェクト ID は #5
出力結果がわかります。tv1 と tv2 は異なるリモコンを持っています。これは、オプション 1 よりもはるかに便利です。シリアル化は再帰的なプロセスです。オブジェクト内で参照されるオブジェクトの数や、オブジェクトを完全にコピーできるレイヤーの数を気にする必要はありません。このソリューションを使用する場合、__clone マジック メソッドをトリガーして追加の操作を完了することはできないことに注意してください。 もちろん、ディープ コピー後に再度クローン操作を実行して __clone マジック メソッドをトリガーすることはできますが、効率には多少の影響があります。さらに、この解決策はコピーされたオブジェクトとすべての参照オブジェクトの __sleep および __wakeup マジック メソッドをトリガーするため、これらの状況を考慮する必要があります。
まとめ
オブジェクトのコピー方法が異なれば、効果も異なります。どの方法を使用するか、特定のアプリケーション要件に基づいてコピー方法を改善する方法を検討する必要があります。 PHP5 のオブジェクト指向機能は比較的 JAVA に近いため、JAVA から多くの貴重な経験を学ぶことができると思います。