Saya telah mendengar bahwa Prinsip Pergantian Liskov (LSP) adalah prinsip dasar desain berorientasi objek. Apa itu dan apa saja contoh penggunaannya?
oop
definition
solid-principles
design-principles
liskov-substitution-principle
Bukan dirimu sendiri
sumber
sumber
Jawaban:
Sebuah contoh yang bagus menggambarkan LSP (diberikan oleh Paman Bob dalam podcast yang saya dengar baru-baru ini) adalah bagaimana kadang-kadang sesuatu yang terdengar benar dalam bahasa alami tidak cukup berfungsi dalam kode.
Dalam matematika, a
Square
adalah aRectangle
. Memang itu adalah spesialisasi persegi panjang. "Is a" membuat Anda ingin memodelkan ini dengan warisan. Namun jika dalam kode yang Anda buatSquare
berasal dariRectangle
, makaSquare
harus dapat digunakan di mana saja Anda harapkanRectangle
. Ini membuat beberapa perilaku aneh.Bayangkan Anda memiliki
SetWidth
danSetHeight
metode diRectangle
kelas dasar Anda ; ini tampaknya sangat logis. Namun jikaRectangle
referensi Anda menunjuk keSquare
, makaSetWidth
danSetHeight
tidak masuk akal karena pengaturan yang satu akan mengubah yang lain untuk mencocokkannya. Dalam hal iniSquare
gagal dengan Tes Substitusi Liskov denganRectangle
dan abstraksi memilikiSquare
warisan dariRectangle
yang buruk.Kalian harus memeriksa Poster Motivational SOLID Principles Principles yang tak ternilai harganya .
sumber
Square.setWidth(int width)
diimplementasikan seperti inithis.width = width; this.height = width;
:? Dalam hal ini dijamin bahwa lebarnya sama dengan tinggi.Prinsip Pergantian Liskov (LSP, lsp) adalah konsep dalam Pemrograman Berorientasi Objek yang menyatakan:
Pada intinya LSP adalah tentang antarmuka dan kontrak serta bagaimana memutuskan kapan harus memperluas kelas vs menggunakan strategi lain seperti komposisi untuk mencapai tujuan Anda.
Cara yang efektif yang paling saya telah melihat untuk menggambarkan hal ini adalah di Head First OOA & D . Mereka menyajikan skenario di mana Anda adalah pengembang pada proyek untuk membangun kerangka kerja untuk permainan strategi.
Mereka menyajikan kelas yang mewakili papan yang terlihat seperti ini:
Semua metode mengambil koordinat X dan Y sebagai parameter untuk menemukan posisi ubin dalam array dua dimensi
Tiles
. Ini akan memungkinkan pengembang game untuk mengelola unit di papan selama permainan.Buku ini selanjutnya mengubah persyaratan untuk mengatakan bahwa kerangka kerja permainan juga harus mendukung papan permainan 3D untuk mengakomodasi permainan yang memiliki penerbangan. Jadi
ThreeDBoard
kelas diperkenalkan yang meluasBoard
.Sepintas ini sepertinya keputusan yang bagus.
Board
memberikan baikHeight
danWidth
properti danThreeDBoard
menyediakan Z sumbu.Di mana itu rusak adalah ketika Anda melihat semua anggota lain yang diwarisi
Board
. Metode untukAddUnit
,GetTile
,GetUnits
dan sebagainya, semua mengambil baik X dan parameter Y diBoard
kelas tetapiThreeDBoard
membutuhkan parameter Z juga.Jadi, Anda harus menerapkan metode itu lagi dengan parameter Z. Parameter Z tidak memiliki konteks ke
Board
kelas dan metode yang diwarisi dariBoard
kelas kehilangan artinya. Unit kode yang mencoba menggunakanThreeDBoard
kelas sebagai kelas dasarnyaBoard
akan sangat tidak beruntung.Mungkin kita harus mencari pendekatan lain. Alih-alih memanjang
Board
,ThreeDBoard
harus terdiri dariBoard
objek. SatuBoard
objek per unit sumbu Z.Ini memungkinkan kita untuk menggunakan prinsip berorientasi objek yang baik seperti enkapsulasi dan penggunaan kembali dan tidak melanggar LSP.
sumber
mari kita lakukan contoh sederhana di Jawa:
Contoh buruk
Bebek bisa terbang karena itu adalah burung, tetapi bagaimana dengan ini:
Burung unta adalah burung, Tapi itu tidak bisa terbang, kelas burung unta adalah subtipe burung kelas, tetapi tidak bisa menggunakan metode terbang, itu berarti bahwa kita melanggar prinsip LSP.
Contoh yang baik
sumber
Bird bird
. Anda harus melemparkan objek ke FlyingBirds untuk menggunakan lalat, yang tidak baik bukan?Bird bird
, itu berarti tidak dapat digunakanfly()
. Itu dia. LulusDuck
tidak mengubah fakta ini. Jika klien memilikiFlyingBirds bird
, maka bahkan jika dilewatkan,Duck
itu harus selalu bekerja dengan cara yang sama.LSP menyangkut invarian.
Contoh klasik diberikan oleh deklarasi kode semu berikut (implementasi dihilangkan):
Sekarang kami memiliki masalah meskipun antarmuka cocok. Alasannya adalah bahwa kita telah melanggar invarian yang berasal dari definisi matematika kuadrat dan persegi panjang. Cara getter dan setters bekerja, a
Rectangle
harus memenuhi invarian berikut:Namun, invarian ini harus dilanggar oleh implementasi yang benar
Square
, oleh karena itu itu bukan pengganti yang validRectangle
.sumber
Robert Martin memiliki makalah yang bagus tentang Prinsip Substitusi Liskov . Ini membahas cara-cara yang halus dan tidak begitu halus di mana prinsip tersebut dilanggar.
Beberapa bagian kertas yang relevan (perhatikan bahwa contoh kedua sangat padat):
sumber
Now the rule for the preconditions and postconditions for derivatives, as stated by Meyer is: ...when redefining a routine [in a derivative], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.
Jika pra-kondisi kelas anak lebih kuat daripada pra-kondisi kelas orang tua, Anda tidak bisa mengganti anak dengan orang tua tanpa melanggar pra-kondisi. Oleh karena itu LSP.LSP diperlukan ketika beberapa kode menganggapnya memanggil metode tipe
T
, dan mungkin tanpa sadar menyebut metode tipeS
, di manaS extends T
(yaituS
mewarisi, berasal dari, atau merupakan subtipe dari, supertipeT
).Sebagai contoh, ini terjadi di mana fungsi dengan parameter input tipe
T
, disebut (yaitu dipanggil) dengan nilai argumen tipeS
. Atau, tempat pengenal tipeT
, diberi nilai tipeS
.LSP membutuhkan ekspektasi (yaitu invarian) untuk metode tipe
T
(misalnyaRectangle
), tidak dilanggar ketika metode tipeS
(misalnyaSquare
) dipanggil sebagai gantinya.Bahkan tipe dengan bidang yang tidak dapat diubah masih memiliki invarian, mis. Penyetel Rectangle yang tidak dapat diubah mengharapkan dimensi untuk dimodifikasi secara independen, tetapi setters Square yang tidak dapat diubah melanggar ekspektasi ini.
LSP mensyaratkan bahwa setiap metode subtipe
S
harus memiliki parameter input kontravarian dan output kovarian.Contravarian berarti varians yang bertentangan dengan arah pewarisan, yaitu tipe
Si
, dari setiap parameter input dari setiap metode subtipeS
, harus sama atau supertipe dari jenisTi
parameter input yang sesuai dari metode yang sesuai dari supertypeT
.Kovarian berarti varians dalam arah yang sama dari warisan, yaitu jenis
So
, dari output masing-masing metode subtipeS
, harus sama atau subtipe dari jenisTo
output yang sesuai dari metode yang sesuai dari supertypeT
.Ini karena jika penelepon mengira itu memiliki tipe
T
, mengira itu memanggil metodeT
, maka ia memasok argumen tipeTi
dan memberikan output ke tipeTo
. Ketika sebenarnya memanggil metode yang sesuaiS
, maka setiapTi
argumen input ditugaskan keSi
parameter input, danSo
output ditugaskan untuk tipe tersebutTo
. Jadi, jikaSi
bukan contravariant wrtTi
, maka subtipeXi
— yang tidak akan menjadi subtipeSi
— dapat ditugaskanTi
.Selain itu, untuk bahasa (misalnya Scala atau Ceylon) yang memiliki anotasi varian-situs definisi pada parameter polimorfisme tipe (yaitu generik), ko-atau kontra-anotasi anotasi varians untuk setiap parameter tipe dari tipe
T
harus berlawanan atau arah yang sama masing-masing untuk setiap parameter input atau output (dari setiap metodeT
) yang memiliki tipe tipe parameter.Selain itu, untuk setiap parameter input atau output yang memiliki tipe fungsi, arah varians yang diperlukan dibalik. Aturan ini diterapkan secara rekursif.
Subtyping sesuai jika invarian dapat disebutkan.
Ada banyak penelitian yang sedang berlangsung tentang cara memodelkan invarian, sehingga mereka ditegakkan oleh kompiler.
Ketik (lihat halaman 3) menyatakan dan memberlakukan invarian negara ortogonal untuk mengetik. Atau, invarian dapat ditegakkan dengan mengubah pernyataan menjadi tipe . Misalnya, untuk menyatakan bahwa file terbuka sebelum menutupnya, maka File.open () dapat mengembalikan tipe OpenFile, yang berisi metode close () yang tidak tersedia di File. Sebuah tic-tac-toe API dapat menjadi contoh lain dari mempekerjakan mengetik untuk menegakkan invariants pada saat kompilasi. Sistem tipe mungkin bahkan Turing-complete, misalnya Scala . Bahasa yang diketik secara tergantung dan teorema provers memformalkan model pengetikan tingkat tinggi.
Karena perlunya semantik untuk abstrak lebih dari ekstensi , saya berharap bahwa menggunakan pengetikan untuk memodelkan invarian, yaitu semantik denotasional tingkat tinggi yang disatukan, lebih unggul daripada Typestate. 'Extension' berarti komposisi tak terbatas yang diijinkan dari pengembangan modular, tidak terkoordinasi. Karena bagi saya tampaknya merupakan kebalikan dari penyatuan dan dengan demikian derajat kebebasan, memiliki dua model yang saling bergantung (misalnya tipe dan Typestate) untuk mengekspresikan semantik bersama, yang tidak dapat disatukan satu sama lain untuk komposisi yang dapat diperluas . Misalnya, ekstensi seperti Masalah Ekspresi disatukan dalam subtipe, fungsi yang berlebihan, dan domain pengetikan parametrik.
Posisi teoritis saya adalah agar pengetahuan ada (lihat bagian “Sentralisasi buta dan tidak layak”), tidak akan pernah ada model umum yang dapat memberlakukan cakupan 100% dari semua invarian yang mungkin dalam bahasa komputer Turing-lengkap. Agar pengetahuan ada, banyak kemungkinan yang tidak terduga, yaitu gangguan dan entropi harus selalu meningkat. Ini adalah kekuatan entropik. Untuk membuktikan semua perhitungan yang mungkin dari ekstensi potensial, adalah untuk menghitung apriori semua ekstensi yang mungkin.
Inilah sebabnya Teorema Halting ada, yaitu tidak dapat diputuskan apakah setiap program yang mungkin dalam bahasa pemrograman Turing-complete berakhir. Dapat dibuktikan bahwa beberapa program tertentu berakhir (yang semua kemungkinan telah didefinisikan dan dihitung). Tetapi tidak mungkin untuk membuktikan bahwa semua kemungkinan perpanjangan dari program itu berakhir, kecuali kemungkinan untuk perpanjangan dari program itu tidak lengkap Turing (mis. Melalui ketergantungan-mengetik). Karena persyaratan mendasar untuk kelengkapan Turing adalah rekursi yang tidak terbatas , intuitif untuk memahami bagaimana teorema ketidaklengkapan Gödel dan paradoks Russell berlaku untuk ekstensi.
Penafsiran teorema ini menggabungkan mereka dalam pemahaman konseptual umum dari kekuatan entropis:
sumber
Saya melihat persegi panjang dan bujur sangkar di setiap jawaban, dan bagaimana cara melanggar LSP.
Saya ingin menunjukkan bagaimana LSP dapat disesuaikan dengan contoh dunia nyata:
Desain ini sesuai dengan LSP karena perilaku tetap tidak berubah terlepas dari implementasi yang kami pilih untuk digunakan.
Dan ya, Anda dapat melanggar LSP dalam konfigurasi ini dengan melakukan satu perubahan sederhana seperti:
Sekarang subtipe tidak dapat digunakan dengan cara yang sama karena mereka tidak menghasilkan hasil yang sama lagi.
sumber
Database::selectQuery
untuk mendukung hanya subset SQL yang didukung oleh semua mesin DB. Itu hampir tidak praktis ... Konon, contohnya masih lebih mudah dipahami daripada kebanyakan yang digunakan di sini.Ada daftar periksa untuk menentukan apakah Anda melanggar Liskov atau tidak.
Daftar periksa:
Batasan Riwayat : Saat mengganti metode Anda tidak diizinkan untuk memodifikasi properti yang tidak dapat dimodifikasi di kelas dasar. Lihatlah kode ini dan Anda dapat melihat Name didefinisikan sebagai tidak dapat dimodifikasi (private set) tetapi SubType memperkenalkan metode baru yang memungkinkan memodifikasinya (melalui refleksi):
Ada 2 item lain: Contravariance dari argumen metode dan Covariance dari tipe yang dikembalikan . Tapi itu tidak mungkin di C # (saya seorang pengembang C #) jadi saya tidak peduli tentang mereka.
Referensi:
sumber
LSP adalah aturan tentang kontrak klausa: jika kelas dasar memenuhi kontrak, maka oleh LSP kelas turunan juga harus memenuhi kontrak itu.
Dalam Pseudo-python
memenuhi LSP jika setiap kali Anda memanggil Foo pada objek yang diturunkan, ia memberikan hasil yang sama persis dengan memanggil Foo pada objek Basis, selama arg adalah sama.
sumber
2 + "2"
). Mungkin Anda bingung "sangat diketik" dengan "diketik secara statis"?Panjang cerita pendek, mari kita tinggalkan persegi panjang persegi panjang dan kotak kotak, contoh praktis ketika memperpanjang kelas induk, Anda harus baik MEMELIHARA tepat orangtua API atau MEMPERPANJANG IT.
Katakanlah Anda memiliki basis ItemsRepository dasar .
Dan sub kelas memperluasnya:
Maka Anda bisa meminta Klien bekerja dengan Base ItemsRepository API dan mengandalkannya.
The LSP rusak ketika mengganti orangtua kelas dengan sub istirahat kelas kontrak API .
Anda dapat mempelajari lebih lanjut tentang cara menulis perangkat lunak yang dapat dipelihara dalam kursus saya: https://www.udemy.com/enterprise-php/
sumber
Ketika saya pertama kali membaca tentang LSP, saya berasumsi bahwa ini dimaksudkan dalam arti yang sangat ketat, pada dasarnya menyamakannya dengan implementasi antarmuka dan casting tipe-aman. Yang berarti bahwa LSP dipastikan atau tidak oleh bahasa itu sendiri. Sebagai contoh, dalam pengertian ketat ini, ThreeDBoard tentu saja dapat diganti untuk Dewan, sejauh menyangkut kompiler.
Setelah membaca lebih lanjut tentang konsepnya meskipun saya menemukan bahwa LSP umumnya ditafsirkan lebih luas dari itu.
Singkatnya, apa artinya kode klien untuk "tahu" bahwa objek di belakang pointer adalah tipe turunan daripada tipe pointer tidak terbatas pada tipe-safety. Ketaatan pada LSP juga dapat diuji melalui penyelidikan perilaku objek aktual. Yaitu, memeriksa dampak keadaan objek dan argumen metode pada hasil pemanggilan metode, atau jenis pengecualian yang dilemparkan dari objek.
Kembali ke contoh lagi, secara teori metode Dewan dapat dibuat berfungsi dengan baik di ThreeDBoard. Namun dalam praktiknya, akan sangat sulit untuk mencegah perbedaan perilaku yang mungkin tidak ditangani klien dengan benar, tanpa mengganggu fungsi yang ingin ditambahkan oleh ThreeDBoard.
Dengan pengetahuan ini, mengevaluasi kepatuhan LSP dapat menjadi alat yang hebat dalam menentukan kapan komposisi adalah mekanisme yang lebih tepat untuk memperluas fungsi yang ada, daripada pewarisan.
sumber
Saya kira semua orang membahas apa yang LSP secara teknis: Anda pada dasarnya ingin dapat abstrak jauh dari rincian subtipe dan menggunakan supertipe dengan aman.
Jadi Liskov memiliki 3 aturan mendasar:
Aturan Tanda Tangan: Seharusnya ada implementasi yang valid dari setiap operasi supertype dalam subtipe secara sintaksis. Sesuatu yang bisa diperiksa oleh kompiler. Ada sedikit aturan tentang melemparkan lebih sedikit pengecualian dan setidaknya dapat diakses seperti metode supertype.
Metode Aturan: Implementasi dari operasi-operasi tersebut secara semantik sehat.
Properti Aturan: Ini melampaui panggilan fungsi individu.
Semua properti ini harus dipertahankan dan fungsionalitas subtipe tambahan tidak boleh melanggar sifat supertipe.
Jika ketiga hal ini diatasi, Anda telah mengambil abstrak dari hal-hal yang mendasarinya dan Anda menulis kode yang digabungkan secara longgar.
Sumber: Pengembangan Program di Jawa - Barbara Liskov
sumber
Contoh penting dari penggunaan LSP adalah dalam pengujian perangkat lunak .
Jika saya memiliki kelas A yang merupakan subkelas B yang sesuai dengan LSP, maka saya dapat menggunakan kembali suite uji B untuk menguji A.
Untuk sepenuhnya menguji subclass A, saya mungkin perlu menambahkan beberapa test case lagi, tetapi setidaknya saya dapat menggunakan kembali semua test case superclass B.
Cara untuk mewujudkannya adalah dengan membangun apa yang oleh McGregor disebut sebagai "Hirarki paralel untuk pengujian":
ATest
Kelas saya akan mewarisi dariBTest
. Beberapa bentuk injeksi kemudian diperlukan untuk memastikan test case bekerja dengan objek tipe A daripada tipe B (pola metode templat sederhana akan dilakukan).Perhatikan bahwa menggunakan kembali paket uji super untuk semua implementasi subclass sebenarnya adalah cara untuk menguji bahwa implementasi subclass ini sesuai dengan LSP. Dengan demikian, orang juga dapat berpendapat bahwa seseorang harus menjalankan suite uji superclass dalam konteks setiap subkelas.
Lihat juga jawaban untuk pertanyaan Stackoverflow " Bisakah saya menerapkan serangkaian tes yang dapat digunakan kembali untuk menguji implementasi antarmuka? "
sumber
Mari kita ilustrasikan di Jawa:
Tidak ada masalah di sini, kan? Mobil jelas merupakan alat transportasi, dan di sini kita dapat melihat bahwa itu menimpa metode startEngine () dari superclassnya.
Mari tambahkan perangkat transportasi lain:
Semuanya tidak berjalan seperti yang direncanakan sekarang! Ya, sepeda adalah alat transportasi, bagaimanapun, ia tidak memiliki mesin dan karenanya, metode startEngine () tidak dapat diimplementasikan.
Solusi untuk masalah ini adalah hierarki warisan yang benar, dan dalam kasus kami, kami akan memecahkan masalah dengan membedakan kelas perangkat transportasi dengan dan tanpa mesin. Meskipun sepeda adalah alat transportasi, ia tidak memiliki mesin. Dalam contoh ini, definisi alat transportasi kita salah. Seharusnya tidak memiliki mesin.
Kami dapat memperbaiki kelas TransportDevice kami sebagai berikut:
Sekarang kita dapat memperluas Alat Transportasi untuk perangkat tidak bermotor.
Dan memperluas Alat Transportasi untuk perangkat bermotor. Di sini lebih tepat untuk menambahkan objek Engine.
Dengan demikian, kelas Mobil kami menjadi lebih terspesialisasi, dengan tetap berpegang pada Prinsip Pergantian Liskov.
Dan kelas Sepeda kami juga mematuhi Prinsip Substitusi Liskov.
sumber
Formulasi LSP ini terlalu kuat:
Yang pada dasarnya berarti bahwa S adalah implementasi lain yang sepenuhnya dienkapsulasi dari hal yang sama persis seperti T. Dan saya dapat menjadi berani dan memutuskan bahwa kinerja adalah bagian dari perilaku ...
Jadi, pada dasarnya, penggunaan apa pun yang mengikat lambat melanggar LSP. Inti dari OO adalah untuk mendapatkan perilaku yang berbeda ketika kita mengganti objek dari satu jenis dengan yang lainnya!
Formulasi yang dikutip oleh wikipedia lebih baik karena properti tergantung pada konteksnya dan tidak harus mencakup seluruh perilaku program.
sumber
Dalam kalimat yang sangat sederhana, kita dapat mengatakan:
Kelas anak tidak boleh melanggar karakteristik kelas dasarnya. Itu harus mampu dengan itu. Kita dapat mengatakan itu sama dengan subtyping.
sumber
Contoh:
Di bawah ini adalah contoh klasik yang melanggar Prinsip Pergantian Liskov. Dalam contoh ini, 2 kelas digunakan: Rectangle dan Square. Mari kita asumsikan bahwa objek Rectangle digunakan di suatu tempat dalam aplikasi. Kami memperluas aplikasi dan menambahkan kelas Square. Kelas kuadrat dikembalikan oleh pola pabrik, berdasarkan pada beberapa kondisi dan kami tidak tahu persis jenis objek yang akan dikembalikan. Tapi kita tahu itu adalah Rectangle. Kami mendapatkan objek persegi panjang, atur lebar ke 5 dan tinggi ke 10 dan dapatkan luasnya. Untuk persegi panjang dengan lebar 5 dan tinggi 10, area harus 50. Sebagai gantinya, hasilnya adalah 100
Lihat juga: Prinsip Buka Tutup
Beberapa konsep serupa untuk struktur yang lebih baik: Konvensi alih konfigurasi
sumber
Prinsip Substitusi Liskov
sumber
Beberapa tambahan:
Saya heran mengapa tidak ada yang menulis tentang Invarian, prasyarat dan memposting kondisi kelas dasar yang harus dipatuhi oleh kelas turunan. Agar kelas D yang diturunkan sepenuhnya berkelanjutan oleh kelas B, kelas D harus mematuhi ketentuan tertentu:
Jadi yang diturunkan harus menyadari ketiga kondisi di atas yang diberlakukan oleh kelas dasar. Oleh karena itu, aturan subtyping sudah diputuskan sebelumnya. Yang berarti, hubungan 'IS A' akan dipatuhi hanya ketika aturan tertentu dipatuhi oleh subtipe. Aturan-aturan ini, dalam bentuk invarian, prekursor dan pascakondisi, harus diputuskan oleh ' kontrak desain ' formal .
Diskusi lebih lanjut tentang ini tersedia di blog saya: Prinsip Pergantian Liskov
sumber
LSP secara sederhana menyatakan bahwa objek dari superclass yang sama harus dapat saling bertukar tanpa melanggar apa pun.
Sebagai contoh, jika kita memiliki
Cat
danDog
kelas turunan dariAnimal
kelas, setiap fungsi menggunakan kelas Hewan harus dapat menggunakanCat
atauDog
dan bersikap normal.sumber
Apakah menerapkan ThreeDBoard dalam hal susunan Dewan akan bermanfaat?
Mungkin Anda mungkin ingin memperlakukan irisan ThreeDBoard di berbagai pesawat sebagai Dewan. Dalam hal ini, Anda mungkin ingin abstrak antarmuka (atau kelas abstrak) untuk Dewan untuk memungkinkan beberapa implementasi.
Dalam hal antarmuka eksternal, Anda mungkin ingin mempertimbangkan antarmuka Dewan untuk TwoDBoard dan ThreeDBoard (meskipun tidak ada metode di atas yang cocok).
sumber
Kotak adalah kotak di mana lebar sama dengan tinggi. Jika kuadrat menetapkan dua ukuran berbeda untuk lebar dan tinggi, itu melanggar invarian kuadrat. Ini dikerjakan dengan memperkenalkan efek samping. Tetapi jika persegi panjang memiliki setSize (tinggi, lebar) dengan prasyarat 0 <tinggi dan 0 <lebar. Metode subtipe turunan membutuhkan tinggi == lebar; prasyarat yang lebih kuat (dan itu melanggar lsp). Ini menunjukkan bahwa meskipun persegi adalah persegi panjang, itu bukan subtipe yang valid karena prasyarat diperkuat. Bekerja di sekitar (secara umum hal yang buruk) menyebabkan efek samping dan ini melemahkan kondisi pos (yang melanggar lsp). setWidth di pangkalan memiliki kondisi posting 0 <lebar. Turunan melemahkannya dengan tinggi == lebar.
Oleh karena itu persegi resizable bukan persegi panjang resizable.
sumber
Prinsip ini diperkenalkan oleh Barbara Liskov pada tahun 1987 dan memperluas Prinsip Terbuka-Tertutup dengan berfokus pada perilaku superclass dan subtipe-nya.
Pentingnya menjadi jelas ketika kita mempertimbangkan konsekuensi dari melanggarnya. Pertimbangkan aplikasi yang menggunakan kelas berikut.
Bayangkan suatu hari, klien menuntut kemampuan untuk memanipulasi kotak selain persegi panjang. Karena persegi adalah persegi panjang, kelas persegi harus diturunkan dari kelas Persegi Panjang.
Namun, dengan melakukan itu kita akan menghadapi dua masalah:
Kuadrat tidak memerlukan variabel tinggi dan lebar yang diwarisi dari persegi panjang dan ini bisa membuat pemborosan yang signifikan dalam memori jika kita harus membuat ratusan ribu objek persegi. Properti setter lebar dan tinggi yang diwarisi dari persegi panjang tidak sesuai untuk sebuah persegi karena lebar dan tinggi persegi adalah identik. Untuk mengatur tinggi dan lebar ke nilai yang sama, kita dapat membuat dua properti baru sebagai berikut:
Sekarang, ketika seseorang akan mengatur lebar objek persegi, tingginya akan berubah sesuai dan sebaliknya.
Mari kita maju dan mempertimbangkan fungsi lain ini:
Jika kami meneruskan referensi ke objek kuadrat ke dalam fungsi ini, kami akan melanggar LSP karena fungsi tersebut tidak berfungsi untuk turunan dari argumennya. Properti lebar dan tinggi bukan polimorfik karena tidak dinyatakan virtual dalam persegi panjang (objek kuadrat akan rusak karena ketinggian tidak akan berubah).
Namun, dengan mendeklarasikan properti setter sebagai virtual, kami akan menghadapi pelanggaran lain, OCP. Bahkan, penciptaan kuadrat kelas turunan menyebabkan perubahan pada persegi panjang kelas dasar.
sumber
Penjelasan paling jelas untuk LSP yang saya temukan sejauh ini adalah "Prinsip Substitusi Liskov mengatakan bahwa objek kelas turunan harus dapat menggantikan objek kelas dasar tanpa membawa kesalahan dalam sistem atau memodifikasi perilaku kelas dasar. "dari sini . Artikel ini memberikan contoh kode untuk melanggar LSP dan memperbaikinya.
sumber
Katakanlah kita menggunakan kotak di dalam kode kita
Di kelas geometri kami, kami belajar bahwa persegi adalah jenis persegi panjang khusus karena lebarnya sama dengan tingginya. Mari kita buat
Square
kelas juga berdasarkan info ini:Jika kita mengganti
Rectangle
denganSquare
dalam kode pertama kita, maka itu akan rusak:Hal ini karena
Square
memiliki prasyarat baru kami tidak memiliki dalamRectangle
kelas:width == height
. Menurut LSPRectangle
instance harus diganti denganRectangle
instance subclass. Ini karena instance ini melewatkan pemeriksaan tipe untukRectangle
instance dan karenanya mereka akan menyebabkan kesalahan tak terduga dalam kode Anda.Ini adalah contoh untuk bagian "prasyarat tidak dapat diperkuat dalam subtipe" di artikel wiki . Singkatnya, melanggar LSP mungkin akan menyebabkan kesalahan dalam kode Anda di beberapa titik.
sumber
LSP mengatakan bahwa '' Objek harus diganti oleh subtipe mereka ''. Di sisi lain, prinsip ini menunjuk ke
dan contoh berikut membantu untuk memiliki pemahaman yang lebih baik tentang LSP.
Tanpa LSP:
Memperbaiki dengan LSP:
sumber
Saya mendorong Anda untuk membaca artikel: Melanggar Prinsip Substitusi Liskov (LSP) .
Anda dapat menemukan di sana penjelasan apa itu Prinsip Pergantian Liskov, petunjuk umum yang membantu Anda menebak jika Anda telah melanggarnya dan contoh pendekatan yang akan membantu Anda membuat hierarki kelas Anda menjadi lebih aman.
sumber
PRINSIP PENGGANTI LISKOV (Dari buku Mark Seemann) menyatakan bahwa kita harus dapat mengganti satu implementasi antarmuka dengan yang lain tanpa melanggar salah satu klien atau implementasi. Ini adalah prinsip ini yang memungkinkan untuk mengatasi persyaratan yang terjadi di masa depan, bahkan jika kita dapat ' t meramalkan mereka hari ini.
Jika kita mencabut komputer dari dinding (Implementasi), outlet dinding (Antarmuka) atau komputer (Klien) tidak rusak (pada kenyataannya, jika itu adalah komputer laptop, ia bahkan dapat menggunakan baterai untuk jangka waktu tertentu) . Namun, dengan perangkat lunak, klien sering mengharapkan layanan akan tersedia. Jika layanan itu dihapus, kami mendapatkan NullReferenceException. Untuk menghadapi situasi seperti ini, kita dapat membuat implementasi antarmuka yang tidak melakukan apa-apa. Ini adalah pola desain yang dikenal sebagai Null Object, [4] dan sesuai dengan mencabut komputer dari dinding. Karena kami menggunakan kopling longgar, kami dapat mengganti implementasi nyata dengan sesuatu yang tidak melakukan apa-apa tanpa menyebabkan masalah.
sumber
Prinsip Substitusi Likov menyatakan bahwa jika modul program menggunakan kelas Base, maka referensi ke kelas Base dapat diganti dengan kelas Derived tanpa memengaruhi fungsionalitas modul program.
Intent - Derived types harus sepenuhnya dapat menggantikan tipe dasar mereka.
Contoh - Jenis pengembalian varian bersama di java.
sumber
Berikut adalah kutipan dari posting ini yang mengklarifikasi hal-hal dengan baik:
[..] untuk memahami beberapa prinsip, penting untuk disadari ketika telah dilanggar. Inilah yang akan saya lakukan sekarang.
Apa arti pelanggaran prinsip ini? Ini menyiratkan bahwa suatu objek tidak memenuhi kontrak yang dipaksakan oleh abstraksi yang dinyatakan dengan antarmuka. Dengan kata lain, itu berarti Anda salah mengidentifikasi abstraksi Anda.
Perhatikan contoh berikut:
Apakah ini pelanggaran terhadap LSP? Iya. Ini karena kontrak akun memberi tahu kita bahwa sebuah akun akan ditarik, tetapi tidak selalu demikian. Jadi, apa yang harus saya lakukan untuk memperbaikinya? Saya hanya memodifikasi kontrak:
Voa, sekarang kontrak puas.
Pelanggaran halus ini seringkali memaksakan klien dengan kemampuan untuk memberi tahu perbedaan antara objek konkret yang digunakan. Misalnya, mengingat kontrak Akun pertama, itu bisa terlihat seperti berikut:
Dan, ini secara otomatis melanggar prinsip buka-tutup [yaitu, untuk persyaratan penarikan uang. Karena Anda tidak pernah tahu apa yang terjadi jika suatu benda yang melanggar kontrak tidak memiliki cukup uang. Mungkin itu tidak mengembalikan apa-apa, mungkin pengecualian akan dilemparkan. Jadi, Anda harus memeriksa apakah itu
hasEnoughMoney()
- yang bukan bagian dari antarmuka. Jadi pemeriksaan paksa yang bergantung pada kelas beton ini merupakan pelanggaran OCP].Poin ini juga membahas kesalahpahaman yang sering saya temui tentang pelanggaran LSP. Dikatakan "jika perilaku orang tua berubah pada anak, maka, itu melanggar LSP." Namun, tidak - selama seorang anak tidak melanggar kontrak orang tuanya.
sumber