J'ai récemment vu une base de code qui avait une classe de données Address
définie quelque part puis à un autre endroit:
fun Address.toAnschrift() = let { address ->
Anschrift().apply {
// mapping code here...
}
}
J'ai trouvé confus de ne pas avoir cette méthode directement sur l'adresse. Existe-t-il des modèles établis ou des meilleures pratiques quant à l'utilisation des méthodes d'extension? Ou le livre à ce sujet doit-il encore être écrit?
Notez que malgré la syntaxe Kotlin dans mon exemple, je suis intéressé par les meilleures pratiques générales car cela s'appliquerait également à C # ou à d'autres langages avec ces capacités.
Les méthodes d'extension ne fournissent pas quelque chose qui ne peut pas être fait autrement. Ce sont des sucres syntaxiques pour rendre le développement plus agréable, sans apporter d'avantages techniques réels.
Les méthodes d'extension sont relativement nouvelles. Les normes et les conventions sont généralement décidées par l'opinion populaire (plus une expérience à long terme sur ce qui finit par être une mauvaise idée), et je ne pense pas que nous soyons au point où nous pouvons tracer une ligne définitive avec laquelle tout le monde est d'accord.
Cependant, je peux voir plusieurs arguments, basés sur des choses que j'ai entendues de collègues.
1. Ils remplacent généralement les méthodes statiques.
Les méthodes d'extension sont le plus souvent basées sur des choses qui auraient autrement été des méthodes statiques, pas des méthodes de classe. Elles ressemblent aux méthodes de classe , mais elles ne le sont pas vraiment.
Les méthodes d'extension ne doivent tout simplement pas être considérées comme une alternative aux méthodes de classe; mais plutôt comme un moyen de donner aux méthodes autrement ugle statiques une approche "classe méthodique".
2. Lorsque votre méthode prévue est spécifique à un projet consommateur mais pas à la bibliothèque source.
Ce n'est pas parce que vous développez à la fois la bibliothèque et le consommateur que vous êtes prêt à mettre la logique là où elle convient.
Par exemple:
PersonDto
provenant de votre projet DataLayer
.WebUI
souhaite pouvoir convertir un PersonDto
en PersonViewModel
.Vous ne pouvez pas (et ne voudriez pas) ajouter cette méthode de conversion à votre projet DataLayer
. Étant donné que cette méthode est étendue au projet WebUI
, elle doit vivre à l'intérieur de ce projet.
Une méthode d'extension vous permet d'avoir cette méthode accessible globalement dans votre projet (et les consommateurs potentiels de votre projet), sans nécessiter la bibliothèque DataLayer
pour l'implémenter.
. Si vous pensez que les classes de données ne doivent contenir que des données.
Par exemple, votre classe Person
contient les propriétés d'une personne. Mais vous voulez pouvoir disposer de quelques méthodes pour formater certaines des données:
public class Person
{
//The properties...
public SecurityQuestionAnswers GetSecurityAnswers()
{
return new SecurityQuestionAnswers()
{
MothersMaidenName = this.Mother.MaidenName,
FirstPetsName = this.Pets.OrderbyDescending(x => x.Date).FirstOrDefault()?.Name
};
}
}
J'ai vu beaucoup de collègues qui détestent l'idée de mélanger les données et la logique dans une classe. Je ne suis pas tout à fait d'accord avec des choses simples comme le formatage des données, mais je concède que dans l'exemple ci-dessus, il semble sale pour Person
de devoir dépendre de SecurityQuestionAnswers
.
Placer cette méthode dans une méthode d'extension empêche d'entacher la classe de données par ailleurs pure.
Notez que cet argument est de style. Ceci est similaire aux classes partielles. Il n'y a pas ou peu d'avantages techniques à le faire, mais cela vous permet de séparer le code en plusieurs fichiers si vous le jugez plus propre (par exemple, si de nombreuses personnes utilisent la classe mais ne se soucient pas de vos méthodes personnalisées supplémentaires).
4. Méthodes d'assistance
Dans la plupart des projets, j'ai tendance à me retrouver avec des cours d'aide. Ce sont des classes statiques qui fournissent généralement une collection de méthodes pour un formatage facile.
Par exemple. une entreprise dans laquelle je travaillais avait un format datetime particulier qu'elle voulait utiliser. Plutôt que d'avoir à coller la chaîne de format partout, ou de faire de la chaîne de format une variable globale, j'ai opté pour une méthode d'extension DateTime:
public static string ToCompanyFormat(this DateTime dt)
{
return dt.ToString("...");
}
Est-ce mieux qu'une méthode d'assistance statique normale? Je le pense. Il nettoie la syntaxe. Au lieu de
DateTimeHelper.ToCompanyFormat(myObj.CreatedOn);
Je pourrais faire:
myObj.CreatedOn.ToCompanyFormat();
Ceci est similaire à une version courante de la même syntaxe. Je l'aime plus, même s'il n'y a aucun avantage technique à avoir l'un sur l'autre.
Tous ces arguments sont subjectifs. Aucun d'entre eux ne couvre un cas qui, autrement, ne pourrait jamais être couvert.
Mais là encore, nous pouvons prendre votre affirmation selon laquelle elles ne sont jamais nécessaires à l'extrême:
La réponse à cette question et à la vôtre reste la même:
Une mention supplémentaire particulière cependant:
Je suis d'accord avec Flater qu'il n'y a pas eu assez de temps pour que la convention se cristallise, mais nous avons l'exemple des bibliothèques de classes .NET Framework elles-mêmes. Considérez que lorsque des méthodes LINQ ont été ajoutées à .NET, elles n'ont pas été ajoutées directement sur IEnumerable
, mais en tant que méthodes d'extension.
Que pouvons-nous en tirer? LINQ est
A.B().C()
au lieu de C(B(A))
), etSi vous disposez de ces trois fonctionnalités, les méthodes d'extension ont beaucoup de sens. En fait, je dirais que si un ensemble de méthodes a la fonctionnalité n ° 3 et l'une des deux premières fonctionnalités, vous devriez envisager d'en faire des méthodes d'extension.
Méthodes d'extension:
null
car une méthode d'extension appelée à partir d'une valeur de null
reçoit simplement le null
comme premier argument,Les méthodes d'extension ont un autre avantage: elles peuvent toujours être utilisées comme méthode statique et peuvent être transmises directement en tant que valeur à une fonction d'ordre supérieur. Donc, si MyMethod
est une méthode d'extension, au lieu de dire myList.Select(x => x.MyMethod())
, vous pouvez simplement utiliser myList.Select(MyMethod)
. Je trouve ça plus agréable.
La force motrice derrière les méthodes d'extension est la possibilité d'ajouter des fonctionnalités dont vous avez besoin à toutes les classes ou interfaces d'un certain type, qu'elles soient scellées ou dans un autre assembly. Vous pouvez en voir la preuve avec le code LINQ, en ajoutant des fonctions à tous les IEnumerable<T>
objets, qu'il s'agisse de listes ou de piles, etc. Le concept était de pérenniser les fonctions de sorte que si vous aviez votre propre IEnumerable<T>
vous pouvez utiliser automatiquement LINQ avec.
Cela dit, la fonction de langue est suffisamment nouvelle pour que vous n'ayez aucune directive sur le moment de les utiliser ou de ne pas les utiliser. Cependant, ils permettent un certain nombre de choses auparavant impossibles:
Pour être juste, seul le temps dira dans quelle mesure est trop loin, ou à quel point les méthodes d'extension deviennent un obstacle plutôt qu'une aubaine. Comme mentionné dans une autre réponse, il n'y a rien dans une méthode d'extension qui ne peut pas être fait avec une méthode statique. Cependant, lorsqu'ils sont utilisés judicieusement, ils peuvent rendre le code plus facile à lire.
Qu'en est-il de l'utilisation intentionnelle de méthodes d'extensions dans le même assembly?
Je pense qu'il est utile d'avoir des fonctionnalités de base bien testées et fiables, et de ne pas jouer avec jusqu'à ce que vous deviez absolument le faire. Nouveau code qui en dépend, mais qui en a besoin pour fournir des fonctionnalités uniquement au profit du nouveau code peut utiliser des méthodes d'extension pour ajouter des fonctionnalités pour prendre en charge le nouveau code. Ces fonctions n'ont d'intérêt que pour le nouveau code, et la portée des méthodes d'extension est limitée à l'espace de noms dans lequel elles sont déclarées.
Je n'écarterais pas aveuglément l'utilisation de méthodes d'extension, mais je considérerais si la nouvelle fonctionnalité devrait vraiment être ajoutée au code de base existant qui est bien testé.
Existe-t-il des modèles établis ou des meilleures pratiques quant à l'utilisation des méthodes d'extension?
L'une des raisons les plus courantes d'utiliser des méthodes d'extension est comme un moyen "d'étendre, pas de modifier" les interfaces. Plutôt que d'avoir IFoo
et IFoo2
, où ce dernier introduit une nouvelle méthode, Bar
, Bar
est ajouté à IFoo
via une méthode d'extension.
L'une des raisons pour lesquelles Linq utilise des méthodes d'extension pour Where
, Select
etc est afin de permettre aux utilisateurs de définir leurs propres versions de ces méthodes, qui sont ensuite également utilisées par la syntaxe de requête Linq.
Au-delà de cela, l'approche que j'utilise, qui semble correspondre à ce que je vois couramment dans le framework .NET au moins, est:
Donc, si j'ai un Option<T>
, Je mettrais des choses comme HasValue
, Value
, ==
, etc. dans le type lui-même. Mais quelque chose comme une fonction Map
, qui convertit la monade de T1
à T2
, Je mettrais une méthode d'extension:
public static Option<TOutput> Map<TInput, TOutput>(this Option<TInput> input,
Func<TInput, TOutput> f) => ...