En programmation orientée objet , une méthode d'extension est une méthode ajoutée à un objet après la compilation de l'objet original . L'objet modifié est souvent une classe, un prototype ou un type. Les méthodes d'extension sont des fonctionnalités de certains langages de programmation orientés objet. Il n'y a aucune différence syntaxique entre appeler une méthode d'extension et appeler une méthode déclarée dans la définition du type.
Cependant, tous les langages n'implémentent pas les méthodes d'extension de manière aussi sûre. Par exemple, des langages comme C#, Java (via Manifold , Lombok ou Fluent ) et Kotlin ne modifient en aucune façon la classe étendue, car cela pourrait rompre les hiérarchies de classes et perturber la répartition des méthodes virtuelles. Ces langages implémentent donc les méthodes d'extension de manière statique et utilisent la répartition statique pour les invoquer.
Assistance en langages de programmation
Les méthodes d'extension sont présentes dans de nombreux langages, notamment C# , Java ( via Manifold , Lombok ou Fluent) , Gosu , JavaScript , Oxygene , Ruby , Smalltalk , Kotlin , Dart , Visual Basic.NET et Xojo . Dans les langages dynamiques comme Python , le concept de méthode d'extension est superflu, car les classes (à l'exception des classes intégrées) peuvent être étendues sans syntaxe particulière (une approche appelée « monkey-patching », utilisée dans des bibliothèques telles que C++ et s'inspirent des méthodes d'extension de style C#.
En Java, les méthodes d'extension sont ajoutées via Manifold , un fichier JAR ajouté au classpath du projet. Comme en C#, une méthode d'extension Java est déclarée statique dans une classe annotée avec `@Extension` , où le premier argument est du même type que la classe étendue et est annoté avec ` @Extension` @This. Le plugin Fluent permet également d'appeler n'importe quelle méthode statique comme une méthode d'extension sans annotation, à condition que la signature de la méthode corresponde.
En Smalltalk, tout code peut ajouter une méthode à n'importe quelle classe à tout moment, en envoyant un message de création de méthode (tel que ` methodsFor:addMethodCreateMethod`) à la classe que l'utilisateur souhaite étendre. La catégorie des méthodes Smalltalk est généralement nommée d'après le package qui fournit l'extension, entouré d'astérisques. Par exemple, lorsque le code d'une application Etoys étend des classes de la bibliothèque principale, les méthodes ajoutées sont placées dans la *etoys*catégorie `addMethod`.
En Ruby, comme en Smalltalk, il n'existe pas de fonctionnalité spécifique pour l'extension, car Ruby permet de rouvrir les classes à tout moment grâce au classmot-clé `require` afin d'y ajouter de nouvelles méthodes. La communauté Ruby décrit souvent une méthode d'extension comme une sorte de « monkey patch » . Il existe également une fonctionnalité plus récente pour ajouter des extensions locales et sécurisées aux objets, appelée `refinements` , mais elle est moins utilisée.
En Rust , les méthodes d'extension n'existent pas. Cependant, pour les types définis par l'utilisateur, il est possible d'ajouter arbitrairement de nouvelles méthodes à l'aide de implblocs `for` ou impl Trait`for` à n'importe quel endroit. Il est interdit d'ajouter impldes blocs `for` à des types qui ne sont pas la propriété de l'utilisateur, ou si le trait, ou au moins l'un des types, n'est pas défini dans la crate courante (règle dite « orpheline »). Ceci permet d'éviter que deux crates n'implémentent le même trait pour le même type de manière incompatible.
En Swift, le extensionmot-clé marque une construction de type classe qui permet l'ajout de méthodes, de constructeurs et de champs à une classe existante, y compris la possibilité d'implémenter une nouvelle interface/un nouveau protocole à la classe existante.
Méthodes d'extension en tant que fonctionnalité activatrice
Outre leur fonction d'extension permettant d'étendre le code écrit par d'autres (comme décrit ci-dessous), les méthodes d'extension offrent également des fonctionnalités utiles en soi. L'introduction des méthodes d'extension est principalement due à LINQ ( Language Integrated Query ). La prise en charge des méthodes d'extension par le compilateur permet une intégration poussée de LINQ avec le code existant, au même titre qu'avec le nouveau, ainsi que la prise en charge d' une syntaxe de requête actuellement exclusive aux principaux langages .NET de Microsoft .
Centraliser les comportements communs
Cependant, les méthodes d'extension permettent d'implémenter des fonctionnalités une seule fois, de manière à permettre leur réutilisation sans avoir recours à l'héritage ni à la surcharge liée aux appels de méthodes virtuelles , et sans exiger des implémenteurs d'une interface qu'ils implémentent des fonctionnalités triviales ou extrêmement complexes.
Un scénario particulièrement utile est celui où la fonctionnalité opère sur une interface pour laquelle il n'existe pas d'implémentation concrète ou pour laquelle l'auteur de la bibliothèque de classes ne fournit pas d'implémentation utile, comme c'est souvent le cas dans les bibliothèques qui offrent aux développeurs une architecture de plugins ou une fonctionnalité similaire.
Considérons le code suivant et supposons qu'il soit le seul contenu d'une bibliothèque de classes. Néanmoins, tout développeur implémentant l'interface ILogger pourra écrire une chaîne formatée, simplement en incluant une instruction using MyCoolLogger , sans avoir à l'implémenter une seule fois ni à créer une sous-classe de l'implémentation de ILogger fournie par la bibliothèque.
utiliser comme :
Meilleur couplage lâche
Les méthodes d'extension permettent aux utilisateurs de bibliothèques de classes de s'abstenir de déclarer un argument, une variable ou tout autre élément d'un type issu de cette bibliothèque. La construction et la conversion des types utilisés dans la bibliothèque de classes peuvent être implémentées sous forme de méthodes d'extension. Après une implémentation soignée des conversions et des fabriques, le passage d'une bibliothèque de classes à une autre se résume à modifier l'instruction `using` qui rend les méthodes d'extension accessibles au compilateur.
Interfaces de programmation d'applications fluides
Les méthodes d'extension sont particulièrement utiles pour implémenter les interfaces dites fluides. L'API de configuration d'Entity Framework de Microsoft en est un exemple : elle permet notamment d'écrire du code aussi proche que possible de l'anglais courant.
On pourrait certes arguer que cela est tout aussi possible sans méthodes d'extension, mais on constatera qu'en pratique, les méthodes d'extension offrent une expérience supérieure car moins de contraintes sont imposées à la hiérarchie des classes pour qu'elle fonctionne – et se lise – comme souhaité.
L'exemple suivant utilise Entity Framework et configure la classe TodoList pour qu'elle soit stockée dans la table de base de données Lists. Il définit également une clé primaire et une clé étrangère . Le code peut être interprété comme suit : « Une TodoList possède une clé TodoListID, son ensemble d'entités est Lists et elle contient plusieurs TodoItem, chacun ayant une TodoList associée. »
Productivité
Prenons l'exemple d'IEnumerable et notons sa simplicité : une seule méthode constitue la base de LINQ. De nombreuses implémentations de cette interface existent dans Microsoft .NET. Il aurait été fastidieux d'exiger de chacune d'elles qu'elle implémente l'ensemble des méthodes définies dans l' espace de noms System.Linq pour manipuler les IEnumerable. Cela aurait contraint les développeurs autres que Microsoft à envisager d'utiliser IEnumerable pour implémenter également ces méthodes, ce qui aurait été inefficace compte tenu de l'utilisation répandue de cette interface. En implémentant cette unique méthode, LINQ est utilisable quasiment immédiatement. Dans la plupart des cas, la méthode GetEnumerator d'IEnumerable est déléguée à l'implémentation GetEnumerator d'une collection, d'une liste ou d'un tableau privé.
Performance
Cela dit, des implémentations supplémentaires d'une fonctionnalité fournie par une méthode d'extension peuvent être ajoutées pour améliorer les performances ou pour gérer des implémentations d'interface différentes, comme par exemple en fournissant au compilateur une implémentation IEnumerablespécifiquement pour les tableaux (dans System.SZArrayHelper), qu'il choisira automatiquement pour les appels de méthode d'extension sur les références de type tableau, puisque leur argument sera plus spécifique ( this T[] value) que la méthode d'extension du même nom qui opère sur les instances de l' IEnumerableinterface ( this IEnumerable value).
Réduire le besoin d'une classe de base commune
Avec les classes génériques, les méthodes d'extension permettent d'implémenter un comportement disponible pour toutes les instanciations du type générique, sans qu'elles aient besoin d'hériter d'une classe de base commune ni de restreindre les paramètres de type à une branche d'héritage spécifique. Ceci est avantageux car, dans les cas où cet argument est valable, une classe de base non générique doit implémenter la fonctionnalité partagée, ce qui oblige la sous-classe générique à effectuer des conversions de type (boxing et/ou casts) chaque fois que le type utilisé est un argument de type.
usage conservateur
Concernant la préférence accordée aux méthodes d'extension par rapport à d'autres moyens de réutiliser le code et de garantir une conception orientée objet optimale, ces méthodes peuvent encombrer les fonctionnalités de saisie semi-automatique des éditeurs de code, comme IntelliSense dans Visual Studio. Il est donc préférable de les définir dans un espace de noms dédié afin de permettre au développeur de les importer sélectivement. Alternativement, elles devraient être définies sur un type suffisamment spécifique pour que la méthode n'apparaisse dans IntelliSense que lorsqu'elle est absolument pertinente. Compte tenu de ce qui précède, il faut tenir compte du fait qu'elles peuvent être difficiles à trouver si le développeur s'y attend mais ne les voit pas dans IntelliSense en raison d'une instruction `using` manquante. En effet, le développeur peut avoir associé la méthode non pas à la classe qui la définit, ni même à l'espace de noms dans lequel elle se trouve, mais plutôt au type qu'elle étend et à l'espace de noms de ce type.
Le problème
En programmation, il arrive qu'il soit nécessaire d'ajouter des fonctionnalités à une classe existante, par exemple en ajoutant une nouvelle méthode. Normalement, le programmeur modifierait le code source de la classe , mais cela l'oblige à recompiler tous les binaires avec ces modifications et exige qu'il puisse modifier la classe elle-même, ce qui n'est pas toujours possible, notamment lors de l'utilisation de classes provenant d'un assembly tiers . On contourne généralement ce problème de trois manières, toutes plus ou moins limitées et peu intuitives :
- Héritez de la classe, puis implémentez la fonctionnalité dans une méthode d'instance de la classe dérivée.
- Implémentez la fonctionnalité dans une méthode statique ajoutée à une classe d'assistance .
- Utilisez l'agrégation plutôt que l'héritage .
Solutions C# actuelles
La première option est en principe plus simple, mais elle est limitée car de nombreuses classes restreignent l'héritage de certains membres, voire l'interdisent totalement. C'est le cas notamment des classes scellées et des différents types primitifs de C# tels que ` int` , `float` et `string` . Une autre option ne présente pas ces restrictions, mais elle peut s'avérer moins intuitive car elle nécessite une référence à une classe distincte au lieu d'utiliser directement les méthodes de la classe en question.
À titre d'exemple, considérons le besoin d'étendre la classe `string` avec une nouvelle méthode `reverse` dont la valeur de retour est une chaîne de caractères inversée. La classe `string` étant un type scellé, la méthode serait généralement ajoutée à une nouvelle ; chaîne y = Utility . Reverse ( x );