Rumah  >  Artikel  >  Java  >  Analisis contoh warisan di Jawa

Analisis contoh warisan di Jawa

WBOY
WBOYke hadapan
2023-04-20 17:19:081211semak imbas

 Antara Muka dan Kelas?
Pernah, saya menghadiri mesyuarat kumpulan pengguna Java. Pada persidangan itu, James Gosling (bapa Jawa) memberikan ucapan pemula. Dalam segmen Soal Jawab yang tidak dapat dilupakan itu, beliau ditanya: "Jika anda memfaktorkan semula Java, apakah yang akan anda ubah?". "Saya nak buang kelas" jawabnya. Selepas ketawa reda, dijelaskan bahawa masalah sebenar bukanlah kelas itu sendiri, tetapi pelaksanaan hubungan pusaka. Warisan antara muka (melaksanakan hubungan) adalah lebih baik. Anda harus mengelak daripada melaksanakan pewarisan sebanyak mungkin.
Hilang fleksibiliti
Mengapa anda perlu mengelakkan warisan? Masalah pertama ialah penggunaan nama kelas konkrit secara eksplisit mengunci anda ke dalam pelaksanaan tertentu, menjadikan perubahan asas menjadi sukar.
Dalam kaedah pengaturcaraan tangkas semasa, terasnya ialah konsep reka bentuk dan pembangunan selari. Anda memulakan pengaturcaraan sebelum anda mereka bentuk program secara terperinci. Teknik ini berbeza daripada pendekatan tradisional - di mana reka bentuk harus disiapkan sebelum pengekodan bermula - tetapi banyak projek yang berjaya telah membuktikan bahawa anda boleh membangunkan kod berkualiti tinggi dengan lebih cepat berbanding kaedah langkah demi langkah tradisional. Tetapi teras pembangunan selari adalah fleksibiliti. Anda perlu menulis kod anda dengan cara supaya keperluan yang baru ditemui boleh digabungkan ke dalam kod sedia ada semudah mungkin.
Daripada melaksanakan ciri yang mungkin anda perlukan, anda hanya perlu melaksanakan ciri yang anda perlukan dengan jelas dan bertolak ansur secara sederhana terhadap perubahan. Jika anda tidak mempunyai pembangunan selari yang fleksibel seperti ini, ia adalah mustahil.

Pengaturcaraan untuk Inteface ialah teras seni bina yang fleksibel. Untuk menggambarkan sebabnya, mari kita lihat apa yang berlaku apabila ia digunakan. Pertimbangkan kod berikut:

f()
{
senarai LinkedList = LinkedList(); 🎜>
g( Senarai LinkedList )
{
list.add(); ... );
g2( list )
}

Anggap keperluan untuk pertanyaan pantas dibangkitkan supaya LinkedList ini tidak dapat diselesaikan. Anda perlu menggunakan HashSet sebaliknya. Dalam kod sedia ada, perubahan tidak boleh disetempatkan, kerana anda perlu mengubah suai bukan sahaja f() tetapi juga g() (yang mengambil parameter LinkedList), dan mana-mana kod yang g() lulus senarai itu. Tulis semula kod seperti berikut:

f()
{
Senarai koleksi = LinkedList();g( Senarai koleksi )
, hanya boleh menggunakan cincang HashSet() baharu dan bukannya LinkedList() baharu. itu sahaja. Tiada apa-apa lagi untuk diubah suai.

Sebagai contoh lain, bandingkan dua keping kod berikut:

f()
{
Koleksi c = new HashSet();
//...
g( c );
}

g( Koleksi c )
( i.next () ); }

g2( Iterator i )
next() );
 }
Kaedah g2() kini boleh lelaran ke atas terbitan Koleksi, sama seperti anda boleh mendapatkan pasangan nilai kunci daripada Peta. Malah, anda boleh menulis iterator yang menjana data dan bukannya melintasi Koleksi. Anda boleh menulis iterator yang mendapat maklumat daripada rangka kerja ujian atau fail. Ini membolehkan fleksibiliti yang luar biasa.

 
Gandingan

Untuk melaksanakan pewarisan, isu yang lebih kritikal ialah gandingan---pergantungan yang menjengkelkan, iaitu pergantungan satu bahagian program pada bahagian lain. Pembolehubah global memberikan contoh klasik mengapa gandingan yang kuat boleh menyebabkan masalah. Contohnya, jika anda menukar jenis pembolehubah global, maka semua fungsi yang menggunakan pembolehubah itu mungkin terjejas, jadi semua kod ini perlu diperiksa, ditukar dan diuji semula. Selain itu, semua fungsi yang menggunakan pembolehubah ini digandingkan antara satu sama lain melalui pembolehubah ini. Iaitu, jika nilai pembolehubah diubah pada masa yang sukar untuk digunakan, satu fungsi boleh menjejaskan kelakuan fungsi lain secara salah. Masalah ini tersembunyi dengan ketara dalam program berbilang benang.
Sebagai pereka bentuk, anda harus berusaha untuk meminimumkan hubungan gandingan. Anda tidak boleh menghapuskan gandingan sama sekali, kerana panggilan kaedah daripada objek satu kelas ke objek kelas lain ialah satu bentuk gandingan longgar. Anda tidak boleh mempunyai program tanpa sebarang gandingan. Walau bagaimanapun, anda boleh meminimumkan beberapa gandingan dengan mengikuti peraturan OO (paling penting, pelaksanaan objek harus disembunyikan sepenuhnya daripada objek yang menggunakannya). Sebagai contoh, pembolehubah contoh objek (medan yang bukan pemalar) hendaklah sentiasa peribadi. Maksud saya untuk tempoh masa tertentu, tanpa pengecualian, secara berterusan. (Anda kadang-kadang boleh menggunakan kaedah yang dilindungi dengan berkesan, tetapi pembolehubah contoh yang dilindungi adalah kebencian.) Atas sebab yang sama, anda tidak sepatutnya menggunakan fungsi get/set --- mereka hanya berasa terlalu rumit untuk menjadi awam kepada domain (walaupun pengubah suai kembali Sebab untuk mengakses fungsi objek dan bukannya nilai primitif adalah dalam beberapa kes, dan dalam kes itu kelas objek yang dikembalikan adalah abstraksi utama dalam reka bentuk).

Di sini, saya tidak bodoh. Dalam kerja saya sendiri, saya mendapati korelasi langsung antara ketegasan pendekatan OO saya, pembangunan kod pesat dan pelaksanaan kod yang mudah. Setiap kali saya melanggar prinsip OO pusat, seperti penyembunyian pelaksanaan, saya akhirnya menulis semula kod itu (biasanya kerana kod itu tidak boleh nyahpepijat). Saya tidak mempunyai masa untuk menulis semula kod, jadi saya mengikut peraturan tersebut. Adakah saya mengambil berat tentang alasan praktikal semata-mata. Saya tidak berminat dengan alasan yang bersih.

  Masalah kelas asas yang rapuh

Sekarang, mari kita gunakan konsep gandingan kepada pewarisan. Dalam sistem pelaksanaan yang menggunakan extends, kelas terbitan digandingkan dengan sangat rapat dengan kelas asas, dan gandingan ketat ini tidak diingini. Pereka bentuk telah menggunakan nama samaran "masalah kelas asas rapuh" untuk menerangkan tingkah laku ini. Kelas asas dianggap rapuh kerana anda mengubah suai kelas asas nampaknya selamat, tetapi apabila mewarisi daripada kelas terbitan, tingkah laku baharu boleh menyebabkan kelas terbitan menjadi tidak berfungsi. Anda tidak boleh mengetahui sama ada perubahan kepada kelas asas adalah selamat dengan hanya memeriksa kelas asas secara berasingan, sebaliknya, anda juga mesti melihat (dan menguji) semua kelas terbitan. Tambahan pula, anda mesti menyemak semua kod yang turut digunakan dalam objek kelas asas dan terbitan, kerana kod ini mungkin dipecahkan oleh tingkah laku baharu. Perubahan mudah kepada kelas asas boleh menyebabkan keseluruhan program tidak dapat dikendalikan.

Mari kita periksa isu kelas asas yang rapuh dan gandingan kelas asas. Kelas berikut memanjangkan kelas ArrayList Java untuk menjadikannya berkelakuan seperti tindanan:

kelas Stack memanjangkan ArrayList
{
private int stack_pointer = 0;

public void push ( Object article )
 {
 tambah( stack_pointer++, artikel );
 }

 public Object pop()
 {
 return remove( --stack_pointer );
public void push_many( Object[] articles )
{
for( int i = 0; i < articles.length; ++i )
push( articles[i ] );
 }
 }

Kelas simple macam ni pun ada masalah. Pertimbangkan perkara yang berlaku apabila pengguna mengimbangi warisan dan menggunakan kaedah clear() ArrayList untuk memunculkan tindanan:

Tindanan a_stack = new Stack(); a_stack.push("1"); ");
a_stack.clear();

Kod ini berjaya dikompil, tetapi kerana kelas asas tidak tahu tentang tindanan penuding tindanan, objek tindanan kini berada dalam keadaan tidak ditentukan . Panggilan seterusnya untuk push() meletakkan item baharu pada indeks 2. (nilai semasa stack_pointer), jadi tindanan berkesan mempunyai tiga elemen - dua bahagian bawah adalah sampah. (Kelas tindanan Java mempunyai masalah ini, jangan gunakannya). operasi yang betul penuding Tindanan atau buang pengecualian. (Kaedah removeRange() adalah calon yang baik untuk membuang pengecualian).

Kaedah ini mempunyai dua kelemahan. Pertama, jika anda merangkumi segala-galanya, kelas asas sepatutnya menjadi antara muka, bukan kelas. Jika anda tidak menggunakan sebarang kaedah yang diwarisi, tiada gunanya melaksanakan warisan. Kedua, dan yang lebih penting, anda tidak boleh mempunyai timbunan menyokong semua kaedah ArrayList. Sebagai contoh, removeRange() yang menjengkelkan tidak melakukan apa-apa. Satu-satunya cara yang munasabah untuk melaksanakan kaedah yang tidak berguna ialah menjadikannya membuang pengecualian, kerana ia tidak sepatutnya dipanggil. Kaedah ini secara berkesan menukar ralat kompilasi kepada ralat masa jalan. Perkara yang buruk ialah jika kaedah itu tidak ditakrifkan, pengkompil akan mencetak kaedah yang tidak dijumpai ralat. Jika kaedah itu wujud tetapi membuang pengecualian, anda tidak akan mengetahui tentang ralat panggilan sehingga program itu benar-benar berjalan.

Penyelesaian yang lebih baik untuk masalah kelas asas ini ialah merangkum struktur data dan bukannya menggunakan warisan. Ini ialah versi Stack yang baharu dan dipertingkatkan:
class Stack
{
private int stack_pointer = 0;
private ArrayList the_data = new ArrayList() public void push (); Artikel objek )
 {
 the_data.add( stack_poniter++, artikel );
 }

 public Object pop()
 {
 kembali_pointer --stack. ;
 }

Public void push_many( Object[] articles )
( articles[i] );
 }
 }

Setakat ini, sangat bagus, tetapi memandangkan masalah kelas asas yang rapuh, katakan anda ingin mencipta pembolehubah dalam tindanan dan menggunakannya Menjejaki saiz tindanan maksimum dalam satu tempoh. Pelaksanaan yang mungkin kelihatan seperti ini:

kelas Monitorable_stack memanjangkan Tindanan
{
private int high_water_mark = 0;
private int current_size;

public void push( Objek artikel )
 {
 jika( ++saiz_semasa > tanda_air_tinggi )
 tanda_air_tinggi = saiz_arus;
 super.push( artikel );
 }
 }
、 🎜> {
 --saiz_semasa;
 kembali super.pop();
 }

 public int maximum_size_so_far()
 {
watermark
 🎜> }
Kelas baharu ini berfungsi dengan baik, sekurang-kurangnya untuk seketika. Malangnya, kod ini mengeksploitasi fakta bahawa push_many() beroperasi dengan memanggil push(). Pertama sekali, butiran ini tidak kelihatan seperti pilihan yang buruk. Ia memudahkan kod, dan anda boleh mendapatkan versi terbitan push() walaupun Monitorable_stack diakses melalui rujukan Stack, supaya high_water_mark dikemas kini dengan betul.

Atas ialah kandungan terperinci Analisis contoh warisan di Jawa. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam