Why is polymorphism in PHP not like other languages? Is there a bug in PHP? [message #185026] |
Mon, 24 February 2014 19:49 |
kurtk(at)pobox(dot)com
Messages: 10 Registered: May 2012
Karma:
|
Junior Member |
|
|
Why can I (quite surprisingly) call a member function that only exists in a derived class through a reference to a base class
I stumbled upon this unexpected behaviour when implementing the Observer pattern in PHP.
The observer is class Quackologist
class Quackologist implements \SplObserver {
public function update(\SplSubject $subject)
{
echo ". $subject->whoami() . " just quacked.\n\n";
}
}
who observes various types of ducks that implement both \SplSubject (using the Observable trait below) and another interface, Quackable.
interface Quackable {
function quack();
}
trait Observable {
private $observers = array();
public function attach ( \SplObserver $observer )
{
$this->observers[spl_object_hash($observer)] = $observer;
}
public function detach( \SplObserver $observer )
{
unset($this->observers[spl_object_hash($observer)]);
}
public function notify()
{
foreach($this->observers as $observer) {
$observer->update($this);
}
}
}
class RedheadDuck implements Quackable, \SplSubject {
use Observable;
public function __construct() {}
public function quack()
{
echo "Quack";
$this->notify();
}
public function whoami() { return "Redhead Duck"; }
}
class RubberDuck implements Quackable, \SplSubject {
use Observable;
public function __construct() {}
public function quack()
{
echo "Squeak";
$this->notify();
}
public function whoami() { return "Rubber Duck"; }
}
The main logic is:
$array_of_duck = array();
$array_of_ducks[] = new RedheadDuck();
$array_of_ducks[] = new RubberDuck();
$quackologist = new Quackologist();
foreach ($array_of_ducks as $duck) {
$duck->attach($quackologist);
}
foreach ($array_of_ducks as $duck) {
$duck->quack();
}
The output is
Quack. Redhead Duck just quacked.
Squeak. Rubber Duck just quacked.
Calling $duck->quack() calls $this->notify(), which calls $observer->update($this). $this is a derived duck class; however, the update method of Quackologist takes \SplSubject, which does not contain the whoamI() method. So I expected a runtime error message similar to what you get with this
simple example.
class Base {}
class Derived extends Base {
public function whoami() { echo "I am Derived\n"; }
}
function test(Base $b)
{
$b->whoami();
}
$b = new Base();
$d = new Derived();
test($b);
test($d);
This expectedly results in the error message **Call to undefined method Base::whoami() in /home/kurt/public_html/spl/observer/test.php**.
If you try to implement similar Observer Pattern code in C++, you will get the expected compile error on the call to whoami() in Quackologist::update(Subject&)
class Subject;
class Observer { // abstract base
public:
virtual void update(Subject&) = 0;
};
class ObservableBase : public Subject { // Adds the virtual method whoami() not in Subject
public:
virtual string whoami() = 0;
};
// mixin: Reusable observer code
class Observable : public ObservableBase {
map<Observer *, Observer *> assoc_array;
public:
void registerObserver(Observer& obs);
void unregisterObserver(Observer& obs);
void notifyObservers();
};
void Observable::notifyObservers()
{
for ( auto current : assoc_array) {
(current.second)->update(*this);
}
}
class Subject { // abstract base
public:
virtual void notifyObservers() = 0;
virtual void registerObserver(Observer& obs) = 0;
virtual void unregisterObserver(Observer& obs) = 0;
};
class Quackologist : public Observer {
public:
void update(Subject& subject)
{
// This line below will not compile, which is expected.
cout << subject.whoami() << "\n" << endl;
}
};
Quackologist::update(Subject&) gives the expected compile error **error: no member named 'whoami' in 'Subject'**.
Why does in the simple Base/Derived PHP example give the expected runtime error but not the first example?
|
|
|