Tutoriel: Mettre à jour les interfaces avec les membres d'interface par défaut dans C # 8.0

À partir de C # 8.0 sur .NET Core 3.0, vous pouvez définir une implémentation lorsque vous déclarez un membre d'une interface. 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 didacticiel, vous apprendrez à:


  • Étendez les interfaces en toute sécurité en ajoutant des méthodes avec des implémentations.
  • Créez des implémentations paramétrées pour offrir une plus grande flexibilité.
  • Permet aux implémenteurs de fournir une implémentation plus spécifique sous la forme d'un remplacement.



Prérequis


Vous devrez configurer votre machine pour exécuter .NET Core, y compris le compilateur de prévisualisation C # 8.0. Le compilateur d'aperçu C # 8.0 est disponible à partir de Visual Studio 2019 ou du dernier SDK d'aperçu .NET Core 3.0 . Les membres de l'interface par défaut sont disponibles à partir de l'aperçu 4 de .NET Core 3.0.


Présentation du scénario


Ce didacticiel commence par la version 1 d'une bibliothèque de relations client. Vous pouvez obtenir l'application de démarrage sur notre dépôt d'échantillons sur GitHub . L'entreprise qui a construit cette bibliothèque a souhaité que les clients avec des applications existantes adoptent leur bibliothèque.Ils ont fourni des définitions d'interface minimales pour les utilisateurs de leur bibliothèque à mettre en œuvre.Voici la définition d'interface pour un client:


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

Ils ont défini une deuxième interface qui représente une commande:


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

À partir de ces interfaces, l'équipe pourrait créer une bibliothèque pour leurs utilisateurs afin de créer une meilleure expérience pour leurs clients. Leur objectif était de créer une relation plus profonde avec les clients existants et d'améliorer leurs relations avec les nouveaux clients.


Il est maintenant temps de mettre à niveau la bibliothèque pour la prochaine version. L'une des fonctionnalités demandées permet une remise de fidélité pour les clients qui ont beaucoup de commandes. Cette nouvelle remise de fidélité est appliquée chaque fois qu'un client passe une commande. La remise spécifique est la propriété de chaque client individuel. Chaque implémentation de ICustomer peut définir des règles différentes pour la remise de fidélité.


La façon la plus naturelle d'ajouter cette fonctionnalité est d'améliorer l'interface ICustomer avec une méthode pour appliquer toute remise de fidélité. Cette suggestion de conception a suscité des inquiétudes parmi les développeurs expérimentés: "Les interfaces sont immuables une fois qu'elles ont été publiées! C'est un changement de rupture! ” C # 8.0 ajoute des implémentations d'interface par défaut pour la mise à niveau des interfaces. Les auteurs de la bibliothèque peuvent ajouter de nouveaux membres à l'interface et fournir une implémentation par défaut pour ces membres.


Les implémentations d'interface par défaut permettent aux développeurs de mettre à niveau une interface tout en permettant à tous les implémenteurs de remplacer cette implémentation. Les utilisateurs de la bibliothèque peuvent accepter l'implémentation par défaut comme une modification incessante. Si leurs règles métier sont différentes, elles peuvent remplacer.


Mettre à niveau avec les membres de l'interface par défaut


L'équipe s'est mise d'accord sur l'implémentation par défaut la plus probable: une remise de fidélité pour les clients.


La mise à niveau devrait fournir la fonctionnalité permettant de définir deux propriétés: le nombre de commandes nécessaires pour être éligible à la remise et le pourcentage de la remise, ce qui en fait un scénario parfait pour les membres de l'interface par défaut. Vous pouvez ajouter une méthode à l'interface ICustomer et fournir l'implémentation la plus probable. Toutes les implémentations existantes et nouvelles peuvent utiliser l'implémentation par défaut ou fournir la leur.


Tout d'abord, ajoutez la nouvelle méthode à l'implémentation:


 // Version 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 un 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); // Check the discount: ICustomer theCustomer = c; Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}"); 

Remarquez la partie suivante du test:


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

Cette SampleCustomer de SampleCustomer en ICustomer est nécessaire. La classe SampleCustomer n'a pas besoin de fournir une implémentation pour ComputeLoyaltyDiscount ; qui 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 déclarée et implémentée dans l'interface, la variable doit être le type de l'interface, ICustomer dans cet exemple.


Fournir le paramétrage


Voilà un bon début. Mais l'implémentation par défaut est trop restrictive. De nombreux consommateurs de ce système peuvent choisir différents seuils pour le nombre d'achats, une durée d'adhésion différente ou un pourcentage de remise différent. Vous pouvez offrir une meilleure expérience de mise à niveau à 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 contrôlant l'implémentation par défaut:


 // Version 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 fragment de code offre de nombreuses nouvelles capacités linguistiques. Les interfaces peuvent désormais inclure des membres statiques, y compris des champs et des méthodes. Différents modificateurs d'accès sont également activés. Les champs supplémentaires sont privés, la nouvelle méthode est publique. Tous les modificateurs sont autorisés sur les membres de l'interface.


Les applications qui utilisent la formule générale pour calculer la remise de fidélité, mais différents paramètres, n'ont pas besoin de fournir une implémentation personnalisée; ils peuvent définir les arguments via une méthode statique. Par exemple, le code suivant définit une «appréciation client» qui récompense tout client ayant plus d'un mois d'adhésion:


 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é jusqu'à présent a fourni une implémentation pratique pour les scénarios où les utilisateurs veulent quelque chose comme l'implémentation par défaut, ou pour fournir un ensemble de règles sans rapport. Pour une dernière fonctionnalité, refactorisons un peu le code pour activer 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 un rabais de 50% sur la première commande d'un nouveau client. Sinon, les clients existants bénéficient de la remise standard. L'auteur de la bibliothèque doit déplacer l'implémentation par défaut dans une méthode protected static afin que toute classe implémentant cette interface puisse réutiliser le code dans son implémentation. L'implémentation par défaut du membre d'interface appelle également cette méthode partagée:


 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 une implémentation d'une classe qui implémente cette interface, le remplacement peut appeler la méthode d'assistance statique et étendre cette logique pour fournir la remise "nouveau client":


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

Vous pouvez voir l'intégralité du code fini dans notre [dépôt d'échantillons sur GitHub] (Vous pouvez obtenir l'application de démarrage sur notre dépôt d'échantillons sur GitHub .


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

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


All Articles