Apakah Prinsip Pengganti Liskov tidak sesuai dengan Introspeksi atau Mengetik Bebek?

11

Apakah saya mengerti benar bahwa Prinsip Pergantian Liskov tidak dapat diamati dalam bahasa di mana objek dapat memeriksa diri mereka sendiri, seperti apa yang biasa dalam bahasa yang diketik bebek?

Misalnya, di Ruby, jika kelas Bmewarisi dari kelas A, maka untuk setiap objek xdari A, x.classakan kembali A, tetapi jika xadalah obyek B, x.classtidak akan kembali A.

Berikut ini adalah pernyataan LSP:

Biarkan q (x) menjadi dibuktikan properti tentang obyek x tipe T . Kemudian q (y) harus dibuktikan untuk benda y tipe S di mana S adalah subtipe dari T .

Jadi di Ruby, misalnya,

class T; end
class S < T; end

melanggar LSP dalam formulir ini, seperti yang disaksikan oleh properti q (x) =x.class.name == 'T'


Tambahan. Jika jawabannya "ya" (LSP tidak sesuai dengan introspeksi), maka pertanyaan saya yang lain adalah: apakah ada beberapa bentuk LSP "lemah" yang dimodifikasi yang dapat digunakan untuk bahasa yang dinamis, mungkin dalam beberapa kondisi tambahan dan hanya dengan jenis khusus dari sifat .


Memperbarui. Untuk referensi, berikut adalah formulasi lain dari LSP yang saya temukan di web:

Fungsi yang menggunakan pointer atau referensi ke kelas dasar harus dapat menggunakan objek dari kelas turunan tanpa menyadarinya.

Dan satu lagi:

Jika S adalah subtipe T yang dinyatakan, objek tipe S harus berperilaku sebagai objek tipe T yang diharapkan berperilaku, jika mereka diperlakukan sebagai objek tipe T.

Yang terakhir dijelaskan dengan:

Perhatikan bahwa LSP adalah semua tentang perilaku objek yang diharapkan. Seseorang hanya dapat mengikuti LSP jika dia jelas tentang apa perilaku objek yang diharapkan.

Ini tampaknya lebih lemah daripada yang asli, dan mungkin dapat diamati, tetapi saya ingin melihatnya diformalkan, khususnya yang menjelaskan siapa yang memutuskan apa perilaku yang diharapkan.

Apakah kemudian LSP bukan properti sepasang kelas dalam bahasa pemrograman, tetapi sepasang kelas bersama-sama dengan serangkaian properti yang diberikan, puas oleh kelas leluhur? Secara praktis, apakah ini berarti bahwa untuk membangun subclass (kelas keturunan) yang menghormati LSP, semua kemungkinan penggunaan kelas leluhur harus diketahui? Menurut LSP, kelas leluhur seharusnya bisa diganti dengan kelas turunan, kan?


Memperbarui. Saya sudah menerima jawabannya, tetapi saya ingin menambahkan satu lagi contoh nyata dari Ruby untuk mengilustrasikan pertanyaan itu. Di Ruby, setiap kelas adalah modul dalam arti bahwa Classkelas adalah turunan dari Modulekelas. Namun:

class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module

module M; end
M.class # => Module

o = Object.new

o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Alexey
sumber
2
Hampir semua bahasa modern memberikan sedikit introspeksi, jadi pertanyaannya tidak terlalu spesifik untuk Ruby.
Joachim Sauer
Saya mengerti, saya memberi Ruby hanya sebagai contoh. Saya tidak tahu, mungkin dalam beberapa bahasa lain dengan introspeksi ada beberapa "bentuk lemah" dari LSP, tetapi, jika saya memahami prinsipnya dengan benar, itu tidak sesuai dengan introspeksi.
Alexey
Saya telah menghapus "Ruby" dari judul.
Alexey
2
Jawaban singkatnya adalah mereka kompatibel. Berikut adalah posting blog yang sebagian besar saya setujui: Prinsip Substitusi Liskov untuk Mengetik Bebek
K.Steff
2
@Alexey Properties dalam konteks ini adalah invarian dari suatu objek. Misalnya, objek yang tidak dapat diubah memiliki properti yang nilainya tidak berubah. Jika Anda melihat tes unit yang baik, mereka harus menguji dengan tepat untuk properti ini.
K.Steff

Jawaban:

29

Inilah prinsip yang sebenarnya :

Membiarkan q(x)menjadi properti yang dapat dibuktikan tentang objek xbertipe T. Maka q(y)harus dapat dibuktikan untuk objek ybertipe Smana Sadalah subtipe T.

Dan ringkasan wikipedia yang luar biasa :

Ini menyatakan bahwa, dalam program komputer, jika S adalah subtipe dari T, maka objek tipe T dapat diganti dengan objek tipe S (yaitu, objek tipe S dapat diganti untuk objek tipe T) tanpa mengubah salah satu dari sifat-sifat yang diinginkan dari program itu (kebenaran, tugas dilakukan, dll).

Dan beberapa kutipan yang relevan dari koran:

Apa yang dibutuhkan adalah persyaratan yang lebih kuat yang membatasi perilaku sub-tipe: properti yang dapat dibuktikan menggunakan spesifikasi objek yang diduga tipe harus dipegang walaupun objek tersebut sebenarnya adalah anggota subtipe dari jenis itu ...

Spesifikasi tipe termasuk informasi berikut:
- Nama tipe;
- Deskripsi ruang nilai tipe;
- Untuk masing-masing metode tipe:
--- Namanya;
--- Tanda tangannya (termasuk pengecualian yang ditandai);
--- Perilakunya dalam hal pra-kondisi dan pasca-kondisi.

Demikian pertanyaan selanjutnya:

Apakah saya mengerti benar bahwa Prinsip Pergantian Liskov tidak dapat diamati dalam bahasa di mana objek dapat memeriksa diri mereka sendiri, seperti apa yang biasa dalam bahasa yang diketik bebek?

Tidak.

A.classmengembalikan kelas.
B.classmengembalikan kelas.

Karena Anda dapat melakukan panggilan yang sama pada jenis yang lebih spesifik dan mendapatkan hasil yang kompatibel, LSP menahan. Masalahnya adalah bahwa dengan bahasa dinamis, Anda masih dapat memanggil hal-hal pada hasil yang mengharapkan mereka ada di sana.

Tapi mari kita pertimbangkan bahasa yang diketik secara statis, struktural (bebek). Dalam hal ini, A.classakan mengembalikan tipe dengan batasan yang harus Aatau subtipe A. Ini memberikan jaminan statis bahwa subtipe apa pun Aharus menyediakan metode T.classyang hasilnya adalah jenis yang memenuhi kendala itu.

Ini memberikan pernyataan yang lebih kuat yang dimiliki LSP dalam bahasa yang mendukung pengetikan bebek, dan bahwa setiap pelanggaran LSP dalam sesuatu seperti Ruby terjadi lebih karena penyalahgunaan dinamis normal daripada ketidakcocokan desain bahasa.

Telastyn
sumber
1
"Karena Anda dapat melakukan panggilan yang sama pada jenis yang lebih spesifik dan mendapatkan hasil yang kompatibel, LSP menahan". LSP berlaku jika hasilnya identik, jika saya memahaminya dengan benar. Mungkin ada beberapa bentuk "minggu" LSP sehubungan dengan kendala yang diberikan, membutuhkan alih-alih semua properti yang hanya kendala yang diberikan dapat dipenuhi. Bagaimanapun, saya akan menghargai referensi apa pun.
Alexey
@Alexey diedit untuk memasukkan apa artinya LSP. Jika saya bisa menggunakan B di mana saya mengharapkan A, maka LSP berlaku. Saya ingin tahu bagaimana menurut Anda. Kelas Ruby mungkin melanggar itu.
Telastyn
3
@ Alexey - Jika program Anda berisi fail unless x.foo == 42dan subtipe mengembalikan 0 itu hal yang sama. Ini bukan kegagalan LSP, ini adalah operasi normal program Anda. Polimorfisme bukanlah pelanggaran terhadap LSP.
Telastyn
1
@ Alexey - Tentu. Mari kita asumsikan bahwa itu adalah properti. Jika demikian, kode Anda melanggar LSP karena tidak mengizinkan subtipe memiliki perilaku semantik yang sama. Tapi itu tidak terlalu khusus untuk bahasa yang diketik dinamis atau bebek. Tidak ada yang mereka lakukan dalam desain bahasa mereka yang menyebabkan pelanggaran. Kode yang Anda tulis tidak. Ingat, LSP adalah prinsip desain program (karenanya harus dalam definisi) daripada sifat matematika program.
Telastyn
6
@Alexey: jika Anda menulis sesuatu yang bergantung pada x.class == A, maka kode Anda yang melanggar LSP , bukan bahasa. Dimungkinkan untuk menulis kode yang melanggar LSP di hampir setiap bahasa pemrograman.
Andres F.
7

Dalam konteks LSP "properti" adalah sesuatu yang dapat diamati pada suatu tipe (atau objek). Secara khusus ia berbicara tentang "properti yang dapat dibuktikan".

"Properti" seperti itu dapat memiliki foo()metode yang tidak memiliki nilai pengembalian (dan mengikuti kontrak yang ditetapkan dalam dokumentasinya).

Pastikan Anda tidak mengacaukan istilah ini dengan "properti" seperti pada " classadalah properti dari setiap objek di Ruby". Seperti sebuah "properti" bisa menjadi "LSP properti", tapi itu tidak secara otomatis sama!

Sekarang jawaban atas pertanyaan Anda sangat tergantung pada seberapa ketat Anda mendefinisikan "properti". Jika Anda mengatakan "milik kelas Aadalah bahwa .classakan kembali jenis objek", maka Bsebenarnya tidak memiliki properti itu.

Namun, jika Anda mendefinisikan "properti" menjadi " .classkembali A", maka jelas Btidak tidak memiliki properti itu.

Namun, definisi kedua tidak terlalu berguna, karena pada dasarnya Anda telah menemukan cara bulat untuk mendeklarasikan konstanta.

Joachim Sauer
sumber
Saya hanya dapat memikirkan satu definisi dari "properti" suatu program: untuk input yang diberikan, ia mengembalikan nilai yang diberikan, atau, lebih umum, ketika digunakan sebagai blok di program lain, bahwa program lain untuk input yang diberikan akan mengembalikan nilai yang diberikan. Dengan definisi ini, saya tidak melihat apa artinya " .classakan mengembalikan jenis objek". Jika itu berarti x.class == x.class, ini bukan properti yang menarik.
Alexey
1
@ Alexey: Saya telah memperbarui pertanyaan saya dengan klarifikasi tentang apa yang dimaksud dengan "properti" dalam konteks LSP.
Joachim Sauer
2
@ Alexey: melihat ke dalam kertas saya tidak menemukan definisi khusus atau "properti". Itu mungkin karena istilah ini digunakan dalam pengertian CS umum "sesuatu yang dapat diamati / dibuktikan tentang beberapa objek". Ini tidak ada hubungannya dengan yang lain berarti "sebuah bidang objek".
Joachim Sauer
4
@ Alexey: Saya tidak tahu apa lagi yang bisa saya katakan. Saya menggunakan definisi "properti adalah kualitas atau sifat dari suatu objek". "warna" adalah properti dari objek fisik yang terlihat. "Kepadatan" adalah properti material. "memiliki metode yang ditentukan" adalah properti dari kelas / objek.
Joachim Sauer
4
@ Alexey: Saya pikir Anda melempar bayi dengan air mandi: Hanya karena untuk beberapa properti LSP tidak dapat dibuat untuk menahan tidak berarti bahwa itu tidak berguna atau "tidak berlaku dalam bahasa apa pun". Tetapi diskusi itu akan jauh di sini.
Joachim Sauer
5

Seperti yang saya pahami, tidak ada apapun tentang introspeksi yang tidak sesuai dengan LSP. Pada dasarnya, selama suatu objek mendukung metode yang sama seperti yang lain, keduanya harus dipertukarkan. Artinya, jika kode Anda mengharapkan Addressobjek, maka tidak peduli apakah itu CustomerAddressatau WarehouseAddress, asalkan keduanya memberikan (misalnya) getStreetAddress(), getCityName(), getRegion()dan getPostalCode(). Anda tentu bisa membuat beberapa jenis dekorator yang mengambil berbagai jenis objek dan menggunakan introspeksi untuk memberikan metode yang diperlukan (misalnya, DestinationAddresskelas yang mengambil Shipmentobjek dan menyajikan kapal-ke alamat sebagai Address), tetapi itu tidak diperlukan dan tentu saja tidak tidak dapat mencegah LSP diterapkan.

TMN
sumber
2
@ Alexey: Objeknya "sama" jika mereka mendukung metode yang sama. Ini berarti nama yang sama, jumlah dan jenis argumen yang sama, jenis pengembalian yang sama dan efek samping yang sama (selama mereka terlihat oleh kode panggilan). Metodenya mungkin berperilaku sangat berbeda, tetapi selama mereka menghormati kontrak, tidak apa-apa.
TMN
1
@ Alexey: tapi mengapa saya memiliki kontrak seperti itu? Apa penggunaan aktual yang kontrak yang memenuhi? Jika saya memiliki kontrak seperti itu, saya dapat dengan mudah mengganti setiap kejadian x.class.namedengan 'A' , secara efektif membuat x.class.name tidak berguna .
Joachim Sauer
1
@Alexey: lagi: hanya karena Anda dapat menentukan kontrak yang tidak dapat dipenuhi dengan memperluas kelas lain tidak merusak LSP. Itu hanya berarti Anda telah membangun kelas yang tidak dapat diperpanjang. Jika saya mendefinisikan metode untuk "mengembalikan jika blok kode yang disediakan berakhir dalam waktu yang terbatas " maka saya juga memiliki kontrak yang tidak dapat dipenuhi. Bukan berarti pemrograman itu tidak berguna.
Joachim Sauer
2
@ Alexey mencoba untuk menentukan apakah x.class.name == 'A'anti-pola dalam mengetik bebek: setelah semua, mengetik bebek berasal dari "Jika dukun dan berjalan seperti bebek, itu bebek". Jadi jika itu berperilaku seperti Adan menghormati kontrak yang Adiajukan, itu adalah Acontoh
K.Steff
1
@Alexey Anda telah disajikan dengan definisi yang jelas. Kelas objek bukan bagian dari perilaku atau kontraknya atau apa pun yang Anda ingin menyebutnya. Anda menyamakan "properti" dengan "bidang objek, seperti x.myField", yang telah ditunjukkan, BUKAN sama. Dalam konteks ini, properti lebih seperti properti matematika , seperti tipe invarian. Selain itu, ini merupakan anti-pola untuk memeriksa jenis yang tepat jika Anda ingin mengetik bebek. Jadi, apa masalah Anda dengan LSP dan bebek lagi? ;)
Andres F.
4

Setelah membaca makalah asli Barbara Liskov, saya menemukan cara untuk melengkapi definisi Wikipedia sehingga LSP benar-benar dapat dipenuhi dalam hampir semua bahasa.

Pertama-tama, kata "dapat dibuktikan" penting dalam definisi. Itu tidak dijelaskan dalam artikel Wikipedia, dan "kendala" disebutkan di tempat lain tanpa referensi.

Inilah kutipan penting pertama dari makalah ini:

Apa yang diperlukan adalah persyaratan yang lebih kuat yang membatasi perilaku sub-tipe: properti yang dapat dibuktikan menggunakan spesifikasi objek yang diduga tipe harus dipegang walaupun objek tersebut sebenarnya adalah anggota subtipe jenis itu ...

Dan inilah yang kedua, menjelaskan apa itu spesifikasi tipe :

Spesifikasi tipe termasuk informasi berikut:

  • Nama tipe;
  • Deskripsi ruang nilai tipe;
  • Untuk setiap metode tipe ini:
    • Namanya;
    • Tanda tangannya (termasuk pengecualian yang ditandai);
    • Perilakunya dalam hal pra-kondisi dan pasca-kondisi.

Jadi, LSP hanya masuk akal berkenaan dengan spesifikasi tipe yang diberikan , dan untuk spesifikasi tipe yang sesuai (untuk yang kosong misalnya), dapat dipenuhi dalam bahasa apa pun.

Saya menganggap jawaban Telastyn sebagai yang paling dekat dengan apa yang saya cari karena "kendala" disebutkan secara eksplisit.

Alexey
sumber
Telastyn, jika Anda dapat menambahkan kutipan ini ke jawaban Anda, saya lebih suka menerima kutipan Anda daripada saya sendiri.
Alexey
2
markupnya tidak persis sama, dan saya mengubah sedikit penekanannya, tetapi kutipannya disertakan.
Telastyn
Maaf, Joachim Sauer telah menyebutkan properti "dapat dibuktikan", yang tidak Anda sukai. Secara umum, Anda hanya menyatakan kembali jawaban yang ada. Jujur, saya tidak tahu apa yang Anda cari ...
Andres F.
Tidak, itu tidak dijelaskan "dapat dibuktikan dari apa?". Properti x.class.name = 'A'ini terbukti untuk semua xkelas Ajika Anda mengizinkan terlalu banyak pengetahuan. The tipe spesifikasi tidak didefinisikan, dan hubungan yang tepat dengan LSP tidak baik, meskipun informal beberapa indikasi diberikan. Saya telah menemukan apa yang saya cari di koran Liskov dan menjawab pertanyaan saya di atas.
Alexey
Saya pikir kata-kata yang Anda berani adalah kuncinya. Jika supertipe mendokumentasikan bahwa untuk jenis apa pun xitu, x.woozleakan menghasilkan undefined, maka tidak ada jenis yang x.woozletidak menghasilkan undefinedakan menjadi subtipe yang tepat. Jika supertype tidak mendokumentasikan apa pun tentang x.woozle, fakta bahwa penggunaan x.woozlepada supertype akan menghasilkan tidak undefinedakan menyiratkan apa-apa tentang apa yang mungkin dilakukan pada subtipe.
supercat
3

Mengutip artikel Wikipedia tentang LSP , "substitusi adalah prinsip dalam pemrograman berorientasi objek." Ini adalah prinsip dan bagian dari desain program Anda. Jika Anda menulis kode yang tergantung x.class == A, maka kode Anda yang melanggar LSP. Perhatikan bahwa kode rusak semacam ini juga dimungkinkan di Jawa, tidak perlu mengetik bebek.

Tidak ada dalam mengetik bebek yang secara inheren merusak LSP. Hanya jika Anda menyalahgunakannya, seperti dalam contoh Anda.

Pemikiran tambahan: bukankah secara eksplisit memeriksa kelas suatu objek mengalahkan tujuan dari mengetik bebek, toh?

Andres F.
sumber
Andres, bisakah Anda memberikan definisi Anda tentang LSP?
Alexey
1
@Alexey Definisi tepat LSP dinyatakan dalam Wikipedia dalam hal subtipe. Definisi informal adalah Liskov's notion of a behavioral subtype defines a notion of substitutability for mutable objects; that is, if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program (e.g., correctness).. Kelas yang tepat dari suatu objek BUKAN salah satu dari "sifat yang diinginkan dari program"; jika tidak, hal itu akan bertentangan dengan tidak hanya mengetik bebek tetapi subtipe secara umum, termasuk rasa Jawa.
Andres F.
2
@ Alexey Juga perhatikan bahwa contoh Anda x.class == Amelanggar LSP dan mengetik bebek. Tidak ada gunanya menggunakan bebek mengetik jika Anda akan memeriksa jenis yang sebenarnya.
Andres F.
Andres, definisi ini tidak cukup tepat untuk saya mengerti. Program mana, program tertentu, atau apa saja? Apa properti yang diinginkan? Jika kelas ada di perpustakaan, aplikasi yang berbeda dapat mempertimbangkan sifat yang berbeda yang diinginkan. A tidak melihat bagaimana baris kode dapat melanggar LSP, karena saya berpikir bahwa LSP adalah properti dari sepasang kelas dalam bahasa pemrograman yang diberikan: apakah ( A, B) memenuhi LSP atau tidak. Jika LSP tergantung pada kode yang digunakan di tempat lain, tidak dijelaskan kode apa yang diizinkan. Saya berharap menemukan sesuatu di sini: cse.ohio-state.edu/~neelam/courses/788/lwb.pdf
Alexey
2
@Alexey LSP memegang (atau tidak) untuk desain tertentu. Ini adalah sesuatu yang harus dicari dalam desain; ini bukan properti bahasa secara umum. Tidak bisa lebih tepat daripada definisi yang sebenarnya: Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T. Sudah jelas bahwa x.classini bukan salah satu properti yang menarik di sini; jika tidak , polimorfisme Jawa juga tidak akan berfungsi. Tidak ada yang melekat pada bebek mengetik dalam "masalah x.class" Anda. Apakah Anda setuju sejauh ini?
Andres F.