Ilegal di PHP: Apakah ada alasan desain OOP?

16

Warisan antarmuka di bawah ini ilegal di PHP, tapi saya pikir itu akan sangat berguna dalam kehidupan nyata. Apakah ada masalah antipattern atau terdokumentasi aktual dengan desain di bawah ini, sehingga PHP melindungi saya?

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

}
kojiro
sumber

Jawaban:

22

Mari kita abaikan sejenak bahwa metode yang dimaksud adalah __constructdan menyebutnya frobnicate. Sekarang anggaplah Anda memiliki objek apipelaksana IHttpApi, dan configpelaksana objek IHttpConfig. Jelas, kode ini sesuai dengan antarmuka:

$api->frobnicate($config)

Tapi mari kita misalkan kita upcast apiuntuk IApi, misalnya lewat itu untuk function frobnicateTwice(IApi $api). Sekarang dalam fungsi itu, frobnicatedipanggil, dan karena itu hanya berurusan dengan IApi, itu dapat melakukan panggilan seperti di $api->frobnicate(new SpecificConfig(...))mana SpecificConfigmengimplementasikan IConfigtetapi tidak IHttpConfig. Tidak ada seorangpun yang melakukan sesuatu yang tidak menyenangkan dengan tipe, tetapi IHttpApi::frobnicatemendapat SpecificConfigtempat yang diharapkan a IHttpConfig.

Ini tidak baik. Kami tidak ingin melarang upcasting, kami ingin subtyping, dan kami jelas ingin beberapa kelas mengimplementasikan antarmuka. Jadi satu-satunya pilihan yang masuk akal adalah melarang metode subtipe yang membutuhkan jenis parameter yang lebih spesifik . (Masalah serupa terjadi ketika Anda ingin mengembalikan jenis yang lebih umum .)

Secara formal, Anda telah memasuki jebakan klasik seputar polimorfisme, varian . Tidak semua kemunculan jenis Tdapat diganti dengan subtipe U. Sebaliknya, tidak semua kemunculan suatu tipe Tdapat digantikan oleh supertype S . Pertimbangan yang cermat (atau lebih baik lagi, penerapan teori jenis yang ketat) diperlukan.

Kembali ke __construct: Karena AFAIK Anda tidak dapat membuat instance antarmuka secara tepat, hanya implementer konkret, ini mungkin tampak seperti pembatasan tanpa tujuan (tidak akan pernah dipanggil melalui antarmuka). Tetapi dalam kasus itu, mengapa termasuk __constructdalam antarmuka untuk memulai? Bagaimanapun, itu akan sedikit berguna untuk kasus khusus di __constructsini.


sumber
19

Ya, ini mengikuti langsung dari Prinsip Pergantian Liskov (LSP) . Saat Anda mengganti metode, tipe pengembalian bisa menjadi lebih spesifik, sedangkan tipe argumen harus tetap sama atau bisa menjadi lebih umum.

Ini lebih jelas dengan metode selain __construct. Mempertimbangkan:

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

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

A CarDriveradalah a Driver, jadi CarDriverinstance harus dapat melakukan apa pun yang Driverbisa. Termasuk mengemudi Motorcycles, karena hanya a Vehicle. Tetapi tipe argumen untuk drivemengatakan bahwa a CarDriverhanya dapat menggerakkan Cars - sebuah kontradiksi: CarDriver tidak dapat menjadi subkelas yang tepat Driver.

Kebalikannya lebih masuk akal:

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

A CarDriverhanya dapat mengemudi Cars. A MultiTalentedDriverjuga dapat menggerakkan Cars, karena a Carhanya a Vehicle. Oleh karena itu, MultiTalentedDriveradalah subkelas yang tepat CarDriver.

Dalam contoh Anda, apa pun IApidapat dibangun dengan IConfig. Jika IHttpApimerupakan subtipe dari IApi, kita harus dapat membangun IHttpApimenggunakan IConfigcontoh apa pun - tetapi hanya menerima IHttpConfig. Ini adalah kontradiksi.

amon
sumber
Tidak semua pengemudi dapat mengendarai mobil dan motor ...
sakisk
3
@faif: Dalam abstraksi khusus ini, mereka tidak hanya bisa, mereka harus. Karena, seperti yang Anda lihat, seorang Driverdapat mendorong setiap Vehicle, dan karena keduanya Cardan Motorcyclemeluas Vehicle, semua Drivers harus dapat menangani keduanya.
Alex