Mengapa koleksi Java diimplementasikan dengan "metode opsional" di antarmuka?

69

Selama implementasi pertama saya memperluas kerangka koleksi Java, saya cukup terkejut melihat bahwa antarmuka koleksi berisi metode yang dinyatakan sebagai opsional. Pelaksana diharapkan untuk membuang UnsupportedOperationExceptions jika tidak didukung. Ini langsung mengejutkan saya sebagai pilihan desain API yang buruk.

Setelah membaca banyak buku "Efektif Java" karya Joshua Bloch yang luar biasa, dan kemudian mengetahui bahwa ia mungkin bertanggung jawab atas keputusan-keputusan ini, tampaknya tidak sesuai dengan prinsip-prinsip yang dianut dalam buku ini. Saya akan berpikir mendeklarasikan dua antarmuka: Koleksi, dan MutableCollection yang memperluas Koleksi dengan metode "opsional" akan menyebabkan kode klien yang jauh lebih dapat dikelola.

Ada ringkasan yang sangat baik dari masalah di sini .

Apakah ada alasan bagus mengapa metode opsional dipilih daripada implementasi dua antarmuka?

glenviewjeff
sumber
IMO, tidak ada alasan yang bagus. C ++ STL dibuat oleh Stepanov di awal 80-an. Meskipun kegunaannya dibatasi oleh sintaks template C ++ yang canggung, ini adalah paragon dari konsistensi dan kegunaan jika dibandingkan dengan kelas koleksi Java.
kevin cline
Ada porting C ++ STL 'ke Jawa. Tapi itu 5 kali lebih besar dalam hitungan kelas. Dengan pikiran ini saya tidak percaya bahwa yang lebih besar lebih konsisten dan bermanfaat. docs.oracle.com/javase/6/docs/technotes/guides/collections/…
m3th0dman
@ m3th0dman Ini jauh lebih besar di Jawa karena Java tidak memiliki kekuatan apa pun yang setara dengan templat C ++.
kevin cline
Mungkin hanya beberapa gaya aneh saya telah dikembangkan, tetapi kode saya cenderung memperlakukan semua koleksi sebagai "read only", (lebih tepatnya, hanya sesuatu yang Anda menghitung atau di mana Anda iterate) kecuali untuk beberapa metode yang benar-benar membuat koleksi. Mungkin praktik yang bagus (terutama untuk konkurensi). Dan metode opsional yang banyak dikeluhkan tidak pernah menjadi masalah bagi saya. Mimpi buruk Generik juga tidak memiliki "super" dan "meluas". Hanya ingin tahu apakah orang lain menggunakan praktik umum ini?
user949300

Jawaban:

28

The FAQ memberikan jawabannya. Singkatnya, mereka melihat ledakan kombinatorial potensial dari antarmuka yang diperlukan dengan tampilan yang dapat dimodifikasi, tidak dapat dimodifikasi, delete-only, add-only, fixed-length, immutable (untuk threading), dan seterusnya untuk setiap set kemungkinan metode metode opsi yang diterapkan.

ratchet freak
sumber
6
Semua ini akan dihindari jika Java memiliki constkata kunci seperti C ++.
Etienne de Martel
@Tienne: Lebih baik metaclasses seperti Python. Kemudian, Anda secara pemrograman dapat membangun sejumlah antarmuka kombinatorial. Masalah dengan const adalah bahwa ia hanya memberikan satu atau dua dimensi: vector<int>, const vector<int>, vector<const int>, const vector<const int>. Sejauh ini bagus, tetapi kemudian Anda mencoba mengimplementasikan grafik, dan Anda ingin membuat struktur grafik konstan, tetapi atribut simpul dapat dimodifikasi, dll.
Neil G
2
@ Etienne, alasan lain untuk menambahkan "Pelajari Scala" di daftar tugas saya!
glenviewjeff
2
Yang tidak saya mengerti adalah, mengapa mereka tidak membuat canmetode yang akan menguji apakah operasi itu mungkin? Itu akan membuat antarmuka yang sederhana dan cepat.
Mehrdad
3
@ Etienne de Martel Nonsense. Bagaimana hal itu membantu dalam ledakan kombinatorial?
Tom Hawtin - tackline
10

Bagiku itu terdengar seperti yang Interface Segregation Principlebelum dieksplorasi dengan baik seperti sekarang; cara melakukan hal-hal (yaitu antarmuka Anda mencakup semua operasi yang mungkin dan Anda memiliki metode "merosot" yang membuang pengecualian untuk yang tidak Anda butuhkan) populer sebelum SOLID dan ISP menjadi standar de-facto untuk kode kualitas.

Wayne Molina
sumber
2
Mengapa downvote? Seseorang tidak menyukai ISP?
Wayne Molina
Perlu juga dicatat bahwa dalam kerangka kerja yang mendukung varians ada keuntungan BESAR untuk memisahkan aspek-aspek dari antarmuka yang dapat kovarian atau contravariant dari mereka yang secara fundamental invarian. Meskipun tidak ada dukungan seperti itu, dalam kerangka kerja yang tidak menggunakan penghapusan tipe akan ada nilai dalam memisahkan aspek antarmuka yang tipe-independen (misalnya memungkinkan orang mendapatkan Countkoleksi tanpa harus khawatir tentang jenis item apa yang dikandungnya), tetapi dalam kerangka kerja berbasis-tipe seperti Java tidak menjadi masalah.
supercat
4

Sementara beberapa orang mungkin membenci "metode opsional", mereka mungkin dalam banyak kasus menawarkan semantik yang lebih baik daripada antarmuka yang sangat terpisah. Di antara hal-hal lain, mereka memungkinkan untuk kemungkinan bahwa suatu objek dapat memperoleh kemampuan atau karakteristik dalam masa hidupnya, atau bahwa suatu objek (terutama objek pembungkus) mungkin tidak tahu kapan dibangun kemampuan apa yang harus dilaporkan.

Walaupun saya tidak akan menyebut paragraf kelas koleksi Java dengan desain yang bagus, saya menyarankan kerangka kerja koleksi yang baik harus menyertakan sejumlah besar metode opsional pada dasarnya dengan cara menanyakan koleksi tentang karakteristik dan kemampuannya . Desain seperti itu akan memungkinkan kelas pembungkus tunggal untuk digunakan dengan berbagai macam koleksi tanpa secara tidak sengaja mengaburkan kemampuan koleksi yang mungkin dimiliki. Jika metode tidak opsional, maka perlu memiliki kelas pembungkus yang berbeda untuk setiap kombinasi fitur yang mungkin didukung oleh koleksi, atau jika beberapa pembungkus tidak dapat digunakan dalam beberapa situasi.

Misalnya, jika koleksi mendukung penulisan item dengan indeks, atau menambahkan item di akhir, tetapi tidak mendukung memasukkan item di tengah, maka kode yang ingin merangkumnya dalam pembungkus yang akan mencatat semua tindakan yang dilakukan di atasnya akan memerlukan versi pembungkus logging yang menyediakan kombinasi tepat dari kemampuan yang didukung, atau jika tidak ada yang tersedia harus menggunakan pembungkus yang mendukung baik ditambahkan atau ditulis-oleh-indeks tetapi tidak keduanya. Namun, jika antarmuka pengumpulan terpadu menyediakan ketiga metode sebagai "opsional", tetapi kemudian menyertakan metode untuk menunjukkan metode opsional mana yang dapat digunakan, maka kelas pembungkus tunggal dapat menangani koleksi yang mengimplementasikan kombinasi fitur apa pun. Ketika ditanya fitur apa yang didukungnya, pembungkus dapat dengan mudah melaporkan apa pun yang didukung oleh koleksi yang dienkapsulasi.

Perhatikan bahwa keberadaan "kemampuan opsional" mungkin dalam beberapa kasus memungkinkan koleksi agregat untuk mengimplementasikan fungsi tertentu dengan cara yang jauh lebih efisien daripada yang mungkin jika kemampuan didefinisikan oleh keberadaan implementasi. Sebagai contoh, anggaplah sebuah concatenatemetode digunakan untuk membentuk koleksi komposit dari dua lainnya, yang pertama adalah ArrayList dengan 1.000.000 elemen dan yang terakhir adalah koleksi dua puluh elemen yang hanya dapat diulang dari awal. Jika koleksi komposit diminta untuk elemen 1.000.013 (indeks 1.000.012), itu bisa menanyakan ArrayList berapa banyak item yang terkandung di dalamnya (yaitu 1.000.000), kurangi dari indeks yang diminta (menghasilkan 12), baca dan lewati dua belas elemen dari yang kedua koleksi, dan lalu kembalikan elemen berikutnya.

Dalam situasi seperti itu, meskipun koleksi komposit tidak akan memiliki cara instan untuk mengembalikan item dengan indeks, meminta koleksi komposit untuk item 1.000.013 masih akan jauh lebih cepat daripada membaca 1.000.013 item dari itu secara individual dan mengabaikan semua kecuali yang terakhir satu.

supercat
sumber
@kevincline: Apakah Anda tidak setuju dengan kata-kata yang dikutip? Saya akan mempertimbangkan desain sarana dimana implementasi antarmuka dapat menggambarkan karakteristik penting dan kemampuan mereka untuk menjadi salah satu aspek terpenting dari desain antarmuka, meskipun seringkali tidak mendapatkan banyak perhatian. Jika implementasi yang berbeda dari antarmuka akan memiliki cara optimal yang berbeda untuk menyelesaikan tugas-tugas umum tertentu, harus ada sarana untuk klien yang peduli dengan kinerja untuk memilih pendekatan terbaik dalam skenario di mana itu akan menjadi masalah.
supercat
1
maaf, komentar tidak lengkap. Saya baru saja akan mengatakan bahwa "'cara bertanya tentang koleksi' ..." memindahkan apa yang seharusnya menjadi waktu kompilasi untuk menjalankan-waktu.
kevin cline
@kevincline: Dalam kasus di mana klien perlu memiliki kemampuan tertentu dan tidak dapat hidup tanpanya, pemeriksaan waktu kompilasi dapat membantu. Di sisi lain, ada banyak situasi di mana koleksi dapat memiliki kemampuan di luar yang dapat dikompilasi dengan waktu. Dalam beberapa kasus, masuk akal untuk memiliki antarmuka turunan yang kontraknya menentukan bahwa semua implementasi yang sah dari antarmuka turunan harus mendukung metode tertentu yang opsional di antarmuka dasar, tetapi dalam kasus di mana kode akan dapat menangani sesuatu terlepas dari apakah ia memiliki beberapa fitur ...
supercat
... tetapi akan mendapat manfaat dari fitur yang ada, lebih baik memiliki kode menanyakan objek apakah mendukung fitur daripada meminta sistem tipe apakah tipe objek berjanji untuk mendukung fitur. Jika antarmuka "spesifik" tertentu akan digunakan secara luas, orang mungkin memasukkan AsXXXmetode di antarmuka dasar yang akan mengembalikan objek yang dipanggil jika itu mengimplementasikan antarmuka itu, mengembalikan objek pembungkus yang mendukung antarmuka itu jika memungkinkan, atau melempar pengecualian jika tidak. Misalnya, sebuah ImmutableCollectionantarmuka mungkin memerlukan kontrak ...
supercat
2
Ada masalah dengan proposal Anda: pola "tanya lalu lakukan" memiliki masalah konkurensi jika kemampuan suatu objek dapat berubah selama masa pakainya. (Jika Anda tetap harus mengambil risiko pengecualian, Anda mungkin tidak perlu repot bertanya ...)
jhominal
-1

Saya akan mengaitkannya dengan pengembang asli hanya tidak tahu lebih baik saat itu. Kami telah melangkah jauh dalam desain OO sejak 1998 atau lebih ketika Java 2 dan Koleksi pertama kali dirilis. Apa yang tampak seperti desain buruk yang jelas sekarang tidak begitu jelas pada hari-hari awal OOP.

Tapi itu mungkin dilakukan untuk mencegah pengecoran ekstra. Jika itu adalah antarmuka kedua Anda harus membuat instance koleksi Anda untuk memanggil metode-metode opsional yang juga agak jelek. Seperti sekarang Anda akan segera menangkap UnsupportedOperationException dan memperbaiki kode Anda. Tetapi jika ada dua antarmuka Anda harus menggunakan instanceof dan casting di semua tempat. Mungkin mereka menganggap itu sebagai tradeoff yang valid. Juga kembali di Jawa 2 hari awal contoh sangat disukai karena kinerja yang lambat, mereka mungkin telah mencoba untuk mencegah penggunaannya yang berlebihan.

Tentu saja ini semua spekulasi liar, saya ragu kita bisa menjawab ini dengan pasti kecuali salah satu arsitek koleksi asli berdebat.

Jberg
sumber
7
Casting apa? Jika suatu metode mengembalikan Anda Collectiondan bukan a MutableCollection, itu adalah tanda yang jelas bahwa itu tidak dimaksudkan untuk dimodifikasi. Saya tidak tahu mengapa ada orang yang perlu membuangnya. Memiliki antarmuka terpisah berarti Anda akan mendapatkan kesalahan semacam itu pada waktu kompilasi alih-alih mendapatkan pengecualian pada saat dijalankan. Semakin awal Anda mendapatkan kesalahan, semakin baik.
Etienne de Martel
1
Karena cara itu kurang fleksibel, salah satu manfaat terbesar ke perpustakaan koleksi adalah Anda dapat mengembalikan antarmuka tingkat tinggi di mana-mana dan tidak khawatir dengan implementasi yang sebenarnya. Jika Anda menggunakan dua antarmuka sekarang Anda lebih erat. Dalam kebanyakan kasus Anda hanya ingin mengembalikan Daftar, dan bukan ImmutableList karena Anda biasanya ingin menyerahkannya ke kelas panggilan untuk ditentukan.
Jberg
6
Jika saya memberi Anda koleksi read only, itu karena saya tidak ingin itu diubah. Melempar pengecualian dan mengandalkan dokumentasi terasa sangat mirip tambalan. Dalam C ++, saya hanya akan mengembalikan constobjek, langsung memberi tahu pengguna bahwa objek tidak dapat dimodifikasi.
Etienne de Martel
7
1998 bukanlah "hari-hari awal desain OO".
quant_dev