Kapan referensi melingkar ke pointer orangtua dapat diterima?


24

Pertanyaan Stack Overflow ini adalah tentang seorang anak yang memiliki referensi ke induknya, melalui sebuah pointer.

Komentar awalnya cukup kritis dari desain menjadi ide yang mengerikan.

Saya mengerti ini mungkin bukan ide terbaik secara umum. Dari aturan umum tampaknya adil untuk mengatakan, "jangan lakukan ini!"

Namun, saya bertanya-tanya kondisi seperti apa yang akan ada di mana Anda perlu melakukan sesuatu seperti ini. Pertanyaan ini di sini dan jawaban / komentar terkait menyarankan bahkan untuk grafik untuk tidak melakukan hal seperti ini.


1
Pertanyaan yang Anda tautkan tampaknya cukup komprehensif tentang masalah ini.
Lightness Races dengan Monica

4
@LightnessRacesinOrbit "jangan lakukan ini" tidak terlalu berguna sejauh memahami mengapa .
akhir

2
Saya melihat lebih banyak daripada "jangan lakukan ini" di sana. Saya melihat pro dan kontra diperdebatkan oleh banyak ahli.
Lightness Races dengan Monica

1
Anda mungkin memiliki daftar dua arah yang perlu dilintasi, semacam penyangga bundar, mungkin Anda mewakili dua potong jalan yang terhubung dalam sebuah permainan - jika Anda perlu merepresentasikan sesuatu yang melingkar, maka ini mungkin ide yang bagus.
rhughes

2
Aturan praktis saya tentang ibu jari adalah pertanyaan "Bisakah seorang Anak ada tanpa Orang Tua?". (Jika Anda mempertimbangkan XmlDocuments dan simpulnya: sebuah simpul tidak dapat ada tanpa konteks pohon dokumen. Itu omong kosong). Jika jawabannya tidak, maka tautan dua arah tidak apa-apa: Anda memiliki dua objek yang hanya bisa ada bersama. Jika objek dapat ada secara independen, maka saya menghapus salah satu dari dua tautan itu.
mikalai

Jawaban:


43

Kuncinya di sini bukanlah apakah dua objek memiliki referensi melingkar, tetapi apakah referensi tersebut menunjukkan kepemilikan satu sama lain.

Dua objek tidak dapat "memiliki" satu sama lain: ini menyebabkan dilema yang sulit untuk inisialisasi dan urutan penghapusan. Satu harus menjadi referensi opsional, atau menunjukkan bahwa satu objek tidak akan mengatur masa pakai yang lain.

Pertimbangkan daftar yang ditautkan dua kali lipat: dua simpul saling bolak-balik, tetapi tidak ada yang "memiliki" yang lain (daftar itu memiliki keduanya). Ini berarti tidak ada node yang mengalokasikan memori untuk yang lain atau bertanggung jawab atas identitas atau manajemen seumur hidup yang lain.

Pohon memiliki hubungan yang serupa, meskipun simpul dalam pohon dapat mengalokasikan anak-anak dan orang tua memiliki anak sendiri. Tautan dari anak ke orang tua membantu dengan traversal, tetapi sekali lagi tidak mendefinisikan kepemilikan.

Dalam sebagian besar desain OO, referensi ke objek lain sebagai anggota data objek menyiratkan kepemilikan. Sebagai contoh, misalkan kita memiliki kelas Mobil dan Mesin. Tak satu pun dari mereka yang sangat berguna. Kita dapat mengatakan bahwa objek-objek ini saling bergantung : mereka membutuhkan kehadiran yang lain untuk melakukan pekerjaan yang bermanfaat. Tetapi yang "memiliki" yang lain? Dalam hal ini kita akan mengatakan bahwa Mobil memiliki Mesin karena mobil adalah "wadah" di mana semua komponen otomotif hidup. Baik dalam desain OO dan dunia nyata, mobil adalah jumlah dari bagian-bagiannya, dan semua bagian itu terhubung bersama dalam konteks mobil. Engine mungkin memiliki referensi kembali ke Mobil, atau mungkin memiliki referensi ke TorqueConverter,

Referensi melingkar bisa menjadi bau desain yang buruk, tetapi tidak harus. Ketika digunakan secara bijaksana dan didokumentasikan dengan benar, mereka dapat membuat penggunaan struktur data lebih mudah.

Coba lewati sebatang pohon tanpa referensi dari kedua orang tua dan anak-anak. Tentu, Anda bisa membuat pendekatan berbasis tumpukan yang rapuh dan kompleks, atau Anda bisa menggunakan pendekatan berbasis referensi yang sederhana.


16

Ada beberapa aspek yang perlu dipertimbangkan dalam desain seperti:

  • ketergantungan struktural
  • hubungan kepemilikan (iecomposition vs jenis asosiasi lainnya)
  • kebutuhan navigasi

Ketergantungan struktural antar kelas:

Jika Anda bertujuan menggunakan kembali kelas komponen, Anda harus menghindari ketergantungan yang tidak perlu dan menghindari struktur melingkar yang tertutup.

Namun demikian kadang-kadang dua kelas secara konsep sangat terkait. Dalam hal ini, menghindari ketergantungan bukanlah pilihan nyata. Contoh: pohon dan daunnya, atau lebih umum komposit dan komponennya .

Kepemilikan benda:

Apakah satu objek memiliki yang lain? Atau dinyatakan lain: jika satu objek dihancurkan, apakah yang lain akan dihancurkan juga?

Topik ini dibahas secara mendalam oleh Snowman, jadi saya tidak akan membahasnya di sini.

Kebutuhan navigasi antara objek:

Masalah terakhir adalah kebutuhan navigasi. Mari kita ambil contoh favorit saya, pola desain komposit dari Gang empat .

Gamma & al. secara eksplisit menyebutkan kebutuhan potensial untuk memiliki referensi orang tua yang eksplisit: " Mempertahankan referensi dari komponen anak ke orang tua mereka dapat menyederhanakan traversal dan pengelolaan struktur komposit " Tentu saja Anda dapat membayangkan traversal top-down yang sistematis, tetapi untuk objek komposit yang sangat besar itu dapat secara signifikan memperlambat operasi dan secara eksponensial. Referensi langsung, bahkan lingkaran dapat secara signifikan memudahkan manipulasi komposit Anda.

Contohnya bisa menjadi model grafis dari sistem elektronik. Struktur komposit dapat mewakili papan elektronik, sirkuit, elemen. Untuk menampilkan dan memanipulasi model, Anda memerlukan beberapa proksi geometris dalam tampilan GUI. Maka tentu saja jauh lebih mudah untuk menavigasi dari elemen GUI yang dipilih oleh pengguna ke komponen, untuk mengetahui yang merupakan induk dan dengan elemen saudara / saudari terkait, daripada memulai pencarian top-down.

Tentu saja, seperti yang ditunjukkan oleh Gamma & al, Anda harus memastikan invarian dari hubungan sirkuler. Ini bisa rumit, seperti yang ditunjukkan oleh pertanyaan SO yang Anda lihat. Tapi itu sangat mudah dikelola dan dengan cara yang aman.

Kesimpulan

Kebutuhan navigasi tidak boleh diremehkan. Bukan tanpa alasan bahwa UML telah secara eksplisit menekannya dalam notasi pemodelan. Dan ya, ada situasi yang benar-benar valid di mana referensi melingkar diperlukan.

Satu-satunya titik adalah bahwa kadang-kadang orang cenderung pergi ke arah yang cepat. Jadi ada baiknya mempertimbangkan semua 3 aspek yang terlibat sebelum mengambil keputusan untuk melakukannya atau tidak.


1
Jawaban ini sangat undervote. OP bertanya: "Mengingat sudah ada hubungan orangtua-anak, kapan boleh menerapkan ini dengan referensi melingkar". Jadi struktur dan "kepemilikan" (dalam arti yang disebutkan di sini) sudah jelas. Itu berarti satu - satunya kriteria untuk menambahkan referensi di satu sisi atau yang lain adalah pertanyaan "kebutuhan navigasi" dan "penggunaan kembali anak secara mandiri".
Doc Brown

@DocBrown - Anda selalu bisa memberi hadiah pada pertanyaan ini setelah memenuhi syarat. :-)

1
@ GlenH7: itu tidak akan memberikan jawaban ini lebih banyak suara, hanya jawaban Snowman (yang menurut saya agak merindukan inti pertanyaan).
Doc Brown

1
@DocBrown - Tapi repz, man, repz!

... dan jika Anda menambahkan pilihan visibilitas seperti yang dilindungi publik-swasta, itu memberi Anda 9 pilihan berbeda :)
mikalai

8

Biasanya, referensi melingkar adalah ide yang sangat buruk karena artinya ketergantungan sirkular. Anda mungkin sudah tahu mengapa edaran melingkar buruk, tetapi demi kelengkapan, versi dr adalah bahwa setiap kali kelas A dan B keduanya saling bergantung, tidak mungkin untuk memahami / memperbaiki / mengoptimalkan / dll, baik A atau B tanpa A juga B memahami / memperbaiki / mengoptimalkan / dll kelas lain secara bersamaan. Yang dengan cepat mengarah ke basis kode di mana Anda tidak dapat mengubah apa pun tanpa mengubah segalanya.

Namun, adalah mungkin untuk memiliki referensi melingkar tanpa menciptakan kejahatan dependensi melingkar. Ini berfungsi selama referensi secara ketat opsional dalam arti fungsional. Maksud saya, Anda dapat dengan mudah menghapusnya dari kelas dan mereka akan tetap bekerja, bahkan jika mereka bekerja lebih lambat. Kasus penggunaan utama yang saya ketahui untuk referensi non-dependensi yang melingkar seperti ini memungkinkan lintasan cepat struktur data berbasis simpul seperti daftar tertaut, pohon, dan tumpukan. Misalnya, pada prinsipnya operasi apa pun yang dapat Anda lakukan pada daftar yang ditautkan dua kali lipat, Anda juga dapat melakukan pada daftar yang terhubung sendiri, kebetulan ada beberapa operasi (seperti bergerak mundur melalui daftar) yang memiliki besar lebih baik O dengan versi yang ditautkan dua kali lipat.


6

Alasan biasanya bukan ide yang baik untuk melakukan ini adalah karena itu melanggar Prinsip Ketergantungan Inversi . Orang-orang telah menulis banyak tentang hal ini secara lebih terperinci daripada yang dapat saya bahas secara memadai dalam posting ini, tetapi hal ini membuat sulit untuk mempertahankannya, karena sambungannya sangat rapat. Mengubah salah satu kelas hampir selalu mengharuskan adanya perubahan di yang lain, sedangkan jika dependensi hanya menunjuk satu arah, perubahan di satu sisi antarmuka terisolasi. Jika kedua kelas menunjuk ke antarmuka abstrak, bahkan lebih baik.

Satu pengecualian utama adalah ketika Anda tidak memiliki dua kelas yang berbeda pada tingkat abstraksi yang berbeda, tetapi dua simpul dari kelas yang sama, seperti di pohon, daftar yang ditautkan dua kali lipat, dll. Ini lebih merupakan hubungan struktural daripada hubungan abstraksi. Referensi melingkar demi efisiensi algoritmik dapat diterima dan bahkan didorong dalam kasus-kasus seperti ini.


5

[...] bahkan menyarankan agar grafik tidak melakukan hal seperti ini.

Terkadang Anda hanya perlu mengakses hal-hal secara bottom-up dari struktur data yang berbeda dari pohon, sedangkan pohon perlu mengakses hal-hal dengan cara top-down.

Sebagai contoh, quadtree mungkin menyimpan elemen dalam perangkat lunak grafik vektor. Namun, pilihan pengguna disimpan dalam daftar pilihan referensi elemen vektor yang terpisah. Ketika pengguna ingin menghapus pilihan itu, kami harus memperbarui quadtree, dan mungkin ada jauh lebih efisien untuk memperbarui pohon dengan cara bottom-up mulai dari daun daripada top-down. Kalau tidak, Anda harus, untuk setiap elemen, bekerja dari akar ke daun dan kemudian kembali lagi.


1
Ya, contoh ini sangat tepat. Tepatnya hal-hal yang saya coba jelaskan dalam argumen saya tentang navigasi dalam suatu komposit: backpointer mempercepat kecepatan. Terima kasih atas anekdot kinerja menarik Anda dan diagram yang sangat jelas! +1
Christophe

@ Christophe Saya suka contoh Anda juga! Saya tidak yakin apakah saya benar-benar menjawab pertanyaan dengan benar karena mungkin itu lebih tentang "kepemilikan melingkar" daripada hanya pointer belakang yang memungkinkan kita mendapatkan melintasi struktur data ke atas / ke belakang. Tetapi saya terutama merespons bagian "grafik" terakhir dari pertanyaan itu.

2

Doom 3 memiliki contoh objek anak dengan pointer ke objek induk. Secara khusus ia menggunakan daftar yang mengganggu . Untuk meringkas, daftar intrusi seperti daftar tertaut kecuali setiap node berisi pointer ke daftar itu sendiri.

Keuntungan:

  • Ketika objek dapat ada di beberapa daftar secara bersamaan, memori untuk daftar node perlu dialokasikan dan dideallokasi hanya sekali.

  • Ketika sebuah objek perlu dihancurkan, Anda dapat dengan mudah menghapusnya dari semua daftar yang ada di dalamnya tanpa mencari melalui setiap daftar secara linear.

Saya pikir ini adalah skenario yang cukup spesifik, tetapi jika saya memahami pertanyaan Anda, ini adalah contoh penggunaan objek anak yang dapat diterima yang berisi pointer ke objek induknya.

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.