Fermer

janvier 18, 2024

Connaître et appliquer les modèles de conception

Connaître et appliquer les modèles de conception


Les modèles de conception sont des solutions réutilisables aux problèmes courants auxquels les développeurs sont confrontés lors de la conception et du développement d’applications. Découvrez comment implémenter un modèle de conception bien connu dans une application ASP.NET Core.

Les modèles de conception aident les développeurs à résoudre les problèmes courants lors de la création d’applications et de fonctionnalités. Dans le contexte d’ASP.NET Core, il est essentiel de connaître les modèles de conception. Après tout, l’écosystème ASP.NET lui-même est basé sur bon nombre de ces modèles, tels que le modèle MVC.

Dans cet article, nous découvrirons certains des principaux modèles et implémenterons l’un d’entre eux dans une application ASP.NET Core. Ainsi, à la fin de l’article, vous serez familiarisé avec les modèles de conception et pourrez les appliquer chaque fois que l’occasion se présente. .

Que sont les modèles de conception ?

Les modèles de conception sont des solutions réutilisables et éprouvées aux problèmes courants qui surviennent lors de la conception et du développement de logiciels.

Ils ne sont pas spécifiques à un langage ou à une technologie de programmation spécifique, mais fournissent plutôt des lignes directrices et des modèles généraux visant à atteindre divers objectifs de conception de logiciels tels que la flexibilité, la maintenabilité et l’évolutivité.

Les modèles de conception aident les développeurs à créer un code plus efficace, organisé et maintenable en encapsulant les meilleures pratiques et en favorisant la réutilisation du code.

Ces modèles de conception ne sont pas des modèles rigides qu’il faut suivre à tout prix, mais plutôt des principes et des lignes directrices qui peuvent être adaptés et personnalisés pour répondre aux besoins spécifiques d’un projet logiciel. Une utilisation appropriée des modèles de conception peut conduire à des systèmes logiciels plus extensibles et plus efficaces.

Il existe plusieurs catégories de modèles de conception, parmi lesquelles quatre se démarquent :

1. Modèles de création

Ces modèles traitent des mécanismes permettant de créer des objets d’une manière adaptée à la situation. Ils impliquent souvent le recours à des constructeurs, des usines et des prototypes.

Parmi les modèles de création figurent :

  • Modèle singleton
  • Modèle de méthode d’usine
  • Modèle d’usine abstrait
  • Modèle de constructeur
  • Modèle prototype

2. Normes structurelles

Ces modèles traitent de la composition des objets, définissant généralement leurs relations pour former des structures plus grandes. Ils aident à concevoir un système flexible et efficace.

Parmi les modèles structurels figurent :

  • Adaptateur standard
  • Modèle de décorateur
  • Modèle composite
  • Modèle de proxy
  • Modèle de pont

3. Modèles comportementaux

Ces modèles traitent de l’interaction et de la communication entre les objets, en se concentrant sur la manière dont les objets répartissent les responsabilités et collaborent les uns avec les autres.

Parmi les modèles de comportement figurent :

  • Modèle d’observateur
  • Modèle de stratégie
  • Modèle de commande
  • Norme d’État
  • Norme de chaîne de responsabilité

4. Modèles architecturaux

Ces modèles de haut niveau fournissent un modèle pour la structure et l’organisation globales d’une application logicielle. Ils guident la conception architecturale de systèmes ou sous-systèmes entiers.

Parmi les normes architecturales figurent :

  • MVC (Modèle-Vue-Contrôleur)
  • MVVM (Modèle-Vue-VueModèle)
  • Injection de dépendances
  • Modèle de référentiel

Connaître et appliquer le modèle d’usine abstraite

Le modèle de conception d’usine abstraite dans le contexte d’ASP.NET Core est une approche utilisée pour créer des familles d’objets liés ou dépendants de manière flexible et extensible. Cela est particulièrement utile lorsque vous devez vous assurer qu’un ensemble d’objets est compatible et cohérent, tout en conservant la flexibilité nécessaire pour échanger facilement ces familles d’objets.

Dans cet article, nous allons créer un exemple dans lequel nous devons calculer le montant du paiement d’un étudiant, où, en fonction de la date d’échéance, une réduction ou des frais supplémentaires seront appliqués.

Dans cet exemple, nous aurons une classe abstraite qui aura une méthode appelée « CalculateFeeAmount » et deux classes dérivées qui seront chargées d’appliquer la remise ou les frais supplémentaires.

Chaque modèle de conception contient un certain degré de complexité, cet article se concentrera donc sur un seul modèle de conception. Nous explorerons sa signification et comment l’implémenter en pratique dans une application réelle dans ASP.NET Core, alors commençons !

Création de l’exemple d’application

Dans cet article, nous allons créer une API minimale ASP.NET Core qui est responsable de l’enregistrement des frais de scolarité.

Vous trouverez ci-dessous trois prérequis pour mettre en œuvre l’application :

  • Version récente du .NET – Cet article utilise la version 7, bien que la version 8 soit désormais disponible.
  • Un environnement de développement intégré (IDE) – Cet article utilise le Visual Studio Code.
  • MySQL préconfiguré localement – Cet article utilise MySQL comme base de données, vous devez donc disposer d’un serveur MySQL local. Vous pouvez utiliser une autre base de données, mais de telles configurations ne sont pas couvertes dans cet article.

Pour créer l’application de base via le terminal, utilisez la commande suivante :

dotnet new web -o StudentFeesTracker

Vous pouvez accéder au code source complet ici : Code source du suivi des frais étudiants.

Ajout des packages NuGet

Installons maintenant les packages NuGet dont nous aurons besoin plus tard dans le projet. Dans le terminal, exécutez les commandes suivantes :

  • dotnet add package Swashbuckle.AspNetCore
  • dotnet add package Dapper --version 2.0.151
  • dotnet add package MySql.Data --version 8.1.0

Création des modèles

Pour créer les classes d’entités, allez dans le projet et créez un dossier appelé « Modèles ». À l’intérieur, créez les classes suivantes :

namespace StudentFeesTracker.Models;

public class EntityDto
{
    public Guid Id { get; set; }
}
namespace StudentFeesTracker.Models;

public class StudentFee : EntityDto
{
    public Guid StudentId { get; set; }
    public decimal Amount { get; set; }
    public DateTime DueDate { get; set; }
    public bool IsPaid { get; set; }
}
namespace StudentFeesTracker.Models;

public class ConnectionString
{
    public string? ProjectConnection { get; set; }
}

Création du référentiel

Dans ASP.NET Core et dans le développement de logiciels en général, un référentiel est une technique de conception utilisée pour séparer la logique qui récupère et stocke les données du reste de l’application, ce qui contribue à améliorer la maintenabilité, la testabilité et l’évolutivité de la base de données.

Créez un nouveau dossier appelé « Dépôts » et à l’intérieur, créez la classe et l’interface ci-dessous :

using StudentFeesTracker.Models;

namespace StudentFeesTracker.Repositories;

public interface IStudentFeeRepository
{
    Task<List<StudentFee>> FindAll();
    Task Create(StudentFee studentFee);
}
using System.Data;
using Dapper;
using Microsoft.Extensions.Options;
using MySql.Data.MySqlClient;
using StudentFeesTracker.Models;

namespace StudentFeesTracker.Repositories;

public class StudentFeeRepository : IStudentFeeRepository
{
    private readonly IDbConnection _dbConnection;

    public StudentFeeRepository(IOptions<ConnectionString> connectionString)
    {
        _dbConnection = new MySqlConnection(connectionString.Value.ProjectConnection);
    }

    public async Task<List<StudentFee>> FindAll()
    {
        string query = @"select 
                            id Id, 
                            student_id StudentId, 
                            amount Amount, 
                            due_date DueDate, 
                            is_paid IsPaid
                        from student_fees";

        var projects = await _dbConnection.QueryAsync<StudentFee>(query);
        return projects.ToList();
    }

    public async Task Create(StudentFee studentFee)
    {
        string query = @"insert into student_fees(id, student_id, amount, due_date, is_paid) 
                         values(@Id, @StudentId, @Amount, @DueDate, @IsPaid)";

        await _dbConnection.ExecuteAsync(query, studentFee);
    }
}

Code MySQL

Vous trouverez ci-dessous le code SQL nécessaire pour créer la base de données et la table utilisées dans l’exemple de publication :


CREATE DATABASE student_fee_management;


USE student_fee_management;


CREATE TABLE student_fees (
    id CHAR(36) PRIMARY KEY,
    student_id CHAR(36),
    amount DECIMAL(10, 2),
    due_date DATETIME,		
    is_paid BOOLEAN
);

Création de l’usine abstraite

Notez que le code ci-dessus dispose d’une méthode pour insérer des enregistrements dans la base de données, mais imaginez qu’avant de les insérer, nous devons calculer la réduction ou les frais supplémentaires sur le montant des frais, en fonction de la date d’échéance.

Pour suivre un modèle de bonnes pratiques, nous pouvons utiliser le modèle d’usine abstraite pour résoudre ce problème. Pour ce faire, nous pouvons créer une classe d’usine de base qui contiendra une méthode appelée « CalculateAmount() » et deux classes dérivées, une pour calculer la remise et l’autre pour calculer les frais de retard supplémentaires.

À la racine du projet, créez un nouveau dossier appelé « Factory » et à l’intérieur créez les classes ci-dessous :

namespace StudentFeesTracker.Factories;
public abstract class StudentFeeFactory
{
    public abstract decimal CalculateFeeAmount(decimal amount);
}
  • RemiseÉtudiantFraisUsine
namespace StudentFeesTracker.Factories;
public class DiscountStudentFeeFactory : StudentFeeFactory
{
    public override decimal CalculateFeeAmount(decimal amount)
    {
        return amount * 0.9m; 
    }
}
namespace StudentFeesTracker.Factories;
public class LateStudentFeeFactory : StudentFeeFactory
{
    public override decimal CalculateFeeAmount(decimal amount)
    {
        return amount * 1.1m; 
    }
}

Notez que la classe StudentFeeFactory définit une méthode (CalculateFeeAmount()) qui est implémenté par les deux classes d’usine LateStudentFeeFactory et DiscountStudentFeeFactory.

Il est important de noter que le CalculateFeeAmount() La méthode définit uniquement les valeurs d’entrée et de sortie ; elle ne définit pas le comportement. Par conséquent, chaque classe dérivée peut gérer le calcul du taux de manière indépendante.

Dans ce scénario, nous utilisons un simple calcul de remise ou d’ajout de frais qui pourrait être créé dans la classe de service du projet elle-même, mais imaginez s’il y avait plus de règles métier dans le calcul, comme la communication avec des API externes. Il serait difficile de tout garder dans une seule classe. C’est pourquoi il est très important de séparer la logique en classes distinctes, même si le calcul réel est simple. De cette façon, vous préparez le système afin qu’il puisse être mis à l’échelle si nécessaire, facilitant ainsi la maintenance du code.

Démo de modèle d'usine abstraite

Création de la classe de service

La classe de service sera utilisée pour contenir les règles métier de l’application et pour accéder aux méthodes du référentiel. C’est dans cette classe que seront injectées les dépendances sur les classes factory précédemment créées.

Alors, créez un nouveau dossier appelé « Services » et à l’intérieur, créez la classe suivante :

using StudentFeesTracker.Factories;
using StudentFeesTracker.Models;
using StudentFeesTracker.Repositories;

namespace StudentFeesTracker.Services;

public class StudentFeeService
{
    private readonly IStudentFeeRepository _repository;
    private readonly StudentFeeFactory _lateStudentFeeFactory;
    private readonly StudentFeeFactory _discountStudentFeeFactory;

    public StudentFeeService(IStudentFeeRepository repository, LateStudentFeeFactory lateStudentFeeFactory, DiscountStudentFeeFactory discountStudentFeeFactory)
    {
        _repository = repository;
        _lateStudentFeeFactory = lateStudentFeeFactory;
        _discountStudentFeeFactory = discountStudentFeeFactory;
    }

    public async Task<List<StudentFee>> FindAll()
    {
        var studentFees = await _repository.FindAll();
        return studentFees;
    }

    public async Task<Guid> Create(StudentFee studentFee)
    {
        CalculateFee(studentFee);
        GenerateId(studentFee);
        await _repository.Create(studentFee);
        return studentFee.Id;
    }

    private void CalculateFee(StudentFee studentFee)
    {
        if (IsLate(studentFee.DueDate))
        {
            studentFee.Amount = _lateStudentFeeFactory.CalculateFeeAmount(studentFee.Amount);
        }
        else if (IsDiscount(studentFee.DueDate))
        {
            studentFee.Amount = _discountStudentFeeFactory.CalculateFeeAmount(studentFee.Amount);
        }
    }

    private bool IsLate(DateTime dueDate)
    {
        const int lateDayThreshold = 10;
        return dueDate.Day > lateDayThreshold;
    }

    private bool IsDiscount(DateTime dueDate)
    {
        const int discountDayThreshold = 5;
        return dueDate.Day < discountDayThreshold;
    }

    private void GenerateId(StudentFee studentFee)
    {
        studentFee.Id = Guid.NewGuid();
    }
}

Notez que dans le code ci-dessus, les deux classes d’usine sont transmises via le constructeur et leurs méthodes sont utilisées pour calculer la remise ou les frais de retard en fonction de la date saisie. De cette façon, si nous avions plus de méthodes ou de classes d’usine, elles seraient prêtes à être utilisées par la classe de service, rendant l’application modulaire et flexible.

Créer les points de terminaison et rendre l’application fonctionnelle

Pour que l’application soit fonctionnelle, il est nécessaire de créer les points de terminaison de l’API et d’injecter des dépendances des classes de service, de référentiel et d’usine, en plus de réaliser la chaîne de connexion avec la base de données.

Remplacez le code dans le fichier « Program.cs » par le code suivant :

using StudentFeesTracker.Factories;
using StudentFeesTracker.Models;
using StudentFeesTracker.Repositories;
using StudentFeesTracker.Services;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddTransient<IStudentFeeRepository, StudentFeeRepository>();
builder.Services.AddSingleton<StudentFeeService>();
builder.Services.Configure<ConnectionString>(builder.Configuration.GetSection("ConnectionStrings"));
builder.Services.AddTransient<LateStudentFeeFactory>();
builder.Services.AddTransient<DiscountStudentFeeFactory>();
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapGet("/v1/student/fees", async (StudentFeeService service) =>
{
    var fees = await service.FindAll();
    return fees.Any() ? Results.Ok(fees) : Results.NotFound("No fees found");
})
.WithName("FindAllFees");

app.MapPost("/v1/student/fees", async (StudentFeeService service, StudentFee newStudentFee) =>
{
    var createdId = await service.Create(newStudentFee);
    return Results.Created($"/v1/contacts/{createdId}", createdId);
})
.WithName("CreateNewFee");

app.Run();

Et dans le fichier « appsettings.json », ajoutez l’extrait de code ci-dessous, en remplaçant les mots-clés en majuscules par vos paramètres MySQL locaux.

"ConnectionStrings": {
  "ProjectConnection": "host=localhost; port=PORT; database=student_fee_management; user=USER; password=PASSWORD;"
  },

Une fois cela fait, nous pouvons exécuter l’application et tester ses fonctions. Pour cela, exécutez simplement la commande dans le terminal : dotnet run et dans le navigateur accédez à l’adresse http://localhost:5168/swagger/index.html.

Ensuite, vous verrez la page Swagger et pourrez effectuer les opérations définies, comme indiqué dans le GIF ci-dessous :

Tester l'application

Conclusion

Les modèles de conception sont extrêmement utiles, car en plus de résoudre de nombreux problèmes, ils aident également les développeurs et les concepteurs à créer des applications robustes, prêtes à être mises à l’échelle en utilisant les bonnes pratiques.

Il est important de dire que les modèles de conception doivent être mis en œuvre lorsqu’il y a un problème à résoudre, et non utilisés simplement pour les utiliser, quel qu’en soit le coût.

Dans cet article, nous avons vu un modèle très connu, l’usine abstraite, qui est normalement utilisé pour découpler les composants d’une application, car il encourage la création d’interfaces abstraites pour la création de logiques et de règles métier.

Pensez donc toujours à utiliser des modèles de conception lors de la création d’une nouvelle application ou de la refactorisation d’une application existante.




Source link