J'ai un ami qui vient de se lancer dans le développement .NET après avoir développé en Java pendant des siècles et, après avoir consulté certains de ses codes, je remarque qu'il fait assez souvent les opérations suivantes:
IDictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();
Il déclare que le dictionnaire est l'interface plutôt que la classe. En règle générale, je ferais ce qui suit:
Dictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();
Je n'utiliserais l'interface IDictionary que lorsque cela est nécessaire (par exemple, pour passer le dictionnaire à une méthode qui accepte une interface IDictionary).
Ma question est: y a-t-il des mérites à sa façon de faire les choses? Est-ce une pratique courante en Java?
Si IDictionary est un type "plus générique" que Dictionary, alors il est logique d'utiliser le type plus générique pour déclarer des variables. De cette façon, vous n'avez pas à vous soucier autant de la classe d'implémentation affectée à la variable et vous pouvez changer le type facilement à l'avenir sans avoir à changer beaucoup de code suivant. Par exemple, en Java il est souvent préférable de faire
List<Integer> intList=new LinkedList<Integer>();
que de faire
LinkedList<Integer> intList=new LinkedList<Integer>();
De cette façon, je suis sûr que tout le code suivant traite la liste comme une liste et non comme une LinkedList, ce qui facilitera à l'avenir la désactivation de LinkedList pour Vector ou de toute autre classe qui implémente List. Je dirais que c'est commun à Java et à une bonne programmation en général.
Cette pratique ne se limite pas à Java.
Il est également souvent utilisé dans .NET lorsque vous souhaitez dissocier l'instance de l'objet de la classe que vous utilisez. Si vous utilisez l'interface plutôt que la classe, vous pouvez modifier le type de support chaque fois que nécessaire sans casser le reste de votre code.
Vous verrez également que cette pratique est largement utilisée pour traiter les conteneurs IoC et l'instanciation à l'aide du modèle Factory.
Votre ami suit le principe très utile:
"Abstenez-vous des détails de mise en œuvre"
Pour les variables locales et les champs privés, qui sont déjà des détails d'implémentation, il est préférable d'utiliser des types concrets que des interfaces pour les déclarations car les classes concrètes offrent une amélioration des performances (la distribution directe est plus rapide que la distribution virtuelle/interface). Le JIT pourra également intégrer plus facilement les méthodes si vous ne convertissez pas inutilement en types d'interface dans les détails d'implémentation locale. Si une instance d'un type concret est renvoyée par une méthode qui renvoie une interface, la conversion est automatique.
Vous devez toujours essayer de programmer sur l'interface plutôt que sur la classe concrète.
Dans Java ou tout autre langage de programmation orienté objet.
Dans le monde .NET, il est courant d'utiliser un I
pour indiquer que c'est une interface que vous utilisez. Je pense que c'est plus courant car en C # ils n'ont pas implements
et extends
pour faire référence à l'héritage classe vs interface.
Je pense que le lactosérum taperait
class MyClass:ISome,Other,IMore
{
}
Et vous pouvez dire que ISome
an IMore
sont des interfaces tandis que Other
est une classe
En Java il n'y a pas besoin d'une telle chose
class MyClass extends Other implements Some, More {
}
Le concept s'applique toujours, vous devriez essayer de coder sur l'interface.
Pour autant que j'ai vu Java ont tendance à utiliser l'abstraction (et les modèles de conception) plus souvent que les développeurs .NET. Cela semble un autre exemple: pourquoi déclarer la classe concrète quand il essentiellement travailler uniquement avec les membres de l'interface?
Le plus souvent, vous voyez le type d'interface (IDictionary) utilisé lorsque le membre est exposé à du code externe, que ce soit en dehors de l'assembly ou juste en dehors de la classe. En règle générale, la plupart des développeurs utilisent le type concret en interne pour une définition de classe alors qu'ils exposent une propriété encapsulée à l'aide du type d'interface. De cette façon, ils peuvent tirer parti des capacités du type concret, mais s'ils changent le type concret, l'interface de la classe déclarante n'a pas besoin de changer.
Widget de classe publique { Dictionnaire privé <chaîne, chaîne> map = nouveau Dictionnaire <chaîne, chaîne> (); public IDictionary <chaîne, chaîne> Carte { Obtenez {carte retour; } } }
plus tard peut devenir:
classe SpecialMap <TKey, TValue>: IDictionary <TKey, TValue> {...} Widget de classe publique { private SpecialMap <string, string> map = new SpecialMap <string, string> (); public IDictionary <string, string> Map { get {return map ; } } }
sans changer l'interface de Widget et sans avoir à changer d'autres codes qui l'utilisent déjà.
Les collections Java incluent une multitude d'implémentations. Par conséquent, il m’est beaucoup plus facile d’utiliser
List<String> myList = new ArrayList<String>();
Ensuite, à l'avenir, lorsque je me rendrai compte que j'ai besoin que "myList" soit thread-safe pour changer simplement cette seule ligne en
List<String> myList = new Vector<String>();
Et ne changez aucune autre ligne de code. Cela inclut également les getters/setters. Si vous regardez le nombre d'implémentations de Map par exemple, vous pouvez imaginer pourquoi cela pourrait être une bonne pratique. Dans d'autres langages, où il n'y a que quelques implémentations pour quelque chose (désolé, pas un gros gars .NET) mais dans Objective-C, il n'y a vraiment que NSDictionary et NSMutableDictionary. Donc, cela n'a pas autant de sens.
Modifier:
Je n'ai pas réussi à toucher l'un de mes points clés (je viens d'y faire allusion avec le getter/setters).
Ce qui précède vous permet d'avoir:
public void setMyList(List<String> myList) {
this.myList = myList;
}
Et le client utilisant cet appel n'a pas à se soucier de l'implémentation sous-jacente. Utiliser n'importe quel objet conforme à l'interface List qu'ils peuvent avoir.
Dans la situation décrite, presque tous les développeurs Java utiliseraient l'interface pour déclarer la variable. La façon dont les collections Java sont utilisées est probablement l'un des meilleurs exemples:
Map map = new HashMap();
List list = new ArrayList();
Je suppose qu'il accomplit simplement un couplage lâche dans de nombreuses situations.
Venant d'un monde Java Java, je suis d'accord que le mantra "programme vers une interface" est percé en vous. En programmant sur une interface, pas une implémentation, vous rendez vos méthodes plus extensibles aux besoins futurs .
J'ai rencontré la même situation avec un développeur Java. Il instancie les collections ET les objets vers leur interface de la même manière. Par exemple,
IAccount account = new Account();
Les propriétés sont toujours récupérées/définies en tant qu'interfaces. Cela provoque des problèmes de sérialisation, ce qui est très bien expliqué ici
J'ai trouvé que pour les variables locales, cela n'a généralement pas beaucoup d'importance si vous utilisez l'interface ou la classe concrète.
Contrairement aux membres de classe ou aux signatures de méthode, il y a très peu d'effort de refactoring si vous décidez de changer de type, et la variable n'est pas visible en dehors de son site d'utilisation. En fait, lorsque vous utilisez var
pour déclarer des sections locales, vous n'obtenez pas le type d'interface, mais plutôt le type de classe (sauf si vous effectuez un cast explicite sur l'interface).
Cependant, lors de la déclaration de méthodes, de membres de classe ou d'interfaces, je pense que cela vous évitera beaucoup de maux de tête pour utiliser le type d'interface à l'avance, plutôt que de coupler l'API publique à un type de classe spécifique.
L'utilisation d'interfaces signifie que "dictionnaire" dans le code suivant peut être n'importe quelle implémentation d'IDictionary.
Dictionary1 dictionary = new Dictionary1();
dictionary.operation1(); // if operation1 is implemented only in Dictionary1() this will fail for every other implementation
Il est préférable de le voir lorsque vous masquez la construction de l'objet:
IDictionary dictionary = DictionaryFactory.getDictionary(...);
IDictionary
est une interface et Dictionary
est une classe.
Dictionary
implémente IDictionary
.
Cela signifie qu'il est possible de faire référence à l'instance Dictionary
avec/par l'instance IDictionary
et d'appeler la plupart des méthodes et propriétés Dictionary
via l'instance IDictionary
.
Il est très recommandé d'utiliser autant d'interfaces que possible, car les interfaces résument les modules et les assemblages des applications, permettent le polymorphisme, qui est à la fois très courant et utile dans de nombreuses situations et cas et permet de remplacer un module par un autre sans toucher les autres modules .
Supposons que dans le présent, le programmeur ait écrit:
IDictionary<string> dictionary = new Dictionary<string>();
Et maintenant, dictionary
invoque les méthodes et propriétés de Dictionary<string>
.
À l'avenir, les bases de données ont été agrandies et nous constatons que Dictionary<string>
Est trop lent, nous voulons donc remplacer Dictionary<string>
Par RedBlackTree<string>
, Ce qui est plus rapide.
Il suffit donc de remplacer l'instruction ci-dessus pour:
IDictionary<string> dictionary = new RedBlackTree<string>();
Bien sûr, si RedBlackTree
implémente IDictionary
alors tout le code de l'application se compile avec succès et vous avez une version plus récente de votre application, où l'application fonctionne maintenant plus rapidement et plus efficacement que la version précédente.
Sans interfaces, ce remplacement serait plus difficile à faire et nécessiterait que les programmeurs et les développeurs modifient davantage de code susceptible de provoquer des bogues.