更改方法签名以在PHP中实现类


9

有没有关于PHP缺少泛型的体面工作,这些泛型允许静态代码检查以检测类型一致性?

我有一个抽象类,我想对其进行子类化,并且还强制其中一种方法从采用一种类型的参数更改为采用作为该参数的子类的参数。

abstract class AbstractProcessor {
    abstract function processItem(Item $item);
}

class WoodProcessor extends AbstractProcessor {
    function processItem(WoodItem $item){}
}

PHP不允许这样做,因为它正在更改不允许的方法签名。使用Java样式泛型,您可以执行以下操作:

abstract class AbstractProcessor<T> {
    abstract function processItem(T $item);
}

class WoodProcessor extends AbstractProcessor<WoodItem> {
    function processItem(WoodItem $item);
}

但是很明显,PHP不支持这些功能。

谷歌针对这个问题,人们建议使用instanceof在运行时检查错误,例如

class WoodProcessor extends AbstractProcessor {
    function processItem(Item $item){
        if (!($item instanceof WoodItem)) {
            throw new \InvalidArgumentException(
                "item of class ".get_class($item)." is not a WoodItem");
        } 
    }
}

但这仅在运行时有效,它不允许您使用静态分析检查代码中的错误-因此,在PHP中是否有任何明智的处理方式?

该问题的一个更完整的示例是:

class StoneItem extends Item{}
class WoodItem extends Item{}

class WoodProcessedItem extends ProcessedItem {
    function __construct(WoodItem $woodItem){}
}

class StoneProcessedItem extends ProcessedItem{
    function __construct(StoneItem $stoneItem){}
}

abstract class AbstractProcessor {
    abstract function processItem(Item $item);

    function processAndBoxItem(Box $box, Item $item) {
       $processedItem = $this->processItem($item);
       $box->insertItem($item);
    }

    //Lots of other functions that can call processItem
}

class WoodProcessor extends AbstractProcessor {
    function processItem(Item $item) {
        return new ProcessedWoodItem($item); //This has an inspection error
    }
}

class StoneProcessor extends AbstractProcessor {
    function processItem(Item $item) {
        return new ProcessedStoneItem($item);//This has an inspection error
    }
}

因为我只传递了一个Itemto new ProcessedWoodItem($item)并且它期望将WoodItem作为参数,所以代码检查表明存在错误。


1
为什么不使用接口?我相信,您可以使用接口继承来实现所需的功能。
RibaldEddie 2014年

因为这两个类共享其代码的80%,所以它们在抽象类中。使用接口意味着要么复制代码,要么进行大量重构以将共享的代码移到可以与这两个类组合在一起的另一个类中。
Danack 2014年

我很确定您可以同时使用两者。
RibaldEddie 2014年

是的-但是它不能解决以下问题:i)共享方法需要使用“ Item”的基类; ii)特定于类型的方法希望使用子类“ WoodItem”,但是会出现类似“声明”的错误。 Process :: bar()必须与AbstractProcessor :: bar(Item $ item)兼容”
Danack 2014年

我没有时间实际测试行为,但是如果您提示接口(创建IItem和IWoodItem并让IWoodItem从IItem继承),该怎么办?然后在基类的函数签名中提示IItem,在子级中提示IWoodItem。达可能会工作。也许不会。
RibaldEddie 2014年

Answers:


3

您可以使用不带参数的方法,而用doc-blocks记录参数:

<?php

class Foo
{
    /**
     * @param string $world
     */
    public function hello()
    {
        list($world) = func_get_args();

        echo "Hello, {$world}\n";
    }
}

class Bar extends Foo
{
    /**
     * @param string $greeting
     * @param string $world
     */
    public function hello()
    {
        list($greeting, $world) = func_get_args();

        echo "{$greeting}, {$world}\n";
    }
}

$foo = new Foo();
$foo->hello('World');

$bar = new Bar();
$bar->hello('Bonjour', 'World');

我不会说我认为这是个好主意。

您的问题是,您的上下文具有可变数量的成员-而不是试图通过强制将这些成员作为参数,而是一种更好且更具前瞻性的想法是引入一个上下文类型来携带所有可能的参数,以便该参数列表永远不需要更改。

像这样:

<?php

class HelloContext
{
    /** @var string */
    public $greeting;

    /** @var string */
    public $world;

    public static function create($world)
    {
        $context = new self;

        $context->world = $world;

        return $context;
    }

    public static function createWithGreeting($greeting, $world)
    {
        $context = new self;

        $context->greeting = $greeting;
        $context->world = $world;

        return $context;
    }
}

class Foo
{
    public function hello(HelloContext $context)
    {
        echo "Hello, {$context->world}\n";
    }
}

class Bar extends Foo
{
    public function hello(HelloContext $context)
    {
        echo "{$context->greeting}, {$context->world}\n";
    }
}

$foo = new Foo();
$foo->hello(HelloContext::create('World'));

$bar = new Bar();
$bar->hello(HelloContext::createWithGreeting('Bonjour', 'World'));

静态工厂方法当然是可选的-但如果只有某些特定的成员组合产生有意义的上下文,它可能会很有用。如果是这样,您不妨同时声明__construct()为受保护/私有。


篮筐必须跳过去,以用PHP模拟OOP。
图兰斯·科尔多瓦

4
@ user61852我并不是说要捍卫PHP(相信我),但是,但是大多数语言都不允许您更改继承的方法签名-历史上,这在PHP中是可能的,但是出于各种原因决定(人为地)限制程序员这样做会导致语言的根本问题;进行此更改是为了使该语言与其他语言更加一致,因此我不确定您要与哪种语言进行比较。我的答案的第二部分中演示的模式适用于其他语言,例如C#或Java。
mindplay.dk 2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.