Partager via


Itération #4 : Rendre l’application faiblement couplée (VB)

par Microsoft

Télécharger le code

Dans cette quatrième itération, nous profitons de plusieurs modèles de conception logicielle pour faciliter la maintenance et la modification de l’application Gestionnaire de contacts. Par exemple, nous refactorisons notre application pour utiliser le modèle référentiel et le modèle d’injection de dépendances.

Génération d’une application Gestion des contacts ASP.NET MVC (VB)

Dans cette série de tutoriels, nous créons une application de gestion des contacts complète du début à la fin. L’application Gestionnaire de contacts vous permet de stocker des informations de contact (noms, numéros de téléphone et adresses e-mail) pour une liste de personnes.

Nous créons l’application sur plusieurs itérations. À chaque itération, nous améliorons progressivement l’application. L’objectif de cette approche à itérations multiples est de vous permettre de comprendre la raison de chaque modification.

  • Itération #1 : créez l’application. Dans la première itération, nous créons le Gestionnaire de contacts de la manière la plus simple possible. Nous ajoutons la prise en charge des opérations de base de données de base de données : Create, Read, Update et Delete (CRUD).

  • Itération n°2 : rendre l’application agréable. Dans cette itération, nous améliorons l’apparence de l’application en modifiant la ASP.NET vue MVC par défaut master page et la feuille de style en cascade.

  • Itération n°3 - Ajouter la validation de formulaire. Dans la troisième itération, nous ajoutons la validation de formulaire de base. Nous empêcherons les utilisateurs d’envoyer un formulaire sans remplir les champs de formulaire requis. Nous validons également les adresses e-mail et les numéros de téléphone.

  • Itération n° 4 : rendre l’application faiblement couplée. Dans cette quatrième itération, nous profitons de plusieurs modèles de conception logicielle pour faciliter la maintenance et la modification de l’application Gestionnaire de contacts. Par exemple, nous refactorisons notre application pour utiliser le modèle référentiel et le modèle d’injection de dépendances.

  • Itération #5 - Créer des tests unitaires. Dans la cinquième itération, nous rendons notre application plus facile à gérer et à modifier en ajoutant des tests unitaires. Nous modélisons nos classes de modèle de données et créons des tests unitaires pour nos contrôleurs et notre logique de validation.

  • Itération n°6 - Utiliser le développement piloté par les tests. Dans cette sixième itération, nous ajoutons de nouvelles fonctionnalités à notre application en écrivant d’abord des tests unitaires et en écrivant du code sur les tests unitaires. Dans cette itération, nous ajoutons des groupes de contacts.

  • Itération n°7 - Ajouter une fonctionnalité Ajax. Dans la septième itération, nous améliorons la réactivité et les performances de notre application en ajoutant la prise en charge d’Ajax.

Cette itération

Dans cette quatrième itération de l’application Gestionnaire de contacts, nous refactorisons l’application pour qu’elle soit plus faiblement couplée. Lorsqu’une application est faiblement couplée, vous pouvez modifier le code dans une partie de l’application sans avoir à modifier le code dans d’autres parties de l’application. Les applications faiblement couplées sont plus résilientes aux changements.

Actuellement, toutes les logiques d’accès et de validation des données utilisées par l’application Gestionnaire de contacts sont contenues dans les classes de contrôleur. C’est une mauvaise idée. Chaque fois que vous devez modifier une partie de votre application, vous risquez d’introduire des bogues dans une autre partie de votre application. Par exemple, si vous modifiez votre logique de validation, vous risquez d’introduire de nouveaux bogues dans votre logique d’accès aux données ou de contrôleur.

Notes

(SRP), une classe ne doit jamais avoir plus d’une raison de changer. La combinaison du contrôleur, de la validation et de la logique de base de données est une violation massive du principe de responsabilité unique.

Il existe plusieurs raisons pour lesquelles vous devrez peut-être modifier votre application. Vous devrez peut-être ajouter une nouvelle fonctionnalité à votre application, corriger un bogue dans votre application ou modifier la façon dont une fonctionnalité de votre application est implémentée. Les applications sont rarement statiques. Ils ont tendance à croître et à muter au fil du temps.

Imaginez, par exemple, que vous décidiez de modifier la façon dont vous implémentez votre couche d’accès aux données. À l’heure actuelle, l’application Gestionnaire de contacts utilise Microsoft Entity Framework pour accéder à la base de données. Toutefois, vous pouvez décider de migrer vers une technologie d’accès aux données nouvelle ou alternative, telle que ADO.NET Data Services ou NHibernate. Toutefois, étant donné que le code d’accès aux données n’est pas isolé du code de validation et du contrôleur, il n’existe aucun moyen de modifier le code d’accès aux données dans votre application sans modifier un autre code qui n’est pas directement lié à l’accès aux données.

Quand une application est faiblement couplée, en revanche, vous pouvez apporter des modifications à une partie d’une application sans toucher d’autres parties d’une application. Par exemple, vous pouvez changer de technologies d’accès aux données sans modifier votre logique de validation ou de contrôleur.

Dans cette itération, nous profitons de plusieurs modèles de conception logicielle qui nous permettent de refactoriser notre application Contact Manager en une application plus faiblement couplée. Lorsque nous avons terminé, le gestionnaire de contacts ne fera rien de ce qu’il n’a pas fait auparavant. Toutefois, nous pourrons modifier l’application plus facilement à l’avenir.

Notes

La refactorisation est le processus de réécriture d’une application de telle sorte qu’elle ne perde aucune fonctionnalité existante.

Utilisation du modèle de conception de logiciels de dépôt

Notre première modification consiste à tirer parti d’un modèle de conception logicielle appelé modèle de dépôt. Nous allons utiliser le modèle Référentiel pour isoler notre code d’accès aux données du reste de notre application.

L’implémentation du modèle référentiel nous oblige à effectuer les deux étapes suivantes :

  1. Créer une interface
  2. Créer une classe concrète qui implémente l’interface

Tout d’abord, nous devons créer une interface qui décrit toutes les méthodes d’accès aux données que nous devons effectuer. L’interface IContactManagerRepository est contenue dans la liste 1. Cette interface décrit cinq méthodes : CreateContact(), DeleteContact(), EditContact(), GetContact et ListContacts().

Listing 1 - Models\IContactManagerRepository.vb

Public Interface IContactManagerRepository
Function CreateContact(ByVal contactToCreate As Contact) As Contact
Sub DeleteContact(ByVal contactToDelete As Contact)
Function EditContact(ByVal contactToUpdate As Contact) As Contact
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

Ensuite, nous devons créer une classe concrète qui implémente l’interface IContactManagerRepository. Étant donné que nous utilisons Microsoft Entity Framework pour accéder à la base de données, nous allons créer une classe nommée EntityContactManagerRepository. Cette classe est contenue dans la liste 2.

Listing 2 - Models\EntityContactManagerRepository.vb

Public Class EntityContactManagerRepository
Implements IContactManagerRepository

Private _entities As New ContactManagerDBEntities()

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Return (From c In _entities.ContactSet _
            Where c.Id = id _
            Select c).FirstOrDefault()
End Function


Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
    Return _entities.ContactSet.ToList()
End Function


Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    _entities.AddToContactSet(contactToCreate)
    _entities.SaveChanges()
    Return contactToCreate
End Function


Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    Dim originalContact = GetContact(contactToEdit.Id)
    _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit)
    _entities.SaveChanges()
    Return contactToEdit
End Function


Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
    Dim originalContact = GetContact(contactToDelete.Id)
    _entities.DeleteObject(originalContact)
    _entities.SaveChanges()
End Sub

End Class

Notez que la classe EntityContactManagerRepository implémente l’interface IContactManagerRepository. La classe implémente les cinq méthodes décrites par cette interface.

Vous vous demandez peut-être pourquoi nous devons nous embêter avec une interface. Pourquoi devons-nous créer à la fois une interface et une classe qui l’implémente ?

À une exception près, le reste de notre application interagit avec l’interface et non avec la classe concrète. Au lieu d’appeler les méthodes exposées par la classe EntityContactManagerRepository, nous allons appeler les méthodes exposées par l’interface IContactManagerRepository.

De cette façon, nous pouvons implémenter l’interface avec une nouvelle classe sans avoir à modifier le reste de notre application. Par exemple, à une date ultérieure, nous pourrions implémenter une classe DataServicesContactManagerRepository qui implémente l’interface IContactManagerRepository. La classe DataServicesContactManagerRepository peut utiliser ADO.NET Data Services pour accéder à une base de données au lieu de Microsoft Entity Framework.

Si notre code d’application est programmé par rapport à l’interface IContactManagerRepository au lieu de la classe EntityContactManagerRepository concrète, nous pouvons changer de classes concrètes sans modifier le reste de notre code. Par exemple, nous pouvons passer de la classe EntityContactManagerRepository à la classe DataServicesContactManagerRepository sans modifier notre logique d’accès aux données ou de validation.

La programmation sur des interfaces (abstractions) au lieu de classes concrètes rend notre application plus résiliente aux changements.

Notes

Vous pouvez créer rapidement une interface à partir d’une classe concrète dans Visual Studio en sélectionnant l’option de menu Refactoriser, Extraire l’interface. Par exemple, vous pouvez d’abord créer la classe EntityContactManagerRepository, puis utiliser Extract Interface pour générer automatiquement l’interface IContactManagerRepository.

Utilisation du modèle de conception du logiciel d’injection de dépendances

Maintenant que nous avons migré notre code d’accès aux données vers une classe référentiel distincte, nous devons modifier notre contrôleur de contacts pour utiliser cette classe. Nous allons tirer parti d’un modèle de conception logicielle appelé Injection de dépendances pour utiliser la classe Repository dans notre contrôleur.

Le contrôleur de contact modifié est contenu dans la liste 3.

Listing 3 - Controllers\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _repository As IContactManagerRepository 

    Sub New()
        Me.New(new EntityContactManagerRepository())
    End Sub

    Sub New(repository As IContactManagerRepository)
        _repository = repository
    End Sub

    Protected Sub ValidateContact(contactToValidate As Contact)
        If contactToValidate.FirstName.Trim().Length = 0 Then
            ModelState.AddModelError("FirstName", "First name is required.")
        End If
        If contactToValidate.LastName.Trim().Length = 0 Then
            ModelState.AddModelError("LastName", "Last name is required.")
        End If
        If (contactToValidate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
            ModelState.AddModelError("Phone", "Invalid phone number.")
        End If        
        If (contactToValidate.Email.Length > 0 AndAlso  Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
            ModelState.AddModelError("Email", "Invalid email address.")
        End If
    End Sub

    Function Index() As ActionResult
        Return View(_repository.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToCreate)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.CreateContact(contactToCreate)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToEdit)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.EditContact(contactToEdit)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        Try
            _repository.DeleteContact(contactToDelete)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

End Class

Notez que le contrôleur de contact dans la liste 3 a deux constructeurs. Le premier constructeur transmet une instance concrète de l’interface IContactManagerRepository au second constructeur. La classe du contrôleur de contact utilise l’injection de dépendances de constructeur.

Le seul emplacement utilisé par la classe EntityContactManagerRepository se trouve dans le premier constructeur. Le reste de la classe utilise l’interface IContactManagerRepository au lieu de la classe EntityContactManagerRepository concrète.

Cela facilite le changement d’implémentations de la classe IContactManagerRepository à l’avenir. Si vous souhaitez utiliser la classe DataServicesContactRepository au lieu de la classe EntityContactManagerRepository, modifiez simplement le premier constructeur.

L’injection de dépendances du constructeur rend également la classe du contrôleur de contact très testable. Dans vos tests unitaires, vous pouvez instancier le contrôleur de contacts en passant une implémentation fictive de la classe IContactManagerRepository. Cette fonctionnalité de l’injection de dépendances sera très importante pour nous lors de la prochaine itération lorsque nous générerons des tests unitaires pour l’application Contact Manager.

Notes

Si vous souhaitez dissocier complètement la classe du contrôleur de contact d’une implémentation particulière de l’interface IContactManagerRepository, vous pouvez tirer parti d’une infrastructure qui prend en charge l’injection de dépendances, telle que StructureMap ou Microsoft Entity Framework (MEF). En tirant parti d’une infrastructure d’injection de dépendances, vous n’avez jamais besoin de faire référence à une classe concrète dans votre code.

Création d’une couche de service

Vous avez peut-être remarqué que notre logique de validation est toujours mélangée à notre logique de contrôleur dans la classe de contrôleur modifiée dans la liste 3. Pour la même raison qu’il est judicieux d’isoler notre logique d’accès aux données, il est judicieux d’isoler notre logique de validation.

Pour résoudre ce problème, nous pouvons créer une couche de service distincte. La couche de service est une couche distincte que nous pouvons insérer entre nos classes de contrôleur et de dépôt. La couche de service contient notre logique métier, y compris toute notre logique de validation.

ContactManagerService est contenu dans la liste 4. Il contient la logique de validation de la classe du contrôleur de contact.

Listing 4 - Models\ContactManagerService.vb

Public Class ContactManagerService
Implements IContactManagerService

Private _validationDictionary As IValidationDictionary
Private _repository As IContactManagerRepository


Public Sub New(ByVal validationDictionary As IValidationDictionary)
    Me.New(validationDictionary, New EntityContactManagerRepository())
End Sub


Public Sub New(ByVal validationDictionary As IValidationDictionary, ByVal repository As IContactManagerRepository)
    _validationDictionary = validationDictionary
    _repository = repository
End Sub


Public Function ValidateContact(ByVal contactToValidate As Contact) As Boolean
    If contactToValidate.FirstName.Trim().Length = 0 Then
        _validationDictionary.AddError("FirstName", "First name is required.")
    End If
    If contactToValidate.LastName.Trim().Length = 0 Then
        _validationDictionary.AddError("LastName", "Last name is required.")
    End If
    If contactToValidate.Phone.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}")) Then
        _validationDictionary.AddError("Phone", "Invalid phone number.")
    End If
    If contactToValidate.Email.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")) Then
        _validationDictionary.AddError("Email", "Invalid email address.")
    End If
    Return _validationDictionary.IsValid
End Function


#Region "IContactManagerService Members"

Public Function CreateContact(ByVal contactToCreate As Contact) As Boolean Implements IContactManagerService.CreateContact
    ' Validation logic
    If Not ValidateContact(contactToCreate) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.CreateContact(contactToCreate)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function EditContact(ByVal contactToEdit As Contact) As Boolean Implements IContactManagerService.EditContact
    ' Validation logic
    If Not ValidateContact(contactToEdit) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.EditContact(contactToEdit)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function DeleteContact(ByVal contactToDelete As Contact) As Boolean Implements IContactManagerService.DeleteContact
    Try
        _repository.DeleteContact(contactToDelete)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerService.GetContact
    Return _repository.GetContact(id)
End Function

Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerService.ListContacts
    Return _repository.ListContacts()
End Function

#End Region
End Class

Notez que le constructeur de ContactManagerService a besoin d’un ValidationDictionary. La couche de service communique avec la couche contrôleur par le biais de ce ValidationDictionary. Nous abordons le ValidationDictionary en détail dans la section suivante lorsque nous abordons le modèle Décorateur.

Notez, en outre, que ContactManagerService implémente l’interface IContactManagerService. Vous devez toujours vous efforcer de programmer sur des interfaces plutôt que sur des classes concrètes. Les autres classes de l’application Contact Manager n’interagissent pas directement avec la classe ContactManagerService. Au lieu de cela, à une exception près, le reste de l’application Contact Manager est programmé sur l’interface IContactManagerService.

L’interface IContactManagerService est contenue dans listing 5.

Listing 5 - Models\IContactManagerService.vb

Public Interface IContactManagerService
Function CreateContact(ByVal contactToCreate As Contact) As Boolean
Function DeleteContact(ByVal contactToDelete As Contact) As Boolean
Function EditContact(ByVal contactToEdit As Contact) As Boolean
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

La classe de contrôleur contact modifiée est contenue dans la liste 6. Notez que le contrôleur de contacts n’interagit plus avec le référentiel ContactManager. Au lieu de cela, le contrôleur de contacts interagit avec le service ContactManager. Chaque couche est isolée autant que possible des autres couches.

Listing 6 - Controllers\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _service As IContactManagerService 

    Sub New()
        _service = new ContactManagerService(New ModelStateWrapper(ModelState))
    End Sub

    Sub New(service As IContactManagerService)
        _service = service
    End Sub

    Function Index() As ActionResult
        Return View(_service.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        If _service.CreateContact(contactToCreate) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        If _service.EditContact(contactToEdit) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        If _service.DeleteContact(contactToDelete) Then
            return RedirectToAction("Index")
        End If
        Return View()
    End Function

End Class

Notre application ne respecte plus le principe de responsabilité unique (SRP). Le contrôleur de contacts de la liste 6 a été supprimé de toutes les responsabilités autres que le contrôle du flux d’exécution de l’application. Toute la logique de validation a été supprimée du contrôleur de contacts et poussée dans la couche de service. Toute la logique de base de données a été poussée dans la couche du référentiel.

Utilisation du modèle décorateur

Nous voulons être en mesure de dissocier complètement notre couche de service de notre couche de contrôleur. En principe, nous devons être en mesure de compiler notre couche de service dans un assembly distinct de notre couche de contrôleur sans avoir besoin d’ajouter une référence à notre application MVC.

Toutefois, notre couche de service doit être en mesure de transmettre les messages d’erreur de validation à la couche contrôleur. Comment pouvons-nous permettre à la couche de service de communiquer des messages d’erreur de validation sans associer le contrôleur et la couche de service ? Nous pouvons tirer parti d’un modèle de conception de logiciel nommé motif décorateur.

Un contrôleur utilise un ModelStateDictionary nommé ModelState pour représenter les erreurs de validation. Par conséquent, vous pouvez être tenté de passer ModelState de la couche contrôleur à la couche de service. Toutefois, l’utilisation de ModelState dans la couche de service rend votre couche de service dépendante d’une fonctionnalité de l’infrastructure MVC ASP.NET. Cela serait mauvais, car, un jour, vous voudrez peut-être utiliser la couche de service avec une application WPF au lieu d’une application MVC ASP.NET. Dans ce cas, vous ne souhaitez pas référencer l’infrastructure MVC ASP.NET pour utiliser la classe ModelStateDictionary.

Le modèle Décorateur vous permet d’encapsuler une classe existante dans une nouvelle classe afin d’implémenter une interface. Notre projet Gestionnaire de contacts inclut la classe ModelStateWrapper contenue dans listing 7. La classe ModelStateWrapper implémente l’interface dans listing 8.

Listing 7 - Models\Validation\ModelStateWrapper.vb

Public Class ModelStateWrapper
Implements IValidationDictionary

Private _modelState As ModelStateDictionary

Public Sub New(ByVal modelState As ModelStateDictionary)
    _modelState = modelState
End Sub

Public Sub AddError(ByVal key As String, ByVal errorMessage As String) Implements IValidationDictionary.AddError
    _modelState.AddModelError(key, errorMessage)
End Sub

Public ReadOnly Property IsValid() As Boolean Implements IValidationDictionary.IsValid
    Get
        Return _modelState.IsValid
    End Get
End Property

End Class

Listing 8 - Models\Validation\IValidationDictionary.vb

Public Interface IValidationDictionary

Sub AddError(ByVal key As String, ByVal errorMessage As String)
ReadOnly Property IsValid() As Boolean

End Interface

Si vous examinez de près la description 5, vous verrez que la couche de service ContactManager utilise exclusivement l’interface IValidationDictionary. Le service ContactManager ne dépend pas de la classe ModelStateDictionary. Lorsque le contrôleur de contacts crée le service ContactManager, le contrôleur encapsule son ModelState comme suit :

_service = new ContactManagerService(New ModelStateWrapper(ModelState))

Résumé

Dans cette itération, nous n’avons ajouté aucune nouvelle fonctionnalité à l’application Gestionnaire de contacts. L’objectif de cette itération était de refactoriser l’application Contact Manager afin de faciliter la maintenance et la modification.

Tout d’abord, nous avons implémenté le modèle de conception du logiciel référentiel. Nous avons migré tout le code d’accès aux données vers une classe de référentiel ContactManager distincte.

Nous avons également isolé notre logique de validation de notre logique de contrôleur. Nous avons créé une couche de service distincte qui contient tout notre code de validation. La couche de contrôleur interagit avec la couche de service et la couche de service interagit avec la couche du référentiel.

Lorsque nous avons créé la couche de service, nous avons tiré parti du modèle Décorateur pour isoler ModelState de notre couche de service. Dans notre couche de service, nous avons programmé l’interface IValidationDictionary au lieu de ModelState.

Enfin, nous avons tiré parti d’un modèle de conception logicielle nommé modèle d’injection de dépendances. Ce modèle nous permet de programmer sur des interfaces (abstractions) au lieu de classes concrètes. L’implémentation du modèle de conception Injection de dépendances rend également notre code plus testable. Dans l’itération suivante, nous ajoutons des tests unitaires à notre projet.