การเปลี่ยนลายเซ็นวิธีการสำหรับการใช้งานคลาสใน PHP


9

มีการทำงานที่ดีในการขาด Generics ของ PHP ที่ช่วยให้การตรวจสอบรหัสคงที่เพื่อตรวจสอบความสอดคล้องของประเภท?

ฉันมีคลาสนามธรรมที่ฉันต้องการ sub-class และบังคับใช้วิธีการอย่างใดอย่างหนึ่งเปลี่ยนจากการรับพารามิเตอร์ชนิดหนึ่งการรับพารามิเตอร์ซึ่งเป็น sub-class ของพารามิเตอร์นั้น

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

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

สิ่งนี้ไม่ได้รับอนุญาตใน PHP เพราะมันเปลี่ยนลายเซ็นวิธีที่ไม่ได้รับอนุญาต ด้วย generics สไตล์ 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% ของรหัสซึ่งอยู่ในระดับนามธรรม การใช้อินเทอร์เฟซอาจหมายถึงการทำซ้ำรหัสหรือ refactor ขนาดใหญ่เพื่อย้ายรหัสที่ใช้ร่วมกันไปยังคลาสอื่นที่สามารถนำมาประกอบกับทั้งสองคลาส
Danack

ฉันค่อนข้างมั่นใจว่าคุณสามารถใช้ทั้งสองอย่างร่วมกันได้
RibaldEddie

ใช่ - แต่มันไม่ได้แก้ปัญหาที่ i) วิธีการที่ใช้ร่วมกันจำเป็นต้องใช้คลาสพื้นฐานของ 'รายการ' ii) วิธีการเฉพาะประเภทต้องการใช้คลาสย่อย "WoodItem" แต่นั่นทำให้เกิดข้อผิดพลาดเช่น "การประกาศ ของ WoodProcessor :: bar () จะต้องเข้ากันได้กับ AbstractProcessor :: bar (รายการ $ item) "
Danack

ฉันไม่มีเวลาที่จะทดสอบพฤติกรรม แต่ถ้าคุณพูดถึงส่วนต่อประสาน (สร้าง IItem และ IWoodItem และมี IWoodItem สืบทอดมาจาก IItem) จากนั้นบอกใบ้ IItem ในฟังก์ชันลายเซ็นสำหรับคลาสฐานและ IWoodItem ในเด็ก ตาดอาจทำงานได้ อาจจะไม่.
RibaldEddie

คำตอบ:


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()ว่าได้รับการปกป้อง / เป็นส่วนตัวเช่นกัน


ห่วงต้องกระโดดผ่านเพื่อจำลอง OOP ใน PHP
Tulains Córdova

4
@ user61852 ฉันไม่ได้พูดแบบนี้เพื่อปกป้อง PHP (เชื่อฉัน) แต่ แต่ภาษาส่วนใหญ่จะไม่ยอมให้คุณเปลี่ยนวิธีการลงนามซึ่งสืบทอดมา - ในอดีตมีความเป็นไปได้ใน PHP แต่ด้วยเหตุผลหลายประการที่ตัดสินใจ จำกัด โปรแกรมเมอร์ จากการทำเช่นนี้มันทำให้เกิดปัญหาพื้นฐานกับภาษา; การเปลี่ยนแปลงนี้มีขึ้นเพื่อให้ภาษาสอดคล้องกับภาษาอื่น ๆ มากขึ้นดังนั้นฉันไม่แน่ใจว่าภาษาใดที่คุณเปรียบเทียบ รูปแบบที่แสดงในส่วนที่สองของคำตอบของฉันใช้ได้และมีประโยชน์ในภาษาอื่นเช่น C # หรือ Java เช่นกัน
mindplay.dk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.