オブジェクトコピーの起源
オブジェクトに「コピー」という概念があるのはなぜですか? これは、PHP5 のオブジェクトの値転送メソッドと密接に関係しています。次の簡単なコードを見てみましょう phpコード * /** * * テレビ **/ *クラステレビ * { * /** * * 画面の高さ **/ * 保護されています $_screenLength = 300; * * /** * * 画面幅 **/ * 保護されています $_screenHight = 200; * * /** * * TV出演カラー **/ * 保護されています $_color = '黒'; * * /** * * テレビ出演カラーに戻る **/ *公開 関数getColor() * { *戻る $this->_color * } * * /** * * テレビの表示色を設定します **/ *公開 関数setColor($color) * { * $this->_color = (文字列)$color *戻る $これ * } * } * * $tv1 = 新しいテレビ (); * $tv2 = $tv1; このコードはテレビクラス Television を定義しており、$tv1 はテレビのインスタンスであり、通常の変数代入方法に従って $tv1 の値を $t2 に代入します。これで、$tv1 と $tv2 という 2 つのテレビができました。これは本当に事実でしょうか?試してみましょう。 PHPコード *エコー 'tv1 の色は: ' . $tv1->getColor();//tv1 の色は黒です *エコー 「 」 *エコー 'tv2 の色は: ' . $tv2->getColor();//tv2 の色は黒です *エコー 「」 * * //tv2 を白にペイントします * $tv2->setColor('white'); * *エコー 'tv2 の色は: ' . $tv2->getColor();//tv2 の色は白です *エコー 「 」 *エコー 'tv1 の色は: ' . $tv1->getColor();//tv1 の色は白です まず、tv1 と tv2 の色が両方とも黒であることがわかります。今度は tv2 の色を変更したいので、その色を白に設定します。実際に白になっているように見えます。しかし、tv1 の色を確認すると、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 = 新しいテレビ(); * $tv2 = $tv1 のクローン ; * *エコー 'tv1 の色は: ' . $tv1->getColor();//tv1 の色は黒です *エコー 「」 *エコー 'tv2 の色は: ' . $tv2->getColor();//tv2 の色は黒です *エコー 「 」 * * //tv2 を交換して白にペイントします * $tv2->setColor('white'); * *エコー 'tv2 の色は: ' . $tv2->getColor();//tv2 の色は白です *エコー 「」 *エコー 'tv1 の色は: ' . $tv1->getColor();//tv1 の色は黒です このコードの 2 行目では、clone キーワードを使用して tv1 をコピーします。これで、tv1、tv2 の実際のコピーが得られます。コピーが成功したかどうかを確認するために、引き続き前の方法に従います。 tv2 の色を白に変更しましたが、tv1 の色はまだ黒であることがわかり、コピー操作は成功しました。 __クローンマジックメソッド ここで、各テレビに独自の番号が必要であるという状況を考えます。この番号は、ID 番号と同様に一意である必要があるため、テレビをコピーするときに、問題が発生するのを避けるためにこの番号もコピーされることは望ましくありません。私たちが考え出した戦略の 1 つは、割り当てられている TV 番号をクリアし、必要に応じて番号を再割り当てすることです。 このような問題を解決するために、__clone マジック メソッドが特に使用されます。オブジェクトがコピーされる (つまり、クローン操作) ときに、__clone マジック メソッドがトリガーされます。 TV クラス Television のコードを変更し、number 属性と __clone メソッドを追加しました。コードは次のとおりです。 PHP代 * /** * * テレビ **/ *クラステレビ * { * * /** * * テレビ番号 **/ * 保護されています $_identity = 0; * * /** * * 画面の高さ **/ * 保護されています $_screenLength = 300; * * /** * * 画面幅 **/ * 保護されています $_screenHight = 200; * * /** * * TV出演カラー **/ * 保護されています $_color = '黒'; * * /** * * テレビ出演カラーに戻ります **/ *公開 関数getColor() * { *戻る $this->_color; * } * * /** * * テレビの表示色を設定します **/ *公開 関数setColor($color) * { * $this->_color = (文字列)$color; *戻る $これ; * } * * /** * * テレビ番号を返します **/ *公開 関数getIdentity() * { *戻る $this->_identity; * } * * /** * * テレビ番号を設定します **/ *公開 関数 setIdentity($id) * { * $this->_identity = (int)$id; *戻る $これ; * } * *公開 関数__clone() * { * $this->setIdentity(0); * } * } 次に、このような電子ビデオのオブジェクトを作成します。 PHP代 * $tv1 = 新しいテレビ(); * $tv1->setIdentity('111111'); *エコー 'tv1 の ID は ' です。 $tv1->getIdentity();//111111 *エコー 「」; * * $tv2 = $tv1 のクローン; *エコー 'tv2 の ID は ' です。 $tv2->getIdentity();//0 私は一台のテレビテレビ tv1 を作成し、その番号を 111111 に設定しました。その後、クローンを使用して tv1 を作成し、tv2 を取得しました。このとき、__clone 魔方法が起動され、この方法は、取得したオブジェクト tv2 に直接作用します。私は、__clone メソッドで setIdentity メソッドを使用して、tv2 の _identity プロパティをクリアし、後で新しい番号を実行できるようにしました。いくつかの追加の操作が行われました。 クローン操作の危険な缺陷 クローンの真の能力は理想的な複製効果に達していますか? 場合によっては、この問題が発生し、クローン操作が私が考えているような完璧なものではありません。 每台電影机都市附带一個のリモートコントローラー、所以我们将会有一個のリモートコントローラー類、リモートコントローラーと電子ビデオは一種の「聚合」関係関係です一般に、リモコンのない電子機器でも通常使用できるため、私の電子機器オブジェクトには、リモコン オブジェクトへの参照が含まれています。以下のコードを見てください PHPコード * /** * * テレビ **/ *クラステレビ * { * * /** * * テレビ番号 **/ * 保護されています $_identity = 0; * * /** * * 画面の高さ **/ * 保護されています $_screenLength = 300; * * /** * * 画面幅 **/ * 保護されています $_screenHight = 200; * * /** * * TV出演カラー **/ * 保護されています $_color = '黒'; * * /** * * リモコンオブジェクト **/ * 保護されています $_control = null; * * /** * * コンストラクターにリモコンオブジェクトをロードします **/ *公開 関数__construct() * { * $this->setControl(new Telecontrol()); * } * * /** * * リモコンオブジェクトを設定します **/ *公開 関数 setControl(Telecontrol $control) * { * $this->_control = $control *戻る $これ * } * * /** * * リモコンオブジェクトを返します **/ *公開 関数getControl() * { *戻る $this->_control * } * * /** * * テレビ出演カラーに戻ります **/ *公開 関数getColor() * { *戻る $this->_color * } * * /** * * テレビの表示色を設定します **/ *公開 関数setColor($color) * { * $this->_color = (文字列)$color *戻る $これ * } * * /** * * テレビ番号を返します **/ *公開 関数getIdentity() * { *戻る $this->_identity * } * * /** * * テレビ番号を設定します **/ *公開 関数 setIdentity($id) * { * $this->_identity = (int)$id *戻る $これ * } * *公開 関数__clone() * { * $this->setIdentity(0); * } * } * * * /** * * リモコンカテゴリ **/ * クラステレコントロール * { * * } 以下のような TV オブジェクトをコピーし、TV のリモコン オブジェクトを観察してください。 PHPコード * $tv1 = 新しいテレビ (); * $tv2 = $tv1 のクローン ; * * $contr1 = $tv1->getControl() //tv1 のリモコン contr1 を取得します * $contr2 = $tv2->getControl() //tv2 のリモコン contr2 を取得します *エコー $tv1; //tv1 のオブジェクト ID は #1 です *エコー 「」 *エコー $contr1; // contr1 のオブジェクト ID は #2 です *エコー 「 」 *エコー $tv2; //tv2 のオブジェクト ID は #3 です *エコー 「」 *エコー $contr2; // contr2 のオブジェクト ID は #2 です コピー後、オブジェクト ID を確認し、クローン操作を通じて tv1 から tv2 をコピーします。tv1 と tv2 のオブジェクト ID はそれぞれ 1 と 3 であり、これは tv1 と tv2 が 2 つの異なる TV オブジェクトを参照していることを意味します。 . 操作の結果。次に、tv1 のリモコン オブジェクト contr1 と tv2 のリモコン オブジェクト contr2 をそれぞれ取得しました。それらのオブジェクト ID を確認すると、contr1 と contr2 のオブジェクト ID は両方とも 2 であり、同じものへの参照であることがわかります。オブジェクト、つまり、tv1 から tv2 をコピーしたが、リモコンはコピーされていないというのは明らかに不合理です。各テレビにはリモコンが装備されているはずであり、ここで tv2 と tv1 はリモコンを共有しています。 クローン操作には非常に大きな欠陥があることがわかります。クローン操作を使用してオブジェクトをコピーする場合、コピーされたオブジェクトに他のオブジェクトへの参照がある場合、参照されたオブジェクトはコピーされません。ただし、この状況は現在、「継承の再利用」に代わる「合成/集約の再利用」が主に提唱されており、「合成」と「集約」は、あるオブジェクトに別のオブジェクトへの参照を持たせることで、それを再利用します。参照されるオブジェクトのメソッド。クローンを使用する場合は、この状況を考慮する必要があります。では、オブジェクトを複製するときにこのような欠陥をどのように解決すればよいでしょうか?おそらく、前述の __clone マジック メソッドをすぐに思いついたかもしれません。これは確かに解決策です。 オプション 1: __clone マジック メソッドを使用して補います __clone マジック メソッドの使用についてはすでに紹介しました。コピーされたオブジェクト内の他のオブジェクトの参照を、__clone メソッド内の新しいオブジェクトに再参照できます。変更された __clone() マジック メソッドを見てみましょう: [size=+0][size=+0]PHP コード * [size=+0][size=+0]公開 関数__clone() * { * $this->setIdentity(0); * //リモコンオブジェクトをリセットします * $this->setControl(new Telecontrol()); * } 04 行目では、コピーした TV オブジェクトのリモコンをリセットします。前の方法に従ってオブジェクト ID を確認すると、2 台の TV のリモコンのオブジェクト ID が異なることがわかり、問題は解決されました。 しかし、このメソッドはおそらくあまり良くありません。コピーされたオブジェクトに他のオブジェクトへの参照が複数ある場合、__clone メソッドでそれらを 1 つずつリセットする必要があります。サードパーティではコードを変更できないため、基本的にコピー操作はスムーズに完了しません。 オブジェクトのコピーにはクローンを使用します。この種のコピーは「浅いコピー」と呼ばれます。コピーされたオブジェクトのすべての変数には元のオブジェクトと同じ値が含まれており、他のオブジェクトへの参照はすべて元のオブジェクトを指し続けます。つまり、浅いコピーは、参照しているオブジェクトではなく、問題のオブジェクトのみをコピーします。 「浅いコピー」と比較すると、もちろん「深いコピー」もあります。コピーされたオブジェクトのすべての変数には、他のオブジェクトを参照する変数を除き、元のオブジェクトと同じ値が含まれます。つまり、ディープ コピーでは、コピー対象のオブジェクトが参照するすべてのオブジェクトがコピーされます。ディープ コピーでは、何層まで進むかを決定する必要がありますが、これは簡単に決定できない問題であり、さらに、循環参照の問題が発生する可能性があるため、慎重に処理する必要があります。オプション 2 はディープ コピー ソリューションです。 オプション 2: ディープ コピーにシリアル化を使用する PHP にはシリアライズ関数とアンシリアライズ関数があります。serialize() を使用してオブジェクトをストリームに書き込み、ストリームからオブジェクトを読み取るだけで、オブジェクトがコピーされます。 Java 言語では、このプロセスは「冷却」および「解凍」と呼ばれます。以下でこのメソッドをテストします: [size=+0][size=+0]PHP コード * [size=+0][size=+0]$tv1 = 新しいテレビ (); * $tv2 = unserialize(serialize($tv1));//シリアル化してから逆シリアル化する * * $contr1 = $tv1->getControl() //tv1 のリモコン contr1 を取得します * $contr2 = $tv2->getControl() //tv2 のリモコン contr2 を取得します * *エコー $tv1; //tv1 のオブジェクト ID は #1 です *エコー 「」 *エコー $contr1; // contr1 のオブジェクト ID は #2 です *エコー 「 」 *エコー $tv2; //tv2 のオブジェクト ID は #4 です *エコー 「」 *エコー $contr2; // contr2 のオブジェクト ID は #5 です 出力を見ると、tv1とtv2には異なるリモコンが付いています。これは、オプション 1 よりもはるかに便利です。シリアル化は再帰的なプロセスです。オブジェクト内で参照されるオブジェクトの数や、オブジェクトを完全にコピーできるレイヤーの数を気にする必要はありません。このソリューションを使用する場合、__clone マジック メソッドをトリガーして追加の操作を完了することはできないことに注意してください。 もちろん、ディープ コピー後に再度クローン操作を実行して __clone マジック メソッドをトリガーすることはできますが、効率には多少の影響があります。さらに、この解決策はコピーされたオブジェクトとすべての参照オブジェクトの __sleep および __wakeup マジック メソッドをトリガーするため、これらの状況を考慮する必要があります。 概要 オブジェクトのコピー方法が異なれば効果も異なります。どの方法を使用するか、特定のアプリケーション要件に基づいてコピー方法を改善する方法を検討する必要があります。 PHP5 のオブジェクト指向機能は比較的 JAVA に近いため、JAVA から多くの貴重な経験を学ぶことができると思います。 クローン |