Pertimbangkan situasi di mana kelas mengimplementasikan perilaku dasar yang sama, metode, dan lain-lain, tetapi beberapa versi berbeda dari kelas itu bisa ada untuk penggunaan yang berbeda. Dalam kasus khusus saya, saya memiliki vektor (vektor geometris, bukan daftar) dan vektor itu dapat berlaku untuk ruang Euclidean N-dimensi (1 dimensi, 2 dimensi, ...). Bagaimana kelas / tipe ini dapat didefinisikan?
Ini akan mudah di C ++ di mana templat kelas dapat memiliki nilai aktual sebagai parameter, tetapi kami tidak memiliki kemewahan di Jawa.
Dua pendekatan yang dapat saya pikirkan yang dapat diambil untuk menyelesaikan masalah ini adalah:
Memiliki implementasi dari setiap kasus yang mungkin pada waktu kompilasi.
public interface Vector { public double magnitude(); } public class Vector1 implements Vector { public final double x; public Vector1(double x) { this.x = x; } @Override public double magnitude() { return x; } public double getX() { return x; } } public class Vector2 implements Vector { public final double x, y; public Vector2(double x, double y) { this.x = x; this.y = y; } @Override public double magnitude() { return Math.sqrt(x * x + y * y); } public double getX() { return x; } public double getY() { return y; } }
Solusi ini jelas sangat memakan waktu dan sangat membosankan untuk kode. Dalam contoh ini sepertinya tidak terlalu buruk, tetapi dalam kode saya yang sebenarnya saya berurusan dengan vektor yang masing-masing memiliki beberapa implementasi, dengan hingga empat dimensi (x, y, z, dan w). Saat ini saya memiliki lebih dari 2.000 baris kode, meskipun setiap vektor hanya membutuhkan 500.
Menentukan parameter saat runtime.
public class Vector { private final double[] components; public Vector(double[] components) { this.components = components; } public int dimensions() { return components.length; } public double magnitude() { double sum = 0; for (double component : components) { sum += component * component; } return Math.sqrt(sum); } public double getComponent(int index) { return components[index]; } }
Sayangnya solusi ini merusak kinerja kode, menghasilkan kode yang lebih berantakan daripada solusi sebelumnya, dan tidak aman pada saat kompilasi (tidak dapat dijamin pada waktu kompilasi bahwa vektor yang Anda hadapi sebenarnya adalah 2-dimensi, sebagai contoh).
Saya saat ini benar-benar berkembang di Xtend, jadi jika ada solusi Xtend yang tersedia, mereka juga dapat diterima.
sumber
Jawaban:
Dalam kasus seperti ini, saya menggunakan pembuatan kode.
Saya menulis aplikasi java yang menghasilkan kode aktual. Dengan begitu Anda dapat dengan mudah menggunakan for for untuk menghasilkan banyak versi yang berbeda. Saya menggunakan JavaPoet , yang membuatnya cukup mudah untuk membangun kode aktual. Kemudian Anda dapat mengintegrasikan menjalankan pembuatan kode ke dalam sistem build Anda.
sumber
Saya memiliki model yang sangat mirip pada aplikasi saya dan solusi kami adalah hanya menyimpan peta dengan ukuran yang dinamis, mirip dengan solusi Anda 2.
Anda tidak perlu khawatir tentang kinerja dengan java array primitif seperti itu. Kami menghasilkan matriks dengan ukuran batas atas 100 kolom (baca: vektor 100 dimensi) sebanyak 10.000 baris, dan kami telah memiliki kinerja yang baik dengan jenis vektor yang jauh lebih kompleks daripada solusi Anda 2. Anda dapat mencoba menyegel kelas atau menandai metode sebagai final untuk mempercepatnya, tapi saya pikir Anda mengoptimalkan secara prematur.
Anda bisa mendapatkan penghematan kode (dengan biaya kinerja) dengan membuat kelas dasar untuk membagikan kode Anda:
Maka tentu saja, jika Anda menggunakan Java-8 +, Anda dapat menggunakan antarmuka bawaan untuk membuat ini lebih ketat:
Lebih dari itu, Anda kehabisan pilihan dengan JVM. Anda tentu saja dapat menuliskannya dalam C ++ dan menggunakan sesuatu seperti JNA untuk menjembatani mereka - ini adalah solusi kami untuk beberapa operasi matriks cepat, di mana kami menggunakan Fortran dan intel MKL - tetapi ini hanya akan memperlambat segalanya jika Anda cukup menulis matriks Anda di C ++ dan memanggil getter / setternya dari java.
sumber
data class
objek untuk dengan mudah membuat 10 subclass vektor. Dengan java, dengan asumsi Anda dapat menarik semua fungsionalitas Anda ke kelas dasar, setiap subclass akan mengambil 1-10 baris. Mengapa tidak membuat kelas dasar?asArray
metode Anda , berbagai metode tersebut tidak akan diperiksa pada waktu kompilasi (Anda dapat melakukan dot-produk antara skalar dan vektor kartesius dan itu akan dikompilasi dengan baik, tetapi gagal saat runtime) .Pertimbangkan enum dengan masing-masing bernama Vektor yang memiliki konstruktor yang terdiri dari array (diinisialisasi dalam daftar parameter dengan nama dimensi atau serupa, atau mungkin hanya bilangan bulat untuk ukuran atau array komponen kosong - desain Anda), dan lambda untuk metode getMagnitude. Anda dapat memiliki enum juga mengimplementasikan antarmuka untuk setComponents / getComponent (s), dan hanya menetapkan komponen mana yang dalam penggunaannya, menghilangkan getX, et al. Anda perlu menginisialisasi setiap objek dengan nilai komponen yang sebenarnya sebelum digunakan, mungkin memeriksa bahwa ukuran array input cocok dengan nama dimensi atau ukuran.
Kemudian jika Anda memperluas solusi ke dimensi lain, Anda cukup memodifikasi enum dan lambda.
sumber
Berdasarkan pilihan Anda 2, mengapa tidak melakukan ini saja? Jika Anda ingin mencegah penggunaan basis mentah, Anda dapat membuatnya abstrak:
sumber
double[]
tidak diinginkan dibandingkan dengan implementasi yang hanya menggunakan 2 primitifdouble
. Dalam contoh seminimal ini, ini terlihat seperti optimasi mikro, tetapi pertimbangkan kasus yang jauh lebih kompleks di mana lebih banyak metadata terlibat dan jenis yang dimaksud memiliki masa hidup yang singkat.Vector
dengan implementasi yang lebih terspesialisasi (misalnyaVector3
) jika masa pakainya relatif lama.Satu ide:
Ini memberi Anda kinerja yang baik dalam kasus-kasus tertentu dan beberapa keamanan waktu kompilasi (masih dapat ditingkatkan) tanpa mengorbankan kasus umum.
Kerangka kode:
sumber