Bất hợp pháp trong PHP: Có lý do thiết kế OOP không?


16

Kế thừa giao diện dưới đây là bất hợp pháp trong PHP, nhưng tôi nghĩ nó sẽ khá hữu ích trong cuộc sống thực. Có một vấn đề thực tế hoặc mô hình thực tế với thiết kế dưới đây, rằng PHP đang bảo vệ tôi khỏi?

<?php

/**
 * Marker interface
 */
interface IConfig {}

/**
 * An api sdk tool
 */
interface IApi
{
    public __construct(IConfig $cfg);
}

/**
 * Api configuration specific to http
 */
interface IHttpConfig extends IConfig
{
    public getSomeNiceHttpSpecificFeature();
}

/**
 * Illegal, but would be really nice to have.
 * Is this not allowed by design?
 */
interface IHttpApi extends IApi
{
    /**
     * This constructor must have -exactly- the same
     * signature as IApi, even though its first argument
     * is a subtype of the parent interface's required
     * constructor parameter.
     */
    public __construct(IHttpConfig $cfg);

}

Câu trả lời:


22

Chúng ta hãy bỏ qua một giây rằng phương thức đang nói __constructđến và gọi nó frobnicate. Bây giờ giả sử bạn có một đối tượng đang apithực hiện IHttpApivà một đối tượng đang configthực hiện IHttpConfig. Rõ ràng, mã này phù hợp với giao diện:

$api->frobnicate($config)

Nhưng chúng ta hãy giả sử chúng ta bị ném lên trời apiđể IApi, ví dụ đi qua nó để function frobnicateTwice(IApi $api). Bây giờ trong chức năng đó, frobnicateđược gọi và vì nó chỉ xử lý IApi, nên nó có thể thực hiện một cuộc gọi như $api->frobnicate(new SpecificConfig(...))nơi SpecificConfigthực hiện IConfignhưng không IHttpConfig. Không có ai làm bất cứ điều gì không hợp lý với các loại, nhưng vẫn IHttpApi::frobnicatecó một SpecificConfignơi mà nó mong đợi a IHttpConfig.

Điều này là không tốt. Chúng tôi không muốn cấm upcasting, chúng tôi muốn phân nhóm và rõ ràng chúng tôi muốn nhiều lớp thực hiện một giao diện. Vì vậy, tùy chọn hợp lý duy nhất là cấm một phương thức kiểu con yêu cầu các loại cụ thể hơn cho các tham số. (Một vấn đề tương tự xảy ra khi bạn muốn trả về một loại tổng quát hơn .)

Chính thức, bạn đã bước vào một cái bẫy cổ điển xung quanh tính đa hình, phương sai . Không phải tất cả các lần xuất hiện của một loại Tcó thể được thay thế bằng một kiểu con U. Ngược lại, không phải tất cả các lần xuất hiện của một loại Tcó thể được thay thế bằng một siêu kiểu S . Cần xem xét cẩn thận (hoặc tốt hơn nữa, áp dụng nghiêm ngặt lý thuyết loại).

Quay trở lại __construct: Vì AFAIK bạn không thể khởi tạo chính xác một giao diện, chỉ có một người triển khai cụ thể, điều này có vẻ như là một hạn chế vô nghĩa (sẽ không bao giờ được gọi thông qua giao diện). Nhưng trong trường hợp đó, tại sao lại bao gồm __constructtrong giao diện để bắt đầu? Bất kể, nó sẽ ít được sử dụng cho trường hợp đặc biệt __constructở đây.


19

Có, điều này tuân theo trực tiếp từ Nguyên tắc thay thế Liskov (LSP) . Khi bạn ghi đè một phương thức, kiểu trả về có thể trở nên cụ thể hơn, trong khi các loại đối số phải giữ nguyên hoặc có thể trở nên tổng quát hơn.

Điều này là rõ ràng hơn với các phương pháp khác hơn __construct. Xem xét:

class Vehicle {}
class Car extends Vehicle {}
class Motorcycle extends Vehicle {}

class Driver {
    public drive(Vehicle $v) { ... }
}
class CarDriver extends Driver {
    public drive(Car $c) { ... }
}

Một CarDriverDriver, do đó, một CarDriverví dụ phải có khả năng làm bất cứ điều gì mà một Driverlon. Bao gồm cả lái xe Motorcycles, bởi vì nó chỉ là một Vehicle. Nhưng kiểu đối số cho drivebiết rằng CarDriverchỉ có thể điều khiển Cars - mâu thuẫn: CarDriver không thể là một lớp con thích hợp của Driver.

Điều ngược lại có ý nghĩa hơn:

class CarDriver {
    public drive(Car $c) { ... }
}
class MultiTalentedDriver extends CarDriver {
    public drive(Vehicle $v) { ... }
}

Một CarDriverchỉ có thể lái xe Cars. A MultiTalentedDrivercũng có thể lái Cars, vì a Carchỉ là a Vehicle. Do đó, MultiTalentedDriverlà một lớp con thích hợp của CarDriver.

Trong ví dụ của bạn, bất kỳ IApicó thể được xây dựng với một IConfig. Nếu IHttpApilà một kiểu con của IApi, chúng ta phải có khả năng xây dựng một trường hợp IHttpApisử dụng IConfig- nhưng nó chỉ chấp nhận IHttpConfig. Đây là một mâu thuẫn.


Không phải tất cả tài xế đều có thể lái cả ô tô và xe máy ...
sakisk

3
@faif: Trong sự trừu tượng đặc biệt này, họ không chỉ có thể, họ phải. Bởi vì, như bạn có thể thấy, một chiếc Drivercó thể lái bất kỳ Vehicle, và vì cả hai CarMotorcyclekéo dài Vehicle, tất cả đều Driverphải có khả năng xử lý cả hai.
Alex
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.