Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Vous pouvez définir une implémentation quand 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 des clients innombrables.
Dans ce tutoriel, vous apprendrez comment le faire :
- Étendez les interfaces en toute sécurité en ajoutant des méthodes avec des implémentations.
- Créez des implémentations paramétrables pour offrir une plus grande flexibilité.
- Permettre aux implémenteurs de fournir une implémentation plus spécifique sous la forme d’un remplacement.
Conditions préalables
Vous devez configurer votre machine pour qu’elle exécute .NET, y compris le compilateur C#. Le compilateur C# est disponible avec Visual Studio 2022 ou le Kit de développement logiciel (SDK) .NET.
Vue d’ensemble du scénario
Ce tutoriel 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’exemples sur GitHub. L’entreprise qui a créé cette bibliothèque a prévu que les clients disposant d’applications existantes adoptent leur bibliothèque. Ils ont fourni des définitions d’interface minimales pour que les utilisateurs de leur bibliothèque implémentent. Voici la définition de l’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 un ordre :
public interface IOrder
{
DateTime Purchased { get; }
decimal Cost { get; }
}
À partir de ces interfaces, l’équipe peut créer une bibliothèque pour ses utilisateurs afin de créer une meilleure expérience pour leurs clients. Leur objectif était de créer une relation plus approfondie avec les clients existants et d’améliorer leurs relations avec de nouveaux clients.
Maintenant, il est 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 effectue une commande. La remise spécifique est une 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é consiste à améliorer l’interface ICustomer
avec une méthode pour appliquer toute remise de fidélité. Cette suggestion de conception a provoqué des préoccupations chez les développeurs expérimentés : « Les interfaces sont immuables une fois qu’elles ont été publiées ! N’apportez pas de changement cassant ! Vous devez utiliser les implémentations d’interface par défaut pour mettre à niveau des interfaces. Les auteurs de 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 aux implémenteurs de remplacer cette implémentation. Les utilisateurs de la bibliothèque peuvent accepter l'implémentation par défaut comme un changement sans rupture. Si leurs règles d’entreprise sont différentes, elles peuvent passer outre.
Mettre à niveau avec les méthodes d’interface par défaut
L’équipe a convenu de l’implémentation par défaut la plus probable : une remise de fidélité pour les clients.
La mise à niveau doit fournir la fonctionnalité permettant de définir deux propriétés : le nombre de commandes nécessaires pour bénéficier de la remise et le pourcentage de la remise. Ces fonctionnalités constituent un scénario parfait pour les méthodes d’interface par défaut. Vous pouvez ajouter une méthode à l’interface ICustomer
et fournir l’implémentation la plus probable. Toutes les nouvelles implémentations existantes et toutes les nouvelles implémentations peuvent utiliser l’implémentation par défaut ou fournir leur propre implémentation.
Tout d’abord, ajoutez la nouvelle méthode à l’interface, y compris le corps de la méthode :
// 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()}");
Notez la partie suivante du test :
// Check the discount:
ICustomer theCustomer = c;
Console.WriteLine($"Current discount: {theCustomer.ComputeLoyaltyDiscount()}");
La conversion de SampleCustomer
vers ICustomer
est nécessaire. La classe SampleCustomer
n'a pas besoin de fournir une implémentation pour ComputeLoyaltyDiscount
; celle-ci est fournie par l'interface ICustomer
. Toutefois, la SampleCustomer
classe 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 un paramétrage
L’implémentation par défaut est trop restrictive. De nombreux consommateurs de ce système peuvent choisir des seuils différents pour le nombre d’achats, une longueur différente d’appartenance ou une remise en pourcentage différente. Vous pouvez offrir une meilleure expérience de mise à niveau pour plus 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;
}
Il existe de nombreuses nouvelles fonctionnalités de langage indiquées dans ce petit fragment de code. 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 autres champs sont privés, la nouvelle méthode est publique. Tout modificateur est autorisé sur les membres d’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 par le biais d’une méthode statique. Par exemple, le code suivant définit une « appréciation du 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 ces scénarios où les utilisateurs veulent quelque chose comme l’implémentation par défaut ou pour fournir un ensemble de règles non lié. Pour une fonctionnalité finale, nous allons refactoriser le code un peu pour activer les scénarios où les utilisateurs souhaitent peut-être créer sur l’implémentation par défaut.
Imaginez une startup qui souhaite attirer de nouveaux clients. Ils offrent une remise de 50% sur la première commande d’un nouveau client. Sinon, les clients existants obtiennent la remise standard. L’auteur de la bibliothèque doit déplacer l’implémentation par défaut dans une protected static
méthode 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 de l’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, la redéfinition 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 terminé dans notre dépôt d’exemples sur GitHub. Vous pouvez obtenir l’application de démarrage sur notre dépôt d’exemples sur GitHub.
Ces nouvelles fonctionnalités signifient que les interfaces peuvent être mises à jour en toute sécurité lorsqu’il existe une implémentation par défaut raisonnable pour ces nouveaux membres. Concevez soigneusement des interfaces pour exprimer des idées fonctionnelles uniques 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.