Saya mencoba untuk memahami prinsip-prinsip SOLID dari OOP dan saya sampai pada kesimpulan bahwa LSP dan OCP memiliki beberapa kesamaan (jika tidak untuk mengatakan lebih banyak).
prinsip terbuka / tertutup menyatakan "entitas perangkat lunak (kelas, modul, fungsi, dll.) harus terbuka untuk ekstensi, tetapi ditutup untuk modifikasi".
LSP dalam kata-kata sederhana menyatakan bahwa setiap instance Foo
dapat diganti dengan instance Bar
mana saja yang berasal dari Foo
dan program akan bekerja dengan cara yang sama.
Saya bukan programmer OOP pro, tetapi bagi saya tampaknya LSP hanya mungkin jika Bar
, berasal dari Foo
tidak mengubah apa pun di dalamnya tetapi hanya meluas. Itu berarti bahwa dalam program tertentu LSP benar hanya ketika OCP benar dan OCP benar hanya jika LSP benar. Itu berarti mereka setara.
Koreksi saya jika saya salah. Saya benar-benar ingin memahami ide-ide ini. Terima kasih banyak atas jawabannya.
Square
dariRectangle
tidak melanggar LSP. (Tapi itu mungkin masih desain yang buruk dalam kasus abadi karena Anda dapat memiliki kuadratRectangle
yang bukanSquare
yang tidak cocok dengan matematika)Jawaban:
Astaga, ada beberapa kesalahpahaman aneh tentang apa yang OCP dan LSP dan beberapa karena ketidakcocokan beberapa terminologi dan contoh membingungkan. Kedua prinsip hanyalah "hal yang sama" jika Anda menerapkannya dengan cara yang sama. Pola biasanya mengikuti prinsip dalam satu atau lain cara dengan sedikit pengecualian.
Perbedaan akan dijelaskan lebih jauh ke bawah tetapi pertama-tama mari kita selami prinsip-prinsip itu sendiri:
Prinsip Terbuka-Tertutup (OCP)
Menurut Paman Bob :
Perhatikan bahwa kata extended dalam kasus ini tidak selalu berarti bahwa Anda harus mensubklasifikasikan kelas aktual yang memerlukan perilaku baru. Lihat bagaimana saya sebutkan di mismatch pertama terminologi? Kata kunci
extend
hanya berarti subkelas di Jawa, tetapi prinsip-prinsipnya lebih tua dari Jawa.Asli berasal dari Bertrand Meyer pada tahun 1988:
Di sini jauh lebih jelas bahwa prinsip tersebut diterapkan pada entitas perangkat lunak . Contoh buruk akan menimpa entitas perangkat lunak saat Anda memodifikasi kode sepenuhnya alih-alih memberikan beberapa titik ekstensi. Perilaku entitas perangkat lunak itu sendiri harus dapat diperluas dan contoh yang baik untuk hal ini adalah penerapan pola-Strategi (karena ini adalah cara termudah untuk ditunjukkan dari kumpulan pola GoF IMHO):
Pada contoh di atas
Context
adalah dikunci untuk modifikasi lebih lanjut. Sebagian besar programmer mungkin ingin membuat subkelas kelas untuk memperluasnya, tetapi di sini kita tidak melakukannya karena mengasumsikan perilakunya dapat diubah melalui apa pun yang mengimplementasikanIBehavior
antarmuka.Yakni kelas konteks ditutup untuk modifikasi tetapi terbuka untuk ekstensi . Ini sebenarnya mengikuti prinsip dasar lain karena kita menempatkan perilaku dengan komposisi objek daripada pewarisan:
Saya akan membiarkan pembaca membaca tentang prinsip itu karena berada di luar ruang lingkup pertanyaan ini. Untuk melanjutkan dengan contoh, katakan kita memiliki implementasi antarmuka IBehavior berikut:
Dengan menggunakan pola ini kita dapat memodifikasi perilaku konteks saat runtime, melalui
setBehavior
metode sebagai titik ekstensi.Jadi, kapan pun Anda ingin memperluas kelas konteks "tertutup", lakukan dengan mensubklasifikasikan ketergantungan "terbuka" itu. Ini jelas bukan hal yang sama dengan subklasifikasi konteks itu sendiri tetapi OCP. LSP tidak menyebutkan tentang ini juga.
Memperluas dengan Mixin Alih-alih Warisan
Ada cara lain untuk melakukan OCP selain dari subklasifikasi. Salah satu caranya adalah menjaga kelas Anda tetap terbuka untuk ekstensi melalui penggunaan mixin . Ini berguna misalnya dalam bahasa yang berbasis prototipe daripada berbasis kelas. Idenya adalah untuk mengubah objek dinamis dengan lebih banyak metode atau atribut yang diperlukan, dengan kata lain objek yang menyatu atau "bercampur" dengan objek lain.
Berikut ini adalah contoh javascript dari mixin yang merender template HTML sederhana untuk jangkar:
Idenya adalah untuk memperluas objek secara dinamis dan keuntungannya adalah bahwa objek dapat berbagi metode bahkan jika mereka berada dalam domain yang sama sekali berbeda. Dalam kasus di atas, Anda dapat dengan mudah membuat jangkar html jenis lain dengan memperluas implementasi spesifik Anda dengan
LinkMixin
.Dalam hal OCP, "mixin" adalah ekstensi. Dalam contoh di atas
YoutubeLink
adalah entitas perangkat lunak kami yang ditutup untuk modifikasi, tetapi terbuka untuk ekstensi melalui penggunaan mixin. Hirarki objek diratakan yang membuatnya tidak mungkin untuk memeriksa jenis. Namun ini bukan benar-benar hal yang buruk, dan saya akan menjelaskan lebih jauh ke bawah bahwa memeriksa jenis umumnya adalah ide yang buruk dan merusak ide dengan polimorfisme.Perhatikan bahwa dimungkinkan untuk melakukan banyak pewarisan dengan metode ini karena sebagian besar
extend
implementasi dapat menggabungkan beberapa objek:Satu-satunya hal yang perlu Anda ingat adalah untuk tidak bertabrakan nama, yaitu mixin kebetulan mendefinisikan nama yang sama dari beberapa atribut atau metode karena mereka akan diganti. Dalam pengalaman saya yang sederhana ini adalah masalah non-dan jika itu terjadi itu merupakan indikasi desain yang cacat.
Prinsip Pergantian Liskov (LSP)
Paman Bob mendefinisikannya hanya dengan:
Prinsip ini sudah tua, pada kenyataannya definisi Paman Bob tidak membedakan prinsip-prinsip karena membuat LSP masih terkait erat dengan OCP oleh fakta bahwa, dalam contoh Strategi di atas, supertipe yang sama digunakan (
IBehavior
). Jadi mari kita lihat definisi asli dari Barbara Liskov dan lihat apakah kita dapat menemukan sesuatu yang lain tentang prinsip ini yang terlihat seperti teorema matematika:Mari kita angkat bahu untuk sementara waktu, perhatikan karena tidak menyebutkan kelas sama sekali. Dalam JavaScript Anda benar-benar dapat mengikuti LSP meskipun tidak berbasis kelas secara eksplisit. Jika program Anda memiliki daftar setidaknya beberapa objek JavaScript yang:
... maka objek dianggap memiliki "tipe" yang sama dan tidak terlalu penting untuk program. Ini pada dasarnya adalah polimorfisme . Dalam arti umum; Anda tidak perlu tahu subtipe yang sebenarnya jika Anda menggunakan antarmuka itu. OCP tidak mengatakan sesuatu yang eksplisit tentang ini. Ini juga menunjukkan kesalahan desain yang dilakukan oleh kebanyakan programmer pemula:
Setiap kali Anda merasakan keinginan untuk memeriksa subtipe suatu objek, kemungkinan besar Anda melakukannya SALAH.
Oke, jadi itu mungkin tidak salah sepanjang waktu, tetapi jika Anda memiliki keinginan untuk melakukan beberapa jenis pengecekan dengan
instanceof
atau enum, Anda mungkin melakukan program sedikit lebih berbelit-belit untuk diri sendiri daripada yang seharusnya. Tetapi ini tidak selalu terjadi; peretasan yang cepat dan kotor untuk membuat semuanya berfungsi adalah konsesi yang baik untuk dibuat di benak saya jika solusinya cukup kecil, dan jika Anda mempraktikkan refactoring tanpa ampun , itu dapat ditingkatkan setelah perubahan menuntutnya.Ada beberapa cara untuk mengatasi "kesalahan desain" ini, tergantung pada masalah sebenarnya:
Kedua hal ini adalah "kesalahan" desain kode yang umum. Ada beberapa refactoring berbeda yang dapat Anda lakukan, seperti metode pull-up , atau refactor untuk pola seperti pola Pengunjung .
Saya sebenarnya sangat menyukai pola Pengunjung karena dapat menangani spageti if-statement yang besar dan lebih mudah diterapkan daripada apa yang Anda pikirkan pada kode yang ada. Katakanlah kita memiliki konteks berikut:
Hasil dari pernyataan jika dapat diterjemahkan ke pengunjung mereka sendiri karena masing-masing tergantung pada beberapa keputusan dan beberapa kode untuk dijalankan. Kita dapat mengekstrak ini seperti ini:
Pada titik ini, jika pemrogram tidak tahu tentang pola Pengunjung, ia malah menerapkan kelas Konteks untuk memeriksa apakah itu dari jenis tertentu. Karena kelas Pengunjung memiliki
canDo
metode boolean , implementor dapat menggunakan pemanggilan metode itu untuk menentukan apakah itu adalah objek yang tepat untuk melakukan pekerjaan itu. Kelas konteks dapat menggunakan semua pengunjung (dan menambahkan yang baru) seperti ini:Kedua pola mengikuti OCP dan LSP, namun keduanya menunjukkan berbagai hal tentang mereka. Jadi bagaimana kode terlihat jika melanggar salah satu prinsip?
Melanggar satu prinsip tetapi mengikuti yang lain
Ada cara untuk melanggar salah satu prinsip tetapi masih ada yang lain yang harus diikuti. Contoh di bawah ini sepertinya dibuat-buat, untuk alasan yang baik, tetapi saya benar-benar melihat ini muncul dalam kode produksi (dan bahkan lebih buruk):
Mengikuti OCP tetapi tidak LSP
Katakanlah kita memiliki kode yang diberikan:
Sepotong kode ini mengikuti prinsip buka-tutup. Jika kita memanggil metode konteks
GetPersons
, kita akan mendapatkan banyak orang dengan implementasi mereka sendiri. Itu berarti bahwa IPerson ditutup untuk modifikasi, tetapi terbuka untuk perpanjangan. Namun segala sesuatunya berubah menjadi gelap ketika kita harus menggunakannya:Anda harus melakukan pengecekan tipe dan konversi tipe! Ingat bagaimana saya sebutkan di atas bagaimana pengecekan tipe adalah hal yang buruk ? Oh tidak! Tapi jangan takut, seperti juga disebutkan di atas baik melakukan pull-up refactoring atau menerapkan pola Pengunjung. Dalam hal ini kita cukup melakukan pull up refactoring setelah menambahkan metode umum:
Keuntungannya sekarang adalah Anda tidak perlu lagi mengetahui jenis pastinya, mengikuti LSP:
Mengikuti LSP tetapi tidak OCP
Mari kita lihat beberapa kode yang mengikuti LSP tetapi tidak OCP, itu agak dibuat-buat tapi tetap saya yang ini kesalahan yang sangat halus:
Kode tidak LSP karena konteksnya dapat menggunakan LiskovBase tanpa mengetahui tipe yang sebenarnya. Anda akan berpikir kode ini mengikuti OCP juga tetapi perhatikan dengan teliti, apakah kelasnya benar-benar tertutup ? Bagaimana jika
doStuff
metode ini lebih dari sekadar mencetak satu baris?Jawabannya jika mengikuti OCP adalah sederhana: TIDAK , itu bukan karena dalam desain objek ini kita diharuskan untuk menimpa kode sepenuhnya dengan sesuatu yang lain. Ini membuka cut-and-paste kaleng cacing karena Anda harus menyalin kode dari kelas dasar untuk membuat semuanya berfungsi. The
doStuff
Metode yakin ini terbuka untuk ekstensi, tapi itu tidak benar-benar tertutup untuk modifikasi.Kita dapat menerapkan pola metode Templat pada ini. Pola metode templat sangat umum dalam kerangka kerja yang Anda mungkin telah menggunakannya tanpa menyadarinya (misalnya komponen java swing, formulir dan komponen c #, dll.). Inilah salah satu cara untuk menutup
doStuff
metode modifikasi dan memastikannya tetap ditutup dengan menandainya denganfinal
kata kunci java . Kata kunci itu mencegah siapa pun dari subklas kelas lebih lanjut (dalam C # Anda dapat menggunakansealed
untuk melakukan hal yang sama).Contoh ini mengikuti OCP dan tampak konyol, tetapi bayangkan ini ditingkatkan dengan lebih banyak kode untuk ditangani. Saya terus melihat kode yang digunakan dalam produksi di mana subclass sepenuhnya menimpa segalanya dan kode yang ditimpa sebagian besar dipotong-n-disisipkan antara implementasi. Ini bekerja, tetapi karena dengan semua duplikasi kode juga merupakan set-up untuk mimpi buruk pemeliharaan.
Kesimpulan
Saya harap ini semua membersihkan beberapa pertanyaan tentang OCP dan LSP dan perbedaan / kesamaan di antara mereka. Mudah untuk mengabaikannya sebagai hal yang sama tetapi contoh di atas harus menunjukkan bahwa mereka tidak sama.
Perhatikan bahwa, kumpulkan dari kode contoh di atas:
OCP adalah tentang mengunci kode kerja tetapi tetap tetap membukanya dengan beberapa titik ekstensi.
Ini untuk menghindari duplikasi kode dengan merangkum kode yang berubah seperti contoh pola Metode Templat. Ini juga memungkinkan untuk gagal puasa karena melanggar perubahan menyakitkan (yaitu mengubah satu tempat, memecahnya di tempat lain). Demi pemeliharaan, konsep enkapsulasi perubahan adalah hal yang baik, karena perubahan selalu terjadi.
LSP adalah tentang membiarkan pengguna menangani objek berbeda yang mengimplementasikan supertype tanpa memeriksa apa tipe sebenarnya mereka. Inilah yang menjadi sifat polimorfisme .
Prinsip ini memberikan alternatif untuk melakukan pengecekan tipe dan konversi tipe, yang tidak dapat dilakukan ketika jumlah tipe bertambah, dan dapat dicapai melalui pull-up refactoring atau menerapkan pola seperti Visitor.
sumber
Ini adalah sesuatu yang menyebabkan banyak kebingungan. Saya lebih suka mempertimbangkan prinsip-prinsip ini secara filosofis, karena ada banyak contoh berbeda untuk mereka, dan kadang-kadang contoh nyata tidak benar-benar menangkap seluruh esensi mereka.
Apa yang OCP coba perbaiki
Katakanlah kita perlu menambahkan fungsionalitas ke program yang diberikan. Cara termudah untuk melakukannya, terutama bagi orang-orang yang dilatih untuk berpikir secara prosedural, adalah menambahkan klausa if di mana pun dibutuhkan, atau semacamnya.
Masalah dengan itu adalah
Anda dapat melakukan ini dengan menambahkan bidang tambahan ke semua buku bernama "is_on_sale", dan kemudian Anda dapat memeriksa bidang itu saat mencetak harga buku apa pun, atau sebagai alternatif , Anda dapat membuat instance buku penjualan dari database menggunakan jenis yang berbeda, yang dicetak "(DIJUAL)" dalam string harga (bukan desain yang sempurna tetapi memberikan titik pulang).
Masalah dengan solusi prosedural yang pertama, adalah bidang ekstra untuk setiap buku, dan kompleksitas berlebihan yang berlebihan dalam banyak kasus. Solusi kedua hanya memaksa logika di mana sebenarnya diperlukan.
Sekarang pertimbangkan fakta bahwa mungkin ada banyak kasus di mana data dan logika yang berbeda diperlukan, dan Anda akan melihat mengapa mengingat OCP saat merancang kelas Anda, atau bereaksi terhadap perubahan persyaratan, adalah ide yang bagus.
Sekarang Anda harus mendapatkan ide utama: Cobalah untuk menempatkan diri Anda dalam situasi di mana kode baru dapat diimplementasikan sebagai ekstensi polimorfik, bukan modifikasi prosedural.
Tetapi jangan pernah takut untuk menganalisis konteksnya, dan lihat apakah kekurangannya melebihi manfaatnya, karena bahkan prinsip seperti OCP dapat membuat kekacauan 20-kelas dari program 20-line, jika tidak ditangani dengan hati-hati .
Apa yang LSP coba perbaiki
Kita semua suka menggunakan kembali kode. Suatu penyakit yang mengikuti adalah bahwa banyak program tidak memahaminya sepenuhnya, ke titik di mana mereka secara membabi buta memfaktorkan garis-garis kode yang umum hanya untuk menciptakan kompleksitas yang tidak dapat dibaca dan penggabungan erat yang berlebihan antar modul yang, selain beberapa baris kode, tidak memiliki kesamaan sejauh pekerjaan konseptual yang harus dilakukan berjalan.
Contoh terbesar dari hal ini adalah antarmuka digunakan kembali . Anda mungkin menyaksikannya sendiri; kelas mengimplementasikan antarmuka, bukan karena itu implementasi logis dari itu (atau ekstensi dalam kasus kelas dasar beton), tetapi karena metode itu menyatakan pada saat itu memiliki tanda tangan yang tepat sejauh yang bersangkutan.
Tapi kemudian Anda menemui masalah. Jika kelas mengimplementasikan antarmuka hanya dengan mempertimbangkan tanda tangan dari metode yang mereka nyatakan, maka Anda mendapati diri Anda dapat meneruskan instance kelas dari satu fungsi konseptual ke tempat-tempat yang menuntut fungsionalitas yang sama sekali berbeda, yang hanya bergantung pada tanda tangan yang serupa.
Itu tidak terlalu mengerikan, tetapi menyebabkan banyak kebingungan, dan kami memiliki teknologi untuk mencegah diri dari membuat kesalahan seperti ini. Yang perlu kita lakukan adalah memperlakukan antarmuka sebagai API + Protokol . API jelas dalam deklarasi, dan protokolnya jelas dalam penggunaan antarmuka yang ada. Jika kita memiliki 2 protokol konseptual yang berbagi API yang sama, protokol tersebut harus direpresentasikan sebagai 2 antarmuka yang berbeda. Kalau tidak, kita terjebak dalam dogmatisme KERING dan, ironisnya, hanya membuat lebih sulit untuk mempertahankan kode.
Sekarang Anda harus dapat memahami definisi dengan sempurna. LSP mengatakan: Jangan mewarisi dari kelas dasar dan mengimplementasikan fungsionalitas dalam sub-kelas yang, tempat lain, yang bergantung pada kelas dasar, tidak akan cocok.
sumber
Dari pengertian saya:
OCP mengatakan: "Jika Anda akan menambahkan fungsionalitas baru, buat kelas baru dengan memperluas yang sudah ada, daripada mengubahnya."
LSP mengatakan: "Jika Anda membuat kelas baru yang memperluas kelas yang sudah ada, pastikan itu benar-benar dapat dipertukarkan dengan basisnya."
Jadi saya pikir mereka saling melengkapi tetapi mereka tidak setara.
sumber
Meskipun benar bahwa OCP dan LSP keduanya berkaitan dengan modifikasi, jenis modifikasi yang dibicarakan oleh OCP bukanlah yang dibicarakan oleh LSP.
Memodifikasi berkaitan dengan OCP adalah tindakan fisik dari pengembang menulis kode di kelas yang ada.
LSP berurusan dengan modifikasi perilaku yang diturunkan oleh kelas turunan dibandingkan dengan kelas dasarnya, dan perubahan runtime dari eksekusi program yang dapat disebabkan oleh penggunaan subclass bukan superclass.
Jadi meskipun mereka mungkin terlihat serupa dari jarak OCP! = LSP. Sebenarnya saya pikir mereka mungkin satu-satunya 2 prinsip SOLID yang tidak dapat dipahami dalam hal satu sama lain.
sumber
Ini salah. LSP menyatakan bahwa kelas Bar tidak boleh memperkenalkan perilaku, yang tidak diharapkan ketika kode menggunakan Foo, ketika Bar berasal dari Foo. Ini tidak ada hubungannya dengan hilangnya fungsionalitas. Anda dapat menghapus fungsionalitas, tetapi hanya ketika kode menggunakan Foo tidak tergantung pada fungsi ini.
Tetapi pada akhirnya, ini biasanya sulit dicapai, karena sebagian besar waktu, kode menggunakan Foo tergantung pada semua perilaku itu. Jadi menghapus itu melanggar LSP. Tetapi menyederhanakannya seperti ini hanya bagian dari LSP.
sumber
Tentang benda yang mungkin dilanggar
Untuk memahami perbedaannya, Anda harus memahami subjek dari kedua prinsip. Bukan bagian abstrak dari kode atau situasi yang mungkin melanggar atau bukan prinsip. Selalu ada komponen tertentu - fungsi, kelas atau modul - yang dapat melanggar OCP atau LSP.
Siapa yang mungkin melanggar LSP
Satu dapat memeriksa apakah LSP rusak hanya ketika ada antarmuka dengan beberapa kontrak dan implementasi dari antarmuka itu. Jika implementasi tidak sesuai dengan antarmuka atau, secara umum, dengan kontrak, maka LSP rusak.
Contoh paling sederhana:
Kontrak dengan jelas menyatakan bahwa
addObject
harus menambahkan argumennya ke wadah. DanCustomContainer
jelas melanggar kontrak itu. Dengan demikianCustomContainer.addObject
fungsinya melanggar LSP. Dengan demikianCustomContainer
kelas melanggar LSP. Konsekuensi yang paling penting adalah bahwaCustomContainer
tidak dapat diteruskan kefillWithRandomNumbers()
.Container
tidak dapat diganti denganCustomContainer
.Ingatlah hal yang sangat penting. Bukan seluruh kode ini yang memecah LSP, melainkan secara khusus
CustomContainer.addObject
dan umumCustomContainer
yang memecah LSP. Ketika Anda menyatakan bahwa LSP dilanggar, Anda harus selalu menentukan dua hal:Itu dia. Hanya kontrak dan implementasinya. Downcast dalam kode tidak mengatakan apa-apa tentang pelanggaran LSP.
Siapa yang mungkin melanggar OCP
Seseorang dapat memeriksa apakah OCP dilanggar hanya ketika ada set data yang terbatas dan komponen yang menangani nilai dari set data itu. Jika batas-batas kumpulan data dapat berubah dari waktu ke waktu dan itu membutuhkan perubahan kode sumber komponen, maka komponen tersebut melanggar OCP.
Kedengarannya rumit. Mari kita coba contoh sederhana:
Set data adalah set platform yang didukung.
PlatformDescriber
adalah komponen yang menangani nilai dari kumpulan data itu. Menambahkan platform baru memerlukan memperbarui kode sumberPlatformDescriber
. Dengan demikianPlatformDescriber
kelas melanggar OCP.Contoh lain:
"Kumpulan data" adalah kumpulan saluran tempat entri log harus ditambahkan.
Logger
adalah komponen yang bertanggung jawab untuk menambahkan entri ke semua saluran. Menambahkan dukungan untuk cara logging lain memerlukan pembaruan kode sumberLogger
. Dengan demikianLogger
kelas melanggar OCP.Perhatikan bahwa dalam kedua contoh, kumpulan data bukanlah sesuatu yang diperbaiki secara semantik. Mungkin berubah seiring waktu. Platform baru mungkin muncul. Saluran logging baru mungkin muncul. Jika komponen Anda harus diperbarui ketika itu terjadi, itu melanggar OCP.
Mendorong batas
Sekarang bagian yang sulit. Bandingkan contoh di atas dengan yang berikut:
Anda mungkin berpikir
translateToRussian
melanggar OCP. Tapi sebenarnya tidak.GregorianWeekDay
memiliki batas spesifik 7 hari kerja dengan nama yang tepat. Dan yang penting adalah bahwa batasan ini secara semantik tidak dapat berubah seiring waktu. Akan selalu ada 7 hari di minggu keemasan. Akan selalu ada hari Senin, Selasa, dll. Kumpulan data ini secara semantik diperbaiki. Tidak mungkin bahwatranslateToRussian
kode sumber akan memerlukan modifikasi. Dengan demikian OCP tidak dilanggar.Sekarang harus jelas bahwa
switch
pernyataan yang melelahkan tidak selalu merupakan indikasi OCP yang rusak.Perbedaan
Sekarang rasakan perbedaannya:
Kondisi ini sepenuhnya ortogonal.
Contohnya
Dalam jawaban @ Spoike ini yang Melanggar satu prinsip tetapi setelah lain bagian benar-benar salah.
Dalam contoh pertama bagian
for
-loop jelas melanggar OCP karena tidak dapat diperpanjang tanpa modifikasi. Tetapi tidak ada indikasi pelanggaran LSP. Dan bahkan tidak jelas apakahContext
kontrak tersebut memungkinkan getPersons untuk mengembalikan apa pun kecualiBoss
atauPeon
. Bahkan dengan asumsi kontrak yang memungkinkan setiapIPerson
subclass untuk dikembalikan, tidak ada kelas yang menimpa pasca-kondisi ini dan melanggarnya. Terlebih lagi, jika getPersons akan mengembalikan instance dari beberapa kelas ketiga,for
-loop akan melakukan tugasnya tanpa kegagalan. Tapi fakta itu tidak ada hubungannya dengan LSP.Lanjut. Dalam contoh kedua baik LSP, maupun OCP dilanggar. Sekali lagi,
Context
bagian itu tidak ada hubungannya dengan LSP - tidak ada kontrak yang ditentukan, tidak ada subclassing, tidak ada melanggar menimpa. BukanContext
siapa yang harus mematuhi LSP, tidakLiskovSub
boleh melanggar kontrak dari pangkalannya. Mengenai OCP, apakah kelasnya benar-benar tertutup? - ya itu. Tidak diperlukan modifikasi untuk memperpanjangnya. Jelas nama titik ekstensi menyatakan Lakukan apa pun yang Anda inginkan, tanpa batas . Contohnya tidak terlalu berguna dalam kehidupan nyata, tetapi jelas tidak melanggar OCP.Mari kita coba membuat beberapa contoh yang benar dengan pelanggaran OCP atau LSP.
Ikuti OCP tetapi bukan LSP
Di sini,
HumanReadablePlatformSerializer
tidak memerlukan modifikasi apa pun ketika platform baru ditambahkan. Jadi itu mengikuti OCP.Tetapi kontrak mengharuskan bahwa
toJson
harus mengembalikan JSON diformat dengan benar. Kelas tidak melakukannya. Karena itu tidak dapat diteruskan ke komponen yang digunakanPlatformSerializer
untuk memformat tubuh permintaan jaringan. Dengan demikianHumanReadablePlatformSerializer
melanggar LSP.Ikuti LSP tetapi tidak OCP
Beberapa modifikasi pada contoh sebelumnya:
Serializer mengembalikan string JSON yang diformat dengan benar. Jadi, tidak ada pelanggaran LSP di sini.
Tetapi ada persyaratan bahwa jika platform paling banyak digunakan maka harus ada indikasi yang sesuai di JSON. Dalam contoh ini OCP dilanggar oleh
HumanReadablePlatformSerializer.isMostPopular
fungsi karena suatu hari iOS menjadi platform yang paling populer. Secara formal itu berarti bahwa set platform yang paling banyak digunakan didefinisikan sebagai "Android" untuk saat ini, danisMostPopular
tidak memadai menangani set data itu. Kumpulan data tidak secara semantik diperbaiki dan dapat dengan bebas berubah seiring waktu.HumanReadablePlatformSerializer
Kode sumber harus diperbarui jika terjadi perubahan.Anda juga dapat melihat pelanggaran Tanggung Jawab Tunggal dalam contoh ini. Saya sengaja membuatnya untuk dapat menunjukkan kedua prinsip pada entitas subjek yang sama. Untuk memperbaiki SRP Anda dapat mengekstrak
isMostPopular
fungsi ke beberapa eksternalHelper
dan menambahkan parameterPlatformSerializer.toJson
. Tapi itu cerita lain.sumber
LSP dan OCP tidak sama.
LSP berbicara tentang kebenaran program yang ada . Jika contoh subtipe akan merusak kebenaran program ketika diganti menjadi kode untuk tipe leluhur, maka Anda telah menunjukkan pelanggaran LSP. Anda mungkin harus membuat tes untuk memperlihatkan ini, tetapi Anda tidak perlu mengubah basis kode yang mendasarinya. Anda memvalidasi program itu sendiri untuk melihat apakah memenuhi LSP.
OCP berbicara tentang kebenaran perubahan dalam kode program, delta dari satu versi sumber ke yang lain. Perilaku tidak boleh dimodifikasi. Seharusnya hanya diperpanjang. Contoh klasik adalah penambahan bidang. Semua bidang yang ada terus beroperasi seperti sebelumnya. Bidang baru hanya menambah fungsionalitas. Namun menghapus bidang, biasanya merupakan pelanggaran OCP. Di sini Anda memvalidasi delta versi program untuk melihat apakah itu memenuhi OCP.
Jadi itulah perbedaan utama antara LSP dan OCP. Yang pertama memvalidasi hanya basis kode yang ada , yang terakhir memvalidasi hanya basis kode dari satu versi ke yang berikutnya . Dengan demikian mereka tidak dapat menjadi hal yang sama, mereka didefinisikan sebagai memvalidasi hal-hal yang berbeda.
Saya akan memberi Anda bukti yang lebih formal: Untuk mengatakan "LSP menyiratkan OCP" akan menyiratkan delta (karena OCP memerlukan satu selain dari kasus sepele), namun LSP tidak memerlukan satu. Jadi itu jelas salah. Sebaliknya, kita dapat menyangkal "OCP menyiratkan LSP" hanya dengan mengatakan OCP adalah pernyataan tentang delta karena itu ia tidak mengatakan apa-apa tentang pernyataan tentang program di tempat. Itu mengikuti dari fakta bahwa Anda dapat membuat delta APAPUN dimulai dengan program APA SAJA di tempat. Mereka sepenuhnya independen.
sumber
Saya akan melihatnya dari sudut pandang klien. jika Klien menggunakan fitur antarmuka, dan secara internal fitur tersebut telah diterapkan oleh Kelas A. Misalkan ada kelas B yang memperluas kelas A, maka besok jika saya menghapus kelas A dari antarmuka itu dan meletakkan kelas B, maka kelas B harus juga menyediakan fitur yang sama kepada klien. Contoh standar adalah kelas Bebek yang berenang, dan jika ToyDuck memperpanjang Bebek maka ia juga harus berenang dan tidak mengeluh bahwa ia tidak dapat berenang, jika tidak ToyDuck tidak boleh memperpanjang kelas Bebek.
sumber