Est-ce une pratique sûre d'utiliser des méthodes par défaut comme une version pauvre des traits dans Java 8?
Certains prétendent que cela peut rendre les pandas tristes si vous les utilisez juste pour le plaisir, parce que c'est cool, mais ce n'est pas mon intention. Il est également souvent rappelé que des méthodes par défaut ont été introduites pour prendre en charge l'évolution des API et la compatibilité descendante, ce qui est vrai, mais cela ne rend pas faux ou tordu de les utiliser comme traits en soi.
J'ai à l'esprit le cas d'utilisation pratique suivant :
public interface Loggable {
default Logger logger() {
return LoggerFactory.getLogger(this.getClass());
}
}
Ou peut-être définir un PeriodTrait
:
public interface PeriodeTrait {
Date getStartDate();
Date getEndDate();
default isValid(Date atDate) {
...
}
}
Certes, la composition pourrait être utilisée (ou même des classes d'aide) mais elle semble plus verbeuse et encombrée et ne permet pas de bénéficier du polymorphisme.
Alors, est-il correct / sûr d'utiliser les méthodes par défaut comme traits de base , ou devrais-je m'inquiéter des effets secondaires imprévus?
Plusieurs questions sur le SO sont liées aux traits Java vs Scala; ce n'est pas le point ici. Je ne demande pas seulement des opinions non plus. Au lieu de cela, je recherche une réponse faisant autorité ou au moins un aperçu du terrain: si vous avez utilisé des méthodes par défaut comme caractéristiques de votre projet d'entreprise, est-ce que cela s'est avéré être une bombe à retardement?
la source
Réponses:
La réponse courte est: c'est sûr si vous les utilisez en toute sécurité :)
La réponse sournoise: dites-moi ce que vous entendez par traits, et peut-être que je vais vous donner une meilleure réponse :)
Sérieusement, le terme «trait» n'est pas bien défini. De nombreux développeurs Java sont plus familiers avec les traits tels qu'ils sont exprimés en Scala, mais Scala est loin d'être le premier langage à avoir des traits, que ce soit en nom ou en effet.
Par exemple, dans Scala, les traits sont avec état (peuvent avoir des
var
variables); dans Fortress, ce sont des comportements purs. Les interfaces de Java avec les méthodes par défaut sont sans état; cela veut-il dire que ce ne sont pas des traits? (Indice: c'était une question piège.)Encore une fois, dans Scala, les traits sont composés par linéarisation; si la classe
A
étend les traitsX
etY
, alors l'ordre dans lequelX
etY
sont mélangés détermine comment les conflits entreX
etY
sont résolus. En Java, ce mécanisme de linéarisation n'est pas présent (il a été rejeté, en partie, car il était trop "différent de Java".)La raison immédiate de l'ajout de méthodes par défaut aux interfaces était de prendre en charge l' évolution des interfaces , mais nous étions bien conscients que nous allions plus loin. Que vous considériez cela comme une "évolution d'interface ++" ou des "traits -" est une question d'interprétation personnelle. Donc, pour répondre à votre question sur la sécurité ... tant que vous vous en tenez à ce que le mécanisme soutient réellement, plutôt que d'essayer de l'étirer à souhait à quelque chose qu'il ne supporte pas, tout devrait aller bien.
Un objectif clé de la conception était que, du point de vue du client d'une interface, les méthodes par défaut ne devraient pas être distinguées des méthodes d'interface "régulières". La valeur par défaut d'une méthode n'est donc intéressante que pour le concepteur et l' implémenteur de l'interface.
Voici quelques cas d'utilisation qui correspondent bien aux objectifs de conception:
Évolution de l'interface. Ici, nous ajoutons une nouvelle méthode à une interface existante, qui a une implémentation par défaut sensible en termes de méthodes existantes sur cette interface. Un exemple serait l'ajout de la
forEach
méthode àCollection
, où l'implémentation par défaut est écrite en termes deiterator()
méthode.Méthodes "facultatives". Ici, le concepteur d'une interface dit: "Les réalisateurs n'ont pas besoin d'implémenter cette méthode s'ils veulent vivre avec les limitations de fonctionnalités que cela implique". Par exemple,
Iterator.remove
a reçu une valeur par défaut qui jetteUnsupportedOperationException
; puisque la grande majorité des implémentationsIterator
ont de toute façon ce comportement, la valeur par défaut rend cette méthode essentiellement facultative. (Si le comportement deAbstractCollection
était exprimé par défaut surCollection
, nous pourrions faire de même pour les méthodes mutatives.)Méthodes de commodité. Ce sont des méthodes strictement pratiques, à nouveau généralement implémentées en termes de méthodes non par défaut sur la classe. La
logger()
méthode de votre premier exemple en est une illustration raisonnable.Combinateurs. Ce sont des méthodes de composition qui instancient de nouvelles instances de l'interface en fonction de l'instance actuelle. Par exemple, les méthodes
Predicate.and()
ouComparator.thenComparing()
sont des exemples de combinateurs.Si vous fournissez une implémentation par défaut, vous devez également fournir une spécification pour
@implSpec
la méthode par défaut (dans le JDK, nous utilisons la balise javadoc pour cela) pour aider les implémenteurs à comprendre s'ils veulent remplacer la méthode ou non. Certaines valeurs par défaut, comme les méthodes pratiques et les combinateurs, ne sont presque jamais remplacées; d'autres, comme les méthodes facultatives, sont souvent remplacées. Vous devez fournir suffisamment de spécification (pas seulement de la documentation) sur ce que la valeur par défaut promet de faire, afin que l'implémenteur puisse prendre une décision judicieuse pour savoir s'il doit le remplacer.la source
MouseListener
? Dans la mesure où ce style d'API a du sens, il s'inscrit dans le compartiment "méthodes facultatives". Assurez-vous de documenter clairement les options!MouseListener
. Merci pour la réponse.