6

在尝试调试一些 PHP 类时,我遇到了一些在我看来非常奇怪的行为。

我已经构建了以下行为的演示:

class BaseClass {
   public function baseMethod () {
      echo (implode (' ', $this -> childMethod ()) . PHP_EOL);
   }
}

class ChildClass extends BaseClass {
   protected function childMethod () {
      return array ('What', 'The', 'Actual', 'Fork!');
   }
}

$a = new ChildClass ();
$a -> baseMethod ();

现在,在我看来,基类根本不应该对子类做出任何假设,除了它通过声明(或继承)抽象方法或通过实现接口为子类强制执行的假设。但是,上面的代码实际上输出了一个字符串,并没有抛出任何错误!

什么是真正的分叉!

这对我来说似乎是一种破碎的行为。除非基类声明abstract protected function childMethod();,否则它应该不能调用它,不是吗?

我一直在网上搜索,试图找到一些可以证明这是预期行为的东西。到目前为止,我设法从PHP 手册中找到以下内容:

其他对象的可见性

相同类型的对象将可以访问彼此的私有成员和受保护成员,即使它们不是相同的实例。这是因为在这些对象内部时已经知道实现特定的细节

那么我在这里目睹的行为是正确的还是 PHP 中的错误?这当然不是我要依赖的行为,因为这对我来说似乎是错误的。

仅供参考,我们在实际代码中发现的问题是子类声明了超类试图调用的私有方法。超类没有声明方法抽象(如果这样做了,它至少必须受到保护)。

4

3 回答 3

8

现在,在我看来,基类根本不应该对子类做出任何假设,除了它通过声明(或继承)抽象方法或通过实现接口为子类强制执行的假设。

这就是静态类型语言的工作方式。在 PHP(和许多其他)中,您可以自由地对任何值调用任何方法(它甚至不必是对象)。如果调用没有意义,则会出现运行时错误。

当然,如果您希望派生类实现该方法,那么声明该方法是个好主意;这只是一个很好的做法。此外,许多著名的 PHP IDE 会检测到这种遗漏并标记调用以引起您的注意,因为编译器不能。

这对我来说似乎是一种破碎的行为。除非基类声明 abstract protected function childMethod();,否则它应该不能调用它,不是吗?

它应该,如上所述。如果您发现这种行为不受欢迎,那么 PHP 不是您应该使用的语言。

旁白:在一个密切相关的注释中,您可能还会发现基类可以protected毫无问题地访问派生类中定义的成员是违反直觉的。这是记录在案的。事实上,这正是您的示例中发生的情况,但我特别提到它是因为它是一种不同的反直觉(如果方法是,您的示例也会令人费解public)。

考虑一下 PHP 允许的无穷无尽的构造列表,从静态类型的角度来看,这些构造也是“不可能的”,其中包括:

// #1
$varName = 'foo';

// How do you know $object has a property named "foo"?
// How do you know that "foo" is a valid property name in the first place?
// How do you know that $object is an object to begin with?
echo $object->$varName;

// #2
$object = new SomeObject;
$methodName = 'someMethod';

// it's practically impossible to reason about this before runtime
call_user_func(array($object, $methodName));
于 2013-01-29T13:22:25.003 回答
2

这不是一个错误。它应该像那样工作。让我尝试评论方法调用:

class BaseClass {
   public function baseMethod () {
      echo (implode (' ', $this -> childMethod ()) . PHP_EOL);
   }
}

class ChildClass extends BaseClass {
   protected function childMethod () {
      return array ('What', 'The', 'Actual', 'Fork!');
   }
}

/****/

$a = new ChildClass ();
/** $a is now instance of ChildClass, that is a sublass of BaseClass
 *  so the Object $a has 2 methods: baseMethod() inherited from BaseClass and childMethod() from ChildClass.
 */

$a -> baseMethod ();
/** now you're calling $a->baseMethod(), which will do a method call on $this->childMethod(). 
 *  As $this is refering $a here, $a has indeed a method named childMethod(), that will be called.
 */

您想知道的是protected. 但是作为$aChildClass 的类型,受保护的方法肯定对它和继承的baseMethod(). 这在每种 OOP 语言中都应该是相同的。

您的代码中不好的部分是baseMethod()调用 a内部的假设childMethod()。如果没有这样的方法,那总是会以错误结束。像 Java 这样的 OOP 语言会在这里抛出编译错误,但 PHP 不会,因为 PHP 中没有预运行编译器。如果将初始化从 eg 更改$a = new ChildClass ();$a = new BaseClass ();,PHP 中也会出现运行时错误。

于 2013-01-29T13:08:40.710 回答
0

PHP 是一种非常动态的语言,它允许您在没有继承的情况下使用多态性。在这种情况下,它只是在运行时查找方法“childMethod”,并且由于它存在,它将调用它。你如何得到那个方法并不重要。您还可以使用 __call 魔术方法动态生成该方法。您永远无法在 C++ 或 Java 中做到这一点,但在 PHP 或 Javascript 等语言中,一切都很好,它为您提供了更多选择。

于 2013-01-29T13:15:57.563 回答