Considérez une situation où une classe implémente le même comportement de base, les mêmes méthodes, et cetera, mais plusieurs versions différentes de cette classe peuvent exister pour différentes utilisations. Dans mon cas particulier, j'ai un vecteur (un vecteur géométrique, pas une liste) et ce vecteur pourrait s'appliquer à n'importe quel espace euclidien à N dimensions (1 dimension, 2 dimensions, ...). Comment définir cette classe / type?
Ce serait facile en C ++ où les modèles de classe peuvent avoir des valeurs réelles comme paramètres, mais nous n'avons pas ce luxe en Java.
Les deux approches auxquelles je peux penser qui pourraient être adoptées pour résoudre ce problème sont les suivantes:
Avoir une implémentation de chaque cas possible au moment de la compilation.
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; } }
Cette solution est évidemment très longue et extrêmement fastidieuse à coder. Dans cet exemple, cela ne semble pas trop mauvais, mais dans mon code actuel, je traite avec des vecteurs qui ont plusieurs implémentations chacun, avec jusqu'à quatre dimensions (x, y, z et w). J'ai actuellement plus de 2000 lignes de code, même si chaque vecteur n'a vraiment besoin que de 500.
Spécification des paramètres lors de l'exécution.
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]; } }
Malheureusement, cette solution nuit aux performances du code, entraîne un code plus désordonné que l'ancienne solution et n'est pas aussi sûre au moment de la compilation (il ne peut pas être garanti au moment de la compilation que le vecteur avec lequel vous traitez est en réalité bidimensionnel, par exemple).
Je suis actuellement en train de développer dans Xtend, donc si des solutions Xtend sont disponibles, elles seraient également acceptables.
la source
Réponses:
Dans des cas comme celui-ci, j'utilise la génération de code.
J'écris une application java qui génère le code réel. De cette façon, vous pouvez facilement utiliser une boucle for pour générer un tas de versions différentes. J'utilise JavaPoet , ce qui facilite la création du code réel. Ensuite, vous pouvez intégrer l'exécution de la génération de code dans votre système de génération.
la source
J'ai un modèle très similaire sur mon application et notre solution était de simplement conserver une carte de taille dynamique, similaire à votre solution 2.
Vous n'aurez tout simplement pas à vous soucier des performances avec une primitive de tableau java comme celle-ci. Nous générons des matrices avec des tailles de limite supérieure de 100 colonnes (lire: 100 vecteurs dimensionnels) par 10 000 lignes, et nous avons eu de bonnes performances avec des types de vecteurs beaucoup plus complexes que votre solution 2. Vous pouvez essayer de sceller la classe ou de marquer les méthodes comme finales pour l'accélérer, mais je pense que vous optimisez prématurément.
Vous pouvez obtenir des économies de code (au détriment des performances) en créant une classe de base pour partager votre code:
Alors bien sûr, si vous êtes sur Java-8 +, vous pouvez utiliser des interfaces par défaut pour rendre cela encore plus serré:
Au-delà de cela, vous n'avez plus d'options avec la JVM. Vous pouvez bien sûr les écrire en C ++ et utiliser quelque chose comme JNA pour les relier - c'est notre solution pour certaines des opérations matricielles rapides, où nous utilisons fortran et MKL d'Intel - mais cela ne fera que ralentir les choses si vous écrivez simplement votre matrice en C ++ et appelez ses getters / setters depuis java.
la source
data class
objets pour créer facilement 10 sous-classes vectorielles. Avec java, en supposant que vous puissiez extraire toutes vos fonctionnalités dans la classe de base, chaque sous-classe prendra 1 à 10 lignes. Pourquoi ne pas créer une classe de base?asArray
méthode, ces différentes méthodes ne seraient pas vérifiées au moment de la compilation (vous pourriez effectuer un produit scalaire entre un scalaire et un vecteur cartésien et cela se compilerait correctement, mais échouerait au moment de l'exécution) .Considérez une énumération avec chaque vecteur nommé ayant un constructeur composé d'un tableau (initialisé dans la liste des paramètres avec les noms de dimension ou similaire, ou peut-être juste un entier pour la taille ou un tableau de composants vide - votre conception), et un lambda pour la méthode getMagnitude. Vous pourriez demander à l'énumération d'implémenter une interface pour setComponents / getComponent (s), et simplement déterminer quel composant était lequel dans son utilisation, en éliminant getX, et al. Vous devrez initialiser chaque objet avec ses valeurs de composant réelles avant utilisation, en vérifiant éventuellement que la taille du tableau d'entrée correspond aux noms ou à la taille de dimension.
Ensuite, si vous étendez la solution à une autre dimension, vous modifiez simplement l'énumération et le lambda.
la source
Sur la base de votre option 2, pourquoi ne pas simplement le faire? Si vous voulez empêcher l'utilisation de la base brute, vous pouvez la rendre abstraite:
la source
double[]
est indésirable par rapport à une implémentation qui utilise simplement 2 primitivesdouble
. Dans un exemple aussi minimal que cela, cela ressemble à une microoptimisation, mais considérons un cas beaucoup plus complexe où beaucoup plus de métadonnées sont impliquées et le type en question a une courte durée de vie.Vector
par une implémentation plus spécialisée (par exempleVector3
) si sa durée de vie devait être relativement longue.Une idée:
Cela vous donne de bonnes performances dans les cas typiques et une certaine sécurité à la compilation (peut encore être améliorée) sans sacrifier le cas général.
Squelette de code:
la source