Top Design Patterns en C#
Les modèles de conception sont des solutions réutilisables à des problèmes courants de développement de logiciels, fournissant des modèles pour structurer et mettre en œuvre un code orienté objet d'une manière plus efficace et plus facile à maintenir. Ils aident les développeurs à résoudre les problèmes liés à la création, à la structure et à la communication d'objets de manière flexible et évolutive. Les modèles de conception sont des concepts de meilleures pratiques qui guident les développeurs dans l'écriture d'un meilleur code. L'un des principes fondamentaux de la conception de logiciels est le principe de responsabilité unique (SRP), qui fait partie des principes SOLID.
Dans sa vidéo, "Design Patterns : Single Responsibility Principle Explained Practically in C# (The S in SOLID)", Tim Corey explore le principe de responsabilité unique (SRP), en soulignant son importance dans la conception de logiciels et en donnant des indications pratiques sur la manière de le mettre en œuvre efficacement. Cet article offre un aperçu concis des principaux points à retenir de cette vidéo, en soulignant l'importance de l'ASR dans la création d'un code propre et facile à maintenir.
Introduction à SRP
Dans la conception de logiciels, les principes SOLID sont essentiels pour créer un code maintenable et évolutif. Ils veillent à ce que le code soit facile à comprendre, à tester et à modifier. Les cinq principes - principe de responsabilité unique (SRP), principe d'ouverture/fermeture (OCP), principe de substitution de Liskov (LSP), principe de séparation des interfaces (ISP) et principe d'inversion des dépendances (DIP) - font partie intégrante de la conception orientée objet et peuvent être appliqués dans le cadre de modèles de conception pour rendre les solutions plus robustes.
En appliquant des modèles de conception en C#, les développeurs peuvent résoudre des problèmes courants plus efficacement. Qu'il s'agisse de créer des objets, de définir des structures arborescentes ou d'assurer la réutilisation d'instances uniques, les modèles de conception fournissent des solutions prédéfinies qui améliorent l'architecture logicielle. Les modèles tels que la méthode Factory, le Builder et le Singleton fournissent des solutions flexibles et réutilisables, tandis que les modèles comportementaux et structurels aident à gérer la complexité et à améliorer la communication au sein des systèmes. En apprenant et en utilisant ces modèles, les développeurs peuvent construire des systèmes plus faciles à maintenir et à étendre.
Tim aborde le concept d'ASR, en soulignant qu'il est essentiel pour les développeurs de s'assurer que leur code respecte les meilleures pratiques. L'ASR stipule qu'une classe ne doit avoir qu'une seule responsabilité ou raison de changer. Ce principe permet de maintenir un code propre, maintenable et évolutif.
Vue d'ensemble du code démo
Tim met en place une application console simple en C# qui demande le nom et le prénom de l'utilisateur, valide ces noms, puis génère un nom d'utilisateur. L'implémentation initiale viole le principe SRP, ce qui constitue une excellente occasion de démontrer comment remanier le code pour qu'il adhère à ce principe.
SRP expliqué
Tim explique l'ASR en soulignant les multiples responsabilités au sein de la classe initiale :
- Interaction avec l'utilisateur : Traitement des messages de bienvenue et des invites.
- Capture de données : Capture du nom et du prénom de l'utilisateur.
- Validation : Validation des noms d'entrée.
- Génération de nom d'utilisateur : Génération d'un nom d'utilisateur à partir des noms d'entrée.
Chacune de ces responsabilités représente une raison différente pour laquelle la classe doit changer, ce qui constitue une violation de l'ASR.
Refacturer pour adhérer à l'ASR
Tim montre comment remanier le code pour suivre l'ASR en extrayant chaque responsabilité dans sa propre classe. Cette approche garantit que chaque classe a une seule raison d'être modifiée, ce qui rend le code plus modulaire et plus facile à maintenir.
Exemple pratique
Tim fournit un exemple pratique de remaniement :
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to my application");
Console.Write("Enter your first name: ");
string firstName = Console.ReadLine();
Console.Write("Enter your last name: ");
string lastName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
Console.WriteLine("You did not give us valid information!");
Console.ReadLine();
return;
}
var userName = $"{firstName.Substring(0, 1)}{lastName}".ToLower();
Console.WriteLine($"Your username is {userName}");
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
}
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Welcome to my application");
Console.Write("Enter your first name: ");
string firstName = Console.ReadLine();
Console.Write("Enter your last name: ");
string lastName = Console.ReadLine();
if (string.IsNullOrWhiteSpace(firstName) || string.IsNullOrWhiteSpace(lastName))
{
Console.WriteLine("You did not give us valid information!");
Console.ReadLine();
return;
}
var userName = $"{firstName.Substring(0, 1)}{lastName}".ToLower();
Console.WriteLine($"Your username is {userName}");
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
}
Refactorisation étape par étape
Étape 1 : Création de la classe StandardMessages
Tout d'abord, Tim crée une classe pour gérer les messages standard affichés à l'utilisateur. Ce cours permettra de gérer les messages de bienvenue et les messages de fin.
public class StandardMessages
{
public static void WelcomeMessage()
{
Console.WriteLine("Welcome to my application");
}
public static void EndApplication()
{
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
public static void ShowValidationErrorMessage()
{
Console.WriteLine("You did not give us valid information!");
}
}
public class StandardMessages
{
public static void WelcomeMessage()
{
Console.WriteLine("Welcome to my application");
}
public static void EndApplication()
{
Console.WriteLine("Press enter to close...");
Console.ReadLine();
}
public static void ShowValidationErrorMessage()
{
Console.WriteLine("You did not give us valid information!");
}
}
Dans la classe Program, remplacez les appels directs à Console.WriteLine et Console.ReadLine par des appels aux méthodes de la classe StandardMessages :
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
// Other code...
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
// Other code...
StandardMessages.EndApplication();
}
}
Étape 2 : Création de la classe PersonDataCapture
Ensuite, Tim crée une classe pour capturer le nom et le prénom de la personne. Cette classe sera chargée de recueillir les données de l'utilisateur et de renvoyer un objet Person.
public class PersonDataCapture
{
public static Person Capture()
{
Person output = new Person();
Console.Write("Enter your first name: ");
output.FirstName = Console.ReadLine();
Console.Write("Enter your last name: ");
output.LastName = Console.ReadLine();
return output;
}
}
public class PersonDataCapture
{
public static Person Capture()
{
Person output = new Person();
Console.Write("Enter your first name: ");
output.FirstName = Console.ReadLine();
Console.Write("Enter your last name: ");
output.LastName = Console.ReadLine();
return output;
}
}
Étape 3 : Création de la classe Person
Vous avez également besoin d'une classe Person pour contenir le nom et le prénom de l'utilisateur.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
Dans la classe Program, remplacez la gestion de la saisie directe de l'utilisateur par un appel à PersonDataCapture.Capture :
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
// Other code...
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
// Other code...
StandardMessages.EndApplication();
}
}
Étape 4 : Création de la classe PersonValidator
Ensuite, Tim crée une classe pour gérer la validation du nom et du prénom de la personne. Cette classe sera chargée de s'assurer que les noms ne comportent pas d'espace nul ou d'espace blanc.
public class PersonValidator
{
public static bool Validate(Person person)
{
if (string.IsNullOrWhiteSpace(person.FirstName))
{
StandardMessages.ShowValidationErrorMessage("first name");
return false;
}
if (string.IsNullOrWhiteSpace(person.LastName))
{
StandardMessages.ShowValidationErrorMessage("last name");
return false;
}
return true;
}
}
public class PersonValidator
{
public static bool Validate(Person person)
{
if (string.IsNullOrWhiteSpace(person.FirstName))
{
StandardMessages.ShowValidationErrorMessage("first name");
return false;
}
if (string.IsNullOrWhiteSpace(person.LastName))
{
StandardMessages.ShowValidationErrorMessage("last name");
return false;
}
return true;
}
}
Dans la classe Program, remplacez le code de validation par un appel à PersonValidator.Validate :
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
if (!PersonValidator.Validate(user))
{
StandardMessages.EndApplication();
return;
}
// Other code...
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
if (!PersonValidator.Validate(user))
{
StandardMessages.EndApplication();
return;
}
// Other code...
StandardMessages.EndApplication();
}
}
Etape 7 : Création de la classe AccountGenerator
Tim déplace la logique de génération de nom d'utilisateur et de création de compte dans une nouvelle classe AccountGenerator.
-
Création de la classe AccountGenerator :
- La classe comprend la logique permettant de générer le nom d'utilisateur et de simuler la création d'un compte.
public class AccountGenerator { public static void CreateAccount(Person user) { string username = $"{user.FirstName.Substring(0, 1)}{user.LastName}".ToLower(); Console.WriteLine($"Your username is: {username}"); } }public class AccountGenerator { public static void CreateAccount(Person user) { string username = $"{user.FirstName.Substring(0, 1)}{user.LastName}".ToLower(); Console.WriteLine($"Your username is: {username}"); } } -
Mise à jour de la classe principale :
- Appelez l'AccountGenerator pour créer le compte dans la classe principale.
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
bool isUserValid = PersonValidator.Validate(user);
if (!isUserValid)
{
StandardMessages.EndApplication();
return;
}
AccountGenerator.CreateAccount(user);
StandardMessages.EndApplication();
}
}
class Program
{
static void Main(string[] args)
{
StandardMessages.WelcomeMessage();
Person user = PersonDataCapture.Capture();
bool isUserValid = PersonValidator.Validate(user);
if (!isUserValid)
{
StandardMessages.EndApplication();
return;
}
AccountGenerator.CreateAccount(user);
StandardMessages.EndApplication();
}
}

Résumé et conclusions
Dans cette dernière section, Tim Corey résume les avantages et la mise en œuvre du principe de responsabilité unique (SRP) à travers le processus de refactorisation du code de démonstration. Il souligne les avantages de diviser l'application en classes plus petites et plus ciblées.
Avantages principaux de SRP
-
Maintenance simplifiée du code :
-
Chaque classe a une responsabilité unique, ce qui facilite la localisation des modifications à apporter. Par exemple, la logique de capture des données utilisateur est clairement placée sous PersonDataCapture.
- Cette structure simplifie la compréhension, car toute personne cherchant à modifier la validation de l'utilisateur sait qu'elle doit consulter PersonValidator.
-
-
Amélioration de la lisibilité :
- Avec des responsabilités clairement définies, le flux principal du programme devient plus lisible. Le code se lit désormais comme un ensemble d'actions claires et séquentielles :
StandardMessages.WelcomeMessage(); Person user = PersonDataCapture.Capture(); bool isUserValid = PersonValidator.Validate(user); if (!isUserValid) { StandardMessages.EndApplication(); return; } AccountGenerator.CreateAccount(user); StandardMessages.EndApplication();StandardMessages.WelcomeMessage(); Person user = PersonDataCapture.Capture(); bool isUserValid = PersonValidator.Validate(user); if (!isUserValid) { StandardMessages.EndApplication(); return; } AccountGenerator.CreateAccount(user); StandardMessages.EndApplication(); -
Réduction de la complexité :
-
Les petites classes dont les responsabilités sont ciblées ont tendance à comporter moins de lignes de code, ce qui facilite leur compréhension et leur maintenance.
- Exemple : Les méthodes de la classe StandardMessages sont concises et servent des objectifs uniques, tels que l'affichage d'un message de bienvenue ou la fin de l'application.
public class StandardMessages { public static void WelcomeMessage() { Console.WriteLine("Welcome to my application"); } public static void EndApplication() { Console.WriteLine("Press enter to close..."); Console.ReadLine(); } public static void ShowValidationErrorMessage(string fieldName) { Console.WriteLine($"You did not give us a valid {fieldName}!"); } }public class StandardMessages { public static void WelcomeMessage() { Console.WriteLine("Welcome to my application"); } public static void EndApplication() { Console.WriteLine("Press enter to close..."); Console.ReadLine(); } public static void ShowValidationErrorMessage(string fieldName) { Console.WriteLine($"You did not give us a valid {fieldName}!"); } }
-
-
Facilité de modification du code :
-
Étant donné que chaque classe a une seule raison de changer, la modification du code en réponse à de nouvelles exigences devient simple.
- Exemple : S'il s'agit de modifier le message de fin, la modification s'effectue uniquement dans la méthode StandardMessages.EndApplication.
-
-
Meilleur débogage et meilleure collaboration :
-
Avec des classes plus petites et bien définies, le débogage devient plus simple car vous pouvez facilement localiser l'emplacement d'un problème.
- Les nouveaux développeurs peuvent s'intégrer plus rapidement, en comprenant la structure claire et les responsabilités de chaque classe.
-
Répondre aux préoccupations de nombreuses classes
Tim répond à une préoccupation commune, à savoir que l'application de l'ASR donne lieu à un trop grand nombre de classes, ce qui alourdit le projet :
-
Navigation et compréhension :
-
Des outils comme IntelliSense dans Visual Studio facilitent la navigation dans plusieurs classes. Par exemple, en appuyant sur F12, on accède directement à la définition des méthodes ou des classes.
- Le fait d'avoir de nombreux petits éléments gérables peut faciliter la compréhension de l'ensemble de l'application par rapport à de grandes classes monolithiques.
-
-
Performance et stockage :
- Les classes supplémentaires n'ont pas d'impact significatif sur l'espace disque ou les performances, compte tenu des capacités de stockage et de calcul modernes.
-
Equilibre et excès :
-
Tim conseille de trouver un équilibre. Si la responsabilité d'une classe devient trop importante, examinez si elle a plusieurs raisons de changer, ce qui indiquerait qu'elle a besoin d'être divisée davantage.
- Il suggère que si vous devez faire défiler une classe de manière extensive dans Visual Studio, il se peut qu'elle soit trop grande et qu'il faille la diviser.
-
Mise en œuvre pratique
Tim encourage les développeurs à appliquer progressivement l'ASR, en particulier dans les bases de code existantes. Commencez par de petites modifications et un nouveau code pour vous aligner sur les principes de l'ASR. Cette approche incrémentale garantit des transitions plus fluides et une amélioration continue.
Conclusion
L'exemple de refactorisation de Tim Corey montre comment le respect du principe de responsabilité unique (SRP) permet d'obtenir un code plus propre et plus facile à maintenir. En divisant les responsabilités en classes plus petites et plus ciblées, les développeurs peuvent améliorer la lisibilité, le débogage et la collaboration au sein de leurs bases de code. Ce principe fondamental des modèles de conception SOLID ouvre la voie à des principes plus avancés et à de meilleures pratiques en matière de développement logiciel.
Pour obtenir des informations plus détaillées et des exemples de code, veuillez regarder sa vidéo et visiter son canal pour d'autres vidéos sur les modèles de conception.
