Mari kita abaikan sejenak bahwa metode yang dimaksud adalah __construct
dan menyebutnya frobnicate
. Sekarang anggaplah Anda memiliki objek api
pelaksana IHttpApi
, dan config
pelaksana objek IHttpConfig
. Jelas, kode ini sesuai dengan antarmuka:
$api->frobnicate($config)
Tapi mari kita misalkan kita upcast api
untuk IApi
, misalnya lewat itu untuk function frobnicateTwice(IApi $api)
. Sekarang dalam fungsi itu, frobnicate
dipanggil, dan karena itu hanya berurusan dengan IApi
, itu dapat melakukan panggilan seperti di $api->frobnicate(new SpecificConfig(...))
mana SpecificConfig
mengimplementasikan IConfig
tetapi tidak IHttpConfig
. Tidak ada seorangpun yang melakukan sesuatu yang tidak menyenangkan dengan tipe, tetapi IHttpApi::frobnicate
mendapat SpecificConfig
tempat 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 T
dapat diganti dengan subtipe U
. Sebaliknya, tidak semua kemunculan suatu tipe T
dapat 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 __construct
dalam antarmuka untuk memulai? Bagaimanapun, itu akan sedikit berguna untuk kasus khusus di __construct
sini.
Driver
dapat mendorong setiapVehicle
, dan karena keduanyaCar
danMotorcycle
meluasVehicle
, semuaDriver
s harus dapat menangani keduanya.