Reputation: 28891
I'm curious if there is a "better" design for the following behavior:
<?php
class Foo {
public function foo() {
// Foo-specific foo stuff.
}
}
class Bar extends Foo {
public function foo() {
// Bar-specific foo stuff.
parent::foo();
}
}
class Baz extends Bar {
public function foo() {
// Baz-specific foo stuff.
parent::foo();
}
}
$boz = new Foo();
$boz->foo(); // should do the stuff in Foo::foo()
$biz = new Bar();
$biz->foo(); // should do the stuff in Bar::foo() and Foo::foo()
$buz = new Baz();
$buz->foo(); // should do the stuff in Baz::foo(), Bar::foo(), and Foo::foo()
// etc...
Essentially, I have a base class, Foo
, with a method Foo::foo()
that contains some common code that should always be run. I also have various subclasses which inherit from Foo
and each have their own specific code that should also always be run.
The design I've used here uses the DRY principle to ensure that the code from Foo::foo()
isn't repeated in Bar::foo()
and Baz::foo()
, and the code in Bar::foo()
isn't repeated in Baz::foo()
, and so on.
The problem(?) with this design is that I'm relying on the subclasses to always explicitly call parent::foo()
in every case, and classes which extend those classes to do the same, and so on ad infinitum. However, there is no way (that I know of) to actually enforce this.
So my question is - is there a better design that accomplishes the same behavior, or some way to enforce this "contract" between parent/child classes?
Update
Some people have asked for a use-case. I have run into this paradigm in several projects over the years, but can't give a real world example due to NDAs and such, so here's a super basic example that might help illustrate the issue better:
<?php
// Vehicle
class Vehicle {
public function start() {
// Vehicle engines are on when you start them.
// Unless they belong to me, that is :-(
$this->setEngineStatus(Vehicle::ENGINE_ON);
}
}
// Vehicle > Automobile
class Automobile extends Vehicle {
public function start() {
// Automobile engines are on when you start them.
parent::start();
// Automobiles idle when you start them.
$this->setEngineRpm(Automobile::RPM_IDLE);
}
}
// Vehicle > Airplane
class Airplane extends Vehicle {
public function start() {
// Airplane engines are on when you start them.
parent::start();
// Airplanes also have radios that need to be turned on when started.
$this->setRadioStatus(Airplane::RADIO_ON);
}
}
// Vehicle > Automobile > Car
class Car extends Automobile {
public function start() {
// Cars engines are on and idle when you start them.
parent::start();
// Cars also have dashboard lights that turn on when started.
$this->setDashLightsStatus(Car::DASH_LIGHTS_ON);
}
}
// Vehicle > Airplane > Jet
class Jet extends Airplane {
public function start() {
// Jet engines and radios are on when you start them.
parent::start();
// Jets also arm their weapons when started.
$this->setWeaponsHot(true);
}
}
// Vehicle > Automobile > BobsSuperAwesomeCustomTruck
class BobsSuperAwesomeCustomTruck extends Automobile {
public function start() {
// Uh-oh... Bob didn't call parent::start() in his class, so his trucks
// don't work, with no errors or exceptions to help him figure out why.
// Bob's trucks also need to reset their pinball machine highscores when started.
$this->resetPinballScores();
}
}
Upvotes: 3
Views: 2936
Reputation: 36
I found a better general way avoiding closures and other ugly tricks.
class A {
/**************************************************************/
// Chain caller helpers, defined in base class only
// (single point of maintenance)
protected $_chain_params;
final public function chain_call($method_name, $params){
$class = get_class($this); // get last child classname
$chain = array($class);
while ($class !== 'A'){ // get all parents classname
$class = get_parent_class($class);
$chain[] = $class;
}
// Call reversed chain
$this->_chain_params = $params;
for ($k = count($chain) - 1; $k >= 0; $k--){
$class = $chain[$k];
$refl = new \ReflectionMethod($class, $method_name);
if ($refl->class === $class)
$ret = call_user_func_array(array($this,
$class.'::'.$method_name),
$this->_chain_params);
}
return $ret;
}
final protected function chain_modify_params($params){
$this->_chain_params = $params;
}
/*************************************************************/
// Methods overrided by child classes:
public function foo($a, $b){
echo "A foo fired with params a=$a b=$b <br>";
}
protected function bar($a, &$b){
echo "A bar fired with params a=$a b=$b <br>";
return 1000;
}
}
// Child classes extending base class. NOTE: no need to smell the code!
class B extends A {
public function foo($a, $b){
echo "B foo fired with params a=$a b=$b <br>";
}
protected function bar($a, &$b){
echo "B bar fired with params a=$a b=$b <br>";
return 2000;
}
}
class C extends B {
public function foo($a, $b){
echo "C foo fired with params a=$a b=$b <br>";
}
protected function bar($a, &$b){
echo "C bar fired with params a=$a b=$b <br>";
$a++; // override param value
$b++; // override referenced param value
echo " - C modify => a=$a b=$b <br>";
// reflect changed parameters to the next child class in chain ;)
$this->chain_modify_params(array($a, &$b));
return 3000;
}
}
class D extends C {
public function foo($a, $b){
echo "D foo fired with params a=$a b=$b <br>";
}
protected function bar($a, &$b){
echo "D bar fired with params a=$a b=$b <br>";
return 4000;
}
}
$d = new D();
echo 'Call "foo" directly... <br>';
$d->foo(10, 20);
echo '<br> Call "foo" in chain mode... <br>';
$d->chain_call('foo', array(10, 20));
echo '<br> More complex example: call "bar" in chain mode,'.
'passing $k by reference, '.
'and getting last method result... <br><br>';
$k = 40;
$ret = $d->chain_call('bar', array(30, &$k));
echo "<br> D->bar() return: " . $ret;
echo "<br>k = $k";
Result:
Call "foo" directly...
D foo fired with params a=10 b=20
Call "foo" in chain mode...
A foo fired with params a=10 b=20
B foo fired with params a=10 b=20
C foo fired with params a=10 b=20
D foo fired with params a=10 b=20
More complex example: call "bar" in chain mode,
passing $k by reference, and getting last method result...
A bar fired with params a=30 b=40
B bar fired with params a=30 b=40
C bar fired with params a=30 b=40
- C modify => a=31 b=41
D bar fired with params a=31 b=41
D->bar() return: 4000
k = 41
Upvotes: 2
Reputation: 5301
As long as you're overwriting your methods in subclasses, there is no way in any language I know of to enforce the behavior of the parent's method. If you're writing code just for your app, you should be able to trust your own code to call parent::foo(). But if you're writing a library, framework or API that others will build on, there is value to your idea. Ruby on Rails makes good use of that kind of behavior using callbacks.
Okay, so don't define any foo methods. Instead, use __call and an array of closures as callbacks. My PHP is really rusty, so I forget some of the specifics.
class Foo {
// I forget how to make a class variable in PHP, but this should be one.
// You could define as many callback chains as you like.
$callbacks = array('foo_callback_chain' => []);
// This should be a class function. Again, forget how.
function add_callback($name, $callback) {
$callbacks[$name.'_callback_chain'][] = $callback;
}
// Add your first callback
add_callback('foo', function() {
// do foo stuff
})
def method__call($method, $args) {
// Actually, you might want to call them in reverse order, as that would be more similar
foreach ( $callbacks[$method_name.'_callback_chain'] as $method ) {
$method();
}
}
}
Then in your child classes, just append more callbacks with ""add_callback". This isn't appropriate for everything, but it works very well in some cases. (More about closures at http://php.net/manual/en/functions.anonymous.php.)
Upvotes: 3
Reputation: 99515
There's no way in PHP to enforce this specifically;
But, if Foo::foo must always execute before any subclass::foo, and you don't care about the results; perhaps the actual contents of the methods are badly designed.
If you always must initialize something, perhaps you can do it in the constructor, if you're logging every call, perhaps you need a decorator.
Here's another option that may work:
class Foo {
function doFoo() {
// the code that 'must always run' goes here
...
...
...
// and now we're calling the 'overridden' method.
foo();
}
protected function foo() {
// move along, nothing to see here
}
}
class Bar extends Foo {
protected function foo() {
// Bar-specific foo stuff.
}
}
class Baz extends Foo {
protected function foo() {
// Baz-specific foo stuff.
}
}
The flaw here is that there's no 'multiple inheritance' or chaining.
But yea, maybe you actually need some kind of pub-sub pattern.. or who knows?
You're asking how you can implement your solution to a design problem, you should specifically ask how to solve your design problem.
Upvotes: 1
Reputation: 5488
I dont think this is better, but this is one possible way.
class abstract Foo {
public function foo() {
// Foo-specific foo stuff.
$this->_foo();
}
// Might be abstract, might be an empty implementation
protected abstract function _foo();
}
class Bar extends Foo {
protected function _foo() {
// Bar-specific foo stuff.
}
}
Personally, I prefer the way you have it because I think it is more readable. It also means the child does not have to have its own implementation of foo()
. Seems more OOP. However, if you require each child class to have its own added implementation of foo()
this may do the trick for you.
Upvotes: 4