2012-11-09 157 views
2

我正在玩一些設計模式,並希望使用SPL的觀察者模式創建一個示例。因爲讓觀察者和主題完全通用是沒有意義的,所以我想擴展接口以使它們更適合於手頭的應用程序。問題是,當我運行下面的代碼時,出現像「DataAccess :: update()必須與SplObserver :: update()」兼容的錯誤。如何實現擴展接口?

我知道我可以通過切換方法簽名來匹配那些接口來執行這段代碼而不會出錯。我的問題是這樣的:爲什麼它不允許簽名中定義的類的孩子?下面,ModelObserver是一個SplObserver,Model是一個SplSubject。我會期待這個工作。我錯過了什麼嗎?

僅供參考,我知道我可以使用接口中定義的顯式方法簽名,並在我的代碼邏輯中使用instanceof關鍵字來實現相同的目的。我只是希望找到一個更優雅的解決方案。謝謝!

<?php 
interface ModelObserver extends SplObserver { 
} 

class DataAccess implements ModelObserver { 

    /* 
    * (non-PHPdoc) @see SplObserver::update() 
    */ 
    public function update(Model $subject) { 
     // TODO Auto-generated method stub 
    } 
} 

// Just a generic model for the example 
class Model implements SplSubject { 
    private $_properties = array(); 
    private $_observers = array(); 

    /* 
    * generically handle properties you wouldn't want to do it quite like this 
    * for a real world scenario 
    */ 
    public function __get($name) { 
     return $this->_properties [$name]; 
    } 
    public function __set($name, $value) { 
     $this->_properties [$name] = $value; 
    } 
    public function __call($method, $args) { 
     if (strpos ($method, 'get') === 0) { 
      $name = lcfirst (str_replace ('get', '', $method)); 
      return $this->_properties [$name]; 
     } 

     if (strpos ($method, 'set') === 0) { 
      $name = lcfirst (str_replace ('set', '', $method)); 
      $this->_properties [$name] = $args [0]; 
      return $this; 
     } 
    } 
    public function __toString() { 
     return print_r ($this, true); 
    } 

    /* 
    * (non-PHPdoc) @see SplSubject::attach() 
    */ 
    public function attach(ModelObserver $observer) { 
     $this->_observers [] = $observer; 
     return $this; 
    } 

    /* 
    * (non-PHPdoc) @see SplSubject::detach() 
    */ 
    public function detach(ModelObserver $observer) { 
     if (in_array ($observer, $this->_observers)) { 
      $f = function ($value) { 
       if ($value != $observer) { 
        return $value; 
       } 
      }; 
      $observers = array_map ($f, $this->_observers); 
     } 
     return $this; 
    } 

    /* 
    * (non-PHPdoc) @see SplSubject::notify() 
    */ 
    public function notify() { 
     foreach ($this->_observers as $observer) { 
      $observer->update($this); 
     } 
    } 
} 

$da = new DataAccess(); 

$model = new Model(); 
$model->setName ('Joshua Kaiser')->setAge (32)->setOccupation ('Software Engineer') 
    ->attach($da); 

echo $model; 

回答

2

限制DataAccess::update()接受你的孩子Model打破了這個接口的合同。

確實,所有Model對象屬於SplSubject類,但並非全部SplSubject屬於Model類。接口是一種保證實現類支持接口支持的所有類的合約。

您的代碼,如果它的工作將限制DataAccess::update()方法只有Model子類,而不是更廣泛的父類SplSubjects。您無法縮小傳遞給由接口定義的方法的參數範圍。

假設您將一個屬性public $foo添加到Model類。如果允許,你可以在你的DataAccess::update()方法中使用該屬性$foo。有人可能會來SplSubjects延伸到一個孩子OddModel沒有$foo財產。他們不能再通過OddModel進入你的DataAccess::update()函數 - 如果可以的話,它會破壞,因爲OddModel沒有$foo屬性。

這是接口背後的整體思想,通過實現它們,您同意100%支持接口定義的內容。在這種情況下,你的界面說:

如果實現了我,你必須接受每一個SplSubject或類,它擴展SplSubject

你實現接口企圖撕毀合同的。

+0

感謝您的反饋意見。在這種情況下,似乎有意義的是,可能需要縮小要傳遞參數的範圍,因爲並非所有SplObserver對象都必須適用於處理模型。如果我不能通過繼承來縮小接口的範圍,我能想到的下一個最好的事情是在方法本身內部測試SplObserver和SplSubject的實例,並在它不是兼容的觀察者或主題時拋出異常。你會同意嗎,還是我錯過了什麼? –

+0

...而不是重新定義方法,您可以簡單地在'DataAccess'中添加一個檢查。例如:'if(!$ subject instanceOf Model)拋出新的DataAccessException('不是有效的模型主題');'或類似的東西。 –

+0

@JoshuaKaiser在你實現的方法裏面你可以做任何你想做的事情,但是整個想法是完全支持這個接口。你應該問爲什麼你要實現一個接口,它不能完全滿足接口定義所要求的所有事情。界面是一個承諾。如果你不能遵守承諾,請不要使用界面。如何不能所有的SplObserver處理模型?模型的類型是SplSubject - 它們可能無法很好地處理,在這種情況下,肯定會拋出異常 – Ray