Thay đổi chữ ký phương thức để triển khai các lớp trong PHP


9

Có công việc nào phù hợp với việc thiếu Generics của PHP cho phép kiểm tra mã tĩnh để phát hiện tính nhất quán của loại không?

Tôi có một lớp trừu tượng, tôi muốn lớp con và cũng thực thi rằng một trong các phương thức thay đổi từ lấy tham số của một loại, sang lấy tham số là lớp con của tham số đó.

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

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

Điều này không được phép trong PHP vì nó thay đổi chữ ký phương thức không được phép. Với các kiểu chung Java, bạn có thể làm một cái gì đó như:

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

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

Nhưng rõ ràng PHP không hỗ trợ những thứ đó.

Google cho vấn đề này, mọi người đề nghị sử dụng instanceofđể kiểm tra lỗi trong thời gian chạy, vd

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");
        } 
    }
}

Nhưng điều đó chỉ hoạt động trong thời gian chạy, nó không cho phép bạn kiểm tra lỗi mã của mình bằng phân tích tĩnh - vậy có cách xử lý nào hợp lý trong PHP không?

Một ví dụ đầy đủ hơn về vấn đề là:

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
    }
}

Bởi vì tôi chỉ Itemchuyển đến new ProcessedWoodItem($item)và nó mong đợi WoodItem là tham số, việc kiểm tra mã cho thấy có lỗi.


1
Tại sao bạn không sử dụng Giao diện? Bạn có thể sử dụng kế thừa giao diện để đạt được những gì bạn muốn, tôi tin.
RibaldEddie

Bởi vì hai lớp chia sẻ 80% mã của chúng, nằm trong lớp trừu tượng. Sử dụng các giao diện có nghĩa là sao chép mã hoặc một bộ tái cấu trúc lớn để di chuyển mã được chia sẻ sang một lớp khác có thể được kết hợp với hai lớp.
Danack

Tôi khá chắc chắn rằng bạn có thể sử dụng cả hai cùng nhau.
RibaldEddie

Có - nhưng nó không giải quyết được vấn đề i) các phương thức được chia sẻ cần sử dụng lớp cơ sở của 'Mục' ii) Các phương thức cụ thể loại muốn sử dụng một lớp con "WoodItem" nhưng điều đó gây ra lỗi như "Khai báo của WoodProcessor :: bar () phải tương thích với AbstractProcessor :: bar (Item $ item) "
Danack

Tôi không có thời gian để thực sự kiểm tra hành vi nhưng nếu bạn gợi ý giao diện (tạo IItem và IWoodItem và có IWoodItem thừa hưởng từ IItem) thì sao? Sau đó, gợi ý IItem trong chữ ký hàm cho lớp cơ sở và IWoodItem ở trẻ. Tất nhiên có thể làm việc. Có thể không.
RibaldEddie

Câu trả lời:


3

Thay vào đó, bạn có thể sử dụng các phương thức không có đối số, ghi lại các tham số bằng các khối 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');

Tôi sẽ không nói rằng tôi nghĩ rằng đây là một ý tưởng tốt.

Vấn đề của bạn là, bạn có một bối cảnh với số lượng thành viên thay đổi - thay vì cố gắng ép buộc những người đó làm đối số, một ý tưởng tốt hơn và có tính tương lai hơn là giới thiệu một loại ngữ cảnh để mang tất cả các đối số có thể, để đối số danh sách không bao giờ cần thay đổi.

Thích như vậy:

<?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'));

Tất nhiên, các phương thức nhà máy tĩnh là tùy chọn - nhưng có thể hữu ích, nếu chỉ một số kết hợp cụ thể của các thành viên tạo ra một bối cảnh có ý nghĩa. Nếu vậy, bạn cũng có thể muốn tuyên bố __construct()là được bảo vệ / riêng tư.


Các vòng quay phải nhảy qua để mô phỏng OOP trong PHP.
Tulains Córdova

4
@ user61852 Tôi không nói điều này để bảo vệ PHP (tin tôi) nhưng, nhưng hầu hết các ngôn ngữ sẽ không cho phép bạn thay đổi chữ ký phương thức được kế thừa - về mặt lịch sử, điều này là có thể trong PHP, nhưng vì nhiều lý do đã quyết định (giả tạo) hạn chế các lập trình viên từ việc này, vì nó gây ra các vấn đề cơ bản với ngôn ngữ; thay đổi này được thực hiện để mang ngôn ngữ phù hợp hơn với các ngôn ngữ khác, vì vậy tôi không chắc bạn đang so sánh với ngôn ngữ nào. Mẫu được trình bày trong phần thứ hai của câu trả lời của tôi có thể áp dụng và hữu ích trong các ngôn ngữ khác như C # hoặc Java.
mindplay.dk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.