Paris, Bordeaux, Niort, Nantes, Rennes, Toulouse, Aix, Nice
filtre$

Design Patterns : Strategy

RAPPEL SUR LES design patterns

En génie logiciel, un design pattern (ou patron de conception) est une solution générale et reproductible à un problème courant de conception de logiciels. Un design pattern n’est pas une conception finie qui peut être transformée directement en code source mais il s’agit d’une description ou d’un modèle pour résoudre un problème qui peut être utilisé dans de nombreuses situations différentes.

Une conception de logiciel efficace nécessite de prendre en compte des problèmes qui peuvent ne devenir visibles que plus tard dans la mise en œuvre. La réutilisation des modèles de conception permet d’éviter les problèmes subtils qui peuvent causer des problèmes majeurs et améliore la lisibilité du code pour les codeurs et les architectes qui connaissent bien les modèles.

Les modèles de conception fournissent des solutions générales, documentées dans un format qui ne nécessite pas de détails liés à un problème particulier.

Strategy

but de strategy

Le Gang Of Four définit Strategy comme suit : Définir une famille d’algorithmes, les encapsulés en tant qu’objet et les rendre interchangeables. Strategy permet de rendre l’algorithme indépendant du client qui l’utilise. En d’autres termes, on réponds à la problématique  : comment faire pour réaliser différentes opérations avec un seul et même objet ?
La première solution serait de faire une classe avec toutes les opérations mais ceci violerait un principe SOLID : le Single Responsibility principle.

Cas concret

Nous allons voir un exemple concret pour illustrer Strategy. J’ai décidé de prendre l’exemple des filtres que l’on applique aux images ; beaucoup de filtres existent et chaque algorithme est différent. Liés tout ces filtres aux classes qui en ont besoin n’est pas désirable pour plusieurs raisons :

  • Différents filtres seront appropriés à différents moments. Nous ne voulons pas prendre en charge plusieurs algorithmes de filtre si nous ne les utilisons pas tous.
  • Il est difficile d’ajouter de nouveaux filtres et de modifier les existants s’ils font partie intégrante d’un client.

Diagramme DE CLASSE

Avant de rentrer dans le code, voyons ensemble un diagramme de classe qui illustre comment va être implémenté Strategy dans notre cas.

Nous pouvons y voir 3 éléments :

Le Context PictureFilter n’implémente pas directement un filtre. Au lieu de cela, il fait référence à la Strategy -l’interface FilterStrategy – pour exécuter un algorithme (FilterStrategy.ApplicateFilter()), ce qui rend le Context indépendant de la manière dont un filtre est implémenté. Les classes FilterBlackAndWhite et FilterVintage implémentent l’interface FilterStrategy, c’est-à-dire qu’elles encapsulent un algorithme (ici un filtre).

VU SUR LE CODE

Le premier fichier que nous allons regarder est l’interface FilterStrategy. C’est cette interface qui va définir la méthode commune aux filtres, ici nommée applicateFilter qui prends en paramètre une Matrice (ici Mat) et qui retourne la matrice modifiée.

interface FilterStrategy
{
    Mat applicateFilter(Mat mat);
}

Les deux fichiers suivants sont nos filtres, le FilterVintage et le FilterBlackAndWhite. (pour l’exemple, le traitement algorithmique sur les matrices n’a pas été implémenté)

class FilterVintage : FilterStrategy
{
    public Mat applicateFilter(Mat mat)
    {
        //TODO : Implémenter l'algorithme qui rends une photo avec un filtre Vintage
        Console.WriteLine("Filtre Vintage appliqué");
        return mat;
    }
}
class FilterBlackAndWhite : FilterStrategy
{
    public Mat applicateFilter(Mat mat)
    {           
        //TODO : Implémenter l'algorithme qui rends une photo avec un filtre Noir et Blanc
        Console.WriteLine("Filtre Noir et Blanc appliqué");
        return mat;
    }
}

On remarque que nos deux classes implémentent l’interface FilterStrategy, ce qui leur permet d’avoir la méthode applicateFilter. Dans chacune des méthodes, nous y ajoutons un Console.WriteLine pour vérifier le retour dans le console.

Le prochain fichier que nous allons voir est le Context, il s’agit de PictureFilter. C’est lui qui va appeler les filtres, peu importe lesquels, puisse qu’il va se baser sur le contrat (l’interface) qui les relient.

class PictureFilter
{
    FilterStrategy _filterStrategy;

    public FilterStrategy FilterStrategy { set { _filterStrategy = value; } }

    public Mat Process(Mat mat)
    {
        return _filterStrategy.applicateFilter(mat);
    }
}

On peut voir que cette classe possède une propriété du type FilterStrategy, c’est grâce à cette interface que nous pouvons rendre nos algorithmes interchangeables.

Cette classe possède aussi la méthode Process, qui est ce que l’utilisateur va utiliser pour lancer le traitement algorithmique.

Enfin, voyons comment ces classes peuvent être implémentées dans un petit programme.

    static void Main(string[] args)
    {
        PictureFilter pictureFilter = new PictureFilter();
        Mat matrice = Mat.FromImage("C:\\img.png");

        pictureFilter.FilterStrategy = new FilterBlackAndWhite();
        pictureFilter.Process(matrice); // out : "Filtre Noir et Blanc appliqué"

        pictureFilter.FilterStrategy = new FilterVintage();
        pictureFilter.Process(matrice); // out : "Filtre Vintage appliqué"
    }

La première étape est d’instanciée un PictureFilter, qui est celui qui exécute les filtres.
Puis nous venons charger une image sous forme de matrice. (pour rendre l’exemple plus parlant)
Nous venons charger l’algorithme FilterBlackAndWhite dans notre contexte et lançons le Process sur la matrice.
Nous pouvons ensuite changer l’algorithme, ici nous passons à FilterVintage et relançons le Process.

Nous remarquons bien que la modification de l’algorithme se fait au moment de l’exécution selon la situation.

Si cet exemple ne vous a pas été très parlant, en voici un deuxième :

Prenons l’exemple du tri, nous avons mis en place un tri à bulles mais les données ont commencé à croître et le tri à bulles a commencé à être très lent. Pour y remédier, nous avons mis en place le tri rapide. Mais maintenant, bien que l’algorithme de tri rapide soit plus performant pour les grands ensembles de données, il est très lent pour les petits ensembles de données. Pour y remédier, nous avons mis en place une stratégie qui prévoit d’utiliser le tri à bulles pour les petits ensembles de données et le tri rapide pour les ensembles plus importants.

Dans le prochain article, nous traiterons le design pattern Factory toujours en gardant l’exemple des filtres.

CET ARTICLE A ÉTÉ RÉDIGÉ PAR :

ADRIEN LAFFARGUE
INGÉNIEUR INFORMATIQUE JUNIOR
ALLTECH BORDEA
UX