Setelah Circle
memperpanjangEllipse
istirahat Prinsip Substisi Liskov , karena memodifikasi postkondisi: yaitu, Anda dapat mengatur X dan Y secara independen untuk menggambar elips, tetapi X harus selalu sama dengan Y untuk lingkaran.
Tapi bukankah masalah di sini disebabkan oleh memiliki Circle menjadi subtipe dari Ellipse? Tidak bisakah kita membalikkan hubungan?
Jadi, Circle adalah supertipe - ia memiliki metode tunggal setRadius
.
Kemudian, Ellipse memperluas Circle dengan menambahkan setX
dan setY
. Memanggil setRadius
Ellipse akan mengatur X dan Y - artinya postcondition pada setRadius dipertahankan, tetapi Anda sekarang dapat mengatur X dan Y secara independen melalui antarmuka yang diperluas.
object-oriented
solid
liskov-substitution
HorusKol
sumber
sumber
Jawaban:
Masalah dengan ini (dan masalah persegi / persegi panjang) salah mengasumsikan hubungan dalam satu domain (geometri) berlaku di domain lain (perilaku)
Lingkaran dan elips terkait jika Anda melihatnya melalui prisma teori geometri. Tapi itu bukan satu-satunya domain yang bisa Anda lihat.
Penawaran desain berorientasi objek dengan perilaku .
Karakteristik yang menentukan dari suatu objek adalah perilaku yang menjadi tanggung jawab objek tersebut. Dan dalam domain perilaku, sebuah lingkaran dan elips memiliki perilaku yang berbeda sehingga mungkin lebih baik untuk tidak menganggap mereka terkait sama sekali. Dalam domain ini, elips dan lingkaran tidak memiliki hubungan yang signifikan.
Pelajaran di sini adalah memilih domain yang paling masuk akal untuk OOD, bukan untuk mencoba dan menyisir dalam suatu hubungan hanya karena ada di domain yang berbeda.
Contoh dunia nyata yang paling umum dari kesalahan ini adalah mengasumsikan objek terkait (atau bahkan kelas yang sama) karena mereka memiliki data yang sama bahkan jika perilakunya sangat berbeda. Ini adalah masalah umum ketika Anda mulai membangun objek "data pertama" dengan menentukan ke mana data berjalan. Anda bisa berakhir dengan kelas yang terkait melalui data yang memiliki perilaku yang sama sekali berbeda. Sebagai contoh, baik payslip dan objek karyawan mungkin memiliki atribut "gaji kotor", tetapi karyawan bukan tipe payslip dan payslip bukan tipe karyawan.
sumber
Lingkaran adalah kasus khusus elips, yaitu bahwa kedua sumbu elipsis adalah sama. Ini pada dasarnya salah dalam domain masalah (geometri) untuk menyatakan bahwa elips mungkin semacam lingkaran. Menggunakan model cacat ini akan melanggar banyak jaminan lingkaran, misalnya "semua titik pada lingkaran memiliki jarak yang sama ke pusat". Itu juga akan menjadi pelanggaran Prinsip Pergantian Liskov. Bagaimana sebuah elips memiliki jari-jari tunggal? (Tidak
setRadius()
tetapi yang lebih pentinggetRadius()
)Sementara pemodelan lingkaran sebagai subtipe elips tidak salah secara mendasar, itu adalah pengenalan dari mutabilitas yang memecah model ini. Tanpa
setX()
dansetY()
metode, tidak ada pelanggaran LSP. Jika ada kebutuhan untuk memiliki objek dengan dimensi yang berbeda, membuat instance baru adalah solusi yang lebih baik:sumber
Ellipse
danCircle
(sepertigetArea
) yang akan diabstraksi menjadi tipeShape
- dapatEllipse
danCircle
secara terpisah subtipe dariShape
dan memenuhi LSP?Cormac memiliki jawaban yang sangat bagus, tetapi saya hanya ingin menguraikan sedikit tentang alasan kebingungan di tempat pertama.
Warisan dalam OO sering diajarkan menggunakan metafora dunia nyata, seperti "apel dan jeruk keduanya sub-kelas buah". Sayangnya ini mengarah pada kepercayaan yang salah bahwa tipe dalam OO harus dimodelkan menurut beberapa hierarki taksonomi yang ada terlepas dari program.
Tetapi dalam desain perangkat lunak, jenis harus dimodelkan sesuai dengan persyaratan aplikasi. Klasifikasi di domain lain biasanya tidak relevan. Dalam aplikasi aktual dengan objek "Apple" dan "Orange" - katakan sistem manajemen persediaan untuk supermarket - mereka mungkin tidak akan menjadi kelas yang berbeda sama sekali, dan kategori seperti "Buah" akan menjadi atribut daripada supertipe.
Masalah lingkaran-elips adalah herring merah. Dalam geometri, lingkaran adalah spesialisasi elips, tetapi kelas dalam contoh Anda bukan angka geometris. Yang terpenting, figur geometris tidak dapat berubah. Meskipun demikian, mereka dapat ditransformasikan , tetapi kemudian sebuah lingkaran dapat diubah menjadi elipsis. Jadi model di mana lingkaran dapat mengubah radius tetapi tidak berubah menjadi elipsis tidak sesuai dengan geometri. Model seperti itu mungkin masuk akal dalam aplikasi tertentu (misalnya alat gambar) tetapi klasifikasi geometri tidak relevan untuk bagaimana Anda mendesain hierarki kelas.
Jadi haruskah Circle menjadi subclass dari Ellipse atau sebaliknya? Ini benar-benar tergantung pada persyaratan aplikasi tertentu yang menggunakan objek-objek ini. Aplikasi menggambar dapat memiliki pilihan berbeda dalam cara memperlakukan lingkaran dan elips:
Perlakukan lingkaran dan elips sebagai jenis bentuk yang berbeda dengan UI yang berbeda (mis. Dua gagang pengubah ukuran pada elipsis, satu gagang pada lingkaran). Ini berarti Anda dapat memiliki elips yang secara geometris merupakan lingkaran tetapi bukan Lingkaran dari perspektif aplikasi.
Perlakukan semua elips termasuk lingkaran yang sama, tetapi memiliki opsi untuk "mengunci" x dan y dengan nilai yang sama.
Elips hanyalah lingkaran tempat transformasi penskalaan telah diterapkan.
Setiap desain yang memungkinkan akan mengarah pada model objek yang berbeda -
Dalam kasus pertama, Circle dan Ellipses akan menjadi kelas saudara kandung
Di yang ke-2, tidak akan ada kelas Lingkaran yang berbeda sama sekali
Di yang ke-3, tidak akan ada kelas Ellipse yang berbeda. Jadi masalah yang disebut lingkaran-elips tidak memasukkan gambar dalam semua ini.
Jadi untuk menjawab pertanyaan seperti yang diajukan: Haruskah lingkaran memanjang elips? Jawabannya adalah: Itu tergantung pada apa yang ingin Anda lakukan dengannya. Tapi mungkin juga tidak.
sumber
Ini adalah kesalahan sejak awal untuk bersikeras memiliki kelas "Ellipse" dan "Circle" di mana satu adalah subkelas dari yang lain. Anda memiliki dua pilihan realistis: Pertama adalah memiliki kelas yang terpisah. Mereka mungkin memiliki superclass umum, untuk hal-hal seperti warna, apakah objek diisi, lebar garis untuk menggambar dll.
Yang lain adalah memiliki satu kelas bernama "Ellipse" saja. Jika Anda memiliki kelas itu, cukup mudah untuk menggunakannya untuk mewakili lingkaran (mungkin ada jebakan tergantung pada detail implementasi; Ellipse akan memiliki beberapa sudut dan perhitungan sudut itu tidak boleh mengalami masalah untuk elips berbentuk lingkaran). Anda bahkan dapat memiliki metode khusus untuk elips melingkar, tetapi "elips melingkar" ini masih akan menjadi objek "Ellipse" penuh.
sumber
Mengikuti poin-poin LSP, satu solusi 'tepat' untuk masalah ini adalah ketika @HorusKol dan @Ixrec muncul - memperoleh kedua jenis dari Shape. Tapi itu tergantung pada model tempat Anda bekerja, jadi Anda harus selalu kembali ke sana.
Apa yang diajarkan kepada saya adalah:
Jika sub-tipe tidak dapat melakukan perilaku yang sama dengan super-type, hubungannya tidak berlaku pada premis IS-A - itu harus diubah.
Dalam Bahasa Inggris:
(Contoh:
Begitulah cara kerja klasifikasi (mis. Di dunia hewan), dan pada prinsipnya, di OO.
Dengan menggunakan ini sebagai definisi warisan dan polimorfisme (yang selalu ditulis bersama), jika prinsip ini dilanggar, Anda harus mencoba memikirkan kembali jenis-jenis yang Anda coba modelkan.
Seperti disebutkan oleh @HorusKul dan @Ixrec, dalam matematika Anda memiliki tipe yang jelas. Tetapi dalam matematika, lingkaran adalah elips karena itu adalah SUBSET dari elips. Tetapi dalam OOP ini bukan cara kerja warisan. Kelas seharusnya hanya mewarisi jika itu adalah SUPERSET (ekstensi) dari kelas yang ada - artinya, itu masih kelas dasar dalam semua konteks.
Berdasarkan itu, saya pikir solusinya harus sedikit disusun ulang.
Miliki tipe dasar Bentuk, lalu RoundedShape (secara efektif sebuah lingkaran tetapi saya telah menggunakan nama yang berbeda di sini DENGAN SENGAJA ...)
... lalu Ellipse.
Seperti itu:
(Ini sekarang masuk akal bagi orang-orang dalam bahasa. Kami sudah memiliki konsep yang jelas tentang 'lingkaran' dalam pikiran kami, dan apa yang kami coba lakukan di sini dengan generalisasi (agregasi) mematahkan konsep itu.)
sumber
Dari sudut pandang OO elips memperpanjang lingkaran, ia mengkhususkan diri dengan menambahkan beberapa properti. Properti lingkaran yang ada masih bertahan di elips, hanya saja semakin kompleks dan lebih spesifik. Saya tidak melihat masalah dengan perilaku dalam hal ini seperti Cormac tidak, bentuk tidak memiliki perilaku. Satu-satunya masalah adalah bahwa dalam pengertian liguistik atau matematis, rasanya tidak benar untuk mengatakan "sebuah elips IS A circle". Karena seluruh inti dari latihan yang tidak disebutkan tetapi tetap tersirat, adalah untuk mengklasifikasikan bentuk geometris. Itu mungkin merupakan alasan yang baik untuk menganggap lingkaran dan elips sebagai teman sebaya, tidak menghubungkan mereka dengan warisan dan menerima bahwa mereka kebetulan memiliki beberapa sifat yang sama dan TIDAK membiarkan pikiran OO bengkok Anda memiliki cara dengan pengamatan itu.
sumber