【问题标题】:"Overloading" a private method in PHP“重载”PHP中的私有方法
【发布时间】:2013-08-17 12:22:52
【问题描述】:

我知道我不能在 PHP 中重载方法。而且,据我所知,类中的private 方法对于扩展基类的类是不可见的。那么为什么这不起作用呢?

class Base {
  private function foo($arg) {
     print "Base $arg";
  }
}

class Child extends Base {
  public function foo() {
     print "Child";
  }
}

$c = new Child;
print $c->foo();

错误:

PHP Strict Standards: Declaration of Child::foo() should be compatible with Base::foo($arg) in /var/www/boludo.php on line 17

我假设foo($arg) 方法在Child 类中是不可见的,因为它是private。所以,我没有重载foo,我只是创建了一个名为foo的方法。

【问题讨论】:

  • 你调用的是属性,而不是方法
  • @Simon_eQ 抱歉,现在它已修复(我正在尝试另一个代码,如果您查看错误 Child:foo() is called :P)
  • 它们在核心级别不是不可见的。它们无法访问。
  • 如果Base 中的某个其他方法调用$this->foo(),并且该方法在Child 类型的对象中被调用,您期望得到什么?您已经覆盖了该方法,但原始代码并没有预料到它,因为该方法是私有的。
  • @enrmarc 实际上我发现它非常粗略地说:你不能对 PHP 隐藏任何东西。如果您扩展一个类,PHP 核心确实知道基类中的每个方法,无论它是私有的、公共的、受保护的、静态的等等……而且它的工作是阻止您访问它们。因此,一旦 PHP 意识到这一点,在编译时它会阻止您访问私有方法。这不是关于可见性,而是关于可访问性。例如,如果您阅读相同内容的 C# 手册,您将永远不会看到“可见性”一词:msdn.microsoft.com/en-us/library/st6sy9xe.aspx

标签: php oop overloading


【解决方案1】:

要修复通知,只需将 Child 中的 foo() 更改为

public function foo($arg = null) {

关于“为什么这不起作用”的问题:

PHP 中的可见性严格来说是关于运行时访问。它不会影响您如何扩展/组合/重载类和方法。从子类型中的超类型中放松私有方法的可见性将在子类型中添加一个单独的方法,而无法访问超类型中的相同命名方法。但是,PHP 会为这些假定父子关系。但这并没有引起通知。至少,不是靠它自己。

您收到通知的原因是您还试图更改方法签名。您的 foo() 不再需要将 $arg 传递给它。当您假设方法之间存在父子关系时,这是一个问题,因为Liskov Substitution Principle 声明 “如果 S 是 T 的子类型,则 T 类型的对象可能会被 S 类型的对象替换” 不破坏程序。换句话说:如果你有使用Base的代码,你应该能够用Child替换Base,并且程序应该仍然像使用Base一样工作。

假设您的Base 也有一个公共方法bar()

class SomeClientUsingBase
{
    public function doSomethingWithBase(Base $base)
    {
        $result = $base->bar();
        // …

现在想象Child 更改bar() 以要求一个参数。如果您随后将Child for Base 传递给客户端,您将破坏客户端,因为客户端在没有参数的情况下调用$base->bar();

显然,您可以更改客户端以传递参数,但是代码实际上取决于Child 如何定义该方法,因此 Typehint 是错误的。事实上,Child 不是 Base,因为它的行为不像 Base。那么继承就被破坏了。

现在有趣的是,如果你从 foo() 中删除 $arg,从技术上讲,你并没有违反 LSP,因为客户端仍然可以工作。通知在这里是错误的。在以前使用 Base 的客户端中调用 $base->foo(42) 仍然可以使用 Child,因为 Child 可以简单地忽略该参数。但是 PHP 希望你将参数设为可选。

请注意,LSP 也适用于方法可能返回的内容。 PHP 只是不包括签名中的返回类型,所以你自己考虑到这一点。您的方法必须返回 Supertype 返回的内容或行为等效的内容。

【讨论】:

  • 谢谢,现在我明白了。
  • 原来的问题没有违反LSP,因为作者想在公共接口中添加一个方法,而不是改变它的签名。这是语言的一个限制:如果一个方法是私有的,它应该被派生类完全忽略。也就是说,我不会为了解决这个限制而将忽略的字段添加到公共方法中,更改私有方法的名称将是一种选择。
  • @lacopo 仅更改可见性就可以了。但是更改签名也是导致通知的原因。并且通知是由于PHP对LSP的解释。这里实际上有两件事在起作用,我的回答解释了这两者。您可能想将其称为错误或语言的限制,但我认为解释语言的工作原理对 OP 更有帮助,而不是漫无目的地讨论它应该如何工作。附带说明:派生孩子不需要更改父母。
  • 老实说,关于 LSP 的故事与私有函数无关。实际上,PHP 实现了这一点,并没有让子函数覆盖父函数。但是,由于某种原因,它确实要求签名相同(以严格错误为代价),我会说这是 PHP 的错误。
  • @Jasper 我同意,但这并不是无关紧要的。 PHP 在这里假设 LSP。无论正确与否,“出于某种原因”是它使有关 LSP 的解释相关的原因。
【解决方案2】:

您可以使用 __call 函数在 PHP 中进行函数重载:http://www.php.net/manual/en/language.oop5.overloading.php#object.call

除此之外,您的问题在于,您违反了可替代性原则: http://en.wikipedia.org/wiki/Liskov_substitution_principle

PHP 使用的东西。这样,如果您将 Base 类类型的对象替换为 Child 类类型的对象,则违反了可替换性。您在派生类中更改基类的接口,删除方法 foo(...) 的参数,这样,在不破坏程序的情况下,不能用子类类型的对象替换基类类型的对象,从而违反了 Liskov 的可替代性原则 (LSP)。

【讨论】:

    猜你喜欢
    • 2015-09-07
    • 2011-06-30
    • 2013-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多