Apakah praktik yang baik untuk menggunakan refleksi jika sangat mengurangi jumlah kode boilerplate?
Pada dasarnya ada trade-off antara kinerja dan mungkin keterbacaan di satu sisi dan abstraksi / otomatisasi / pengurangan kode boilerplate di sisi lain.
Sunting: Berikut adalah contoh penggunaan refleksi yang disarankan .
Untuk memberi contoh, anggaplah ada kelas abstrak Base
yang memiliki 10 bidang dan memiliki 3 subclass SubclassA
, SubclassB
dan SubclassC
masing-masing dengan 10 bidang yang berbeda; mereka semua adalah kacang sederhana. Masalahnya adalah bahwa Anda mendapatkan dua Base
jenis referensi dan Anda ingin melihat apakah objek yang sesuai memiliki jenis (sub) yang sama dan sama.
Sebagai solusi ada solusi mentah di mana Anda pertama kali memeriksa apakah jenisnya sama dan kemudian memeriksa semua bidang atau Anda dapat menggunakan refleksi dan melihat secara dinamis apakah mereka dari jenis yang sama dan beralih ke semua metode yang dimulai dengan "dapatkan" (konvensi). lebih dari konfigurasi), panggil mereka pada kedua objek dan sebut sama pada hasil.
boolean compare(Base base1, Base, base2) {
if (base1 instanceof SubclassA && base2 instanceof SubclassA) {
SubclassA subclassA1 = (SubclassA) base1;
SubclassA subclassA2 = (SubclassA) base2;
compare(subclassA1, subclassA2);
} else if (base1 instanceof SubclassB && base2 instanceof SubclassB) {
//the same
}
//boilerplate
}
boolean compare(SubclassA subA1, SubclassA subA2) {
if (!subA1.getField1().equals(subA2.getField1)) {
return false;
}
if (!subA1.getField2().equals(subA2.getField2)) {
return false;
}
//boilerplate
}
boolean compare(SubclassB subB1, SubclassB subB2) {
//boilerplate
}
//boilerplate
//alternative with reflection
boolean compare(Base base1, Base base2) {
if (!base1.getClass().isAssignableFrom(base2.getClass())) {
System.out.println("not same");
System.exit(1);
}
Method[] methods = base1.getClass().getMethods();
boolean isOk = true;
for (Method method : methods) {
final String methodName = method.getName();
if (methodName.startsWith("get")) {
Object object1 = method.invoke(base1);
Object object2 = method.invoke(base2);
if(object1 == null || object2 == null) {
continue;
}
if (!object1.equals(object2)) {
System.out.println("not equals because " + object1 + " not equal with " + object2);
isOk = false;
}
}
}
if (isOk) {
System.out.println("is OK");
}
}
sumber
Jawaban:
Refleksi dibuat untuk tujuan tertentu, untuk menemukan fungsionalitas kelas yang tidak diketahui pada waktu kompilasi, mirip dengan apa yang dilakukan
dlopen
dandlsym
fungsinya dalam C. Setiap penggunaan di luar itu harus banyak diteliti.Pernahkah terpikir oleh Anda bahwa desainer Java sendiri mengalami masalah ini? Itu sebabnya praktis setiap kelas memiliki
equals
metode. Kelas yang berbeda memiliki definisi kesetaraan yang berbeda pula. Dalam beberapa keadaan objek yang diturunkan bisa sama dengan objek dasar. Dalam beberapa keadaan, kesetaraan dapat ditentukan berdasarkan bidang pribadi tanpa getter. Kamu tidak tahu.Itu sebabnya setiap objek yang menginginkan kesetaraan khusus harus menerapkan
equals
metode. Akhirnya, Anda ingin meletakkan objek ke dalam set, atau menggunakannya sebagai indeks hash, maka Anda harusequals
tetap menerapkannya . Bahasa lain melakukannya secara berbeda, tetapi Java menggunakanequals
. Anda harus tetap berpegang pada konvensi bahasa Anda.Juga, kode "boilerplate", jika dimasukkan ke dalam kelas yang benar, cukup sulit untuk dikacaukan. Refleksi menambah kompleksitas tambahan, yang berarti peluang tambahan untuk bug. Dalam metode Anda, misalnya, dua objek dianggap sama jika satu kembali
null
untuk bidang tertentu dan yang lainnya tidak. Bagaimana jika salah satu getter Anda mengembalikan salah satu objek Anda, tanpa yang sesuaiequals
? Andaif (!object1.equals(object2))
akan gagal. Juga membuatnya rawan bug adalah fakta bahwa refleksi jarang digunakan, jadi programmer tidak terbiasa dengan gotchasnya.sumber
Terlalu sering menggunakan refleksi mungkin tergantung pada bahasa yang digunakan. Di sini Anda menggunakan Java. Dalam hal itu, refleksi harus digunakan dengan hati-hati karena seringkali hanya solusi untuk desain yang buruk.
Jadi, Anda membandingkan kelas yang berbeda, ini adalah masalah yang sempurna untuk metode utama . Perhatikan bahwa instance dari dua kelas yang berbeda tidak boleh dianggap sama. Anda dapat membandingkan untuk kesetaraan hanya jika Anda memiliki instance dari kelas yang sama. Lihat /programming/27581/overriding-equals-and-hashcode-in-java untuk contoh bagaimana menerapkan perbandingan kesetaraan dengan benar.
sumber
compare()
metode Anda dengan asumsi bahwa setiap metode yang dimulai dengan "get" adalah pengambil dan aman dan pantas untuk dipanggil sebagai bagian dari operasi perbandingan. Ini merupakan pelanggaran dari definisi antarmuka yang dimaksudkan objek, dan sementara itu mungkin bijaksana, itu hampir selalu salah.get
pengambil dan bukan metode kustom mengembalikan sesuatu?Saya pikir Anda memiliki dua masalah di sini.
Kode dinamis vs statis
Ini adalah pertanyaan abadi dan jawabannya sangat tegas.
Di satu sisi kompiler Anda sangat pandai menangkap semua jenis kode buruk. Ini melakukan ini melalui berbagai bentuk analisis, Analisis jenis menjadi yang umum. Ia tahu bahwa Anda tidak dapat menggunakan
Banana
objek dalam kode yang mengharapkan aCog
. Ini memberitahu Anda ini melalui kesalahan kompilasi.Sekarang hanya dapat melakukan ini ketika dapat menyimpulkan kedua Jenis yang diterima, dan Jenis yang diberikan dari konteks. Seberapa banyak yang dapat disimpulkan, dan seberapa umum kesimpulan itu, sangat tergantung pada bahasa yang digunakan. Java dapat menyimpulkan tipe informasi melalui mekanisme seperti Inheritance, Interfaces, dan Generics. Jarak tempuh memang bervariasi, beberapa bahasa lain menyediakan lebih sedikit mekanisme, dan beberapa menyediakan lebih banyak. Itu masih bermuara pada apa yang kompiler dapat ketahui benar.
Di sisi lain kompiler Anda tidak dapat memprediksi bentuk kode asing, dan kadang-kadang algoritma umum dapat diekspresikan pada banyak jenis yang tidak dapat dengan mudah diekspresikan menggunakan sistem tipe bahasa. Dalam kasus ini kompilator tidak selalu dapat mengetahui hasil sebelumnya, dan bahkan mungkin tidak dapat mengetahui pertanyaan apa yang harus diajukan. Refleksi, antarmuka, dan kelas Object adalah cara Java menangani masalah ini. Anda harus memberikan pemeriksaan dan penanganan yang benar, tetapi bukan tidak sehat untuk memiliki kode semacam ini.
Apakah akan membuat kode Anda sangat spesifik, atau sangat umum berarti masalah yang Anda coba tangani. Jika Anda dapat dengan mudah mengekspresikannya menggunakan tipe sistem, lakukanlah. Biarkan kompiler bermain dengan kekuatannya dan membantu Anda. Jika sistem jenis tidak mungkin diketahui sebelumnya (kode asing), atau sistem jenisnya kurang cocok untuk implementasi umum dari algoritma Anda maka refleksi (dan cara dinamis lainnya) adalah alat yang tepat untuk digunakan.
Perlu diketahui bahwa melangkah keluar dari jenis sistem bahasa Anda mengejutkan. Bayangkan berjalan ke teman Anda dan memulai percakapan dalam bahasa Inggris. Tiba-tiba menjatuhkan beberapa kata dari Spanyol, Prancis, dan Kanton yang mengekspresikan pikiran Anda dengan tepat. Konteks akan memberi tahu teman Anda banyak, tetapi mereka juga mungkin tidak tahu bagaimana menangani kata-kata yang mengarah ke segala macam kesalahpahaman. Apakah menangani kesalahpahaman itu lebih baik daripada menjelaskan ide-ide itu dalam bahasa Inggris menggunakan lebih banyak kata?
Kesetaraan Kustom
Sementara saya mengerti bahwa Java sangat bergantung pada
equals
metode untuk umumnya membandingkan dua objek yang tidak selalu cocok dalam konteks yang diberikan.Ada cara lain, dan itu juga standar Java. Itu disebut pembanding .
Mengenai bagaimana Anda menerapkan pembanding Anda akan bergantung pada apa yang Anda bandingkan, dan bagaimana.
equals
implementasi spesifik mereka .sumber
Saya lebih suka menghindari pemrograman reflektif sebanyak mungkin karena itu
Ini juga jauh lebih sedikit performanya daripada pemanggilan metode sederhana; dulu lebih lambat oleh urutan besarnya atau lebih.
Memeriksa Kode Statis
Setiap kode reflektif mencari kelas dan metode menggunakan string; pada contoh asli, ia mencari metode apa pun yang dimulai dengan "get"; itu akan mengembalikan getter tetapi metode lain sebagai tambahan, seperti "gettysburgAddress ()". Aturan bisa diperketat dalam kode, tetapi intinya tetap bahwa itu adalah pemeriksaan runtime ; sebuah IDE dan kompiler tidak dapat membantu. Secara umum saya tidak suka kode "diketik ketat" atau "terobsesi primitif".
Sulit untuk Berpikir Tentang
Kode reflektif lebih verbose daripada panggilan metode sederhana. Lebih banyak kode = lebih banyak bug, atau setidaknya lebih banyak potensi bug, lebih banyak kode untuk dibaca, untuk diuji, dll. Lebih sedikit lebih banyak.
Sulit Diperbaiki
Karena kode ini berbasis string / dinamis; segera setelah refleksi ada di tempat Anda tidak dapat refactor kode dengan kepercayaan 100% menggunakan alat refactoring IDE karena IDE tidak dapat mengambil penggunaan reflektif.
Pada dasarnya, hindari refleksi dalam kode umum jika memungkinkan; mencari desain yang lebih baik.
sumber
Definisi sesuatu yang berlebihan terlalu buruk, bukan? Jadi mari kita singkirkan (over) untuk saat ini.
Apakah Anda menyebut penggunaan refleksi internal yang luar biasa berat oleh semi merupakan kebiasaan buruk?
Spring menjinakkan pantulan dengan menggunakan anotasi - begitu pula Hibernate (dan mungkin puluhan / ratusan alat lainnya).
Ikuti pola-pola itu jika Anda menggunakannya dalam kode Anda sendiri. Gunakan anotasi untuk memastikan IDE pengguna Anda masih dapat membantu mereka (Bahkan jika Anda adalah satu-satunya "Pengguna" kode Anda, penggunaan refleksi yang tidak hati-hati mungkin akan kembali menggigit Anda di pantat pada akhirnya).
Namun tanpa mempertimbangkan bagaimana kode Anda akan digunakan oleh pengembang, bahkan penggunaan refleksi paling sederhana mungkin berlebihan.
sumber
Saya pikir sebagian besar jawaban ini melewatkan intinya.
Ya, Anda mungkin harus menulis
equals()
danhashcode()
, seperti dicatat oleh @KarlBielefeldt.Tapi, untuk kelas dengan banyak bidang, ini bisa menjadi boilerplate yang membosankan.
Jadi, itu tergantung .
Kemungkinan lain: jika kelas Anda benar-benar memiliki banyak bidang yang menulis persamaan sama membosankan, pertimbangkan
Map.equals()
untuk perbandingan Anda. (atauMap.hashcode()
)mis. ( catatan : mengabaikan cek nol, mungkin harus menggunakan Enums daripada kunci String, banyak yang tidak ditampilkan ...)
sumber