Зміна підпису методу для реалізації класів у PHP


9

Чи є якась гідна робота щодо відсутності в програмі PHP відсутності Generics, що дозволяє статичному контролю коду виявити узгодженість типу?

У мене є абстрактний клас, який я хочу підклас, а також примусити, що один з методів змінюється від прийняття параметра одного типу до прийняття параметра, який є підкласом цього параметра.

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 не підтримує їх.

Google для цієї проблеми люди пропонують використовувати 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
    }
}

Оскільки я переходжу лише Itemдо new ProcessedWoodItem($item)та, і він очікує WoodItem як параметр, перевірка коду дозволяє припуститись помилку.


1
Чому ви не використовуєте інтерфейси? Ви можете використовувати наслідування інтерфейсу для досягнення того, що ви хочете, я вважаю.
RibaldEddie

Тому що два класи поділяють 80% свого коду, що є в абстрактному класі. Використання інтерфейсів означатиме або дублювання коду, або великий рефактор для переміщення загального коду в інший клас, який можна скласти з двома класами.
Данак

Я впевнений, що ви можете використовувати обох разом.
RibaldEddie

Так - але це не вирішує проблему, що i) загальним методам потрібно використовувати базовий клас 'Item'. Ii) Методи, характерні для типу, хочуть використовувати підклас "WoodItem", але це призводить до помилки типу "Декларація" від WoodProcessor :: bar () має бути сумісним з AbstractProcessor :: bar (Item $ item) "
Danack

Я не встигаю фактично перевірити поведінку, але що робити, якщо ви натякнули на інтерфейс (створити IItem та IWoodItem та передати успадкований IWoodItem від IItem)? Потім підкажіть IItem у підписі функції для базового класу та IWoodItem у дитини. Тат може працювати. Не може.
RibaldEddie

Відповіді:


3

Ви можете використовувати методи без аргументів, документуючи параметри замість doc-блоків:

<?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()захищеними / приватними.


Обручі повинні проскочити, щоб імітувати OOP в PHP.
Tulains Córdova

4
@ user61852 Я не кажу це, щоб захищати PHP (повірте), але, але більшість мов не дозволить вам змінити успадкований метод-підпис - історично це було можливо в PHP, але з різних причин вирішили (штучно) обмежити програмістів робити це, оскільки це спричиняє принципові проблеми з мовою; ця зміна була внесена для того, щоб привести мову в більшу відповідність з іншими мовами, тому я не впевнений, з якою мовою ви порівнюєте. Шаблон, продемонстрований у другій частині моєї відповіді, застосовний і корисний для інших мов, таких як C # або Java.
mindplay.dk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.