DotnetDojo

Développer des applications modernes avec les technologies Microsoft et Open source

  • Blog
  • Vidéos
  • Formations
  • Outils

Concevoir des applications SOLID : SRP (2/6)

Cette article est la suite d’une série de plusieurs articles sur les principes SOLID (je vous invite à relire l’article Conception d’applications SOLID pour en savoir plus)

Principe de responsabilité unique (SRP: The Single Responsibility Principle)

Présentation de SRP

Ce principe indique qu’une classe ne doit avoir qu’une seule raison d’être modifiée.

Si une classe comporte plusieurs responsabilités alors ces responsabilités sont couplées. Le fait de changer une responsabilité va influencer les autres responsabilités de la classe.
Les classes comportant des responsabilités couplées entraîneront des difficultés de maintenance ou des dysfonctionnement inattendus.

La modification d’une responsabilité de la classe aura un impact sur les autres responsabilités.

Qu’est-ce qu’une responsabilité ?

Selon Robert C. Martin, une responsabilité de la classe est une « raison de changer ». S’il y a plusieurs raisons de modifier une classe, c’est qu’elle possède plusieurs responsabilités.

Comment mettre en pratique SRP ?

Voici les étapes à suivre pour mettre en pratique le principe de responsabilité unique (SRP) :

  1. Lister les responsabilités d’une classe
  2. Choisir une responsabilité et extraire les méthodes pour en créer une nouvelle classe
  3. Continuer tant que la liste des responsabilité contient plus d’un élément

Exemple concret

Pour mieux comprendre ce principe, je vous propose un exemple réel.

Imaginons que nous souhaitions mettre en place un système de vente en ligne qui charge des produits depuis une base de données.
Pour la simplicité de l’exemple, la base de données est simulée avec une liste d’objet en mémoire.

Le projet d’exemple contient deux classes importantes :

  • La classe Product qui contient les informations d’un produit
  • La classe Database qui s’occupe de charger les données

Voici le code de la classe Product :
[csharp] namespace SrpDemo
{
public class Product
{
public string Name { get; private set ; }

public double Price { get; private set ; }

public Product(string name, double price)
{
Name = name;
Price = price;
}
}
}
[/csharp]

Voici le code de la classe Database :

[csharp] using System.Collections.Generic;
using System.Linq;

namespace SrpDemo
{
public class Database
{
List<Product > productsInMemory;

public void Connect()
{
// simulate database connection
}

public void LoadData()
{
productsInMemory = new List <Product>();
productsInMemory.Add( new Product ("Product1", 100));
productsInMemory.Add( new Product ("Product2", 255));
}

public IEnumerable <Product> GetProducts( bool applyVAT)
{
Connect();
LoadData();

// simulate database query (using linq)
var products = (from p in productsInMemory select p).AsEnumerable();
double vat = applyVAT ? 1.196 : 1;

// apply VAT
return from p in products
select new Product(p.Name, p.Price * vat);
}
}
}
[/csharp]

Dans cet exemple, j’ai crée une application du type console (ce qui est le projet le plus simple possible). Voici le code de la classe Program :

[csharp] using System;

namespace SrpDemo
{
class Program
{
static void Main(string[] args)
{
Database db = new Database();
var products = db.GetProducts(true );

foreach (var p in products)
Console.WriteLine("{0} = {1} Euros TTC" , p.Name, p.Price);

// To make a pause
Console.Read();
}
}
}
[/csharp]

En analysant la classe Database, on remarque qu’elle possède les responsabilités suivantes :

  1. Gestion des connexions à la base de données
  2. Chargement des données (requêtes Linq)
  3. Calcule de la TVA

Pour appliquer le principe SRP, il suffit donc de séparer les reponsabilités de la classe.

Refactoring 1 :
Responsabilité : Gestion des connexions

Dans mon exemple, j’utilise une liste en mémoire. Pour séparer cette responsabilité, je peux utiliser une classe DbContext provenant d’Entity Framework. Je vous propose donc de créer une classe qui hérite de DbContext et qui contient les éléments d’accès à la base de données.

[csharp] using System.Data.Entity;

namespace CodeFirst.Models
{
public class DatabaseContext : DbContext
{
public DatabaseContext()
: base("name=DatabaseContext" )
{
}

public DbSet <Product> Products { get; set ; }
}
}
[/csharp]

Je modifie Database pour l’adapter. Je renomme également la classe en ProductRepository qui me semble plus adapté.

Refactoring 2 :
Responsabilité : Chargement des données

Je modifie la classe ProductRepository pour qu’elle s’occupe uniquement de charger les données.

Voici le code après modification :

[csharp] using System.Collections.Generic;
using System.Linq;

namespace SrpDemo
{
public class ProductRepository
{
public IEnumerable <Product> GetProducts(bool applyVAT)
{
using (var context = new DatabaseContext())
{
var products = (from p in context.Products select p).AsEnumerable();
return products;
}
}
}
}
[/csharp]

Refactoring 3 :
Responsabilité : Calcule du prix TTC à partir du prix HT.

Je choisi de placer les règles de calcul de tarif dans l’objet métier. C’est la classe Product qui contiendra les règles métier d’un produit.

Une fois la classe modifiée, le code ressemblera à ceci :

[csharp] namespace SrpDemo
{
public class Product
{
public const double VATRate = 0.196;

public string Name { get; private set ; }

public double Price { get; private set ; }

public Product(string name, double price)
{
Name = name;
Price = price;
}

public double GetPriceWithVAT()
{
return Price * (1 + VATRate);
}
}
}
[/csharp]

Il reste à adapter la classe Program pour prendre en compte la nouvelle méthode GetPriceWithVAT, voici le code :

[csharp] using System;

namespace SrpDemo
{
class Program
{
static void Main(string[] args)
{
ProductRepository db = new ProductRepository();
var products = db.GetProducts();

foreach (var p in products)
Console.WriteLine("{0} = {1} Euros TTC" , p.Name, p.GetPriceWithVAT());

// To make a pause
Console.Read();
}
}
}
[/csharp]

Conclusion

Au travers de cet exemple très simple, je vous ai montré comment mettre en œuvre le principe SRP.
J’espère simplement que cela vous aura sensibilisé à ce principe et je vous invite à l’essayer sur certaines de vos classes. Grâce à SRP, vos classes seront plus simples à comprendre et plus simples à faire évoluer !

Le code du projet est disponible sur l’archive suivante : SrpDemo

Au premier lancement, l’application va créer une base de données (SQLExpress). Il faudra donc ajouter des produits dans la table pour que l’exemple affiche des données.

Pour continuer, je vous invite à lire l’article sur le principe d’ouvert-fermé (3/6).

[ninja-inline id=3695]

Besoin de résultats rapides ?

Découvrez les formations vidéos que je propose :

  

 

Formations en présentiel (dans toute la France)

Découvrez également les formations C# et .NET que je donne en présentiel (en France)

A propos de DotnetDojo

Pascal Lacroix

Je m’appelle Pascal et je suis passionné par le développement logiciel, l’efficacité et l’entrepreneuriat. Sur DotnetDojo, je vous propose des méthodes pour apprendre à développer des applications modernes avec les technologies Microsoft et Open Source.

En savoir plus

Liens complémentaires

  • A propos de DotnetDojo
  • 18 principes pour professionnaliser le développement logiciel
  • Boite à outils du développeur
  • Tous les articles
  • Liste des formations
  • Contact

Copyright 2019 Jupiteo · Mentions légales · Contact · CGV · Offres d'emploi .NET · Formations Dotnet