Multiplication des interfaces ou usage immodéré des UnsupportedOperationException

Le
MattBan
Bonjour.

Je suis très fier de mon titre. Je vous explique mon problème. J'écris
des interfaces et des classes, et certaines classes n'implémentent
certaines fonctionnalités de mes interfaces. Dois-je donc ajouter des
interfaces pour ces classes ou autoriser que mon implémentation
balance un UnsupportedOperationException.

Explication sur un exemple concret.
Le package java.util contient une interface Iterator qui comprend une
méthode 'remove()'. Cette méthode n'est cependant pas autorisée par
toutes les implémentations. Voir la doc :
«
Throws:
UnsupportedOperationException - if the remove operation is not
supported by this Iterator.
»
Mais moi, je trouve ça super inquiétant. Ça veut dire que si j'utilise
un itérateur sur une collection généré(e) par une implémentation
tierce, je dois (dans le meilleur des cas) regarder la doc pour
vérifier si la méthode est autorisée, et dans le pire des cas croiser
les doigts. On pourra me répondre que le fait d'avoir le droit de
supprimer l'élément dérive logiquement de la définition de la class=
e,
mais je ne suis pas d'accord et puis ça nécessite de toute façon une
gymnastique intellectuelle non négligeable. Bref, je trouve ça peu
pratique.

L'autre approche serait de créer une interface UnmodifiableIterator,
étendue par Iterator, qui ne requiert que l'implémentation de next()
et hasNext(). En plus, ça permet de dégrader facilement les
possibilités de l'utilisateur de votre code puisque vous pouvez
renvoyer un UnmodifiableIterator qui est en fait un Iterator, alors
que dans l'implémentation actuelle il faut passer par
Collections.unmodifiableCollection(Collection c).iterator(). Je vois
bien les problèmes de cette seconde approche : on crée un grand nombre
d'interface supplémentaires, notamment si on a un grand nombre de
méthodes qui peuvent indépendamment être supportées ou pas.

Quel est votre avis sur le sujet ? Faut-il privilégier la seconde
approche quand on a peu de méthodes qui posent problème ?
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Black Myst
Le #228530
Bonjour.

(...)

Quel est votre avis sur le sujet ? Faut-il privilégier la seconde
approche quand on a peu de méthodes qui posent problème ?



Je pense que dans ce cas précis, tu aborde le problème par le mauvais
coté. Pour ce qui est de la gestion des collections en Java, on peut
dire que dans 99% des cas, le développeur à besoin d'une liste modifiable.

En fait, je ne vois qu'un seul cas ou on veut rendre une liste immuable,
c'est lorsque l'on fait de la programmation défensive, pour ne pas
exposer la représentation interne de notre classe à l'extérieur et que
l'on souhaite éviter le clone pour des questions de performance.

Dans le cas de programmation défensive, il suffira de préciser dans la
doc que la liste renvoyée est immuable. Par défaut on considérera toutes
collection mutable.

Pour en revenir à ta question, je dirais que que cela dépends du cas
général. Si ton interface définie une méthode suffisamment particulières
et que tu sais pertinemment que la plupart des impléms ne pourront pas y
répondre, alors il faut peut-être envisager de découper en plusieurs
interfaces.

De l'autre coté, il arrive parfois d'avoir des solutions optimisés pour
implémenter ton interface, mais qui rendes certaine action impossible.
Dans ce cas, un bon commentaire et un UnsupportedOperationException
peuvent être une solution tout à fait pertinente.

Une dernière possibilité peut être de donner le moyen à
l'implémentation, au travers de l'interface, de dire qi elle est capable
de gérer certaine fonctionnalité

interface UsersManager {
User[] getAllUsers();
boolean acceptUser(User, Pass);
boolean canUpdatePassword()
void upatePassword(User, old, new)
boolean canRemoveUser()
void removeUser(User)
}

A++
Black Myst

Hervé AGNOUX
Le #228529
MattBan wrote:


Quel est votre avis sur le sujet ? Faut-il privilégier la seconde
approche quand on a peu de méthodes qui posent problème ?


Je suis tout à fait d'accord avec toi, et regrette également l'apparition de
méthodes susceptibles d'envoyer cet "unsupported" dans le jdk.

Moi je l'utilise lorsque je ne sais pas très bien comment définir une
interface ou une classe, avec toute sa parentée ; j'y mets alors des
Unsupported, en me disant que si je tombe dessus, je l'écrirai. Netbeans
fonctionne aussi quelque fois comme ça, quand on lui demande de générer des
classes automatiquement, il remplit le bloc avec un Unsupported ; je trouve
ça bien.

Je l'utilise donc plutôt pour faciliter le refactoring. Normalement, à la
livraison, il n'y en a plus :-)


--
Hervé AGNOUX
http://www.diaam-informatique.com

Maxime Daniel
Le #228527
On 8 juin, 20:34, Hervé AGNOUX
informatique.com.zz> wrote:
MattBan wrote:

Quel est votre avis sur le sujet ? Faut-il privilégier la seconde
approche quand on a peu de méthodes qui posent problème ?


Je suis tout à fait d'accord avec toi, et regrette également l'appari tion de
méthodes susceptibles d'envoyer cet "unsupported" dans le jdk.
Hervé, je crois pour ma part que cette introduction vient de la

volonté de faire un arbitrage assez fin entre le design propre et bien
général du besoin, qui introduit les "méthodes naturelles" sur
l'interface considérée, et une implémentation partielle possible pour
assurer l'implémentation d'une qualité de service différente (on
sacrifie alors l'implémentation complète de l'interface pour fournir
une classe plus rapide ou moins gourmande en mémoire ou les deux).
L'emploi de ce pattern avec cet objectif nécessite à mon avis une
grande maturation du design, beaucoup de recul, et ne devrait pas être
envisagé sans être prêt à itérer sur la conception. C'est d'une
certaine façon une approche qui, a posteriori, retire des
implémentations de méthodes jugées trop dispendieuses.


Hervé AGNOUX
Le #228481
Maxime Daniel wrote:

Hervé, je crois pour ma part que cette introduction vient de la
volonté de faire un arbitrage assez fin entre le design propre et bien
général du besoin, qui introduit les "méthodes naturelles" sur
l'interface considérée, et une implémentation partielle possible pour
assurer l'implémentation d'une qualité de service différente (on
sacrifie alors l'implémentation complète de l'interface pour fournir
une classe plus rapide ou moins gourmande en mémoire ou les deux).
L'emploi de ce pattern avec cet objectif nécessite à mon avis une
grande maturation du design, beaucoup de recul, et ne devrait pas être
envisagé sans être prêt à itérer sur la conception. C'est d'une
certaine façon une approche qui, a posteriori, retire des
implémentations de méthodes jugées trop dispendieuses.


Oui, il faut faire des compromis. Mais, là où tu parles de "arbitrage assez
fin", je parlerais, moi, de "arbitrage assez grossier" :-)

Il est vrai que c'est une technique pour définir les interfaces : on y met
tous les possibles, prétendument assuré d'avoir ainsi réponse à tout. Je
n'aime pas beaucoup cette approche, y préférant celle qui consiste à
proposer une organisation de ces possibles, organisation qui prend le
risque d'être mauvaise.

J'admets que le paquetage des collections est plutôt dans la seconde
approche (proposer une organisation) que dans la première, donc c'est bon.


--
Hervé AGNOUX
http://www.diaam-informatique.com

Maxime Daniel
Le #228479
On 9 juin, 12:03, Hervé AGNOUX
informatique.com.zz> wrote:
...
Oui, il faut faire des compromis. Mais, là où tu parles de "arbitrage assez
fin", je parlerais, moi, de "arbitrage assez grossier" :-)
...

Je viens de relire la classe Iterator de Java 1.5 et je maintiens le
'assez fin'. Iterator a les méthodes hasNext, next et remove. hasNext
et next constituent un noyau dur de la fonctionnalité (on pourrait se
passer de hasNext, mais le pattern classique propose hasNext et les
développeurs qui adhèrent aux thèses de Meyer sur les prérequis
préfèrent n'appeler next qu'à coup sûr, plutôt que de traiter une
exception). remove est très désirable par une prolongation naturelle
du design minimaliste. A posteriori cependant, on se rend compte que
les implémentations les plus efficaces pour certaines structures de
données sont incapables de fournir remove, et on décide donc de le
rendre facultatif. L'alternative est alors de créer une interface
héritière MutableIterator et de supprimer remove de Iterator, ou bien
de trouver une astuce pour autoriser certaines implémentations à ne
pas implémenter remove. La décision qui a été prise (seconde approc he)
s'appuie sur des jugements de valeur quant à l'équilibre général de
l'API, son nombre de classes et d'interfaces, la granularité des
interfaces les plus utilisées, etc. Elle n'est forcément pas exempte
de reproches, mais résulte de l'examen poussé de notions subtiles.
C'est donc un arbitrage 'assez fin'...

Hervé AGNOUX
Le #228478
Maxime Daniel wrote:

interfaces les plus utilisées, etc. Elle n'est forcément pas exempte
de reproches, mais résulte de l'examen poussé de notions subtiles.
C'est donc un arbitrage 'assez fin'...


Je n'ai pas accès à toutes les considérations des équilibres généraux des
apis etc. L'équilibre général du paquetage des collections est assez bien
fait, donc la méthode employée pour y parvenir a des chances d'être bonne.

Pour ce qui concerne le remove, j'observe que, la plupart du temps, je ne
l'emploie pas. (je crois pouvoir dire que je ne l'emploie jamais). De plus,
comme principe général, je ne modifie pas une liste en même temps que je la
parcours.

Donc, pour moi, ce remove est maladroit, parce qu'il facilite une forme de
traitement qui me parait mauvaise.

Cependant il existe des exceptions, bien sûr, heureusement. Il y a des
listes qu'il vaut mieux modifier directement pendant le parcours. Il s'agit
souvent de listes où l'accés est distant. Par exemple, JDBC donne la
possibilité de modifier un curseur.

Ces listes sont cependant très particulières, dans leur mode d'accés, et il
me semble qu'il aurait été bon d'en approfondir les caractéristiques, pour
mieux les décrire et les utiliser, au lieu de placer un simple "remove".

D'ailleurs, le ResultSet, qui parcourt des listes de base de données, cas
dans lequel je peux concevoir que le remove (ici "deleteRow") soit
justifié, n'est pas relié au paquetage des collections.

Bug dans l'arbitrage fin ?...



--
Hervé AGNOUX
http://www.diaam-informatique.com

MattBan
Le #228477
Bonjour.

Merci pour vos explications. :)

--
ban
Publicité
Poster une réponse
Anonyme