如何实现扩展接口?

时间:2012-11-09 17:40:55

标签: php spl

我正在玩一些设计模式,并希望使用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;

1 个答案:

答案 0 :(得分:3)

限制DataAccess::update()接受您的孩子Model违反此界面的合同。

是的,所有Model个对象都属于SplSubject个类,但并非所有SplSubject属于类Model。接口是一种合同,保证实现类支持接口支持的所有内容。

您的代码如果有效,则会将DataAccess::update()方法仅限制为Model子类,而不是更广泛的父类SplSubjects。您不能缩小传递给接口定义的方法的参数范围。

假设您在Model类中添加了属性public $foo。如果允许,您可以在DataAccess::update()方法中使用该属性$foo。有人可以将SplSubjects扩展到没有OddModel属性的子$foo。他们无法再将OddModel传递到您的DataAccess::update()函数中 - 如果它们可能会因为$foo没有OddModel属性而中断。

这是接口背后的整个想法,通过实现它们,您同意100%支持接口定义的内容。在这种情况下,您的界面会说:

  

如果您实施我,则必须接受SplSubject扩展SplSubject

的每个{{1}}或类

您实施的界面试图破坏合同。