【问题标题】:How can I implement extended interfaces?如何实现扩展接口?
【发布时间】:2012-11-09 17:40:54
【问题描述】:

我正在使用一些设计模式,并想使用 SPL 的观察者模式创建一个示例。因为让观察者和主题完全通用是没有意义的,所以我想扩展接口以使它们更具体地用于手头的应用程序。问题是当我运行下面的代码时,我得到了“DataAccess::update() must be compatible with the 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;

【问题讨论】:

    标签: php spl


    【解决方案1】:

    限制DataAccess::update() 接受您的孩子Model 违反了此接口的约定。

    没错,所有Model 对象都属于SplSubject 类,但并非所有SplSubject 都属于Model 类。接口是保证实现类支持接口支持的所有内容的契约。

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

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

    这是接口背后的全部理念,通过实现它们,您同意 100% 支持接口定义的内容。在这种情况下,您的界面会显示:

    如果你实现我,你必须接受每个SplSubject 或扩展SplSubject 的类

    你正在执行的接口试图破坏合约。

    【讨论】:

    • 感谢您的反馈。在这种情况下,可能希望缩小要传递的参数的范围似乎是有道理的,因为并非所有 SplObserver 对象都必须适合处理模型。如果我不能通过继承来缩小接口的范围,我能想到的下一个最好的事情是在方法本身内部测试 SplObserver 和 SplSubject 的实例,如果它不是兼容的观察者或主题,则抛出异常。你会同意,还是我错过了什么?
    • ... 而不是重新定义方法,您可以简单地在DataAccess 中添加一个检查。类似:if (!$subject instanceOf Model) throw new DataAccessException('Not a valid model subject'); 或类似的东西。
    • @JoshuaKaiser 在你实现的方法中你可以做任何你想做的事情,但整个想法是完全支持接口。您应该质疑为什么要实现的接口不能完全满足接口定义所需的所有功能。接口是一个承诺。如果你不能信守承诺,就不要使用接口。所有 SplObserver 怎么不能处理一个模型?模型是 SplSubject 类型——他们可能处理不好,在这种情况下,肯定会抛出异常
    • @Ray,你说得很好。 SPL 观察者模式故意非常通用,但它也非常非常简单。我认为最适合我的目的的就是定义我自己的接口,将特定应用程序限制在我实际上支持这个用例的主题和观察者的范围内。我不喜欢半途而废并依靠异常来处理它的想法。想法?
    • @JoshuaKaiser 你比任何人都更清楚你的应用程序需要什么,但是在从头开始构建你自己的观察者模式之前,你需要长时间地思考核心观察者/主题角色中需要什么功能。所有的模式都是等到一个通知。您可以创建一个不执行任何操作的观察者,除非它观察到来自特定子类的通知(但如果不需要任何操作,则不会抛出异常)。然后通过附加到多个观察者将观察者堆叠到主题。任何观察者都不需要根据合同做任何事情..如果一棵树倒在树林里......
    猜你喜欢
    • 1970-01-01
    • 2013-07-17
    • 1970-01-01
    • 2018-08-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多