Remarque
L’accès à cette page requiert une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page requiert une autorisation. Vous pouvez essayer de modifier des répertoires.
Le filtrage par motif vous permet d’ajouter des fonctionnalités pour les types définis dans d’autres bibliothèques sans modifier ces types. Une autre utilisation des modèles consiste à créer des fonctionnalités requises par une application qui ne sont pas essentielles pour le type étendu.
Dans ce tutoriel, vous allez apprendre à :
- Reconnaître les situations dans lesquelles la correspondance de motifs devrait être utilisée.
- Utiliser des expressions de correspondance de motifs pour mettre en œuvre des comportements en fonction des types et des valeurs de propriétés.
- Combiner la correspondance de motifs avec d'autres techniques pour créer des algorithmes complets.
Prerequisites
- Le plus récent .NET SDK
- éditeur Visual Studio Code
- Le DevKit C#
Instructions d’installation
Sur Windows, ce fichier de configuration WinGet est utilisé pour installer tous les prérequis. Si vous avez déjà installé quelque chose, WinGet ignore cette étape.
- Téléchargez le fichier et double-cliquez pour l’exécuter.
- Lisez le contrat de licence, tapez et, puis sélectionnez Entrer lorsque vous êtes invité à accepter.
- Si vous recevez une invite de contrôle de compte d’utilisateur (UAC) qui clignote dans votre barre des tâches, autorisez la poursuite de l'installation.
Sur d’autres plateformes, vous devez installer chacun de ces composants séparément.
- Téléchargez le programme d’installation recommandé à partir de la page de téléchargement du Kit de développement logiciel (SDK) .NET et double-cliquez pour l’exécuter. La page de téléchargement détecte votre plateforme et recommande le dernier programme d’installation de votre plateforme.
- Téléchargez le dernier programme d’installation à partir de la page d’accueil Visual Studio Code et double-cliquez pour l’exécuter. Cette page détecte également votre plateforme et le lien doit être correct pour votre système.
- Cliquez sur le bouton « Installer » dans la page d’extension devKit C#. Cela ouvre Visual Studio Code et vous demande si vous souhaitez installer ou activer l’extension. Sélectionnez « installer ».
Scénarios de correspondance de motifs
Le développement moderne passe souvent par l’intégration de données provenant de plusieurs sources et par la présentation d’informations et d’insights tirés de ces données dans une seule et même application. Vous et votre équipe ne contrôlez peut-être pas les types qui représentent les données entrantes.
L’approche de conception orientée objet classique préconise de créer des types dans votre application qui représentent chaque type de données de ces multiples sources de données. Ensuite, votre application fonctionne avec ces nouveaux types, génère des hiérarchies d’héritage, crée des méthodes virtuelles et implémente des abstractions. Ces techniques fonctionnent et représentent parfois les meilleurs outils disponibles. Il est possible dans d’autres cas d’écrire du code moins long Dans certains cas, la séparation des données des opérations qui les utilisent peut faciliter la lecture du code.
Dans ce tutoriel, vous allez créer et explorer une application qui accepte des données entrantes à partir de plusieurs sources externes pour un scénario unique. Vous voyez comment filtrage par motif offre un moyen efficace de consommer et de traiter ces données d’une manière qui ne faisait pas partie du système d’origine.
Considérez une région métropolitaine majeure qui utilise les péages et la tarification aux heures de pointe pour gérer le trafic. Nous allons écrire une application qui calcule le péage en fonction du type de véhicule. Lors d’améliorations ultérieures, nous intégrerons aux tarifs le nombre de passagers du véhicule D'autres améliorations ajoutent une tarification basée sur l'heure et le jour de la semaine.
À partir de cette brève description, vous pouvez rapidement dessiner une hiérarchie d’objets pour modéliser ce système. Toutefois, vos données proviennent de plusieurs sources telles que d’autres systèmes de gestion de l’immatriculation des véhicules. Ces systèmes fournissent différentes classes pour modéliser ces données, et ces systèmes ne partagent pas de modèle objet unique. Dans ce tutoriel, vous utilisez ces classes simplifiées pour modéliser les données du véhicule à partir de ces systèmes externes, comme indiqué dans le code suivant :
namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}
namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}
namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}
public class Bus
{
public int Capacity { get; set; }
public int Riders { get; set; }
}
}
Vous pouvez télécharger l’exemple de démarrage à partir du référentiel GitHub dotnet/samples. Vous pouvez voir que les classes de véhicules proviennent de différents systèmes et se trouvent dans différents espaces de noms. Vous ne pouvez pas utiliser une classe de base commune autre que System.Object.
Conceptions basées sur la correspondance de motifs
Le scénario utilisé dans ce didacticiel met en évidence les types de problèmes que la correspondance de motifs résout bien :
- Les objets dont vous avez besoin ne figurent pas dans une hiérarchie d’objets correspondant à vos objectifs. Vous travaillez peut-être avec des classes qui font partie de systèmes non liés.
- Les fonctionnalités ajoutées ne font pas partie de l’abstraction de base de ces classes. Le péage payé change selon les types de véhicules, mais il ne s’agit pas d’une fonction essentielle du véhicule.
Lorsque la forme des données et les opérations effectuées sur celles-ci ne sont pas décrites ensemble, les fonctionnalités de critères spéciaux de C# facilitent la tâche.
Implémenter les calculs de péage de base
Le calcul de péage le plus élémentaire s’appuie uniquement sur le type de véhicule :
-
Car: 2,00 $. -
Taxi: 3,50 $. -
Bus: 5,00 $. - Un
DeliveryTruckcoûte 10,00 $.
Créez une nouvelle classe TollCalculator et implémentez la correspondance de motifs sur le type de véhicule pour obtenir le montant du péage. Le code suivant montre l’implémentation initiale de TollCalculator.
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace Calculators;
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
Le code précédent utilise une expression switch (différente d’une instructionswitch) qui teste le modèle de déclaration. Une expression switch commence par la variable, vehicle dans le code précédent, suivie du mot clé switch. Viennent ensuite toutes les branches de commutation entre accolades. L’expression switch ajoute d’autres perfectionnements à la syntaxe qui entoure l’instruction switch. Le mot clé case est omis, et le résultat de chaque branche est une expression. Les deux derniers bras montrent une nouvelle fonctionnalité du langage. Le cas { } représente n’importe quel objet non Null ne correspondant à aucune des branches antérieures. Ce mécanisme intercepte tous les types incorrects passés à cette méthode. Le cas { } doit suivre les cas de chaque type de véhicules. Si l’ordre était inversé, le cas { } serait prioritaire. Enfin, le nullmotif constant détecte quand null est transmis à cette méthode. Le modèle null peut être en dernier car les autres modèles correspondent uniquement à un objet non null du type correct.
Vous pouvez tester ce code à l’aide du code suivant dans Program.cs :
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
using toll_calculator;
var tollCalc = new TollCalculator();
var car = new Car();
var taxi = new Taxi();
var bus = new Bus();
var truck = new DeliveryTruck();
Console.WriteLine($"The toll for a car is {tollCalc.CalculateToll(car)}");
Console.WriteLine($"The toll for a taxi is {tollCalc.CalculateToll(taxi)}");
Console.WriteLine($"The toll for a bus is {tollCalc.CalculateToll(bus)}");
Console.WriteLine($"The toll for a truck is {tollCalc.CalculateToll(truck)}");
try
{
tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
Console.WriteLine("Caught an argument exception when using the wrong type");
}
try
{
tollCalc.CalculateToll(null!);
}
catch (ArgumentNullException e)
{
Console.WriteLine("Caught an argument exception when using null");
}
Ce code est inclus dans le projet de démarrage, mais il est mis en commentaire. Supprimez les commentaires et vous pouvez tester ce que vous avez écrit.
On commence à voir comment les modèles peuvent nous aider à créer des algorithmes dans lesquels le code et les données sont séparés. L’ expression switch teste le type et produit différentes valeurs en fonction des résultats. Ce n’est que le début.
Ajouter la tarification en fonction du taux d’occupation
L’autorité de péage souhaite inciter les véhicules à circuler à capacité maximale. Ils décident de facturer davantage lorsque les véhicules ont moins de passagers et encouragent les véhicules complets en offrant une tarification plus faible :
- Voitures et taxis sans passager : +0,50 $.
- Voitures et taxis avec deux passagers : remise de 0,50 $.
- Voitures et taxis avec trois passagers ou plus : -1,00 $.
- Bus remplis à moins de 50 % : +2,00 $.
- Bus remplis à plus de 90 % : -1,00 $.
Vous pouvez implémenter ces règles à l’aide d’un modèle de propriété dans la même expression switch. Un modèle de propriété compare une valeur de propriété à une valeur constante. Le motif de propriété examine les propriétés de l’objet une fois que le type est déterminé.
Car {Passengers: 0} est lui-même un motif récursif : le motif de propriété externe sur Car contient un motif constant interne qui teste la valeur Passengers. L’unique cas Car est étendu à quatre cas différents :
vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,
// ...
};
Les trois premiers cas testent le type Car, puis vérifient la valeur de la propriété Passengers. Si les deux correspondent, l’expression est évaluée et une valeur est renvoyée.
Vous pouvez également étendre les cas pour les taxis de la même manière :
vehicle switch
{
// ...
Taxi {Fares: 0} => 3.50m + 1.00m,
Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,
// ...
};
Maintenant, implémentons les règles de taux d’occupation en développant les cas des bus, comme dans l’exemple suivant :
vehicle switch
{
// ...
Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus => 5.00m,
// ...
};
L’autorité de péage ne se soucie pas du nombre de passagers des camions de livraison, et ajuste le montant en fonction du poids du camion, en procédant ainsi :
- Camions de plus de 5 000 lb : +5,00 $.
- Camions de moins de 3000 lb : -2,00 $.
Vous pouvez implémenter cette règle avec le code suivant :
vehicle switch
{
// ...
DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,
};
Le code précédent montre la clause when d'une branche de l'instruction switch. Utilisez la when clause pour tester des conditions autres que l’égalité sur une propriété. Lorsque vous avez terminé, vous disposez d’une méthode qui ressemble beaucoup au code suivant :
vehicle switch
{
Car {Passengers: 0} => 2.00m + 0.50m,
Car {Passengers: 1} => 2.0m,
Car {Passengers: 2} => 2.0m - 0.50m,
Car => 2.00m - 1.0m,
Taxi {Fares: 0} => 3.50m + 1.00m,
Taxi {Fares: 1} => 3.50m,
Taxi {Fares: 2} => 3.50m - 0.50m,
Taxi => 3.50m - 1.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus => 5.00m,
DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
Comme indiqué précédemment, ces branches de switch sont des modèles récursifs, qui imbriquent un modèle constant à l’intérieur d’un modèle de propriété.
Il est possible de rendre ce code moins répétitif avec des switch imbriqués. Dans les exemples précédents, Car et Taxi ont chacun quatre branches différentes. Dans les deux cas, vous pouvez créer un modèle de déclaration qui alimente un modèle constant. Cette technique est illustrée dans le code suivant :
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => c.Passengers switch
{
0 => 2.00m + 0.5m,
1 => 2.0m,
2 => 2.0m - 0.5m,
_ => 2.00m - 1.0m
},
Taxi t => t.Fares switch
{
0 => 3.50m + 1.00m,
1 => 3.50m,
2 => 3.50m - 0.50m,
_ => 3.50m - 1.00m
},
Bus b when ((double)b.Riders / (double)b.Capacity) < 0.50 => 5.00m + 2.00m,
Bus b when ((double)b.Riders / (double)b.Capacity) > 0.90 => 5.00m - 1.00m,
Bus b => 5.00m,
DeliveryTruck t when (t.GrossWeightClass > 5000) => 10.00m + 5.00m,
DeliveryTruck t when (t.GrossWeightClass < 3000) => 10.00m - 2.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
Dans l’exemple précédent, l’imbrication d’une expression de commutateur à l’intérieur d’un autre bras de commutateur signifie que vous ne répétez pas les bras Car et Taxi contenant des sous-branches qui testent la valeur de la propriété. Cette technique n’est pas utilisée pour les bras Bus et DeliveryTruck car ces bras testent des plages pour la propriété, et non des valeurs discrètes.
Ajouter la tarification heures de pointe
Pour la fonctionnalité finale, l’autorité de péage souhaite ajouter une tarification de pointe variable selon l’heure. Pendant les heures d’affluence du matin et du soir, les péages sont doublés. Cette règle ne s’applique au trafic que dans un sens : en allant vers la ville le matin et en en sortant le soir. Le reste du temps, pendant la journée de travail, les péages augmentent de 50 %. Tard le soir et tôt le matin, ils sont réduits de 25 %. Le taux normal s’applique le weekend, quelle que soit l’heure. Vous pouvez utiliser une série d’instructions if et else pour exprimer cette règle à l’aide du code suivant :
public decimal PeakTimePremiumIfElse(DateTime timeOfToll, bool inbound)
{
if ((timeOfToll.DayOfWeek == DayOfWeek.Saturday) ||
(timeOfToll.DayOfWeek == DayOfWeek.Sunday))
{
return 1.0m;
}
else
{
int hour = timeOfToll.Hour;
if (hour < 6)
{
return 0.75m;
}
else if (hour < 10)
{
if (inbound)
{
return 2.0m;
}
else
{
return 1.0m;
}
}
else if (hour < 16)
{
return 1.5m;
}
else if (hour < 20)
{
if (inbound)
{
return 1.0m;
}
else
{
return 2.0m;
}
}
else // Overnight
{
return 0.75m;
}
}
}
Le code précédent fonctionne correctement, mais n’est pas lisible. Vous devez parcourir tous les cas d'entrée et les instructions imbriquées if pour comprendre le code. Plutôt, vous allez utiliser la correspondance de motifs pour cette fonctionnalité, mais vous l’intégrerez avec d’autres techniques. Vous pouvez créer une expression de correspondance à un modèle unique qui tient compte de toutes les combinaisons de direction, jour de la semaine et heure. Le résultat serait une expression compliquée. difficile à lire, à comprendre Cela rend difficile de garantir l'exactitude. Combinons plutôt ces méthodes afin de créer un tuple de valeurs décrivant de manière concise tous ces états. Ensuite, utilisez la correspondance de motif pour calculer un multiplicateur pour le péage. Le tuple comporte trois conditions discrètes :
- Le jour est soit un jour de semaine, soit un week-end.
- La tranche horaire pendant laquelle le péage est perçu.
- Le sens est vers la ville ou à l'extérieur de la ville.
Le tableau suivant montre les combinaisons de valeurs d’entrée et le multiplicateur tarifaire de pointe.
| Day | Time | Direction | Premium |
|---|---|---|---|
| Jour de la semaine | Heure de pointe du matin | entrant | x 2,00 |
| Jour de la semaine | Heure de pointe du matin | sortant | x 1,00 |
| Jour de la semaine | période diurne | entrant | x 1,50 |
| Jour de la semaine | période diurne | sortant | x 1,50 |
| Jour de la semaine | Heure de pointe du soir | entrant | x 1,00 |
| Jour de la semaine | Heure de pointe du soir | Sortant | x 2,00 |
| Jour de la semaine | Nuit | entrant | x 0,75 |
| Jour de la semaine | Nuit | Sortant | x 0,75 |
| Week-end | Heure de pointe du matin | trafic entrant | x 1,00 |
| Week-end | Heure de pointe du matin | Sortant | x 1,00 |
| Week-end | période diurne | entrant | x 1,00 |
| Week-end | période diurne | sortant | x 1,00 |
| Week-end | Heure de pointe du soir | entrant | x 1,00 |
| Week-end | Heure de pointe du soir | sortant | x 1,00 |
| Week-end | Nuit | trafic entrant | x 1,00 |
| Week-end | Nuit | sortant | x 1,00 |
Les trois variables produisent 16 combinaisons différentes. En combinant certaines des conditions, vous simplifiez l’expression switch finale.
Le système qui collecte les péages utilise une structure DateTime pour indiquer l’heure de collecte. Construire des méthodes de membre qui créent les variables à partir du tableau précédent. La fonction suivante utilise une expression de commutation basée sur la correspondance de motifs pour indiquer si DateTime représente un jour de weekend ou de semaine.
private static bool IsWeekDay(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch
{
DayOfWeek.Monday => true,
DayOfWeek.Tuesday => true,
DayOfWeek.Wednesday => true,
DayOfWeek.Thursday => true,
DayOfWeek.Friday => true,
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false
};
Cette méthode est correcte, mais elle est répétitive. On peut la simplifier comme dans le code suivant :
private static bool IsWeekDay(DateTime timeOfToll) =>
timeOfToll.DayOfWeek switch
{
DayOfWeek.Saturday => false,
DayOfWeek.Sunday => false,
_ => true
};
Ensuite, ajoutons une fonction similaire pour catégoriser l’heure dans les blocs :
private enum TimeBand
{
MorningRush,
Daytime,
EveningRush,
Overnight
}
private static TimeBand GetTimeBand(DateTime timeOfToll) =>
timeOfToll.Hour switch
{
< 6 or > 19 => TimeBand.Overnight,
< 10 => TimeBand.MorningRush,
< 16 => TimeBand.Daytime,
_ => TimeBand.EveningRush,
};
Ajoutez un enum privé pour convertir chaque plage de temps en une valeur discrète. Ensuite, la GetTimeBand méthode utilise des modèles relationnels et des modèles logiques. Un modèle relationnel vous permet de tester une valeur numérique à l’aide de <, de >, de <=ou de >=. Les modèles logiques combinent d’autres modèles : un or modèle teste si une expression correspond à un ou plusieurs modèles, un and modèle teste qu’une expression correspond à deux modèles distincts et un not modèle teste qu’une expression ne correspond pas à un modèle.
Après avoir créé ces méthodes, vous pouvez utiliser une autre switch expression avec un modèle de tuple — un modèle positionnel qui correspond à chaque élément d’un tuple — pour calculer la prime de tarification. On pourrait concevoir une expression switch comportant les 16 branches :
public decimal PeakTimePremiumFull(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, true) => 1.50m,
(true, TimeBand.Daytime, false) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, true) => 0.75m,
(true, TimeBand.Overnight, false) => 0.75m,
(false, TimeBand.MorningRush, true) => 1.00m,
(false, TimeBand.MorningRush, false) => 1.00m,
(false, TimeBand.Daytime, true) => 1.00m,
(false, TimeBand.Daytime, false) => 1.00m,
(false, TimeBand.EveningRush, true) => 1.00m,
(false, TimeBand.EveningRush, false) => 1.00m,
(false, TimeBand.Overnight, true) => 1.00m,
(false, TimeBand.Overnight, false) => 1.00m,
};
Le code précédent fonctionne, mais il peut être simplifié. Les huit combinaisons du weekend correspondent au même péage. Vous pouvez remplacer toutes les huit par la ligne suivante :
(false, _, _) => 1.0m,
Le trafic entrant et le trafic sortant ont le même multiplicateur pendant la journée en semaine et pendant la nuit. Vous pouvez remplacer ces quatre branches du commutateur par les deux lignes suivantes :
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
Après ces deux modifications, le code devrait se présenter ainsi :
public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.MorningRush, true) => 2.00m,
(true, TimeBand.MorningRush, false) => 1.00m,
(true, TimeBand.Daytime, _) => 1.50m,
(true, TimeBand.EveningRush, true) => 1.00m,
(true, TimeBand.EveningRush, false) => 2.00m,
(true, TimeBand.Overnight, _) => 0.75m,
(false, _, _) => 1.00m,
};
Enfin, supprimez les deux créneaux horaires de pointe facturés au prix normal. Après avoir retiré ces bras, remplacez le false par un rejet (_) dans le dernier bras du commutateur. Vous obtenez la méthode terminée suivante :
public decimal PeakTimePremium(DateTime timeOfToll, bool inbound) =>
(IsWeekDay(timeOfToll), GetTimeBand(timeOfToll), inbound) switch
{
(true, TimeBand.Overnight, _) => 0.75m,
(true, TimeBand.Daytime, _) => 1.5m,
(true, TimeBand.MorningRush, true) => 2.0m,
(true, TimeBand.EveningRush, false) => 2.0m,
_ => 1.0m,
};
Cet exemple met en évidence l'un des avantages de la correspondance de motifs : les différentes branches du motif sont évaluées dans l'ordre. Si vous les réorganisez de telle sorte qu’une branche antérieure gère un des cas suivants, le compilateur vous avertit que le code est inaccessible. Grâce à ces règles de langage, nous avons pu effectuer facilement les simplifications précédentes sans craindre de modifier le code.
L'appariement de motifs rend certains types de code plus lisibles et offre une alternative aux techniques orientées objet lorsque vous ne pouvez pas ajouter de code à vos classes. Le cloud est à l’origine d’une séparation entre les données et les fonctionnalités. La forme des données et les opérations effectuées sur ces dernières ne sont pas nécessairement décrites ensemble. Dans ce tutoriel, nous avons exploité les données existantes d’une manière totalement différente de leur fonction d’origine. Les motifs vous donnent la possibilité d'écrire des fonctions qui redéfinissent ces types, même sans pouvoir les étendre.
Étapes suivantes
Vous pouvez télécharger le code terminé dans référentiel GitHub dotnet/samples. Explorez les modèles par vous-même et ajoutez cette technique à vos activités de codage régulières. Ces techniques représentent une autre façon d’aborder les problèmes et de créer de nouvelles fonctionnalités.