【问题标题】:Overriding functions incompatibility覆盖函数不兼容
【发布时间】:2012-11-27 11:26:45
【问题描述】:

TL;DR

我想像这样从ArrayObject 覆盖offsetSet($index,$value):offsetSet($index, MyClass $value) 但它会产生一个致命错误(“声明必须兼容”)。

什么和为什么

我正在尝试创建一个ArrayObject 子类,它强制所有值都属于某个对象。我的计划是通过覆盖所有添加值的函数并给它们一个类型提示来做到这一点,所以除了MyClass的值之外你不能添加任何东西

如何

第一站:append($value);
来自 SPL:

/**
 * Appends the value
 * @link http://www.php.net/manual/en/arrayobject.append.php
 * @param value mixed <p>
 * The value being appended.
 * </p>
 * @return void 
 */
public function append ($value) {}

我的版本:

/**
 * @param MyClass $value
 */
public function append(Myclass $value){
    parent::append($value);
}

似乎很有魅力。

You can find and example of this working here

第二站:offsetSet($index,$value);

同样,来自 SPL:

/**
 * Sets the value at the specified index to newval
 * @link http://www.php.net/manual/en/arrayobject.offsetset.php
 * @param index mixed <p>
 * The index being set.
 * </p>
 * @param newval mixed <p>
 * The new value for the index.
 * </p>
 * @return void 
 */
public function offsetSet ($index, $newval) {}

还有我的版本:

/**
 * @param mixed $index
 * @param Myclass $newval
 */
public function offsetSet ($index, Myclass $newval){
    parent::offsetSet($index, $newval);
}

然而,这会产生以下致命错误:

致命错误:声明 命名空间\MyArrayObject::offsetSet() 必须是 兼容 ArrayAccess::offsetSet()

You can see a version of this NOT working here

如果我这样定义就好了:

public function offsetSet ($index, $newval){
    parent::offsetSet($index, $newval);
}

You can see a version of this working here

问题

  1. 为什么覆盖 offsetSet() 不适用于上述代码,但 append() 可以?
  2. 如果我在append() 和offsetSet() 的定义旁边添加exchangeArray() 的定义,我是否拥有添加对象的所有功能?

【问题讨论】:

  • 我知道我可以在调用父函数之前对函数进行类型检查,但除了知道我做错了什么之外,它不会给任何用户这个类是一个易于使用的类型提示。

标签: php overriding arrayobject


【解决方案1】:
abstract public void offsetSet ( mixed $offset , mixed $value )

由ArrayAccess 接口声明,而public void append ( mixed $value ) 没有对应的接口。显然 php 在后一种情况下比接口更“宽容”/lax/whatever。

例如

<?php
class A {
    public function foo($x) { }
}

class B extends A {
    public function foo(array $x) { }
}

"only" 打印警告

Strict Standards: Declaration of B::foo() should be compatible with A::foo($x)

同时

<?php
interface A {
    public function foo($x);
}

class B implements A {
    public function foo(array $x) { }
}

通过

Fatal error: Declaration of B::foo() must be compatible with A::foo($x)

【讨论】:

  • 嗯,当然。与@corbin 的示例一起,这意味着我无论如何都不应该这样做。如果您考虑过如何修复我的最终目标(例如只有一种变量的对象数组),我将不胜感激,但也许我应该回到绘图板和/或为此打开第二个问题:)
【解决方案2】:

API 永远不应该变得更具体。

事实上,我认为append(Myclass $value) 不是致命错误是一个错误。我认为您的 offsetSet() 上的致命错误是正确的。

原因很简单:

function f(ArrayObject $ao) { 
    $ao->append(5); //Error
} 

$ao = new YourArrayObject(); 

如果append 带有类型要求,则会出错。不过看起来没什么问题。您已经有效地使 API 更加具体,并且不再能够假定对基类的引用具有预期的 API。

基本上归结为,如果一个 API 更具体,该子类将不再与其父类兼容。

这种奇怪的差异可以通过f 看到:它允许您将Test 传递给它,但随后将在$ao-&gt;append(5) 执行时失败。如果echo 'hello world'; 在它上面,那将执行。我认为这是不正确的行为。

在像 C++、Java 或 C# 这样的语言中,这就是泛型发挥作用的地方。在 PHP 中,恐怕没有一个很好的解决方案。运行时检查会令人讨厌且容易出错,并且滚动您自己的类将完全消除将 ArrayObject 作为基类的优势。不幸的是,希望将 ArrayObject 作为基类也是这里的问题。它存储混合类型,因此您的子类也必须存储混合类型。

您也许可以在自己的类中实现该 ArrayAccess 接口,并清楚地标记该类仅用于特定类型的对象。不过恐怕还是有点笨拙。

如果没有泛型,就无法在没有运行时 instanceof 样式检查的情况下拥有一个通用的同构容器。唯一的方法是拥有一个 ClassAArrayObject、ClassBArrayObject 等。

【讨论】:

  • 我有点同意你的说法,尤其是你的例子,但我需要一个数据结构来保证(如果可能的话,类型提示和所有)它只有 MyClass 类型的对象。仅在函数中检查它是微不足道的,但这不会轻易帮助使用/创建所述对象。目标是让 ArrayObject 只有一种类型的对象,这似乎是一个有效的选项(除了它不是:))。
猜你喜欢
  • 2011-11-28
  • 2011-01-31
  • 2011-03-09
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
  • 2010-10-25
  • 2021-06-03
  • 2018-07-26
相关资源
最近更新 更多