Jawaban dengan nilai tertinggi untuk pertanyaan ini tentang Prinsip Pergantian Liskov bersusah payah untuk membedakan antara istilah subtipe dan subkelas . Ini juga menunjukkan bahwa beberapa bahasa mengacaukan keduanya, sedangkan yang lain tidak.
Untuk bahasa berorientasi objek yang paling saya kenal (Python, C ++), "type" dan "class" adalah konsep yang identik. Dalam hal C ++, apa artinya memiliki perbedaan antara subtipe dan subkelas? Katakanlah, misalnya, itu Foo
adalah subkelas, tetapi bukan subtipe dari FooBase
. Jika foo
merupakan turunan dari Foo
, apakah baris ini:
FooBase* fbPoint = &foo;
tidak lagi valid?
Jawaban:
Subtyping adalah bentuk polimorfisme tipe di mana subtipe adalah tipe data yang terkait dengan tipe data lain (supertype) oleh beberapa gagasan tentang substitusi, artinya elemen program, biasanya subrutin atau fungsi, ditulis untuk beroperasi pada elemen supertipe juga dapat beroperasi pada elemen subtipe.
Jika
S
merupakan subtipe dariT
, hubungan subtyping sering ditulisS <: T
, yang berarti bahwa setiap istilah tipeS
dapat digunakan dengan aman dalam konteks di mana istilah tipeT
diharapkan. Semantik yang tepat dari subtyping secara krusial tergantung pada keterangan dari apa yang "aman digunakan dalam konteks di mana" berarti dalam bahasa pemrograman yang diberikan.Subclassing tidak harus bingung dengan subtyping. Secara umum, subtyping membentuk hubungan is-a, sedangkan subclassing hanya menggunakan kembali implementasi dan membangun hubungan sintaksis, tidak harus hubungan semantik (pewarisan tidak memastikan subtyping perilaku).
Untuk membedakan konsep-konsep ini, subtyping juga dikenal sebagai pewarisan antarmuka , sedangkan subclassing dikenal sebagai warisan implementasi atau pewarisan kode.
Referensi
subtyping
Warisan
sumber
public
pewarisan memperkenalkan subtipe sementaraprivate
pewarisan memperkenalkan subkelas.Suatu tipe , dalam konteks yang kita bicarakan di sini, pada dasarnya adalah seperangkat jaminan perilaku. Sebuah kontrak , jika Anda mau. Atau, meminjam terminologi dari Smalltalk, sebuah protokol .
Sebuah kelas adalah bundel metode. Ini adalah seperangkat implementasi perilaku .
Subtyping adalah cara untuk menyempurnakan protokol. Subclassing adalah cara menggunakan kembali kode diferensial, yaitu menggunakan kembali kode dengan hanya menggambarkan perbedaan dalam perilaku.
Jika Anda telah menggunakan Java atau C♯, maka Anda mungkin menemukan saran bahwa semua tipe harus
interface
tipe. Bahkan, jika Anda membaca Abstraksi Data Pemahaman William Cook , Dikunjungi Kembali , maka Anda mungkin tahu bahwa untuk melakukan OO dalam bahasa tersebut, Anda hanya boleh menggunakaninterface
s sebagai tipe. (Juga, fakta menyenangkan: Java cribbedinterface
s langsung dari protokol Objective-C, yang pada gilirannya diambil langsung dari Smalltalk.)Sekarang, jika kita mengikuti saran pengkodean ke kesimpulan logis dan membayangkan versi Java, di mana hanya
interface
tipe, dan kelas dan primitif tidak, maka satuinterface
mewarisi dari yang lain akan membuat hubungan subtipe, sedangkan yangclass
mewarisi dari yang lain akan hanya untuk menggunakan kembali kode diferensial melaluisuper
.Sejauh yang saya tahu, tidak ada bahasa yang diketik secara statis arus utama yang membedakan secara ketat antara kode pewarisan (implementasi pewarisan / subklasifikasi) dan kontrak pewarisan (subtipe). Di Java dan C♯, pewarisan antarmuka adalah subtyping murni (atau paling tidak, sampai diperkenalkannya metode default di Java 8 dan kemungkinan juga C♯ 8), tetapi pewarisan kelas juga subtipe dan juga pewarisan implementasi. Saya ingat pernah membaca tentang dialek LISP berorientasi objek eksperimental statis, yang secara ketat membedakan antara mixin (yang berisi perilaku), struct (yang berisi keadaan), interface (yang menggambarkanperilaku), dan kelas (yang menyusun nol atau lebih struct dengan satu atau lebih mixin dan sesuai dengan satu atau lebih antarmuka). Hanya kelas yang dapat dipakai, dan hanya antarmuka yang dapat digunakan sebagai tipe.
Dalam bahasa OO yang diketik secara dinamis seperti Python, Ruby, ECMAScript, atau Smalltalk, kita biasanya menganggap jenis objek sebagai sekumpulan protokol yang sesuai dengannya. Perhatikan bentuk jamak: suatu objek dapat memiliki beberapa tipe, dan saya tidak hanya berbicara tentang fakta bahwa setiap objek tipe
String
juga merupakan objek tipeObject
. (BTW: perhatikan bagaimana saya menggunakan nama kelas untuk berbicara tentang tipe? Betapa bodohnya saya!) Suatu objek dapat mengimplementasikan beberapa protokol. Misalnya, di Ruby,Arrays
dapat ditambahkan ke, mereka dapat diindeks, mereka dapat diiterasi, dan mereka dapat dibandingkan. Itu empat protokol berbeda yang mereka implementasikan!Sekarang, Ruby tidak memiliki tipe. Tetapi komunitas Ruby memiliki tipe! Mereka hanya ada di kepala programmer. Dan dalam dokumentasi. Misalnya, objek apa pun yang merespons metode yang disebut
each
dengan menghasilkan elemen-elemennya satu per satu dianggap sebagai objek yang dapat dihitung . Dan ada mixin yang dipanggilEnumerable
yang tergantung pada protokol ini. Jadi, jika objek Anda memiliki benar tipe (yang hanya ada di kepala programmer), maka diperbolehkan untuk campuran dalam (mewarisi dari)Enumerable
mixin, dan juga mendapatkan segala macam metode keren gratis, sepertimap
,reduce
,filter
dan sebagainya di.Demikian juga, jika sebuah objek merespon
<=>
, maka itu dianggap untuk melaksanakan sebanding protokol, dan dapat mencampur dalamComparable
mixin dan mendapatkan hal-hal seperti<
,<=
,>
,<=
,==
,between?
, danclamp
gratis. Namun, ia juga dapat mengimplementasikan semua metode itu sendiri, dan tidak mewarisi dariComparable
semuanya, dan itu masih akan dianggap sebanding .Contoh yang baik adalah
StringIO
perpustakaan, yang pada dasarnya memalsukan aliran I / O dengan string. Ini mengimplementasikan semua metode yang sama sepertiIO
kelas, tetapi tidak ada hubungan warisan antara keduanya. Namun demikian, aStringIO
dapat digunakan di mana saja danIO
dapat digunakan. Ini sangat berguna dalam unit test, di mana Anda dapat mengganti file ataustdin
denganStringIO
tanpa harus membuat perubahan lebih lanjut pada program Anda. KarenaStringIO
sesuai dengan protokol yang samaIO
, keduanya memiliki tipe yang sama, meskipun mereka adalah kelas yang berbeda, dan tidak memiliki hubungan (selain dari hal sepele yang mereka berdua kembangkanObject
pada beberapa titik).sumber
Mungkin pertama-tama berguna untuk membedakan antara suatu tipe dan kelas dan kemudian menyelami perbedaan antara subtipe dan subklas.
Untuk sisa jawaban ini saya akan berasumsi bahwa tipe dalam diskusi adalah tipe statis (karena subtyping biasanya muncul dalam konteks statis).
Saya akan mengembangkan pseudocode mainan untuk membantu mengilustrasikan perbedaan antara tipe dan kelas karena sebagian besar bahasa mengacaukan mereka setidaknya sebagian (untuk alasan yang baik saya akan menyinggung secara singkat).
Mari kita mulai dengan suatu tipe. Tipe adalah label untuk ekspresi dalam kode Anda. Nilai label ini dan apakah konsisten (untuk beberapa jenis definisi spesifik sistem-spesifik) dengan semua nilai label lain dapat ditentukan oleh program eksternal (pengetik ketik) tanpa menjalankan program Anda. Itulah yang membuat label-label ini istimewa dan pantas untuk namanya sendiri.
Dalam bahasa mainan kami, kami mungkin mengizinkan pembuatan label seperti itu.
Maka kita mungkin memberi label berbagai nilai sebagai tipe ini.
Dengan pernyataan ini, juru ketik kami sekarang dapat menolak pernyataan seperti
jika salah satu persyaratan dari sistem tipe kami adalah bahwa setiap ekspresi memiliki tipe yang unik.
Mari kita kesampingkan untuk sekarang betapa kikuknya ini dan bagaimana Anda akan mengalami masalah dalam menetapkan jumlah jenis ekspresi yang tak terbatas. Kita bisa kembali lagi nanti.
Kelas di sisi lain adalah kumpulan metode dan bidang yang dikelompokkan bersama (berpotensi dengan pengubah akses seperti pribadi atau publik).
Sebuah instance dari kelas ini mendapatkan kemampuan untuk membuat atau menggunakan definisi yang sudah ada sebelumnya dari metode dan bidang ini.
Kita bisa memilih untuk mengaitkan kelas dengan tipe sehingga setiap instance kelas secara otomatis dilabeli dengan tipe itu.
Tetapi tidak setiap jenis perlu memiliki kelas terkait.
Bisa dibayangkan juga bahwa dalam bahasa mainan kita tidak setiap kelas memiliki tipe, terutama jika tidak semua ekspresi kita memiliki tipe. Agak sulit (tapi bukan tidak mungkin) untuk membayangkan seperti apa aturan konsistensi sistem tipe jika beberapa ekspresi memiliki tipe dan beberapa tidak.
Terlebih lagi dalam bahasa mainan kita, asosiasi ini tidak harus unik. Kita bisa mengasosiasikan dua kelas dengan tipe yang sama.
Sekarang ingatlah bahwa tidak ada keharusan bagi juru ketik kami untuk melacak nilai ekspresi (dan dalam kebanyakan kasus tidak akan atau tidak mungkin melakukannya). Yang ia tahu hanyalah label yang Anda beri tahu. Sebagai pengingat sebelumnya, typechecker hanya dapat menolak pernyataan itu
0 is of type String
karena aturan jenis yang dibuat secara artifisial bahwa ekspresi harus memiliki tipe unik dan kami sudah memberi label ekspresi0
lain. Itu tidak memiliki pengetahuan khusus tentang nilai0
.Jadi bagaimana dengan subtyping? Subtipe dengan baik adalah nama untuk aturan umum dalam pengetikan yang merelaksasi aturan lain yang mungkin Anda miliki. Yaitu jika
A is subtype of B
kemudian di mana-mana typechecker Anda meminta labelB
, itu juga akan menerimaA
.Sebagai contoh, kita dapat melakukan hal berikut untuk nomor kita daripada apa yang kita miliki sebelumnya.
Subclassing adalah singkatan untuk mendeklarasikan kelas baru yang memungkinkan Anda untuk menggunakan kembali metode dan bidang yang sebelumnya dinyatakan.
Kami tidak harus mengaitkan contoh
ExtendedStringClass
denganString
seperti yang kami lakukanStringClass
sejak, setelah semua itu adalah kelas yang sama sekali baru, kami hanya tidak perlu menulis terlalu banyak. Ini akan memungkinkan kami untuk memberikanExtendedStringClass
jenis yang tidak kompatibel denganString
dari sudut pandang pengetik huruf.Demikian juga kita dapat memutuskan untuk membuat kelas yang baru
NewClass
dan selesaiSekarang setiap instance
StringClass
dapat diganti denganNewClass
dari sudut pandang typechecker.Jadi dalam teori, subtyping dan subclassing adalah hal yang sangat berbeda. Tapi tidak ada bahasa yang saya tahu yang memiliki tipe dan kelas yang benar-benar melakukan hal-hal seperti ini. Mari kita mulai mengurangi bahasa kita dan menjelaskan alasan di balik beberapa keputusan kita.
Pertama, meskipun dalam teori kelas yang benar-benar berbeda dapat diberikan jenis yang sama atau kelas dapat diberi jenis yang sama dengan nilai-nilai yang bukan contoh dari kelas apa pun, ini sangat menghambat kegunaan dari pengetik huruf. Typechecker dirampas secara efektif dari kemampuan untuk memeriksa apakah metode atau bidang yang Anda panggil dalam ekspresi benar-benar ada pada nilai itu, yang mungkin merupakan cek yang Anda inginkan jika Anda akan kesulitan bermain bersama dengan typechecker. Lagi pula, siapa yang tahu apa sebenarnya nilai di bawah
String
label itu; itu mungkin sesuatu yang tidak memiliki, misalnya,concatenate
metode sama sekali!Oke jadi mari kita menetapkan bahwa setiap kelas secara otomatis menghasilkan tipe baru dengan nama yang sama dengan kelas itu dan
associate
contoh dengan tipe itu. Itu memungkinkan kita menyingkirkanassociate
serta berbagai nama antaraStringClass
danString
.Untuk alasan yang sama, kami mungkin ingin secara otomatis membangun hubungan subtipe antara tipe dua kelas di mana satu adalah subkelas dari yang lain. Setelah semua subclass dijamin memiliki semua metode dan bidang yang dilakukan oleh kelas induknya, tetapi kebalikannya tidak benar. Oleh karena itu sementara subclass dapat lulus kapan saja Anda membutuhkan jenis kelas induk, jenis kelas induk harus ditolak jika Anda memerlukan jenis subclass.
Jika Anda menggabungkan ini dengan ketentuan bahwa semua nilai yang ditentukan pengguna harus merupakan instance dari sebuah kelas, maka Anda dapat
is subclass of
menarik dua tugas dan menyingkirkannyais subtype of
.Dan ini membawa kita pada karakteristik yang dimiliki oleh sebagian besar bahasa OO yang diketik secara statis. Ada satu set "primitif" jenis (misalnya
int
,float
, dll) yang tidak terkait dengan setiap kelas dan tidak ditetapkan pengguna. Kemudian Anda memiliki semua kelas yang ditentukan pengguna yang secara otomatis memiliki jenis nama yang sama dan mengidentifikasi subkelas dengan subtyping.Catatan terakhir yang akan saya buat adalah tentang kekakuan dari mendeklarasikan tipe secara terpisah dari nilai. Sebagian besar bahasa mengacaukan pembuatan keduanya, sehingga deklarasi tipe juga merupakan deklarasi untuk menghasilkan nilai yang sama sekali baru yang secara otomatis dilabeli dengan tipe itu. Sebagai contoh, deklarasi kelas biasanya menciptakan tipe dan juga cara instantiating nilai dari tipe itu. Ini menghilangkan beberapa kecerobohan dan, di hadapan konstruktor, juga memungkinkan Anda membuat label nilai tak terhingga banyak dengan tipe dalam satu pukulan.
sumber