Apakah ada alasan bagus untuk menggunakan antarmuka Koleksi Java?

11

Saya telah mendengar argumen bahwa Anda harus menggunakan antarmuka paling umum yang tersedia sehingga Anda tidak terikat dengan implementasi tertentu dari antarmuka itu. Apakah logika ini berlaku untuk antarmuka seperti java.util.Collection ?

Saya lebih suka melihat sesuatu seperti yang berikut:

List<Foo> getFoos()

atau

Set<Foo> getFoos()

dari pada

Collection<Foo> getFoos()

Dalam kasus terakhir, saya tidak tahu set data apa yang saya hadapi, sedangkan dalam dua contoh pertama saya dapat membuat beberapa asumsi tentang pemesanan dan keunikan. Apakah java.util.Collection memiliki kegunaan di luar menjadi orangtua yang logis untuk kedua set dan daftar?

Jika Anda menemukan kode yang menggunakan Koleksi ketika melakukan peninjauan kode, bagaimana Anda menentukan apakah penggunaannya dibenarkan, dan saran apa yang akan Anda buat untuk penggantiannya dengan antarmuka yang lebih spesifik?

Fil
sumber
3
Apakah Anda memperhatikan di java.security.cert bahwa satu jenis pengembalian adalah Collection<List<?>>? Bicara tentang pengkodean horor!
Macneil
1
@ Macneil Saya tidak tahu kelas mana yang Anda rujuk, tapi tipe pengembalian seperti itu memang bisa sangat masuk akal. Ini pada dasarnya memberitahu Anda bahwa Anda memiliki koleksi (yaitu berisi banyak hal yang tidak memiliki urutan yang masuk akal) dari daftar (yaitu berisi hal-hal yang memiliki urutan yang masuk akal) dari objek (yaitu item yang jenisnya tidak kita kenal secara statis untuk alasan apapun). Sepertinya tidak masuk akal bagi saya.
Zero3

Jawaban:

13

Abstraksi hidup lebih lama dari implementasi

Secara umum, semakin abstrak desain Anda, semakin besar kemungkinan akan berguna. Jadi, karena Koleksi lebih abstrak daripada sub-antarmuka, maka desain API berdasarkan Koleksi lebih cenderung tetap berguna daripada yang didasarkan pada Daftar.

Namun, prinsip menyeluruhnya adalah menggunakan abstraksi yang paling tepat . Jadi jika koleksi Anda harus mendukung elemen yang dipesan maka mandat Daftar, jika tidak ada duplikat maka mandat Set, dan sebagainya.

Catatan tentang desain antarmuka generik

Karena Anda tertarik menggunakan antarmuka Koleksi dengan obat generik, Anda mungkin dapat membantu yang berikut ini. Java Efektif oleh Joshua Bloch merekomendasikan pendekatan berikut saat merancang antarmuka yang akan bergantung pada obat generik: Producers Extend, Consumers Super

Ini juga dikenal sebagai aturan Pecs . Pada dasarnya, jika koleksi umum yang menghasilkan data diteruskan ke kelas Anda, tanda tangan akan terlihat seperti ini:

public void pushAll(Collection<? extends E> producerCollection) {}

Jadi tipe inputnya bisa E atau subclass dari E (E didefinisikan sebagai super-dan sub-kelas dari dirinya sendiri dalam bahasa Java).

Sebaliknya, koleksi umum yang diteruskan untuk menggunakan data harus memiliki tanda tangan seperti ini:

public void popAll(Collection<? super E> consumerCollection) {}

Metode ini akan menangani dengan benar superclass dari E. Secara keseluruhan, menggunakan pendekatan ini akan membuat antarmuka Anda kurang mengejutkan bagi pengguna Anda karena Anda akan dapat masuk Collection<Number>dan Collection<Integer>memperlakukan mereka dengan benar.

Gary Rowe
sumber
6

The Collectioninterface, dan bentuk yang paling permisif Collection<?>, sangat bagus untuk parameter yang Anda terima. Berdasarkan penggunaan di perpustakaan Java itu sendiri lebih umum sebagai tipe parameter daripada tipe kembali.

Untuk jenis kembali, saya pikir poin Anda valid: Jika orang diharapkan mengaksesnya, mereka harus mengetahui urutan (dalam arti Big-O) dari operasi yang dilakukan. Saya akan mengulangi pengembalian Collectiondan menambahkannya ke Koleksi lain, tetapi sepertinya agak gila untuk memanggilnya contains, tidak tahu apakah itu operasi O (1), O (log n), atau O (n). Tentu saja, hanya karena Anda memiliki tanda Setbukan berarti itu hashset atau set yang diurutkan, tetapi pada titik tertentu Anda akan membuat asumsi bahwa antarmuka telah dilaksanakan secara wajar (dan kemudian Anda harus pergi ke rencana B jika asumsi Anda terbukti salah).

Seperti yang disebutkan Tom, terkadang Anda perlu mengembalikan a Collectionuntuk mempertahankan enkapsulasi: Anda tidak ingin detail implementasi bocor, bahkan jika Anda bisa mengembalikan sesuatu yang lebih spesifik. Atau, dalam kasus yang disebutkan Tom, Anda bisa mengembalikan wadah yang lebih spesifik, tetapi Anda harus membuatnya.

Macneil
sumber
2
Saya pikir poin kedua agak lemah. Anda tidak tahu bagaimana koleksi akan melakukan terlepas dari apakah itu Koleksi atau Daftar - karena mereka hanya abstraksi. Kecuali Anda memiliki kelas akhir yang konkret, Anda tidak bisa benar-benar tahu.
Mark H
Juga, jika semua yang Anda tahu adalah sesuatu adalah Koleksi, Anda tidak tahu apakah itu dapat berisi duplikat atau tidak. Membalik itu, sekali kasus di mana Koleksi kembali mungkin tepat adalah jika Anda memiliki koleksi yang tidak mengandung duplikat dan tidak memiliki urutan signifikan (yang secara alami akan menjadi Set), tetapi di mana untuk beberapa alasan yang baik, penerapan metode pengembalian menggunakan Daftar. Anda tidak ingin mengembalikan Daftar, karena itu menyiratkan urutan yang signifikan, tetapi Anda tidak dapat mengembalikan Set tanpa melalui kesulitan pembuatannya. Jadi, Anda mengembalikan Koleksi.
Tom Anderson
@ Tom: Poin bagus!
Macneil
5

Saya akan melihatnya dari sudut pandang yang benar-benar berlawanan, dan bertanya:

Jika Anda menemukan kode yang menggunakan Daftar <> saat melakukan peninjauan kode, bagaimana Anda menentukan apakah penggunaannya dibenarkan?

Sangat mudah untuk membenarkan ini. Anda menggunakan Daftar saat Anda membutuhkan beberapa fungsi yang tidak ditawarkan oleh Koleksi. Jika Anda tidak membutuhkan fungsionalitas ekstra itu - justifikasi apa yang Anda miliki? (Dan saya tidak akan membeli, "Saya lebih suka melihatnya")

Ada banyak kasus di mana Anda akan menggunakan koleksi untuk tujuan hanya-baca, mengisi semuanya sekaligus, mengulangi seluruhnya - apakah Anda perlu mengindeksnya secara manual?

Untuk memberi contoh nyata. Katakanlah saya melakukan permintaan sederhana pada database. ( SELECT id,name,rep FROM people WHERE name LIKE '%squared') Saya mengambil kembali data yang relevan, mengisi objek Person dan memasukkannya ke PersonList)

  • Apakah saya perlu mengakses berdasarkan indeks? - Tidak ada artinya. Tidak ada pemetaan antara indeks dan ID.
  • Apakah saya perlu memasukkan pada indeks? - Tidak, DBMS akan memutuskan di mana meletakkannya, jika saya menambahkan sama sekali.

Jadi pembenaran apa yang akan saya miliki untuk metode ekstra itu? (yang akan tetap tidak diterapkan di PersonList saya)

Markus H
sumber
Titik adil. Saya kira pertanyaan saya merujuk pada contoh spesifik di mana, saat melakukan peninjauan kode, saya terus melihat DAO yang mengembalikan Koleksi, tetapi saya tahu bahwa panggilan DAO ini akan selalu mengembalikan set entitas; pendapat saya adalah bahwa dalam kasus ini tipe pengembalian menunjukkan keunikan, dan informasi ini berguna bagi siapa pun yang harus menggunakan metode itu (misalnya saya tidak perlu memeriksa duplikat).
Fil
Jika Anda menanyakan DB, membandingkan 2 objek yang dihasilkan dengan equals () seharusnya tidak menghasilkan true - jadi Anda perlu cara lain untuk membandingkan objek untuk duplikasi (misalnya, apakah mereka memiliki nama yang sama, ID yang sama, kedua?). Anda harus memberi tahu DAO Anda bagaimana membandingkannya jika ingin menghapus duplikat itu sendiri - tetapi karena Anda adalah pengguna yang memutuskan apakah duplikat itu ada - lebih mudah melakukannya dengan koleksi dari kode panggilan. (Untuk menghindari lebih banyak lapisan abstraksi untuk memberi tahu DAO cara melakukan setiap pemeriksaan kesetaraan yang mungkin di planet ini.)
Mark H
Setuju, tapi kami menggunakan Hibernate, dan memastikan entitas kami menerapkan equals (). Jadi ketika DAO mengembalikan entitas, kita dapat dengan cepat melakukan HashSet (). AddAll (hasil) baru dan mengembalikannya kembali ke metode yang disebut.
Fil