循環参照という言葉はExcelで見たことがある人もいるかと思いますが、プログラミングの現場においても、起こることはあります。
普通に循環参照って検索すると、大体Excelの話が出てきますが、循環参照自体は下記のサイトの説明がわかりやすいと思います。
https://gimo.jp/glossary/details/circular_definition.html
今回は、弊社のプログラマーさんと話していて、このような構成になっていたところ
「なんでいけないんですか?」
と言われてパッと答えられなかったので書いておきます。
例えば、ここに構成が複雑なオブジェクト、Itemクラスがあります。
Itemオブジェクトはあまりに複雑なので、ユーザーもItemからコピーして作りたいという要望があったとします。
なので、ItemMasterというクラスが爆誕します。で、ItemMasterはItemを参照して、そのItemのコピーをバンバン作っていきます。
ちなみに、できたItemはユーザーが個別にプロパティをちょこちょこ編集できる仕様です。
クラスとプロパティを抜粋して書くと、次のようになります。
<?php
class ItemMaster{
public $id;
public $item_id;
}
class Item{
public $id;
public $item_master_id;
}
文章で書くと、
「10番のアイテムができがよいので、コピペして作りたい。そこで、10番をマスターとして、アイテムマスター(Id:1)を作る。アイテムマスターから、11番、12番、13番のアイテムを作る。」
ということです。
ここまでは問題がなさそうです。
実際の問題①
アイテムを更新することを考えてみましょう。
「10番のアイテムのプロパティにちょっと変更を加えた、アイテムマスター (Id:1) から作られた アイテムたちに都度変更を加えるのは大変だから、マスターが更新されたらマスターから作られたアイテムはマスターの変更を反映して変更する。つまり11番、12番、13番のアイテムに10番の変更が反映される。」
そのために、次のようにアップデートメソッドを実装します。
class ItemMaster{
public $id;
public $item_id;
public function updateItem(){
//このマスターから作られたItemを更新する
}
//以下続く
}
class Item{
public $id;
public $item_master_id;
public function updateItemMaster(){
//このアイテムから作られたItemMasterを更新する
}
//以下続く
}
ここで、もしItemのプロパティ $item_master_id と ItemMasterのプロパティ $item_id がお互い同じものを見ることになったとします。
つまり
Item $id=10, $item_master_id=1
ItemMaster $id=1, $item_id=10
文章にすると
「10番のアイテムは、アイテムマスター (Id:1) から作られた。」
「アイテムマスター (Id:1) は10番のアイテムから作られた。」
が両立します。すでに頭の中がぐちゃぐちゃになっちゃいますよね。とりあえず、次のような状態です。
「10番のアイテムができがよいので、コピペして作る。そこで、10番をマスターとして、アイテムマスター(Id:1)を作る。アイテムマスターから、11番、12番、13番のアイテムを作る。後日、誤操作で10番もアイテムマスター (Id:1)から作られたことになった。」
実際の問題①としては、では10番のアイテムを更新したとき、10番をもとに作られたアイテムマスター(Id:1)が更新され、そのアイテムマスターから作られたアイテム10番、11番、12番、13番のアイテムが更新されることになります
10番はそこで更新されているので、またアイテムマスター (Id:1) を更新します。 そのアイテムマスターから作られたアイテム10番、11番、12番、13番のアイテムが更新されます。10番が更新されたので…以下無限に続く。
という無限ループになってしまいます。
実際の問題②
実際の問題②として、ではアイテムマスターから生まれたアイテムをまたアイテムマスターにした時はどうでしょうか?実際の問題①を忘れて頂いて、初期状態になったとします。
「13番のアイテムもできがいいので、アイテムマスターにしよう。」
となった場合
「13番のアイテムをもとに、アイテムマスター(Id:2)を作る。アイテムマスター (Id:2) から、14番、15番のアイテムを作る。13番のアイテムは、アイテムマスター (Id:1)から作られており、 アイテムマスター (Id:1) はアイテム10番から作られている。」
こうなった場合、アイテム10番に更新をした場合、
「 アイテムマスター(Id:1)が更新される。 アイテムマスター(Id:1) から作られた11番、12番、13番が10番と同じ内容に更新される。13番が更新されたので、 アイテムマスター (Id:2) を更新する。 アイテムマスター (Id:2) から、14番、15番のアイテムを更新する。内容は、14番、15番は10番と同じ内容のアイテムになる。 」
ということになります。
この話は、絶対に循環参照がダメという話ではないです。やむを得ずこういう設計になってしまうこともあるでしょう(´ω`)。その場合、こういうリスクがありますよ。ということなので、上記の例でいえば
・絶対にアイテムマスターとアイテムは同じオブジェクトを参照しない
・アイテムマスターから作られたアイテムを、アイテムマスターにしない
などを気を付ければうまく使えるかもしれません。