J'ai certaines interfaces que j'ai l'intention d'implémenter à l'avenir par des tiers et je fournis moi-même une implémentation de base. Je vais seulement utiliser un couple pour montrer l'exemple.
Actuellement, ils sont définis comme
Article:
public interface Item {
String getId();
String getName();
}
ItemStack:
public interface ItemStackFactory {
ItemStack createItemStack(Item item, int quantity);
}
ItemStackContainer:
public interface ItemStackContainer {
default void add(ItemStack stack) {
add(stack, 1);
}
void add(ItemStack stack, int quantity);
}
Maintenant, Item
et ItemStackFactory
je peux absolument prévoir qu'un tiers devra l'étendre à l'avenir. ItemStackContainer
pourrait également être étendu à l'avenir, mais pas de manière que je puisse prévoir, en dehors de mon implémentation par défaut fournie.
Maintenant, j'essaie de rendre cette bibliothèque aussi robuste que possible; ceci est encore aux premiers stades (pré-pré-alpha), donc cela peut être un acte de suringénierie (YAGNI). Est-ce un endroit approprié pour utiliser des génériques?
public interface ItemStack<T extends Item> {
T getItem();
int getQuantity();
}
Et
public interface ItemStackFactory<T extends ItemStack<I extends Item>> {
T createItemStack(I item, int quantity);
}
Je crains que cela ne finisse par rendre les implémentations et l'utilisation plus difficiles à lire et à comprendre; Je pense que c'est la recommandation d'éviter autant que possible l'imbrication de génériques.
Réponses:
Vous utilisez des génériques dans votre interface lorsque votre implémentation est également susceptible d'être générique.
Par exemple, toute structure de données qui peut accepter des objets arbitraires est un bon candidat pour une interface générique. Exemples:
List<T>
etDictionary<K,V>
.Toute situation dans laquelle vous souhaitez améliorer la sécurité des types de manière généralisée est un bon candidat pour les génériques. A
List<String>
est une liste qui ne fonctionne que sur des chaînes.Toute situation dans laquelle vous souhaitez appliquer SRP de manière généralisée et éviter les méthodes spécifiques au type est un bon candidat pour les génériques. Par exemple, un PersonDocument, PlaceDocument et ThingDocument peuvent être remplacés par un
Document<T>
.Le modèle Abstract Factory est un bon cas d'utilisation pour un générique, tandis qu'une méthode Factory ordinaire créerait simplement des objets héritant d'une interface concrète commune.
la source
class StackFactory { Stack<T> Create<T>(T item, int count); }
. Je peux écrire un exemple de ce que je voulais dire par "affiner un paramètre de type dans une sous-classe" dans une réponse si vous voulez plus de précisions, bien que la réponse ne soit que tangentiellement liée à la question d'origine.Votre plan sur la façon d'introduire la généralité dans votre interface me semble correct. Cependant, votre question de savoir si ce serait une bonne idée nécessite une réponse un peu plus compliquée.
Au fil des ans, j'ai interviewé un certain nombre de candidats à des postes de génie logiciel pour le compte d'entreprises pour lesquelles j'ai travaillé, et je me suis rendu compte que la majorité des demandeurs d'emploi sont à l'aise avec l' utilisation de classes génériques (par exemple, les classes de collection standard), mais pas avec l' écriture de classes génériques. La plupart n'ont jamais écrit une seule classe générique au cours de leur carrière et semblent comme s'ils seraient complètement perdus si on leur en demandait une. Donc, si vous forcez l'utilisation de génériques à quiconque souhaite implémenter votre interface ou étendre vos implémentations de base, vous risquez de rebuter les gens.
Ce que vous pouvez essayer, cependant, est de fournir à la fois des versions génériques et non génériques de vos interfaces et de vos implémentations de base, afin que les personnes qui ne font pas de génériques puissent vivre heureux dans leur monde étroit et lourd de type, tandis que les personnes qui Les génériques peuvent-ils profiter de leur compréhension plus large de la langue.
la source
Votre décision est prise pour vous. Si vous ne fournissez pas de génériques, ils devront actualiser leur implémentation à
Item
chaque utilisation:Vous devriez au moins faire des
ItemStack
génériques comme vous le suggérez. L'avantage le plus profond des génériques est que vous n'avez presque jamais besoin de lancer quoi que ce soit, et l'offre de code qui oblige les utilisateurs à lancer est à mon humble avis une infraction pénale.la source
Wow ... J'ai une vision complètement différente de cela.
C'est une erreur. Vous ne devriez pas écrire de code "pour l'avenir".
De plus, les interfaces sont écrites de haut en bas. Vous ne regardez pas une classe et vous dites "Hé, cette classe pourrait totalement implémenter une interface et je pourrai ensuite utiliser cette interface ailleurs". C'est une mauvaise approche, car seule la classe qui utilise une interface sait vraiment ce que devrait être l'interface. Au lieu de cela, vous devriez penser "À cet endroit, ma classe a besoin d'une dépendance. Permettez-moi de définir une interface pour cette dépendance. Lorsque j'aurai fini d'écrire cette classe, j'écrirai une implémentation de cette dépendance." Que quelqu'un puisse réutiliser votre interface et remplacer votre implémentation par défaut par la sienne est complètement hors de propos. Ils ne le feront peut-être jamais. Cela ne signifie pas que l'interface était inutile. Cela fait que votre code suit le principe d'inversion de contrôle, ce qui rend votre code très extensible à de nombreux endroits "
la source
Je pense que l'utilisation de génériques dans votre situation est trop complexe.
Si vous choisissez la version générique des interfaces, la conception indique que
Ces hypothèses et restrictions implicites sont des choses très importantes à décider soigneusement. Ainsi, en particulier au stade précoce, ces conceptions prématurées ne doivent pas être introduites. Une conception simple, facile à comprendre et commune est souhaitée.
la source
Je dirais que cela dépend principalement de cette question: si quelqu'un a besoin d'étendre les fonctionnalités de
Item
etItemStackFactory
,Dans le premier cas, il n'y a pas besoin de génériques, dans le second, probablement.
la source
Peut-être que vous pouvez avoir une hiérarchie d'interface, où Foo est une interface, Bar est une autre. Chaque fois qu'un type doit être à la fois, c'est un FooAndBar.
Pour éviter une surcharge, ne faites le type FooAndBar que lorsque cela est nécessaire, et conservez une liste d'interfaces qui ne prolongeront jamais rien.
C'est beaucoup plus confortable pour la plupart des programmeurs (la plupart aiment leur vérification de type ou aiment ne jamais traiter de typage statique d'aucune sorte). Très peu de gens ont écrit leur propre cours de génériques.
Également comme remarque: les interfaces concernent les actions possibles qui peuvent être exécutées. Ils devraient en fait être des verbes et non des noms.
la source