Dernièrement, j'ai l'habitude de "masquer" les collections Java avec des noms de classes conviviaux. Quelques exemples simples:
// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}
Ou:
// 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>> {
}
Un collègue m'a fait remarquer que c'était une mauvaise pratique et introduisait un décalage / latence, tout en étant un anti-pattern OO. Je peux comprendre que cela introduise un très faible degré de performance, mais je ne peux pas imaginer que cela soit significatif. Je demande donc: est-ce bon ou mauvais de le faire et pourquoi?
java
anti-patterns
generics
collections
herpylderp
la source
la source
ChangeList
la compilation se cassera àextends
, car List est une interface, nécessiteimplements
. @randomA ce que vous imaginez manque le but à cause de cette erreurimplementation
ieHashMap
ou ouTreeMap
et qu'il y avait une faute de frappe.Réponses:
Lag / latence? J'appelle BS à ce sujet. Il devrait y avoir exactement zéro frais généraux de cette pratique. ( Edit: il a été souligné dans les commentaires que cela peut en fait empêcher les optimisations effectuées par la machine virtuelle HotSpot. Je ne connais pas suffisamment l'implémentation de la machine virtuelle pour confirmer ou infirmer cela. Je me suis basé mon commentaire sur C ++. implémentation de fonctions virtuelles.)
Il y a une surcharge de code. Vous devez créer tous les constructeurs de la classe de base souhaitée, en transmettant leurs paramètres.
Je ne vois pas non plus cela comme un anti-modèle, en soi. Cependant, je le vois comme une opportunité manquée. Au lieu de créer une classe qui dérive la classe de base uniquement pour renommer, pourquoi ne pas créer une classe contenant la collection et offrant une interface améliorée spécifique à la casse? Votre cache de widgets doit-il vraiment offrir l'interface complète d'une carte? Ou devrait-il plutôt offrir une interface spécialisée?
De plus, dans le cas de collections, le modèle ne fonctionne tout simplement pas avec la règle générale d’utilisation d’interfaces, et non d’implémentations - c’est-à-dire que dans le code de collection simple, vous créez un
HashMap<String, Widget>
, puis vous l’assignez à une variable de typeMap<String, Widget>
. VotreWidgetCache
ne peut pas s'étendreMap<String, Widget>
, car c'est une interface. Il ne peut s'agir d'une interface qui étend l'interface de base, car elleHashMap<String, Widget>
n'implémente pas cette interface, pas plus que toute autre collection standard. Et bien que vous puissiez en faire une classe qui s'étendHashMap<String, Widget>
, vous devez ensuite déclarer les variables commeWidgetCache
ouMap<String, Widget>
, et le premier vous perd la flexibilité nécessaire pour substituer une collection différente (peut-être la collection de chargement paresseux de certains ORM), tandis que le second défait le point. d'avoir la classe.Certains de ces contrepoints s'appliquent également à la classe spécialisée proposée.
Ce sont tous des points à considérer. Ce peut être ou ne pas être le bon choix. Dans les deux cas, les arguments proposés par votre collègue ne sont pas valides. S'il pense que c'est un anti-modèle, il devrait le nommer.
la source
public class Properties extends Hashtable<Object,Object>
injava.util
indique "Comme Properties hérite de Hashtable, les méthodes put et putAll peuvent être appliquées à un objet Properties. Leur utilisation est fortement déconseillée, car elles permettent à l'appelant d'insérer des entrées dont les clés ou les valeurs ne sont pas des chaînes. ". La composition aurait été beaucoup plus propre.Selon IBM, il s’agit en réalité d’un anti-modèle. Ces classes comme 'typedef' sont appelées types psuedo.
L'article l'explique beaucoup mieux que moi, mais je vais essayer de le résumer au cas où le lien tomberait:
WidgetCache
ne peut pas gérer unMap<String, Widget>
Dans l'article, ils proposent l'astuce suivante pour rendre la vie plus facile sans utiliser de pseudo-types:
Ce qui fonctionne grâce à l'inférence de type automatique.
(Je suis venu à cette réponse via cette question connexe de débordement de pile)
la source
newHashMap
opérateur de diamants n’a-t-il pas résolu le problème maintenant?WidgetCache
puisse être affectée à une variable de typeWidgetCache
ouMap<String,Widget>
sans transt était un moyen par lequel une méthode statique inWidgetCache
pouvait faire une référence àMap<String,Widget>
et la renvoyer en tant que typeWidgetCache
après avoir effectué la validation souhaitée. Une telle fonctionnalité pourrait être particulièrement utile avec les génériques, qui n'auraient pas à être effacés (...La performance atteinte serait tout au plus limitée à une recherche vtable, que vous êtes probablement déjà en train de subir. Ce n'est pas une raison valable pour s'y opposer.
La situation est suffisamment commune pour que la plupart des langages de programmation à typage statique aient une syntaxe spéciale pour les types de crénelage, généralement appelée un
typedef
. Java n'a probablement pas copié ceux-ci car à l'origine, il n'avait pas de types paramétrés. L'extension d'une classe n'est pas idéale, pour les raisons que Sebastian a si bien abordée dans sa réponse, mais cela peut constituer une solution de contournement raisonnable pour la syntaxe limitée de Java.Les typedefs présentent de nombreux avantages. Ils expriment plus clairement l'intention du programmeur, avec un meilleur nom, à un niveau d'abstraction plus approprié. Ils sont plus faciles à rechercher à des fins de débogage ou de refactoring. Envisagez de trouver partout où a
WidgetCache
est utilisé par opposition à trouver ces utilisations spécifiques de aMap
. Ils sont plus faciles à modifier, par exemple si vous vous rendez compte par la suite que vous avez besoin d’unLinkedHashMap
ou de votre propre conteneur personnalisé.la source
Comme d’autres ont déjà mentionné, je vous suggère d’utiliser la composition plutôt que l’héritage, de sorte que vous ne puissiez exposer que les méthodes réellement nécessaires, avec des noms correspondant au cas d’utilisation souhaité. Les utilisateurs de votre classe ont-ils vraiment besoin de savoir qu’il
WidgetCache
s’agit d’une carte? Et être capable de faire avec tout ce qu'ils veulent? Ou ont-ils simplement besoin de savoir qu'il s'agit d'un cache pour les widgets?Exemple de classe de ma base de code, avec la solution au problème similaire que vous avez décrit:
Vous pouvez voir qu’en interne, c’est «juste une carte», mais l’interface publique ne l’affiche pas. Et il a des méthodes "conviviales pour les programmeurs" comme
appendMessage(Locale locale, String code, String message)
pour un moyen plus facile et plus significatif d'insérer de nouvelles entrées. Et les utilisateurs de classe ne peuvent pas faire, par exempletranslations.clear()
, parce queTranslations
ne s'étend pasMap
.Facultativement, vous pouvez toujours déléguer certaines des méthodes nécessaires à la carte utilisée en interne.
la source
Je vois cela comme un exemple d'abstraction significative. Une bonne abstraction a quelques traits:
Il cache des détails d'implémentation qui ne sont pas pertinents pour le code qui le consomme.
C'est aussi complexe que cela doit être.
En étendant, vous exposez toute l'interface du parent, mais dans de nombreux cas, une grande partie de cette information est peut-être mieux masquée. Vous voudrez donc faire ce que Sebastian Redl suggère, préférer la composition à l'héritage et ajouter une instance du parent comme un membre privé de votre classe personnalisée. Toutes les méthodes d'interface utiles pour votre abstraction peuvent facilement être déléguées (dans votre cas) à la collection interne.
En ce qui concerne l’impact sur les performances, il est toujours judicieux d’optimiser d’abord le code pour en améliorer la lisibilité. Si un impact sur les performances est suspecté, profilez le code afin de comparer les deux implémentations.
la source
+1 aux autres réponses ici. J'ajouterai également que c'est en fait une très bonne pratique de la part de la communauté DDD (Domain Driven Design). Ils préconisent que votre domaine et les interactions avec ce dernier aient une signification de domaine sémantique, par opposition à la structure de données sous-jacente. A
Map<String, Widget>
pourrait être un cache, mais cela pourrait aussi être autre chose. Ce que vous avez correctement fait dans Mon opinion pas si humble (IMNSHO) est de modéliser ce que la collection représente, dans ce cas un cache.J'ajouterai une modification importante dans le fait que le wrapper de classe de domaine autour de la structure de données sous-jacente devrait probablement également contenir d'autres variables ou fonctions membres qui en font réellement une classe de domaine avec des interactions, par opposition à une structure de données uniquement (si seulement Java avait des types de valeur , nous les aurons en Java 10 - promis!)
Il sera intéressant de voir quel impact les flux de Java 8 auront sur tout cela. J'imagine que certaines interfaces publiques préféreront peut-être traiter un Stream de (insérer une primitive Java commune ou String) par opposition à un objet Java.
la source