2011-10-08 57 views
4

我在PHP中有一個我想克隆的面向對象的父子樹。 困難的部分是,進入樹並不總是通過根,但有時通過根的孩子,像這樣:在PHP中克隆父子樹,從子開始,避免無窮遞歸

[Root] 
    -- [Element1] START CLONE 
     -- [Element3] 
     -- [Element4] 
    -- [Element2] 
     -- [Element5] 

所以我想要做的是克隆整個樹,通過調用$new = clone $element1;

__clone()方法指出,每個孩子也必須被克隆,並且,如果所示的情況發生*,父母也必須克隆。

* Root在Element1中顯式設置爲父級,因此係統可以識別這種情況並對其進行操作。

問題是,從Element1開始clone操作,Root也必須被克隆。 Root的克隆過程規定必須克隆所有子元素,因此Element1的clone操作再次被調用,然後重複相同的克隆過程,產生無限循環。

此外,根不包含Element1的第一個克隆,但它將生成自己的克隆以作爲子項添加。然後Element1將具有Root作爲其父項,但Root將不具有與小孩相同的Element1。

我希望我以清晰的方式提出問題,並且有人可以幫助我找到解決方案。

編輯:

最終的解決方案:

/** 
* The $replace and $with arguments allow a custom cloning procedure. Instead of 
* being cloned, the original child $replace will be replaced by $with. 
*/ 
public function duplicate($replace = null, $with = null) { 
    // Basic cloning 
    $clone = clone $this; 
    // If parent is set 
    if(isset($this->parent)) { 
     // Clone parent, replace this element by its clone 
     $parentClone = $this->parent->duplicate($this, $clone); 
     $clone->parent = $parentClone; 
    } 

    // Remove all children in the clone 
    $clone->clear(); 

    // Add cloned children from original to clone 
    foreach($this->getChildren() as $child) { 
     if($child === $replace) 
      // If cloning was initiated from this child, replace with given clone 
      $childClone = $with; 
     else 
      // Else duplicate child normally 
      $childClone = $child->duplicate(); 

     // Add cloned child to this clone 
     $clone->add($childClone); 
    } 

    return $clone; 
} 
+1

你使用什麼數據結構?有父母子女屬性的對象?陣列? – deceze

+0

我正在使用數組來列出對象的所有子項。在描述的情況下,Element1也有一個「父」屬性。 – RemiX

回答

1

首先,簡化您的示例:

[Root] 
    -- [Element1] START CLONE 
     -- [Element3] 

然後你做什麼之間的區別,我覺得你有操作

  • 公共克隆方法。
  • 一個自我克隆操作,它返回帶有子級但不是父級的克隆。
  • 返回一個無w/o子級副本的單克隆操作。

您的類之外的代碼正在使用公共克隆方法。但__clone()方法必須不使用該方法,否則會遇到您描述的循環循環問題。所以__clone()實施必須使用其他方法。

cloneSelfcloneSingle方法添加到您的類,使它們受到保護,因此繼承類可以調用它們,但它們不公開。

然後利用他們在__clone()實現:

public function clone() 
{ 
    // clone the parent 
    $parent = $this->getParent(); 
    $parentClone = $parent->cloneSingle(); 

    // clone all children of parent which includes $this 
    $selfClone = NULL; 
    foreach($parent->getChildren() as $child) 
    { 
     $childClone = $child->cloneSelf(); 
     if (!$selfClone && $child === $this) 
      $selfClone = $childClone; 
     $parentClone->addChild($childClone); 

    } 

    assert('$selfClone'); 

    return $selfClone; 
} 

public function __clone() 
{ 
    $message = 'Clone operator is not allowed, use clone() method instead.'; 
    throw new BadMethodCallException($message); 
} 

這些方法也可以幫助你克隆的情況下,沒有父。

+0

感謝您的建議。我認爲推理很好,但根據[PHP手冊](http://php.net/manual/en/language.oop5.cloning.php),從對象_clone_調用了_ _ _ _ clone()方法此後,標準淺層克隆已完成。你從'clone $ object'操作獲得的對象不會是這個方法的$ selfClone。 也正因爲如此,我們無法使用'==='比較父對象的每個子對象與當前對象,因爲克隆不在原始父對象的子對象之間。 – RemiX

+0

@RemiX:是的,我把它混合了。但是,您可以通過提供自己的公共克隆方法來反轉邏輯,我編輯了答案。 – hakre

+0

好吧,除了你不能調用clone()方法的事實,你的答案現在非常有用。我創建了一個自定義的'duplicate()'方法,它首先調用'clone'運算符。然後它克隆它的父代並用它的克隆代替它自己。當父對象有另一個父對象時,這允許更多的遞歸,並且我不需要重新創建克隆(我仍然可以使用'clone'操作符)。 我編輯了最後的代碼到我的問題。謝謝你的幫助。除非有人想出讓這個解決方案看起來像廢話的東西,否則你會得到賞金。 – RemiX

1

如果添加了一個參數什麼的TOT方法__clone()? - 讓我們把它$called_from

基於你做什麼參數的值:

  • 當帕拉姆的缺省值是「外部」,還是有那麼克隆是從一個叫價值child外的地方,所以你會調用__clone()與父母,送「孩子」爲價值
  • __clone()最終被稱爲根節點與「兒童」或「外部」值設置爲$called_from,它通過調用開始真正的克隆過程__clone()$called_from設置爲「父」

編輯

我不知道內建clone關鍵字。所以,你可以創建你的所有的樹對象都繼承一個基類 - 這個類可以有一個static,變量將表明,當設置爲true什麼克隆具有像

  • ,真正clone算法被執行,否則,將__clone()父對象
  • 的默認值是false,只有根節點設置爲true,它開始克隆孩子

這個基類也可以覆蓋__clone之前()方法來實現這個算法ithm在一個地方。

+0

聽起來像一個體面的解決方案,但我不知道如何通過「克隆」操作傳遞參數。這甚至有可能嗎? – RemiX

+0

好了,再來一個障礙:如果我像這樣克隆父項:$ this-> parent = clone $ this-> parent(在__clone()方法中),它的父項將開始克隆所有(仍然是原始的)子項其中$這是一個),但它將是另一個實例,而不是$ this本身。從孩子到父母,從父母回到孩子會讓你到同一個孩子的另一個克隆..對此有什麼建議?或者這是不明確的? – RemiX