Akhir-akhir ini saya terbiasa "menutupi" koleksi Java dengan nama kelas yang ramah manusia. Beberapa contoh sederhana:
// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}
Atau:
// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}
Seorang kolega menunjukkan kepada saya bahwa ini adalah praktik yang buruk, dan memperkenalkan jeda / latensi, serta menjadi anti-pola OO. Saya dapat memahaminya memperkenalkan tingkat kinerja yang sangat kecil , tetapi tidak dapat membayangkan itu sama sekali signifikan. Jadi saya bertanya: apakah ini baik atau buruk untuk dilakukan, dan mengapa?
java
anti-patterns
generics
collections
herpylderp
sumber
sumber
ChangeList
kompilasi akan rusak padaextends
, karena Daftar adalah antarmuka, memerlukanimplements
. @randomA apa yang Anda bayangkan tidak tepat karena kesalahan iniimplementation
yaituHashMap
atauTreeMap
dan apa yang dia punya ada kesalahan ketik.Jawaban:
Lag / Latensi? Saya memanggil BS tentang itu. Seharusnya tidak ada overhead dari praktik ini. ( Sunting: Telah ditunjukkan dalam komentar bahwa ini dapat, pada kenyataannya, menghambat optimasi yang dilakukan oleh HotSpot VM. Saya tidak cukup tahu tentang implementasi VM untuk mengkonfirmasi atau menyangkal ini. Saya mendasarkan komentar saya pada C ++ implementasi fungsi virtual.)
Ada beberapa kode overhead. Anda harus membuat semua konstruktor dari kelas dasar yang Anda inginkan, meneruskan parameternya.
Saya juga tidak melihatnya sebagai anti-pola, per se. Namun, saya melihatnya sebagai peluang yang terlewatkan. Alih-alih membuat kelas yang mendapatkan kelas dasar hanya untuk penggantian nama, bagaimana kalau Anda membuat kelas yang berisi koleksi dan menawarkan antarmuka yang ditingkatkan khusus untuk kasus tertentu? Haruskah cache widget Anda benar-benar menawarkan antarmuka penuh peta? Atau haruskah ia menawarkan antarmuka khusus?
Lebih jauh, dalam hal koleksi, polanya sama sekali tidak bekerja bersama dengan aturan umum penggunaan antarmuka, bukan implementasi - yaitu, dalam kode koleksi polos, Anda akan membuat
HashMap<String, Widget>
, dan kemudian menetapkannya ke variabel tipeMap<String, Widget>
. AndaWidgetCache
tidak dapat memperluasMap<String, Widget>
, karena itu adalah antarmuka. Itu tidak bisa berupa antarmuka yang memperluas antarmuka dasar, karenaHashMap<String, Widget>
tidak mengimplementasikan antarmuka itu, dan begitu pula koleksi standar lainnya. Dan sementara Anda bisa menjadikannya kelas yang memanjangHashMap<String, Widget>
, Anda kemudian harus mendeklarasikan variabel sebagaiWidgetCache
atauMap<String, Widget>
, dan yang pertama kehilangan Anda fleksibilitas untuk mengganti koleksi yang berbeda (mungkin beberapa koleksi pemuatan malas ORM), sedangkan jenis kedua mengalahkan poin memiliki kelas.Beberapa tandingan ini juga berlaku untuk kelas khusus yang saya usulkan.
Ini semua poin yang perlu dipertimbangkan. Ini mungkin atau mungkin bukan pilihan yang tepat. Dalam kedua kasus tersebut, argumen yang ditawarkan kolega Anda tidak valid. Jika dia pikir itu anti-pola, dia harus menyebutkannya.
sumber
public class Properties extends Hashtable<Object,Object>
dalamjava.util
mengatakan "Karena Properties mewarisi dari Hashtable, metode put dan putAll dapat diterapkan ke objek Properties. Penggunaannya sangat tidak dianjurkan karena memungkinkan penelepon untuk memasukkan entri yang kuncinya atau nilai bukan String. " Komposisi akan jauh lebih bersih.Menurut IBM ini sebenarnya anti-pola. Kelas seperti 'typedef' ini disebut tipe psuedo.
Artikel ini menjelaskannya jauh lebih baik daripada saya, tetapi saya akan mencoba meringkasnya jika tautannya turun:
WidgetCache
tidak dapat menangani aMap<String, Widget>
Dalam artikel itu mereka mengusulkan trik berikut untuk membuat hidup lebih mudah tanpa menggunakan tipe pseudo:
Yang berfungsi karena inferensi tipe otomatis.
(Saya sampai pada jawaban ini melalui pertanyaan stack overflow terkait)
sumber
newHashMap
diselesaikan sekarang oleh operator berlian?WidgetCache
dapat ditugaskan ke variabel jenisWidgetCache
atauMap<String,Widget>
tanpa gips, tetapi ada adalah sarana dimana metode statis diWidgetCache
dapat membuat mengambil referensi keMap<String,Widget>
dan mengembalikannya sebagai tipeWidgetCache
setelah melakukan validasi yang diinginkan. Fitur seperti itu bisa sangat berguna dengan obat-obatan generik, yang tidak perlu dihapus jenisnya (karena ...Hit kinerja akan dibatasi paling banyak pada pencarian vtable, yang kemungkinan besar sudah Anda lakukan. Itu bukan alasan yang sah untuk menentangnya.
Situasinya cukup umum sehingga sebagian besar semua bahasa pemrograman yang diketik secara statis memiliki sintaks khusus untuk jenis alias, biasanya disebut a
typedef
. Java mungkin tidak menyalinnya karena pada awalnya tidak memiliki tipe parameter. Memperluas kelas bukanlah hal yang ideal, karena alasan Sebastian begitu baik dalam menjawabnya, tetapi ini bisa menjadi solusi yang masuk akal untuk sintaksis terbatas Java.Typedefs memiliki sejumlah keunggulan. Mereka mengekspresikan maksud programmer lebih jelas, dengan nama yang lebih baik, pada tingkat abstraksi yang lebih tepat. Mereka lebih mudah untuk mencari tujuan debugging atau refactoring. Pertimbangkan menemukan di mana-mana a
WidgetCache
digunakan versus menemukan penggunaan spesifik aMap
. Mereka lebih mudah untuk diubah, misalnya jika nanti Anda menemukan yang Anda butuhkanLinkedHashMap
, atau bahkan wadah kustom Anda sendiri.sumber
Saya menyarankan Anda, seperti yang telah disebutkan orang lain, untuk menggunakan komposisi di atas warisan, sehingga Anda hanya dapat mengekspos metode yang benar-benar diperlukan, dengan nama yang cocok dengan kasus penggunaan yang dimaksudkan. Apakah pengguna kelas Anda benar-benar perlu tahu bahwa itu
WidgetCache
adalah peta? Dan dapat melakukan apa pun yang mereka inginkan? Atau mereka hanya perlu tahu bahwa itu adalah cache untuk widget?Contoh kelas dari basis kode saya, dengan solusi untuk masalah serupa yang Anda deskripsikan:
Anda dapat melihat bahwa secara internal itu "hanya peta", tetapi antarmuka publik tidak menunjukkan ini. Dan ia memiliki metode "ramah-programmer" seperti
appendMessage(Locale locale, String code, String message)
cara memasukkan entri baru yang lebih mudah dan lebih bermakna. Dan pengguna kelas tidak dapat melakukan, misalnyatranslations.clear()
, karenaTranslations
tidak memperluasMap
.Secara opsional, Anda selalu dapat mendelegasikan beberapa metode yang diperlukan ke peta yang digunakan secara internal.
sumber
Saya melihat ini sebagai contoh dari abstraksi yang bermakna. Abstraksi yang baik memiliki beberapa sifat:
Itu menyembunyikan detail implementasi yang tidak relevan dengan kode yang memakannya.
Itu hanya serumit yang seharusnya.
Dengan memperluas, Anda mengekspos seluruh antarmuka induk, tetapi dalam banyak kasus banyak yang mungkin lebih baik disembunyikan, jadi Anda ingin melakukan apa yang disarankan Sebastian Redl dan mendukung komposisi daripada pewarisan dan menambahkan turunan dari induk sebagai anggota pribadi kelas khusus Anda. Metode antarmuka apa pun yang masuk akal untuk abstraksi Anda dapat dengan mudah didelegasikan ke (dalam kasus Anda) koleksi dalam.
Adapun dampak kinerja, itu selalu ide yang baik untuk mengoptimalkan kode untuk keterbacaan pertama, dan jika dampak kinerja diduga, profil kode untuk membandingkan kedua implementasi.
sumber
Beri +1 ke jawaban lain di sini. Saya juga akan menambahkan bahwa itu sebenarnya dianggap praktik yang sangat baik oleh komunitas Desain Domain Driven (DDD). Mereka menganjurkan bahwa domain Anda dan interaksi dengannya harus memiliki makna domain semantik yang bertentangan dengan struktur data yang mendasarinya. A
Map<String, Widget>
bisa berupa cache, tetapi bisa juga berupa sesuatu yang lain, apa yang telah Anda lakukan dengan benar dalam My Not So Humble Opinion (IMNSHO) adalah memodelkan apa yang ditampilkan oleh koleksi, dalam hal ini cache.Saya akan menambahkan suntingan penting karena pembungkus kelas domain di sekitar struktur data yang mendasarinya mungkin juga harus memiliki variabel anggota lain atau fungsi yang benar-benar menjadikannya kelas domain dengan interaksi yang berlawanan dengan hanya struktur data (jika saja Jawa memiliki Jenis Nilai) , kami akan mendapatkannya di Jawa 10 - janji!)
Ini akan menarik untuk melihat apa dampak aliran Java 8 akan memiliki pada semua ini, saya bisa membayangkan bahwa mungkin beberapa antarmuka publik akan lebih suka berurusan dengan Stream (masukkan common Java primitive atau String) sebagai lawan dari objek Java.
sumber