I'm playing with some design patterns, and wanted to create an example using the SPL's observer pattern. Because it doesn't make sense to have observers and subjects be completely generic, I wanted to extend the interfaces to make them more specific to the application at hand. The problem is that when I run the code below, I get errors like "DataAccess::update() must be compatible with that of SplObserver::update()".

I know that I can make this code execute without errors by switching the method signatures to match those of the interfaces. My question is this: Why doesn't it allow children of the classes defined in the signatures? Below, ModelObserver is a SplObserver, and Model is a SplSubject. I would have expected this to work. Am I missing something?

FYI, I know I could use the explicit method signatures as defined in the interface and use the instanceof keyword in my code logic to achieve the same thing. I was just hoping to find a more elegant solution. Thanks!

<?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;
有帮助吗?

解决方案

Limiting DataAccess::update() to accept your child Model breaks the contract of this interface.

True, all Model objects are of class SplSubject , but not all SplSubject are of class Model. An interface is a contract guaranteeing that an implementing class it supports everything the interface supports.

Your code, if it worked would be limiting the DataAccess::update() method to only the Model sub class and not the wider parent class SplSubjects . You cannot narrow the scope of the parameter passed to method defined by an interface.

Let's say you added a property public $foo to the Model class. If it were allowed, you could in your DataAccess::update() method you uses that property $foo. Someone could come along and extend SplSubjects to a child OddModel which didn't have a $foo property. They could no longer pass the OddModel into your DataAccess::update() function--if they could it would break as no $foo property exists for the OddModel.

This is the whole idea behind interfaces, by implementing they you agree 100% to support what is defined by the interface. In this case your interface says:

if you implement me, you must accept every SplSubject or class that extends SplSubject

You're implementation of the interface attempts to break the contract.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top