interface - PHP - 实现接口时的类型提示更具体的类型
问题描述
给定一个对象/接口层次结构,如下所示
<?php
interface Event
{
public function getType(): string;
}
final class MyEvent implements Event
{
public function getType(): string
{
return 'MyEvent';
}
}
interface EventHandler
{
public function handle(Event $event): void;
}
final class MyEventHandler implements EventHandler
{
public function handle(MyEvent $event): void
{
// do something with the concrete MyEvent object...
}
}
是否无法在从接口MyEvent
实现方法的方法中输入更具体的提示?handle
EventHandler
我的 ide 将此标记为错误
方法“MyEventHandler::handle()”与方法“EventHandler::handle()”不兼容。
虽然我觉得以可扩展性的名义,这应该是可能的......
实现方法的签名与接口中方法的签名不同,但在运行时不会将 aMyEvent
与Event
?
显然,如果具体实现类型提示Event
接口,则没有问题,并且在运行时该方法将接受实现MyEvent
,但是我们失去了具体MyEventHandler
应该处理类型事件的意图MyEvent
。Event
和接口可能有非常多的实现EventHandler
,所以我们在这里寻找的是尽可能明确,而不是仅仅在具体的句柄方法中输入提示接口。
如果接口和类型提示无法做到这一点,是否有另一种方法可以用抽象类实现它,同时保留强(ish)类型和传达意图?或者我们是否需要一个显式方法MyEventHandler
来允许具体 Event 类的类型提示?
解决方案
您要问的是“协变方法参数类型”。
问题在于,MyEvent
根据定义,该类可以具有接口不知道/不知道的一些额外行为Event
。而且由于该handler()
方法需要一个MyEvent
类型(而不是一个类型Event
),因此可以得出结论,该handler()
方法实际上使用了这种额外的行为;否则为什么要更改参数的类型?
如果 PHP 允许这样做,那么类handle()
实例的方法可能MyEventHandler
会收到一个非MyEvent
事件对象。然后它将导致运行时错误。
事实上,维基百科在这里对这个问题有很好的描述。包括下面的一小段(代码不是 PHP,但你会明白的):
class AnimalShelter {
void putAnimal(Animal animal) {
//...
}
}
class CatShelter extends AnimalShelter {
void putAnimal(covariant Cat animal) {
// ...
}
}
这不是类型安全的。通过将 CatShelter 向上转换为 AnimalShelter,可以尝试将狗放在猫收容所中。这不符合 CatShelter 参数限制,并且会导致运行时错误。缺乏类型安全(在 Eiffel 社区中称为“catcall 问题”,其中“cat”或“CAT”是更改的可用性或类型)一直是一个长期存在的问题。多年来,已经提出了全局静态分析、局部静态分析和新语言特性的各种组合来解决它,[7] [8] 并且这些已经在一些 Eiffel 编译器中实现。
有趣的是,PHP 确实允许在构造函数参数的情况下使用协变。
interface AnimalInterface {}
interface DogInterface extends AnimalInterface {}
class Dog implements DogInterface {}
class Pet
{
public function __construct(AnimalInterface $animal) {}
}
class PetDog extends Pet
{
public function __construct(DogInterface $dog)
{
parent::__construct($dog);
}
}
如果您将参数从 handle() 方法移动到处理程序类的构造函数,这在您的特定情况下会有所帮助。
推荐阅读
- google-sheets - 根据谷歌工作表下一行中的第一个非空单元格选择日期行中的日期值
- python - Python 代码风格:pathlib 的 joinpath 运算符周围的空格
- python - 在只读容器中运行 Flask 服务
- networking - 是否有可能拥有不充当路由器/网关的 WiFi 接入点?
- c# - 无法创建到 db 表,但异常返回拼写错误?
- c - 线程 1: EXC_BAD_ACCESS (code=1, address=0x68) 尝试打开文件时
- android - Chip() 未正确水平调整大小
- c# - WebAPI - XMLInput Remove 需要指定命名空间
- sql - COUNT 在 WHERE 语句 SQL 中
- php - 有没有办法创建一个可逆的不透明 UUID?