Lors de ma première implémentation d'extension du framework de collection Java, j'ai été assez surpris de voir que l'interface de la collection contienne des méthodes déclarées comme optionnelles. L'implémenteur est censé déclencher des exceptions UnsupportedOperationExceptions s'il n'est pas pris en charge. Cela m'a immédiatement semblé un choix de conception d'API médiocre.
Après avoir lu une bonne partie de l'excellent livre "Effective Java" de Joshua Bloch, et appris plus tard qu'il est peut-être responsable de ces décisions, il n'a pas semblé se fondre dans les principes énoncés dans le livre. Je pense que déclarer deux interfaces: Collection et MutableCollection qui étend Collection avec les méthodes "optionnelles" aurait conduit à un code client beaucoup plus facile à gérer.
Il y a un excellent résumé des problèmes ici .
Y avait-il une bonne raison pour laquelle des méthodes facultatives ont été choisies au lieu de l'implémentation de deux interfaces?
la source
Réponses:
La FAQ fournit la réponse. En bref, ils ont constaté une explosion combinatoire potentielle des interfaces nécessaires avec des vues modifiables, non modifiables, avec suppression seulement, avec ajout seulement, de longueur fixe, immuables (pour le filetage), etc. pour chaque ensemble possible de méthodes d'option implémentées.
la source
const
mot clé comme C ++.vector<int>, const vector<int>, vector<const int>, const vector<const int>
. Jusqu'ici, tout va bien, mais vous essayez ensuite d'implémenter des graphes et vous voulez rendre la structure du graphe constante, mais les attributs de nœud sont modifiables, etc.can
méthode permettant de vérifier si une opération est possible? Cela garderait l'interface simple et rapide.Il me semble que ce
Interface Segregation Principle
n’était pas aussi bien exploré à l’époque que maintenant; cette façon de faire (c’est-à-dire que votre interface inclut toutes les opérations possibles et que vous avez des méthodes "dégénérées" qui jettent des exceptions pour celles dont vous n’avez pas besoin) était populaire avant que SOLID et ISP ne deviennent la norme de facto pour le code qualité.la source
Count
contenu d'une collection sans avoir à se soucier des types d'éléments qu'elle contient), mais dans les frameworks basés sur l'effacement de type comme Java, ce n'est pas un problème.Certaines personnes peuvent détester les "méthodes optionnelles", mais dans de nombreux cas, elles peuvent offrir une meilleure sémantique que des interfaces hautement séparées. Entre autres choses, ils permettent à un objet de gagner des capacités ou des caractéristiques au cours de sa vie, ou qu’un objet (en particulier un objet wrapper) ne sache pas quand il est construit quelles capacités exactes il doit signaler.
Bien que j'appelle difficilement les classes de collection Java comme étant des modèles de bonne conception, je suggérerais qu'un bon cadre de collections devrait inclure à sa base un grand nombre de méthodes facultatives ainsi que des moyens de demander à une collection ses caractéristiques et ses capacités . Une telle conception permettra à une seule classe wrapper d'être utilisée avec une grande variété de collections sans occulter accidentellement les capacités que la collection sous-jacente pourrait posséder. Si les méthodes n'étaient pas facultatives, il serait alors nécessaire de disposer d'une classe de wrapper différente pour chaque combinaison de fonctions pouvant être gérées par les collections, sinon certaines enveloppes seraient inutilisables dans certaines situations.
Par exemple, si une collection prend en charge l'écriture d'un élément par index ou l'ajout d'éléments à la fin, mais ne prend pas en charge l'insertion d'éléments au milieu, le code souhaitant l'encapsuler dans un wrapper qui enregistrerait toutes les actions effectuées sur celui-ci nécessiterait une version. du wrapper de journalisation qui fournissait la combinaison exacte des capacités prises en charge, ou si aucune n'était disponible, devrait utiliser un wrapper prenant en charge l'ajout ou l'écriture par index mais pas les deux. Si, toutefois, une interface de collection unifiée fournissait les trois méthodes en tant que "facultatives", mais incluait ensuite des méthodes pour indiquer laquelle des méthodes facultatives serait utilisable, une seule classe d'encapsuleur pourrait gérer des collections qui implémenteraient n'importe quelle combinaison de caractéristiques. Lorsqu'on lui demande quelles fonctionnalités il prend en charge, un wrapper peut simplement signaler tout ce que la collection encapsulée prend en charge.
Notez que l'existence de "capacités facultatives" peut dans certains cas permettre aux collections agrégées d'implémenter certaines fonctions de manière beaucoup plus efficace que si les capacités étaient définies par l'existence d'implémentations. Par exemple, supposons qu'une
concatenate
méthode soit utilisée pour former une collection composite à partir de deux autres, le premier d'entre eux étant un ArrayList avec 1 000 000 d'éléments et le dernier étant une collection de 20 éléments pouvant uniquement être itérée dès le début. Si la collection composite était demandée pour le 1 000 013e élément (index 1 000 012), elle pourrait demander à ArrayList combien d'éléments elle contenait (c.-à-d. 1 000 000), soustrayez celle-ci de l'index demandé (donnant 12), lisez et ignorez 12 éléments du deuxième collection, puis renvoie l'élément suivant.Dans une telle situation, même si la collection composite ne dispose pas d'un moyen instantané de retourner un élément par index, le demander à la collection composite pour le 1 000 013e élément serait toujours beaucoup plus rapide que de lire 1 000 013 éléments individuellement et d'ignorer tous les éléments sauf le dernier. un.
la source
AsXXX
méthode dans l'interface de base qui renverra l'objet sur lequel elle est invoquée si elle implémente cette interface, renvoyer un objet encapsuleur prenant en charge cette interface si possible ou une exception sinon. Par exemple, uneImmutableCollection
interface peut nécessiter un contrat ...Je l’attribuerais aux développeurs originaux, mais je ne savais pas mieux à l’époque. Nous avons parcouru un long chemin dans la conception orientée objet depuis 1998 ou à peu près, lorsque Java 2 et Collections ont été lancés pour la première fois. Ce qui semble être une mauvaise conception évidente n’était plus si évident au début de la programmation orientée objet.
Mais cela a peut-être été fait pour empêcher un casting supplémentaire. S'il s'agissait d'une deuxième interface, il vous faudrait transtyper vos instances de collections pour appeler ces méthodes facultatives, ce qui est également un peu moche. Dans l'état actuel des choses, vous interceptez immédiatement une exception UnsupportedOperationException et corrigez votre code. Mais s'il y avait deux interfaces, vous devriez utiliser instanceof et casting partout. Peut-être ont-ils considéré cela comme un compromis valable. De plus, au début de Java, une instance de 2 jours était très mal vue en raison de la lenteur de ses performances, ils auraient peut-être tenté d'empêcher une utilisation excessive de celle-ci.
Bien sûr, il s’agit d’une pure spéculation. Je doute que nous puissions y répondre à moins que l’un des architectes de la collection originale ne sonne.
la source
Collection
et pas unMutableCollection
, c'est un signe clair qu'elle n'est pas destinée à être modifiée. Je ne sais pas pourquoi quelqu'un aurait besoin de les lancer. Disposer d'interfaces séparées signifie que vous obtiendrez ce type d'erreur lors de la compilation au lieu d'une exception lors de l'exécution. Plus tôt vous obtenez l'erreur, mieux c'est.const
objet, indiquant instantanément à l'utilisateur qu'il ne peut pas être modifié.