Bisakah masalah lingkaran-elips diselesaikan dengan membalikkan hubungan?

13

Setelah CirclememperpanjangEllipse 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 setXdan setY. Memanggil setRadiusEllipse akan mengatur X dan Y - artinya postcondition pada setRadius dipertahankan, tetapi Anda sekarang dapat mengatur X dan Y secara independen melalui antarmuka yang diperluas.

HorusKol
sumber
1
Apakah Anda melihat ke Wikipedia terlebih dahulu ( en.wikipedia.org/wiki/Circle-ellipse_problem )?
Doc Brown
1
ya - saya bahkan menautkannya dalam pertanyaan saya ...
HorusKol
6
Dan poin yang tepat ini dicakup dalam artikel itu, jadi saya tidak jelas apa yang Anda tanyakan?
Philip Kendall
6
"Beberapa penulis telah menyarankan membalikkan hubungan antara lingkaran dan elips, dengan alasan bahwa elips adalah lingkaran dengan kemampuan tambahan. Sayangnya, elips gagal untuk memenuhi banyak invarian lingkaran; jika Circle memiliki radius metode, Ellipse sekarang akan memiliki untuk menyediakannya juga. "
Philip Kendall
3
Apa yang saya temukan sebagai penjelasan paling jelas tentang mengapa masalah ini memiliki premis buruk terletak di bagian paling bawah artikel wikipedia: en.wikipedia.org/wiki/… . Bergantung pada situasinya, ada beberapa desain yang bersih, tetapi itu tergantung pada apa yang Anda butuhkan dari dua kelas ini untuk dilakukan , bukan menjadi .
Arthur Havlicek

Jawaban:

37

Tapi bukankah masalah di sini disebabkan oleh memiliki Circle menjadi subtipe dari Ellipse? Tidak bisakah kita membalikkan hubungan?

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.

Cormac Mulhall
sumber
Memisahkan keprihatinan domain (aplikasi) vs. kemampuan perilaku dan tanggung jawab OOD adalah poin yang sangat penting. Misalnya, dalam aplikasi menggambar, Anda mungkin harus dapat mengubah bentuk lingkaran menjadi persegi, tetapi ini tidak mudah dimodelkan menggunakan kelas / objek dalam kebanyakan bahasa (karena objek biasanya tidak dapat mengubah kelas). Jadi, domain aplikasi tidak selalu memetakan dengan baik ke hierarki warisan bahasa OOP yang diberikan dan kami tidak boleh mencoba memaksanya; dalam banyak kasus, komposisi lebih baik.
Erik Eidt
3
Jawaban ini sejauh ini adalah hal terbaik yang saya lihat tentang keseluruhan masalah, dan bagaimana potensi kesalahan desain dapat muncul dalam kasus yang lebih umum. Terima kasih
HorusKol
1
@ErikEidt Masalah perilaku perubahan objek dapat diselesaikan dalam OOD melalui dekomposisi. Misalnya jika bentuk berubah menjadi lingkaran, Anda tidak harus mengubah kelas. Sebagai gantinya kelas mengambil objek perilaku geometris saat ini yang dapat Anda tukarkan untuk perilaku lain saat Anda berubah. Kelas lain ini berisi aturan bentuk geometris yang saat ini sedang dimodelkan, dan kelas bentuk morphable mengacu pada kelas ini untuk perilaku geometris. Jika objek berubah menjadi kelas yang berbeda, Anda mengubah kelas perilaku ke sesuatu yang lain.
Cormac Mulhall
2
@Cormac, benar! Secara umum, saya menyebutnya bentuk komposisi, seperti yang saya sebutkan, meskipun Anda dapat mengidentifikasi, lebih spesifik, pola strategi atau sesuatu. Intinya, Anda memiliki identitas yang tidak berubah, dan hal-hal lain yang dapat diubah. Semua dalam semua menyoroti menyoroti perbedaan antara konsep domain aplikasi, dan rincian OOP bahasa tertentu, dan kebutuhan untuk memetakan di antara mereka (yaitu arsitektur, desain, dan pemrograman).
Erik Eidt
1
Tapi pekerjaan bisa menjadi gaji.
8

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 penting getRadius())

Sementara pemodelan lingkaran sebagai subtipe elips tidak salah secara mendasar, itu adalah pengenalan dari mutabilitas yang memecah model ini. Tanpa setX()dan setY()metode, tidak ada pelanggaran LSP. Jika ada kebutuhan untuk memiliki objek dengan dimensi yang berbeda, membuat instance baru adalah solusi yang lebih baik:

class Ellipse {
  final double x;
  final double y;
  ...
  Ellipse withX(double newX) {
    return new Ellipse(x: newX, y: y);
  }
}
amon
sumber
1
oke - jadi, jika ada beberapa antarmuka umum antara Ellipsedan Circle(seperti getArea) yang akan diabstraksi menjadi tipe Shape- dapat Ellipsedan Circlesecara terpisah subtipe dari Shapedan memenuhi LSP?
HorusKol
1
@HorusKol Ya. Dua kelas yang mewarisi antarmuka yang mereka berdua benar-benar lakukan benar dengan benar.
Ixrec
7

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:

  1. 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.

  2. Perlakukan semua elips termasuk lingkaran yang sama, tetapi memiliki opsi untuk "mengunci" x dan y dengan nilai yang sama.

  3. 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.

JacquesB
sumber
1
Jawaban yang sangat bagus!
Utsav T
6

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.

gnasher729
sumber
Mungkin ada metode IsCircle yang akan memeriksa untuk melihat apakah objek tertentu dari kelas Ellipse sebenarnya memiliki kedua sumbu yang sama. Anda menunjukkan masalah sudut juga. Lingkaran tidak dapat 'diputar'.
3

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.

  • Sub-tipe adalah SUPERSET dari tipe-super.
  • Tipe super adalah SUBSET dari sub-tipe.

Dalam Bahasa Inggris:

  • Tipe turunan adalah SUPERSET dari tipe dasar.
  • Tipe dasar adalah SUBSET dari tipe turunan.

(Contoh:

  • Mobil dengan knalpot bad-boy masih mobil (menurut beberapa).
  • Mobil tanpa mesin, roda, rak kemudi, drivetrain, dan hanya cangkang yang tersisa, bukan 'mobil', itu hanya cangkang.)

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:

  • RoundedShape adalah Bentuk.
  • Ellipse adalah RoundedShape.

(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.)

Andy Bagus
sumber
Konsep kami yang jelas tidak selalu berhasil dalam praktik.
-1

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.

Martin Maat
sumber