Bagaimana cara menerapkan pewarisan RealNumber dan ComplexNumber?


11

Semoga tidak terlalu akademik ...

Katakanlah saya membutuhkan angka nyata dan kompleks di perpustakaan SW saya.

Berdasarkan hubungan is-a (atau di sini ), bilangan real adalah bilangan kompleks, di mana b di bagian imajiner bilangan kompleks hanyalah 0.

Di sisi lain, implementasi saya akan menjadi, bahwa anak memperpanjang orangtua, jadi pada orangtua RealNumber saya akan memiliki bagian nyata dan anak ComplexNumber akan menambahkan seni imajiner.

Juga ada pendapat, bahwa warisan itu jahat .

Saya ingat seperti kemarin, ketika saya belajar OOP di universitas, profesor saya berkata, ini bukan contoh yang baik dari warisan karena nilai absolut dari keduanya dihitung secara berbeda (tetapi untuk itu kami memiliki metode kelebihan muatan / polimorfisme, kan?) .. .

Pengalaman saya adalah, bahwa kita sering menggunakan warisan untuk menyelesaikan KERING, akibatnya kita sering memiliki kelas abstrak buatan dalam hierarki (kita sering memiliki masalah untuk menemukan nama karena mereka tidak mewakili objek dari dunia nyata).


7
ini sepertinya tercakup dalam pertanyaan sebelumnya: Haruskah persegi panjang mewarisi dari kotak?
nyamuk

1
@gnat Ya ampun, itu contoh lain yang ingin saya gunakan ... Terima kasih!
Betlista

7
... Perhatikan bahwa kalimat "bilangan real adalah bilangan kompleks" dalam arti matematis hanya berlaku untuk bilangan tidak berubah , jadi jika Anda menggunakan objek tidak berubah, Anda dapat menghindari pelanggaran LSP (pegangan yang sama juga untuk kotak dan persegi panjang, lihat ini JADI jawab ).
Doc Brown

5
... Perhatikan lebih lanjut perhitungan nilai absolut untuk bilangan kompleks bekerja juga untuk bilangan real, jadi saya tidak yakin apa yang dimaksud profesor Anda. Jika Anda menerapkan metode "Abs ()" dengan benar dalam bilangan kompleks yang tidak berubah dan mendapatkan "nyata" darinya, metode Abs () masih akan memberikan hasil yang benar.
Doc Brown

Jawaban:


17

Sekalipun dalam arti matematis, bilangan real adalah bilangan kompleks, bukan ide yang baik untuk memperoleh bilangan real dari kompleks. Itu melanggar Prinsip Pergantian Liskov yang mengatakan (antara lain) bahwa kelas turunan tidak boleh menyembunyikan properti dari kelas dasar.

Dalam hal ini bilangan real harus menyembunyikan bagian imajiner bilangan kompleks. Jelas bahwa tidak masuk akal untuk menyimpan angka floating point tersembunyi (bagian imajiner) jika Anda hanya membutuhkan bagian yang sebenarnya.

Ini pada dasarnya masalah yang sama dengan contoh persegi panjang / persegi yang disebutkan dalam komentar.


2
Hari ini saya melihat "Prinsip Pergantian Liskow" ini beberapa kali, saya harus membaca lebih banyak tentang itu, karena saya tidak tahu itu.
Betlista

7
Sangat baik untuk melaporkan bagian imajiner dari bilangan real sebagai nol, misalnya melalui metode read-only. Tetapi tidak masuk akal untuk mengimplementasikan nyata sebagai bilangan kompleks di mana bagian imajiner ditetapkan ke nol. Ini persis kasus di mana warisan menyesatkan: sementara warisan antarmuka bisa dibilang baik-baik saja di sini, warisan implementasi akan menghasilkan desain yang bermasalah.
amon

4
Masuk akal jika memiliki bilangan real yang diwarisi dari bilangan kompleks, asalkan keduanya tidak berubah. Dan Anda tidak keberatan dengan biaya overhead.
Deduplicator

@Dupuplikator: Poin menarik. Kekekalan menyelesaikan banyak masalah tetapi saya belum sepenuhnya yakin dalam kasus ini. Harus memikirkannya.
Frank Puffer

3

bukan contoh warisan yang baik karena nilai absolut dari keduanya dihitung secara berbeda

Ini sebenarnya bukan alasan kuat terhadap semua warisan di sini, hanya model class RealNumber<-> yang diusulkan class ComplexNumber.

Anda mungkin cukup mendefinisikan interface Number, yang baik RealNumber dan ComplexNumberakan menerapkan.

Itu mungkin terlihat seperti

interface Number
{
    Number Add(Number rhs);
    Number Subtract(Number rhs);
    // ... etc
}

Tapi kemudian Anda ingin membatasi Numberparameter lain dalam operasi ini menjadi tipe turunan yang sama dengan thisyang Anda bisa dekat dengannya

interface Number<T>
{
    Number<T> Add(Number<T> rhs);
    Number<T> Subtract(Number<T> rhs);
    // ... etc
}

Atau alih-alih Anda akan menggunakan bahasa yang memungkinkan polimorfisme struktural, alih-alih polimorfisme subtipe. Untuk kasus angka tertentu, Anda mungkin hanya perlu kemampuan untuk membebani operator aritmatika.

complex operator + (complex lhs, complex rhs);
complex operator - (complex lhs, complex rhs);
// ... etc

Number frobnicate<Number>(List<Number> foos, Number bar); // uses arithmetic operations

0

Solusi: Tidak memiliki RealNumberkelas publik

Saya akan menemukannya benar-benar OK jika ComplexNumbermemiliki metode pabrik statis fromDouble(double)yang akan mengembalikan bilangan kompleks dengan imajiner menjadi nol. Anda kemudian dapat menggunakan semua operasi yang akan Anda gunakan pada RealNumbercontoh pada ComplexNumbercontoh ini .

Tapi saya kesulitan melihat mengapa Anda ingin / perlu memiliki RealNumberkelas turunan publik . Biasanya warisan digunakan untuk alasan-alasan ini (di luar kepalaku, koreksi aku jika ketinggalan beberapa)

  • memperluas perilaku. RealNumberstidak dapat melakukan operasi tambahan yang tidak dapat dilakukan oleh bilangan kompleks, jadi tidak ada gunanya melakukan ini.

  • menerapkan perilaku abstrak dengan implementasi spesifik. Karena ComplexNumbertidak boleh abstrak ini juga tidak berlaku.

  • penggunaan kembali kode. Jika Anda hanya menggunakan ComplexNumberkelas Anda menggunakan kembali 100% dari kode.

  • implementasi yang lebih spesifik / efisien / akurat untuk tugas tertentu. Ini bisa diterapkan di sini, RealNumbersbisa mengimplementasikan beberapa fungsi lebih cepat. Tapi kemudian subkelas ini harus disembunyikan di belakang statis fromDouble(double)dan tidak boleh diketahui di luar. Dengan cara ini tidak perlu menyembunyikan bagian imajiner. Untuk luar hanya ada bilangan kompleks (yang bilangan real). Anda juga bisa mengembalikan kelas RealNumber pribadi ini dari operasi apa pun di kelas bilangan kompleks yang menghasilkan bilangan real. (Ini mengasumsikan kelas tidak dapat diubah karena sebagian besar kelas angka.)

Ini seperti menerapkan subkelas Integer yang disebut Zero dan hardcode beberapa operasi karena mereka sepele untuk nol. Anda bisa melakukan ini, karena setiap nol adalah bilangan bulat, tetapi jangan buat publik, sembunyikan di balik metode pabrik.


Saya tidak terkejut mendapatkan downvote, karena saya tidak punya sumber untuk membuktikan. Juga jika tidak ada orang lain yang punya ide, saya selalu curiga mungkin ada beberapa alasan untuk itu. Tapi tolong beri tahu saya mengapa Anda berpikir itu salah dan bagaimana Anda akan membuatnya lebih baik.
findusl

0

Mengatakan bahwa bilangan real adalah bilangan kompleks memiliki lebih banyak makna dalam matematika, terutama teori himpunan, daripada ilmu komputer.
Dalam matematika kita katakan:

  • Bilangan real adalah bilangan kompleks karena himpunan bilangan kompleks mencakup himpunan bilangan real.
  • Bilangan rasional adalah bilangan real karena himpunan bilangan real meliputi himpunan bilangan rasional (dan himpunan bilangan irasional).
  • Bilangan bulat adalah bilangan rasional karena himpunan bilangan rasional mencakup himpunan bilangan bulat.

Namun, ini tidak berarti Anda harus, atau bahkan harus, menggunakan warisan ketika mendesain pustaka Anda untuk menyertakan kelas RealNumber dan ComplexNumber. Di Jawa Efektif, Edisi Kedua oleh Joshua Bloch; Butir 16 adalah "Komposisi Nikmat Lebih Dari Warisan". Untuk menghindari masalah yang disebutkan dalam item itu, setelah Anda menentukan kelas RealNumber Anda, itu dapat digunakan di kelas ComplexNumber Anda:

public class ComplexNumber {
    private RealNumber realPart;
    private RealNumber imaginaryPart;

    // Implementation details are for you to write
}

Hal ini memungkinkan Anda semua kekuatan menggunakan kembali kelas RealNumber Anda untuk menjaga kode Anda KERING sambil menghindari masalah yang diidentifikasi oleh Joshua Bloch.


0

Ada dua masalah di sini. Yang pertama adalah bahwa itu umum untuk menggunakan istilah yang sama untuk jenis wadah dan jenis isinya, terutama dengan jenis primitif seperti angka. Istilah double, misalnya, digunakan untuk menggambarkan nilai floating-point presisi ganda dan wadah di mana seseorang dapat disimpan.

Masalah kedua adalah bahwa sementara-adalah hubungan di antara wadah-wadah dari mana berbagai jenis benda dapat dibaca berperilaku sama dengan hubungan-hubungan di antara benda-benda itu sendiri, mereka di antara wadah-wadah di mana berbagai jenis benda dapat ditempatkan berperilaku bertolak belakang dengan barang-barang di dalamnya. . Setiap kandang yang dikenal memiliki turunan Catakan menjadi kandang yang menyimpan turunan dari Animal, tetapi tidak perlu menjadi kandang yang menyimpan turunan dari SiameseCat. Di sisi lain, setiap kandang yang bisa menampung semua instance Catakan menjadi kandang yang bisa menampung semua instance SiameseCat, tetapi tidak perlu menjadi kandang yang bisa menampung semua instance Animal. Satu-satunya jenis sangkar yang dapat menampung semua contoh Catdan dapat dijamin tidak pernah memegang apa pun selain dari contohCat, adalah sangkar Cat. Jenis kandang lainnya tidak akan mampu menerima beberapa contoh Catyang seharusnya diterima, atau akan mampu menerima hal-hal yang bukan contoh Cat.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.