Guide: mise à jour des interfaces avec les membres par défaut dans C # 8.0

À partir de C # 8.0 sur .NET Core 3.0, lors de la création d'un membre d'une interface, vous pouvez déterminer son implémentation. Le scénario le plus courant consiste à ajouter en toute sécurité des membres à une interface déjà publiée et utilisée par d'innombrables clients.

Dans ce guide, vous apprendrez à:


  • Il est sûr d'étendre les interfaces en ajoutant des méthodes avec des implémentations.
  • Créez des implémentations paramétrées pour une plus grande flexibilité.
  • Obtenir le droit d'implémenter des implémentations plus spécifiques avec la possibilité d'un contrôle manuel.



Par où commencer?


Vous devez d'abord configurer la machine pour qu'elle fonctionne avec .NET Core, y compris le compilateur de l'aperçu C # 8.0. Un tel compilateur est disponible à partir de Visual Studio 2019 ou du plus récent SDK d'aperçu .NET Core 3.0 . Les membres de l'interface par défaut sont disponibles à partir de .NET Core 3.0 (aperçu 4).


Présentation du scénario


Ce didacticiel commence par la première version de la bibliothèque de relations clients. Vous pouvez obtenir l'application de démarrage dans notre référentiel sur GitHub . L'entreprise qui a créé cette bibliothèque a supposé que les clients avec des applications existantes les adapteraient à cette bibliothèque. Les utilisateurs ont été présentés avec les définitions d'interface minimales pour la mise en œuvre:


public interface ICustomer { IEnumerable<IOrder> PreviousOrders { get; } DateTime DateJoined { get; } DateTime? LastOrder { get; } string Name { get; } IDictionary<DateTime, string> Reminders { get; } } 

Une deuxième interface a également été définie qui montre l'ordre:


 public interface IOrder { DateTime Purchased { get; } decimal Cost { get; } } 

Sur la base de ces interfaces, l'équipe peut créer une bibliothèque pour ses utilisateurs afin de créer la meilleure expérience pour les clients. L'objectif de l'équipe était d'augmenter le niveau d'interaction avec les clients existants et de développer des relations avec de nouveaux clients.


Il est temps de mettre à jour la bibliothèque pour la prochaine version. L'une des fonctionnalités les plus populaires est l'ajout de remises aux clients fidèles qui passent un grand nombre de commandes. Cette nouvelle remise individuelle est appliquée chaque fois qu'un client passe une commande. Pour chaque implémentation de ICustomer, différentes règles de remise fidélité peuvent être définies.

La façon la plus pratique d'ajouter cette fonctionnalité est d'étendre l'interface ICustomer avec des remises. Cette proposition a inquiété les développeurs expérimentés. «Les interfaces sont immuables après la sortie! C'est un changement critique! » Dans C # 8.0, des implémentations d'interface par défaut pour la mise à jour des interfaces ont été ajoutées. Les auteurs de bibliothèque peuvent ajouter de nouveaux membres et les implémenter par défaut


L'implémentation par défaut des interfaces permet aux développeurs de mettre à jour l'interface, tout en permettant aux autres développeurs de remplacer cette implémentation. Les utilisateurs de bibliothèque peuvent accepter l'implémentation par défaut comme une modification non critique.


Mettre à jour à l'aide des membres de l'interface par défaut


L'équipe a approuvé l'implémentation par défaut la plus probable: une remise sur la fidélité des clients.


La mise à jour doit être fonctionnelle pour définir deux propriétés: le nombre de commandes nécessaires pour bénéficier d'une remise et le pourcentage de la remise. Cela en fait un script idéal pour les membres de l'interface par défaut. Vous pouvez ajouter une méthode à l'interface ICustomer et fournir son implémentation la plus probable. Toutes les implémentations existantes et nouvelles peuvent être implémentées par défaut ou avoir leurs propres paramètres.


Ajoutez d'abord une nouvelle méthode à l'implémentation:


 //  1: public decimal ComputeLoyaltyDiscount() { DateTime TwoYearsAgo = DateTime.Now.AddYears(-2); if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10)) { return 0.10m; } return 0; } 

L'auteur de la bibliothèque a écrit le premier test pour vérifier l'implémentation:


 SampleCustomer c = new SampleCustomer("customer one", new DateTime(2010, 5, 31)) { Reminders = { { new DateTime(2010, 08, 12), "childs's birthday" }, { new DateTime(1012, 11, 15), "anniversary" } } }; SampleOrder o = new SampleOrder(new DateTime(2012, 6, 1), 5m); c.AddOrder(o); o = new SampleOrder(new DateTime(2103, 7, 4), 25m); c.AddOrder(o); //  : ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Faites attention à la partie suivante du test:


 //  : ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Cette partie, de SampleCustomer à ICustomer est importante. La classe SampleCustomer ne SampleCustomer pas fournir d'implémentation pour ComputeLoyaltyDiscount ; cela est fourni par l'interface ICustomer . Cependant, la classe SampleCustomer n'hérite pas des membres de ses interfaces. Cette règle n'a pas changé. Pour appeler une méthode implémentée dans une interface, la variable doit être un type d'interface, dans cet exemple ICustomer .


Paramétrisation


C’est un bon début. Mais l'implémentation par défaut est trop limitée. De nombreux consommateurs de ce système peuvent choisir différents seuils pour le nombre d'achats, différentes durées d'adhésion ou différentes remises en pourcentage. Vous pouvez améliorer le processus de mise à niveau pour davantage de clients en fournissant un moyen de définir ces paramètres. Ajoutons une méthode statique qui définit ces trois paramètres qui contrôlent l'implémentation par défaut:


 //  2: public static void SetLoyaltyThresholds( TimeSpan ago, int minimumOrders = 10, decimal percentageDiscount = 0.10m) { length = ago; orderCount = minimumOrders; discountPercent = percentageDiscount; } private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years private static int orderCount = 10; private static decimal discountPercent = 0.10m; public decimal ComputeLoyaltyDiscount() { DateTime start = DateTime.Now - length; if ((DateJoined < start) && (PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; } 

Ce petit morceau de code montre beaucoup de nouvelles fonctionnalités de langage. Les interfaces peuvent désormais inclure des membres statiques, y compris des champs et des méthodes. Divers modificateurs d'accès sont également inclus. Les champs supplémentaires sont privés et la nouvelle méthode est publique. Tous les modificateurs sont autorisés pour les membres de l'interface.


Les applications qui utilisent la formule générale pour calculer les remises de fidélité, mais avec des paramètres différents, ne doivent pas fournir une implémentation personnalisée; ils peuvent définir des arguments par la méthode statique. Par exemple, le code suivant configure «l'appréciation du client», qui récompense tout client avec un abonnement de plus d'un mois:


 ICustomer.SetLoyaltyThresholds(new TimeSpan(30, 0, 0, 0), 1, 0.25m); Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Étendre l'implémentation par défaut


Le code que vous avez ajouté précédemment fournissait une implémentation pratique pour les scénarios où les utilisateurs veulent quelque chose comme l'implémentation par défaut ou fournissent un ensemble de règles sans rapport. Pour la version finale, réorganisons un peu le code pour inclure des scénarios dans lesquels les utilisateurs peuvent vouloir s'appuyer sur l'implémentation par défaut.


Considérez une startup qui veut attirer de nouveaux clients. Ils offrent une remise de 50% sur la première commande d'un nouveau client. Les clients existants bénéficient d'une remise standard. L'auteur de la bibliothèque doit déplacer l'implémentation par défaut vers la méthode protected static afin que toute classe qui implémente cette interface puisse réutiliser le code dans son implémentation. L'implémentation par défaut d'un membre d'interface appelle également cette méthode générique:


 public decimal ComputeLoyaltyDiscount() => DefaultLoyaltyDiscount(this); protected static decimal DefaultLoyaltyDiscount(ICustomer c) { DateTime start = DateTime.Now - length; if ((c.DateJoined < start) && (c.PreviousOrders.Count() > orderCount)) { return discountPercent; } return 0; } 

Dans l'implémentation de la classe qui implémente cette interface, vous pouvez appeler manuellement la méthode d'assistance statique et étendre cette logique pour fournir une remise au «nouveau client»:


 public decimal ComputeLoyaltyDiscount() { if (PreviousOrders.Any() == false) return 0.50m; else return ICustomer.DefaultLoyaltyDiscount(this); } 

Vous pouvez voir tout le code fini dans notre référentiel sur GitHub .


Ces nouvelles fonctionnalités signifient que les interfaces peuvent être mises à jour en toute sécurité s'il existe une implémentation par défaut acceptable pour les nouveaux membres. Concevez soigneusement les interfaces pour exprimer des idées fonctionnelles individuelles qui peuvent être mises en œuvre par plusieurs classes. Cela facilite la mise à jour de ces définitions d'interface lorsque de nouvelles exigences sont trouvées pour la même idée fonctionnelle.

Source: https://habr.com/ru/post/fr456232/


All Articles