我很好奇是否有针对以下行为的“更好”设计:
<?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...
基本上,我有一个基类Foo
,方法Foo::foo()
包含一些应该始终运行的公共代码。我还有从Foo
继承的各种子类,每个子类都有自己的特定代码,也应该始终运行。
我在这里使用的设计使用DRY原则来确保Foo::foo()
和Bar::foo()
中的Baz::foo()
代码不再重复,而Bar::foo()
中的代码不会重复Baz::foo()
中没有重复,等等。
这个设计的问题(?)是我依赖于子类在每种情况下总是显式地调用parent::foo()
,并且扩展这些类的类也是如此,所以无限制地使用。但是,我没有办法(我知道)实际执行此操作。
所以我的问题是 - 是否有更好的设计可以实现相同的行为,或者某种方式在父/子类之间强制实施这种“契约”?
更新
有些人要求用例。多年来我在几个项目中遇到过这个范例,但由于NDAs等原因,我不能给出一个真实世界的例子,所以这里有一个超级基本的例子可能有助于更好地说明问题:
<?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();
}
}
答案 0 :(得分:3)
我认为这不是更好,但这是一种可能的方式。
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.
}
}
就个人而言,我更喜欢你拥有它的方式,因为我觉得它更具可读性。这也意味着孩子不必拥有自己的foo()
实施。似乎更多的OOP。但是,如果您要求每个子类都有自己添加的foo()
实现,这可能适合您。
答案 1 :(得分:3)
只要你在子类中覆盖你的方法,我所知道的任何语言都无法强制执行父方法的行为。如果您只是为您的应用编写代码,您应该能够信任自己的代码来调用parent :: foo()。但是,如果您正在编写其他人将构建的库,框架或API,那么您的想法就有价值。 Ruby on Rails使用回调充分利用了这种行为。
好的,所以不要定义任何 foo方法。相反,使用__call和一组闭包作为回调。我的PHP真的很生疏,所以我忘记了一些细节。
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();
}
}
}
然后在你的子类中,只需用“”add_callback“附加更多的回调。这不适用于所有内容,但在某些情况下它的效果非常好。(更多关于http://php.net/manual/en/functions.anonymous.php的闭包。)
答案 2 :(得分:2)
我找到了一个更好的通用方法来避免闭包和其他丑陋的技巧。
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";
结果:
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
答案 3 :(得分:1)
PHP无法专门执行此操作;
但是,如果Foo :: foo必须始终在任何子类:: foo之前执行,并且您不关心结果;也许这些方法的实际内容设计得很糟糕。
如果你总是必须初始化某些东西,也许你可以在构造函数中执行它,如果你记录每个调用,也许你需要一个装饰器。
这是另一个可行的选项:
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.
}
}
这里的缺陷是没有“多重继承”或链接。
但是,也许你真的需要某种形式的发布模式......或者谁知道呢?
您问的是如何实现解决设计问题的解决方案,您应该具体询问如何解决您的设计问题。