Voici un design pattern assez intéressant puisqu’il permet à la fois de détacher l’implémentation des entités de sa persistance (ou de son stockage), et simplifier les tests unitaires de nos programme.

Séparation de l’implémentation des entités et de la persistance

Prenons une classe définissant un membre. Voici l’implémentation de l’entité :

public class Member {
    public string LoginName { get; set; } // The unique key 
    public int ReputationPoints { get; set; }
}

Voici un exemple de repository associé à cette entité :

public class MembersRepository {
    public IEnumerable GetMembers() { /* Implement me */ }
    public void AddMember(Member member) { /* Implement me */ }
    public Member FetchByLoginName(string loginName) { /* Implement me */ }
    public void SubmitChanges() { /* Implement me */ }
}

Par convention il est profitable de suffixer le nom de la classe par Repository pour une meilleur compréhension.

Un Repository n’est utilisé que pour le chargement et la sauvegarde des données. Il n’y a aucune logique métier dedans.

Simplification des tests unitaires : Composants faiblement liés

Définition de la liaison des composants

La liaison des composants est le niveau de connaissance que les composants ont les uns des autres.

Dans une situation idéale, chaque composant ne connait rien des autres composants et ne fonctionne qu’au travers d’interface abstraites. Ceci est connu sous le nom de liaison faible et cela permet de tester et modifier facilement une application.

En reprenant l’exemple ci-dessus, nous pouvons créer une interface IMemberRepository de ce type :

public interface IMembersRepository
{
    IEnumerable GetMembers();
    void AddMember(Member member);
    Member FetchByLoginName(string loginName);
    void SubmitChanges();
}

Utilisation de l’injection de dépendance

Voici un exemple d’utilisation de cette interface et un petit aperçu de l’injection de dépendance.

Tout d’abord indiquons à MembersRepository que nous héritons de l’interface :

public class MembersRepository : IMembersRepository
{
...
}

Ensuite créons une petite classe prenant en compte l’interface IMemberRepository :

public class ManipulateMembers
{
    private IMembersRepository members = new MembersRepository();

    public IEnumerable<Member> Members
    {
        get
        {
            return members.GetMembers();
        }
    }
}

Cette classe est très simple mais elle est là pour l’exemple.

Regardons la liaison des composants. Si nous devons remplacer la classe MembersRepository par une classe DbMembersRepository ou XmlMembersRepository (implémentant toutes les deux IMembersRepository) nous voyons que nous devons modifier la classe ManipulateMembers. C’est une liaison forte et donc ne convient pas pour une bonne pratique de la programmation.

C’est là qu’entre en jeu l’injection de dépendance. Modifions la classe ManipulateMembers afin de la faire ressembler à ceci :

public class ManipulateMembers
{
    private IMembersRepository repository;

    public ManipulateMembers(IMembersRepository repo)
    {
        repository = repo;
    }

    public IEnumerable<Member> Members
    {
        get
        {
            return repository.GetMembers();
        }
    }
}

En passant au constructeur de la classe un objet implémentant IMembersRepository, nous avons supprimé la liaison forte qui existait. Il suffit maintenant “d’injecter” la classe correspondante via le constructeur :

var manipulator1 = new ManipulateMembers(new MembersRepository());

var manipulator2 = new ManipulateMembers(new DbMembersRepository());

var manipulator3 = new ManipulateMembers(new XmlMembersRepository());

Ceci est donc l’injection de dépendance. Toute nouvelle implémentation de l’interface IMembersRepository ne modifie en rien les composants qui l’utilise.

Ceci permet ainsi de créer de faux repository pour les tests unitaires.

Tests unitaires

Voici une fausse implémentation qui renverra toujours les mêmes données pour les tests unitaires :

public class FakeMembersRepository : IMembersRepository
{
    private IList memberList;

    public FakeMembersRepository()
    {
        memberList = new List();
        memberList.Add(new Member { LoginName = "member1", ReputationPoints = 50 });
        memberList.Add(new Member { LoginName = "member2", ReputationPoints = 75 });
        memberList.Add(new Member { LoginName = "member3", ReputationPoints = 10 });
    }

    public IEnumerable<Member> GetMembers()
    {
        return memberList;
    }

    public void AddMember(Member member){ throw new NotImplementedException(); }

    public Member FetchByLoginName(string loginName){ throw new NotImplementedException(); }

    public void SubmitChanges(){ throw new NotImplementedException(); }
}