Jika kotak adalah jenis persegi panjang, lalu mengapa persegi tidak bisa mewarisi dari persegi panjang? Atau mengapa itu desain yang buruk?
Saya telah mendengar orang berkata:
Jika Anda membuat Square berasal dari Rectangle, maka Square harus dapat digunakan di mana saja yang Anda harapkan persegi panjang
Apa masalah yang terjadi di sini? Dan mengapa Square dapat digunakan di mana saja Anda mengharapkan persegi panjang? Itu hanya dapat digunakan jika kita membuat objek Square, dan jika kita menimpa metode SetWidth dan SetHeight untuk Square daripada mengapa ada masalah?
Jika Anda memiliki metode SetWidth dan SetHeight pada kelas dasar Rectangle Anda dan jika referensi Rectangle Anda menunjuk ke sebuah Square, maka SetWidth dan SetHeight tidak masuk akal karena pengaturan yang satu akan mengubah yang lain untuk mencocokkannya. Dalam kasus ini, Square gagal dalam Tes Substitusi Liskov dengan Rectangle dan abstraksi untuk memiliki Square diwarisi dari Rectangle adalah yang buruk.
Adakah yang bisa menjelaskan argumen di atas? Sekali lagi, jika kita over-naik metode SetWidth dan SetHeight di Square, bukankah itu akan menyelesaikan masalah ini?
Saya juga pernah mendengar / membaca:
Masalah sebenarnya adalah bahwa kita tidak memodelkan persegi panjang, melainkan "persegi panjang yang dibentuk kembali" yaitu, persegi panjang yang lebar atau tingginya dapat dimodifikasi setelah pembuatan (dan kami masih menganggapnya sebagai objek yang sama). Jika kita melihat kelas persegi panjang dengan cara ini, jelas bahwa persegi bukanlah "persegi panjang yang dibentuk kembali", karena persegi tidak dapat dibentuk kembali dan masih menjadi persegi (secara umum). Secara matematis, kita tidak melihat masalah karena ketidakstabilan bahkan tidak masuk akal dalam konteks matematika
Di sini saya percaya "re-sizeable" adalah istilah yang benar. Kotak adalah "re-sizeable" dan begitu juga kotak. Apakah saya kehilangan sesuatu dalam argumen di atas? Sebuah persegi dapat berukuran ulang seperti persegi panjang apa pun.
sumber
Why do we even need Square
? Seperti memiliki dua pena. Satu pena biru dan satu pena merah biru, kuning atau hijau. Pena biru itu mubazir - bahkan lebih dalam kasus kotak karena tidak memiliki manfaat biaya.Jawaban:
Pada dasarnya kami ingin hal-hal berperilaku bijaksana
Pertimbangkan masalah berikut:
Saya diberi sekelompok persegi panjang dan saya ingin menambah luasnya 10%. Jadi yang saya lakukan adalah saya mengatur panjang persegi panjang menjadi 1,1 kali dari sebelumnya.
Sekarang dalam kasus ini, semua persegi panjang saya sekarang memiliki panjangnya meningkat 10%, yang akan meningkatkan area mereka sebesar 10%. Sayangnya, seseorang benar-benar telah memberi saya campuran kotak dan persegi panjang, dan ketika panjang persegi diubah, begitu pula lebarnya.
Tes unit saya lulus karena saya menulis semua tes unit saya untuk menggunakan kumpulan persegi panjang. Saya sekarang telah memperkenalkan bug halus ke dalam aplikasi saya yang dapat luput dari perhatian selama berbulan-bulan.
Lebih buruk lagi, Jim dari akuntansi melihat metode saya dan menulis beberapa kode lain yang menggunakan fakta bahwa jika ia memasukkan kotak ke dalam metode saya, ia mendapatkan peningkatan 21% dalam ukuran yang sangat bagus. Jim bahagia dan tidak ada yang lebih bijak.
Jim dipromosikan untuk pekerjaan luar biasa ke divisi yang berbeda. Alfred bergabung dengan perusahaan sebagai junior. Dalam laporan bug pertamanya, Jill dari Advertising telah melaporkan bahwa meneruskan kuadrat ke metode ini menghasilkan peningkatan 21% dan ingin bug diperbaiki. Alfred melihat bahwa Squares dan Rectangles digunakan di mana-mana dalam kode dan menyadari bahwa memutus rantai pewarisan tidak mungkin. Dia juga tidak memiliki akses ke kode sumber Akuntansi. Jadi Alfred memperbaiki bug seperti ini:
Alfred senang dengan keterampilan meretas uber dan Jill menandatangani bahwa bug diperbaiki.
Bulan depan tidak ada yang dibayar karena Akuntansi bergantung pada kemampuan untuk lulus kuadrat ke
IncreaseRectangleSizeByTenPercent
metode dan mendapatkan peningkatan area 21%. Seluruh perusahaan masuk ke mode "prioritas 1 bugfix" untuk melacak sumber masalah. Mereka melacak masalahnya sampai pada perbaikan Alfred. Mereka tahu bahwa mereka harus membuat Akuntansi dan Periklanan tetap bahagia. Jadi mereka memperbaiki masalah dengan mengidentifikasi pengguna dengan pemanggilan metode seperti:Dan seterusnya dan seterusnya.
Anekdot ini didasarkan pada situasi dunia nyata yang dihadapi programmer setiap hari. Pelanggaran terhadap prinsip Substitusi Liskov dapat menyebabkan bug yang sangat halus yang hanya dapat diambil bertahun-tahun setelah ditulis, dimana saat memperbaiki pelanggaran akan merusak banyak hal dan tidak memperbaikinya akan membuat marah klien terbesar Anda.
Ada dua cara realistis untuk memperbaiki masalah ini.
Cara pertama adalah membuat Rectangle tidak berubah. Jika pengguna Rectangle tidak dapat mengubah properti Panjang dan Lebar, masalah ini hilang. Jika Anda ingin Rectangle dengan panjang dan lebar yang berbeda, Anda membuat yang baru. Kotak dapat mewarisi dari persegi panjang dengan senang hati.
Cara kedua adalah memutus rantai warisan antara kotak dan persegi panjang. Jika sebuah persegi didefinisikan sebagai memiliki satu
SideLength
properti dan persegi panjang memilikiLength
danWidth
properti dan tidak ada warisan, tidak mungkin untuk secara tidak sengaja memecahkan sesuatu dengan mengharapkan persegi panjang dan mendapatkan persegi. Dalam istilah C #, Anda bisaseal
kelas persegi panjang Anda, yang memastikan bahwa semua Persegi Panjang yang Anda dapatkan sebenarnya adalah Persegi Panjang.Dalam hal ini, saya suka cara "objek abadi" untuk memperbaiki masalah. Identitas persegi panjang adalah panjang dan lebarnya. Masuk akal bahwa ketika Anda ingin mengubah identitas suatu objek, apa yang sebenarnya Anda inginkan adalah objek baru . Jika Anda kehilangan pelanggan lama dan mendapatkan pelanggan baru, Anda tidak mengubah
Customer.Id
bidang dari pelanggan lama ke yang baru, Anda membuat yang baruCustomer
.Pelanggaran prinsip Pergantian Liskov adalah hal biasa di dunia nyata, sebagian besar karena banyak kode di luar sana ditulis oleh orang-orang yang tidak kompeten / di bawah tekanan waktu / tidak peduli / melakukan kesalahan. Itu bisa dan memang mengarah pada beberapa masalah yang sangat buruk. Dalam kebanyakan kasus, Anda lebih memilih komposisi daripada warisan .
sumber
Jika semua objek Anda tidak berubah, tidak ada masalah. Setiap kotak juga merupakan persegi panjang. Semua properti dari Rectangle juga properti dari sebuah Square.
Masalahnya dimulai ketika Anda menambahkan kemampuan untuk memodifikasi objek. Atau sungguh - ketika Anda mulai menyampaikan argumen ke objek, tidak hanya membaca getter properti.
Ada modifikasi yang dapat Anda lakukan untuk Rectangle yang mempertahankan semua invarian kelas Rectangle Anda, tetapi tidak semua invarian Square - seperti mengubah lebar atau tinggi. Tiba-tiba perilaku Rectangle tidak hanya sifat-sifatnya, itu juga kemungkinan modifikasi. Bukan hanya apa yang Anda dapatkan dari Rectangle, itu juga apa yang bisa Anda masukkan .
Jika Persegi Panjang Anda memiliki metode
setWidth
yang didokumentasikan sebagai mengubah lebar dan tidak mengubah ketinggian, maka Persegi tidak dapat memiliki metode yang kompatibel. Jika Anda mengubah lebar dan bukan tinggi, hasilnya bukan lagi kotak yang valid. Jika Anda memilih untuk memodifikasi lebar dan tinggi Square saat menggunakansetWidth
, Anda tidak menerapkan spesifikasi RectanglesetWidth
. Anda tidak bisa menang.Saat Anda melihat apa yang dapat Anda "masukkan ke dalam" Persegi Panjang dan Kotak, pesan apa yang dapat Anda kirim kepada mereka, Anda mungkin akan menemukan bahwa pesan apa pun yang dapat Anda kirim secara sah ke Kotak, Anda juga dapat mengirim ke Kotak.
Ini masalah ko-varians vs kontra-varians.
Metode subclass yang tepat, dimana instance dapat digunakan dalam semua kasus di mana superclass diharapkan, mengharuskan setiap metode untuk:
Jadi, kembali ke Rectangle dan Square: Apakah Square dapat menjadi subclass dari Rectangle sepenuhnya tergantung pada metode apa yang dimiliki Rectangle.
Jika Rectangle memiliki setter individu untuk lebar dan tinggi, Kotak tidak akan menjadi subclass yang baik.
Demikian juga, jika Anda membuat beberapa metode menjadi co-varian dalam argumen, seperti memiliki
compareTo(Rectangle)
di Rectangle dancompareTo(Square)
di Square, Anda akan memiliki masalah menggunakan Square sebagai Rectangle.Jika Anda mendesain Square dan Rectangle Anda agar kompatibel, itu kemungkinan akan berhasil, tetapi mereka harus dikembangkan bersama, atau saya berani bertaruh bahwa itu tidak akan berhasil.
sumber
Ada banyak jawaban bagus di sini; Jawaban Stephen secara khusus melakukan pekerjaan yang baik untuk menggambarkan mengapa pelanggaran prinsip substitusi menyebabkan konflik dunia nyata antara tim.
Saya pikir saya mungkin berbicara secara singkat tentang masalah khusus persegi panjang dan bujur sangkar, daripada menggunakannya sebagai metafora untuk pelanggaran lain pada LSP.
Ada masalah tambahan dengan square-is-a-special-of-rectangle yang jarang disebutkan, dan itu adalah: mengapa kita berhenti dengan kotak dan persegi panjang ? Jika kita mau mengatakan bahwa persegi adalah jenis persegi panjang khusus maka tentunya kita juga harus bersedia untuk mengatakan:
Apa hubungan semua hubungan di sini? Bahasa berbasis warisan kelas seperti C # atau Java tidak dirancang untuk mewakili jenis hubungan yang kompleks dengan berbagai jenis kendala. Sebaiknya hindari pertanyaan sepenuhnya dengan tidak mencoba mewakili semua hal ini sebagai kelas dengan hubungan subtipe.
sumber
IShape
jenis yang mencakup kotak pembatas, dan dapat digambar, diskalakan, dan diserialisasi, dan memilikiIPolygon
subtipe dengan metode untuk melaporkan jumlah simpul dan metode untuk mengembalikan sebuahIEnumerable<Point>
. Seseorang kemudian dapat memilikiIQuadrilateral
subtipe yang berasal dariIPolygon
,IRhombus
danIRectangle
, berasal dari itu, danISquare
berasal dariIRhombus
danIRectangle
. Mutability akan membuang semuanya ke luar jendela, dan multiple inheritance tidak bekerja dengan kelas, tapi saya pikir tidak masalah dengan antarmuka yang tidak dapat diubah.IRhombus
jaminan bahwa semuaPoint
dikembalikan dari yangEnumerable<Point>
ditentukanIPolygon
sesuai dengan tepi dengan panjang yang sama? Karena implementasiIRhombus
antarmuka saja tidak menjamin bahwa objek konkret adalah-belah ketupat, warisan tidak bisa menjadi jawabannya.Dari perspektif matematika, kotak adalah-persegi panjang. Jika seorang ahli matematika memodifikasi kotak sehingga tidak lagi mematuhi kontrak persegi, itu berubah menjadi persegi panjang.
Namun dalam desain OO, ini merupakan masalah. Objek adalah apa adanya, dan ini termasuk perilaku serta keadaan. Jika saya memegang objek persegi, tetapi orang lain mengubahnya menjadi persegi panjang, itu melanggar kontrak persegi bukan karena kesalahan saya sendiri. Ini menyebabkan segala macam hal buruk terjadi.
Faktor kunci di sini adalah mutabilitas . Bisakah bentuk berubah setelah dibangun?
Mutable: jika bentuk dibiarkan berubah begitu dibangun, persegi tidak dapat memiliki hubungan is-a dengan persegi panjang. Kontrak persegi panjang mencakup batasan bahwa sisi yang berlawanan harus memiliki panjang yang sama, tetapi sisi yang berdekatan tidak harus. Kotak harus memiliki empat sisi yang sama. Memodifikasi persegi melalui antarmuka persegi panjang dapat melanggar kontrak persegi.
Abadi: jika bentuk tidak dapat berubah setelah dibangun, maka objek persegi juga harus selalu memenuhi kontrak persegi panjang. Kotak dapat memiliki hubungan is-a dengan persegi panjang.
Dalam kedua kasus itu mungkin untuk meminta kotak untuk menghasilkan bentuk baru berdasarkan kondisinya dengan satu atau lebih perubahan. Sebagai contoh, orang bisa mengatakan "buat persegi panjang baru berdasarkan kotak ini, kecuali sisi yang berlawanan A dan C dua kali lebih panjang." Karena objek baru sedang dibangun, alun-alun asli terus mematuhi kontraknya.
sumber
This is one of those cases where the real world is not able to be modeled in a computer 100%
. Kenapa begitu? Kita masih bisa memiliki model fungsional persegi dan persegi panjang. Satu-satunya konsekuensi adalah kita harus mencari konstruk yang lebih sederhana untuk abstrak atas dua objek tersebut.Karena itulah bagian dari makna subtipe (lihat juga: Prinsip substitusi Liskov). Anda dapat melakukannya, harus dapat melakukan ini:
Anda benar-benar melakukan ini sepanjang waktu (kadang-kadang bahkan lebih implisit) ketika menggunakan OOP.
Karena Anda tidak dapat menimpanya dengan masuk akal
Square
. Karena sebuah persegi tidak dapat "berukuran ulang seperti persegi panjang". Ketika ketinggian persegi panjang berubah, lebarnya tetap sama. Tetapi ketika ketinggian kotak berubah, lebar harus berubah sesuai. Masalahnya bukan hanya ukurannya yang besar, tetapi ukurannya yang besar di kedua dimensi secara mandiri.sumber
Rect r = s;
saluran, Anda bisa sajadoSomethingWith(s)
dan runtime akan menggunakan panggilan apa puns
untuk menyelesaikanSquare
metode virtual apa pun .setWidth
dansetHeight
untuk mengubah lebar dan tinggi.Apa yang Anda jelaskan bertentangan dengan apa yang disebut Prinsip Pergantian Liskov . Ide dasar dari LSP adalah bahwa setiap kali Anda menggunakan instance dari kelas tertentu, Anda harus selalu dapat menukar dengan instance dari setiap subkelas dari kelas itu, tanpa memperkenalkan bug.
Masalah Rectangle-Square sebenarnya bukan cara yang sangat baik untuk memperkenalkan Liskov. Ia mencoba menjelaskan prinsip luas menggunakan contoh yang sebenarnya cukup halus, dan berjalan bertabrakan dengan salah satu definisi intuitif paling umum dalam semua matematika. Ada yang menyebutnya masalah Ellipse-Circle karena alasan itu, tetapi sejauh ini hanya sedikit lebih baik. Pendekatan yang lebih baik adalah mengambil sedikit langkah mundur, menggunakan apa yang saya sebut masalah Parallelogram-Rectangle. Ini membuat banyak hal lebih mudah untuk dipahami.
Jajar genjang adalah segi empat dengan dua pasang sisi paralel. Ini juga memiliki dua pasang sudut yang kongruen. Tidak sulit membayangkan objek Paralelogram di sepanjang baris ini:
Salah satu cara umum untuk memikirkan persegi panjang adalah sebagai jajaran genjang dengan sudut kanan. Pada pandangan pertama, ini mungkin membuat Rectangle menjadi kandidat yang baik untuk mewarisi dari Parallelogram , sehingga Anda dapat menggunakan kembali semua kode yummy itu. Namun:
Mengapa kedua fungsi ini memperkenalkan bug di Rectangle? Masalahnya adalah bahwa Anda tidak dapat mengubah sudut dalam persegi panjang : mereka didefinisikan sebagai selalu 90 derajat, dan antarmuka ini sebenarnya tidak berfungsi untuk Rectangle yang diwarisi dari Parallelogram. Jika saya menukar Rectangle ke dalam kode yang mengharapkan Parallelogram, dan kode itu mencoba mengubah sudut, hampir pasti akan ada bug. Kami telah mengambil sesuatu yang dapat ditulisi di subclass dan membuatnya hanya-baca, dan itu merupakan pelanggaran Liskov.
Sekarang, bagaimana ini berlaku untuk Kotak dan Persegi Panjang?
Ketika kami mengatakan bahwa Anda dapat menetapkan nilai, kami biasanya mengartikan sesuatu yang sedikit lebih kuat daripada sekadar menuliskan nilai ke dalamnya. Kami menyiratkan tingkat eksklusivitas tertentu: jika Anda menetapkan nilai, kemudian melarang beberapa keadaan luar biasa, itu akan tetap pada nilai itu sampai Anda menetapkannya lagi. Ada banyak kegunaan untuk nilai-nilai yang dapat ditulis tetapi tidak tetap ditetapkan, tetapi ada juga banyak kasus yang bergantung pada nilai tetap di tempat itu setelah Anda mengaturnya. Dan di situlah kita mengalami masalah lain.
Kelas Square kami mewarisi bug dari Rectangle, tetapi ada beberapa bug baru. Masalah dengan setSideA dan setSideB adalah bahwa tidak satu pun dari ini benar-benar dapat diselesaikan lagi: Anda masih dapat menulis nilai ke salah satu dari keduanya, tetapi akan berubah dari bawah Anda jika yang lain ditulis. Jika saya menukar ini dengan Parallelogram dalam kode yang tergantung pada kemampuan untuk mengatur sisi secara independen satu sama lain, itu akan menjadi aneh.
Itulah masalahnya, dan itulah sebabnya ada masalah dengan menggunakan Rectangle-Square sebagai pengantar Liskov. Rectangle-Square tergantung pada perbedaan antara bisa menulis ke sesuatu dan bisa mengaturnya , dan itu perbedaan yang jauh lebih halus daripada bisa mengatur sesuatu dibandingkan membuatnya hanya bisa dibaca-saja. Rectangle-Square masih memiliki nilai sebagai contoh, karena mendokumentasikan gotcha yang cukup umum yang harus diwaspadai, tetapi tidak boleh digunakan sebagai contoh pengantar . Biarkan pelajar mendapatkan dasar-dasar terlebih dahulu, dan kemudian melemparkan sesuatu yang lebih keras pada mereka.
sumber
Subtyping adalah tentang perilaku.
Untuk tipe
B
menjadi subtipe tipeA
, itu harus mendukung setiap operasi yang mengetikA
mendukung dengan semantik yang sama (pembicaraan mewah untuk "perilaku"). Menggunakan alasan bahwa setiap B adalah A tidak tidak bekerja - kompatibilitas perilaku memiliki kata akhir. Sebagian besar waktu "B adalah sejenis A" tumpang tindih dengan "B berperilaku seperti A", tetapi tidak selalu .Sebuah contoh:
Pertimbangkan himpunan bilangan real. Dalam bahasa apapun, kita bisa mengharapkan mereka untuk mendukung operasi
+
,-
,*
, dan/
. Sekarang pertimbangkan himpunan bilangan bulat positif ({1, 2, 3, ...}). Jelas, setiap bilangan bulat positif juga bilangan real. Tetapi apakah tipe bilangan bulat positif merupakan subtipe dari tipe bilangan real? Mari kita lihat empat operasi dan lihat apakah bilangan bulat positif berperilaku sama seperti bilangan real:+
: Kami dapat menambahkan bilangan bulat positif tanpa masalah.-
: Tidak semua pengurangan bilangan bulat positif menghasilkan bilangan bulat positif. Misalnya3 - 5
.*
: Kita dapat melipatgandakan bilangan bulat positif tanpa masalah./
: Kami tidak selalu dapat membagi bilangan bulat positif dan mendapatkan bilangan bulat positif. Misalnya5 / 3
.Jadi, meskipun bilangan bulat positif menjadi subset dari bilangan real, mereka bukan subtipe. Argumen serupa dapat dibuat untuk bilangan bulat ukuran terbatas. Jelas setiap integer 32-bit juga merupakan integer 64-bit, tetapi
32_BIT_MAX + 1
akan memberi Anda hasil yang berbeda untuk setiap jenis. Jadi jika saya memberi Anda beberapa program dan Anda mengubah jenis setiap variabel integer 32-bit menjadi integer 64-bit, ada kemungkinan program akan berperilaku berbeda (yang hampir selalu berarti salah ).Tentu saja, Anda dapat menetapkan
+
untuk int 32-bit sehingga hasilnya adalah integer 64-bit, tetapi sekarang Anda harus mencadangkan 64 bit ruang setiap kali Anda menambahkan dua angka 32-bit. Itu mungkin atau mungkin tidak dapat Anda terima tergantung pada kebutuhan memori Anda.Mengapa ini penting?
Penting bagi program untuk menjadi benar. Ini bisa dibilang properti paling penting untuk dimiliki sebuah program. Jika suatu program benar untuk beberapa jenis
A
, satu-satunya cara untuk menjamin bahwa program akan terus benar untuk beberapa subtipeB
adalah jikaB
berperilaku sepertiA
dalam segala hal.Jadi Anda memiliki tipe
Rectangles
, yang spesifikasinya mengatakan sisi-sisinya dapat diubah secara independen. Anda menulis beberapa program yang menggunakanRectangles
dan menganggap implementasi mengikuti spesifikasi. Kemudian Anda memperkenalkan subtipe yang disebutSquare
sisi mana yang tidak dapat diubah ukurannya secara independen. Akibatnya, sebagian besar program yang mengubah ukuran persegi panjang sekarang akan salah.sumber
Hal pertama yang pertama, tanyakan pada diri sendiri mengapa Anda berpikir persegi adalah persegi panjang.
Tentu saja kebanyakan orang mengetahui hal itu di sekolah dasar, dan itu akan tampak jelas. Persegi panjang adalah bentuk 4 sisi dengan sudut 90 derajat, dan kotak memenuhi semua sifat itu. Jadi bukankah persegi adalah persegi panjang?
Masalahnya adalah bahwa itu semua tergantung pada apa kriteria awal Anda untuk mengelompokkan objek, konteks apa yang Anda lihat pada objek-objek ini. Dalam bentuk geometri diklasifikasikan berdasarkan sifat titik, garis, dan malaikatnya.
Jadi sebelum Anda bahkan mengatakan "persegi adalah jenis persegi panjang" Anda harus bertanya pada diri sendiri, apakah ini berdasarkan kriteria yang saya pedulikan .
Dalam sebagian besar kasus itu tidak akan menjadi apa yang Anda pedulikan sama sekali. Mayoritas sistem yang memodelkan bentuk, seperti GUI, grafik, dan permainan video, tidak terutama berkaitan dengan pengelompokan geometris suatu objek tetapi itu adalah perilaku. Pernahkah Anda bekerja pada sistem yang penting bahwa persegi adalah jenis persegi panjang dalam arti geometris. Apa yang bahkan memberi Anda, mengetahui bahwa ia memiliki 4 sisi dan sudut 90 derajat?
Anda tidak memodelkan sistem statis, Anda memodelkan sistem dinamis di mana hal-hal akan terjadi (bentuk akan dibuat, dihancurkan, diubah, digambar dll). Dalam konteks ini Anda peduli tentang perilaku bersama antara objek, karena perhatian utama Anda adalah apa yang dapat Anda lakukan dengan bentuk, aturan apa yang harus dipertahankan untuk tetap memiliki sistem yang koheren.
Dalam konteks ini sebuah persegi jelas bukan persegi panjang , karena aturan yang mengatur bagaimana persegi dapat diubah tidak sama dengan persegi panjang. Jadi mereka bukan tipe yang sama.
Dalam hal ini jangan memodelkannya seperti itu. Mengapa kamu akan? Ini memberi Anda apa-apa selain pembatasan yang tidak perlu.
Jika Anda melakukannya meskipun Anda secara praktis menyatakan dalam kode bahwa mereka bukan hal yang sama. Kode Anda akan mengatakan persegi berperilaku seperti ini dan persegi panjang berperilaku seperti itu tetapi mereka tetap sama.
Mereka jelas tidak sama dalam konteks yang Anda pedulikan karena Anda baru saja mendefinisikan dua perilaku yang berbeda. Jadi mengapa berpura-pura mereka sama jika mereka hanya mirip dalam konteks yang tidak Anda pedulikan?
Ini menyoroti masalah signifikan ketika pengembang datang ke domain yang ingin mereka modelkan. Sangat penting untuk memperjelas konteks apa yang Anda minati sebelum Anda mulai berpikir tentang objek dalam domain. Aspek apa yang Anda minati. Ribuan tahun yang lalu orang-orang Yunani memedulikan sifat-sifat bersama dari garis dan malaikat bentuk, dan mengelompokkannya berdasarkan hal-hal ini. Itu tidak berarti Anda dipaksa untuk melanjutkan pengelompokan itu jika bukan itu yang Anda pedulikan (yang dalam 99% pemodelan waktu dalam perangkat lunak Anda tidak akan peduli).
Banyak jawaban untuk pertanyaan ini berfokus pada sub-pengetikan tentang perilaku pengelompokan karena mereka aturan .
Tetapi sangat penting untuk memahami bahwa Anda tidak melakukan ini hanya untuk mengikuti aturan. Anda melakukan ini karena dalam sebagian besar kasus inilah yang sebenarnya Anda pedulikan. Anda tidak peduli jika persegi dan persegi panjang memiliki malaikat internal yang sama. Anda peduli tentang apa yang bisa mereka lakukan saat masih menjadi kotak dan persegi panjang. Anda peduli dengan perilaku objek karena Anda memodelkan sistem yang berfokus pada perubahan sistem berdasarkan aturan perilaku objek.
sumber
Rectangle
hanya digunakan untuk merepresentasikan nilai , maka dimungkinkan bagi suatu kelasSquare
untuk mewarisi dariRectangle
dan sepenuhnya mematuhi kontraknya. Sayangnya, banyak bahasa tidak membuat perbedaan antara variabel yang merangkum nilai dan yang mengidentifikasi entitas.Square
tipe yang tidak dapat diubah yang mewarisi dariRectnagle
tipe yang tidak dapat diubah dapat berguna jika ada beberapa jenis operasi yang hanya dapat dilakukan pada kotak. Sebagai contoh konsep yang realistis, pertimbangkanReadableMatrix
tipe [tipe dasar array persegi panjang yang dapat disimpan dengan berbagai cara, termasuk jarang], danComputeDeterminant
metode. Mungkin masuk akal untukComputeDeterminant
bekerja hanya denganReadableSquareMatrix
jenis yang berasal dariReadableMatrix
, yang saya anggap sebagai contoh yangSquare
berasal dari aRectangle
.Masalahnya terletak pada pemikiran bahwa jika hal-hal terkait dalam beberapa cara dalam kenyataan, mereka harus terkait dengan cara yang persis sama setelah pemodelan.
Hal paling penting dalam pemodelan adalah mengidentifikasi atribut umum dan perilaku umum, mendefinisikannya di kelas dasar dan menambahkan atribut tambahan di kelas anak.
Masalah dengan contoh Anda adalah, itu sepenuhnya abstrak. Selama tidak ada yang tahu, untuk apa Anda berencana menggunakan kelas itu, sulit menebak desain apa yang harus Anda buat. Tetapi jika Anda benar-benar hanya ingin memiliki tinggi, lebar dan ukuran, itu akan lebih logis untuk:
width
parameter danresize(double factor)
mengubah ukuran lebar dengan faktor yang diberikanheight
,, dan mengesampingkanresize
fungsinya, yang memanggilsuper.resize
dan kemudian mengubah ukuran ketinggian dengan faktor yang diberikanDari sudut pandang pemrograman, tidak ada dalam Square, yang tidak dimiliki Rectangle. Tidak ada gunanya membuat Kotak sebagai subclass dari Rectangle.
sumber
Karena oleh LSP, menciptakan hubungan warisan antara keduanya dan mengesampingkan
setWidth
dansetHeight
untuk memastikan kuadrat memiliki keduanya sama-sama memperkenalkan perilaku yang membingungkan dan tidak intuitif. Katakanlah kita memiliki kode:Tetapi jika metode
createRectangle
kembaliSquare
, karena itu mungkin berkatSquare
mewarisi dariRectange
. Kemudian harapan itu rusak. Di sini, dengan kode ini, kami berharap pengaturan lebar atau tinggi hanya akan menyebabkan perubahan lebar atau tinggi masing-masing. Maksud dari OOP adalah bahwa ketika Anda bekerja dengan superclass, Anda tidak memiliki pengetahuan tentang subkelas di bawahnya. Dan jika subclass mengubah perilaku sehingga bertentangan dengan harapan yang kita miliki tentang superclass, maka ada kemungkinan besar bug akan terjadi. Dan bug semacam itu sulit di-debug dan diperbaiki.Salah satu ide utama tentang OOP adalah bahwa itu adalah perilaku, bukan data yang diwarisi (yang juga merupakan salah satu kesalahpahaman utama IMO). Dan jika Anda melihat persegi dan persegi panjang, mereka tidak memiliki perilaku sendiri yang bisa kita hubungkan dalam hubungan pewarisan.
sumber
Yang dikatakan LSP adalah bahwa apa pun yang mewarisi dari
Rectangle
harus aRectangle
. Artinya, ia harus melakukan apa pun yangRectangle
dilakukannya.Mungkin dokumentasi untuk
Rectangle
ditulis untuk mengatakan bahwa perilaku seorangRectangle
bernamar
adalah sebagai berikut:Jika Kotak Anda tidak memiliki perilaku yang sama maka tidak berperilaku seperti
Rectangle
. Jadi LSP mengatakan itu tidak boleh diwarisi dariRectangle
. Bahasa tidak dapat menegakkan aturan ini, karena itu tidak bisa menghentikan Anda melakukan sesuatu yang salah dalam metode override, tetapi itu tidak berarti "tidak apa-apa karena bahasa ini memungkinkan saya mengganti metode" adalah argumen yang meyakinkan untuk melakukannya!Sekarang, mungkin untuk menulis dokumentasi
Rectangle
sedemikian rupa sehingga tidak menyiratkan bahwa kode di atas mencetak 10, dalam hal ini mungkin AndaSquare
bisa menjadiRectangle
. Anda mungkin melihat dokumentasi yang mengatakan sesuatu seperti, "ini tidak X. Selanjutnya, implementasi di kelas ini tidak Y". Jika demikian maka Anda memiliki kasus yang bagus untuk mengekstraksi antarmuka dari kelas, dan membedakan antara apa yang dijamin antarmuka, dan apa yang dijamin oleh kelas di samping itu. Tetapi ketika orang mengatakan "kotak yang bisa berubah bukan persegi yang bisa berubah, sedangkan kotak yang tidak bisa diubah adalah kotak yang tidak bisa diubah", mereka pada dasarnya mengasumsikan bahwa di atas memang bagian dari definisi yang wajar dari kotak yang bisa diubah.sumber
Subtipe dan, dengan ekstensi, pemrograman OO, sering mengandalkan Prinsip Pergantian Liskov, bahwa nilai apa pun dari tipe A dapat digunakan di mana B diperlukan, jika A <= B. Ini cukup banyak aksioma dalam arsitektur OO, yaitu. diasumsikan bahwa semua subclass akan memiliki properti ini (dan jika tidak, subtipe tersebut bermasalah dan perlu diperbaiki).
Namun, ternyata prinsip ini tidak realistis / tidak mewakili sebagian besar kode, atau memang tidak mungkin dipenuhi (dalam kasus yang tidak sepele)! Masalah ini, dikenal sebagai masalah persegi-persegi panjang atau masalah lingkaran-elips ( http://en.wikipedia.org/wiki/Circle-ellipse_problem ) adalah salah satu contoh terkenal tentang betapa sulitnya untuk memenuhi.
Perhatikan bahwa kita dapat mengimplementasikan Kuadrat dan Persegi Panjang yang lebih banyak dan lebih setara secara ekivalen, tetapi hanya dengan membuang lebih banyak fungsionalitas sampai perbedaan tidak berguna.
Sebagai contoh, lihat http://okmij.org/ftp/Computation/Subtyping/
sumber