Dans la pro­gram­ma­tion orientée objet, les Design patterns (patrons de con­cep­tion) aident les dé­ve­lop­peurs grâce à des approches et des modèles de solutions éprouvés. Après avoir choisi un patron approprié, il suffit alors de procéder à des ajus­te­ments in­di­vi­duels. À l’heure actuelle, il existe un total de 70 patrons de con­cep­tion adaptés à des domaines d’ap­pli­ca­tion spé­ci­fiques. Les Strategy design patterns se con­centrent sur le com­por­te­ment des logiciels.

Qu’est-ce que le Strategy pattern ?

Le Strategy pattern fait partie des Be­ha­vio­ral patterns (patrons com­por­te­men­taux) qui équipent un logiciel avec dif­fé­rentes méthodes de ré­so­lu­tion. Ces stra­té­gies sont en réalité une famille d’al­go­rithmes séparés du programme à pro­pre­ment parler et autonomes (= in­ter­chan­geables). Un patron de con­cep­tion Stratégie inclut également des lignes di­rec­trices et des outils pour les dé­ve­lop­peurs. Les Strategy patterns décrivent ainsi comment struc­tu­rer des classes, organiser un groupe de classes et créer des objets. L’une des par­ti­cu­la­ri­tés du Strategy pattern réside dans le fait qu’il est possible de réaliser un com­por­te­ment de programme et d’objet variable même pendant l’exécution d’un logiciel.

À quoi ressemble la re­pré­sen­ta­tion UML d’un Strategy pattern ?

La con­cep­tion des Strategy patterns passe nor­ma­le­ment par le langage de mo­dé­li­sa­tion graphique UML (Unified Modelling Language). Ce dernier permet de vi­sua­li­ser le modèle de con­cep­tion avec une notation stan­dar­di­sée en utilisant des ca­rac­tères et des symboles spéciaux. L’UML met à dis­po­si­tion dif­fé­rents types de dia­grammes pour la pro­gram­ma­tion orientée objet. Pour la re­pré­sen­ta­tion d’un patron de con­cep­tion Stratégie, on choisit en général un diagramme de classes avec au moins trois com­po­sants de base :

  • Context (contexte ou classe de contexte)
  • Strategy (stratégie ou classe de stratégie)
  • Con­cre­teS­tra­tegy (stratégie concrète)

Dans le Strategy design pattern, les com­po­sants de base assument des fonc­tion­na­li­tés spé­ci­fiques : les modèles de com­por­te­ment de la classe Context sont ex­ter­na­li­sés dans dif­fé­rentes classes Strategy. Ces classes séparées abritent les al­go­rithmes appelés Con­cre­teS­tra­te­gies. Si né­ces­saire, le Context peut recourir aux variantes de calcul ex­ter­na­li­sées (Con­cre­teS­tra­te­gyA, Con­cre­teS­tra­te­gyB, etc.) en utilisant une référence (interne). Dans ce cadre, il n’interagit pas di­rec­te­ment avec les al­go­rithmes mais avec une interface.

L’interface Strategy encapsule les variantes de calcul et peut être im­plé­men­tée si­mul­ta­né­ment par tous les al­go­rithmes. Pour interagir avec le Context, l’interface générique met une seule méthode à dis­po­si­tion pour la ré­so­lu­tion des al­go­rithmes Con­cre­teS­tra­tegy. Outre l’appel de la stratégie, les in­te­rac­tions avec le Context incluent également l’échange de données. L’interface Strategy contribue également aux chan­ge­ments de stratégie qui ont lieu pendant l’exécution d’un programme.

Remarque

L’en­cap­su­lage permet d’empêcher l’accès direct aux al­go­rithmes et aux struc­tures de données internes. Les instances externes (client, Context) peuvent uni­que­ment utiliser les calculs et les fonc­tion­na­li­tés via des in­ter­faces définies. Dans ce cadre, seuls les méthodes et les éléments de données d’un objet per­ti­nents pour l’instance externe sont ac­ces­sibles.

Nous vous ex­pli­quons comment ce patron de con­cep­tion est mis en œuvre dans un projet pratique à travers un exemple de Strategy pattern.

Le Strategy pattern expliqué à travers un exemple

Dans notre exemple, nous nous appuyons sur le projet d’étude de l’ingénieur allemand Philipp Hauer : une ap­pli­ca­tion de na­vi­ga­tion doit être réalisée à l’aide d’un patron de con­cep­tion Strategy. L’ap­pli­ca­tion doit exécuter un calcul d’iti­né­raire réalisé à partir de moyens de transport courants. L’uti­li­sa­teur peut choisir parmi trois options :

  • piéton (Con­cre­teS­tra­te­gyA)
  • voiture (Con­cre­teS­tra­te­gyB)
  • trans­ports en commun (Con­cre­teS­tra­te­gyC)

Trans­po­ser ces objectifs dans un graphique UML permet de préciser la structure et le fonc­tion­ne­ment du Strategy pattern né­ces­saire :

Dans notre exemple, le client est l’interface d’uti­li­sa­tion (Graphical User Inferface, IGU) d’une ap­pli­ca­tion de na­vi­ga­tion avec des boutons pour le calcul des iti­né­raires. Lorsque l’uti­li­sa­teur fait un choix et appuie sur un bouton, un iti­né­raire concret est calculé. Le Context (classe na­vi­ga­teur) a pour tâche de calculer et d’afficher une série de points de contrôle sur la carte. La classe na­vi­ga­teur dispose d’une méthode pour changer la stratégie de calcul d’iti­né­raire active. Grâce aux boutons du client, il est alors possible de passer d’un moyen de transport à l’autre sans dif­fi­culté.

Par exemple, si l’on déclenche une commande cor­res­pon­dante avec le bouton « piéton » du client, le service « Calcule l’iti­né­raire pour un piéton » (Con­cre­teS­tra­te­gyA) est appelé. La méthode exe­cu­teAl­go­rithm() (dans notre exemple, la méthode : cal­cu­lI­ti­ne­raire (A, B)) accepte une origine et une des­ti­na­tion et renvoie un ensemble de points de contrôle de l’iti­né­raire. Le Context reçoit la commande du client et décide de la stratégie adaptée (setS­tra­tegy : piéton) sur la base de di­rec­tives pré­dé­fi­nies (Policy). À l’aide de Call, il délègue la requête à l’objet Strategy et à son interface.

getS­tra­tegy() indique la stratégie ac­tuel­le­ment sé­lec­tion­née dans le Context (classe na­vi­ga­teur). Les résultats des calculs Con­cre­teS­tra­tegy sont utilisés dans le trai­te­ment ultérieur ainsi que dans l’affichage graphique de l’iti­né­raire dans l’ap­pli­ca­tion de na­vi­ga­tion. Si l’uti­li­sa­teur opte pour un autre iti­né­raire, par exemple en cliquant sur le bouton « Voiture », le Context passe à la stratégie demandée (Con­cre­teS­tra­te­gyB) et déclenche un nouveau calcul via un autre Call. À la fin de la procédure, un iti­né­raire modifié est indiqué pour le moyen de transport « voiture ».

Dans notre exemple, le mécanisme du patron peut être mis en œuvre avec un code re­la­ti­ve­ment clair :

Context :

public class Context {
    //Valeur standard prédéfinie (comportement par défaut) : ConcreteStrategyA
    private Strategy strategy = new ConcreteStrategyA(); 
    public void execute() { 
        //délègue le comportement à un objet Strategy
        strategy.executeAlgorithm(); 
    }
    public void setStrategy(Strategy strategy) {
        strategy = strategy;
    }
    public Strategy getStrategy() { 
        return strategy; 
    } 
}

Strategy, Con­cre­teS­tra­te­gyA, Con­cre­teS­tra­te­gyB :

interface Strategy { 
    public void executeAlgorithm(); 
} 
class ConcreteStrategyA implements Strategy { 
    public void executeAlgorithm() { 
        System.out.println("Concrete Strategy A"); 
    } 
} 
class ConcreteStrategyB implements Strategy { 
    public void executeAlgorithm() { 
        System.out.println("Concrete Strategy B"); 
    } 
}

Client :

public class Client {
public static void main(String[] args) {
//Comportement par défaut
Context context = new Context();
context.execute();
//Modifier le comportement
context.setStrategy(new ConcreteStrategyB());
context.execute();
}
}

Quels sont les avantages et les in­con­vé­nients du Strategy pattern ?

Les avantages d’un Strategy pattern sont visibles lorsque l’on prend la pers­pec­tive d’un pro­gram­meur ou d’un ad­mi­nis­tra­teur système. En général, la dé­com­po­si­tion en modules et en classes autonomes entraîne une meilleure struc­tu­ra­tion du code du programme. Dans les sous-parties seg­men­tées, le pro­gram­meur de l’ap­pli­ca­tion de notre exemple aura affaire à des segments de code plus fins. Il est ainsi possible de diminuer l’ampleur de la classe Na­vi­ga­teur par l’ex­ter­na­li­sa­tion des stra­té­gies et de renoncer à former des sous-classes dans le domaine du Context.

Dans ce code segmenté de façon plus fine et plus propre, les ré­per­cus­sions des mo­di­fi­ca­tions sont limitées puisque les dé­pen­dances internes des segments restent cadrées. Par con­sé­quent, les re­pro­gram­ma­tions ul­té­rieures – qui peuvent être la­bo­rieuses – sont plus rarement né­ces­saires et peuvent, pour partie, être en­tiè­re­ment exclues. Sur le long terme, la trans­pa­rence des segments de code permet un meilleur entretien et facilite les diag­nos­tics de problèmes ainsi que la recherche d’erreurs.

L’uti­li­sa­tion en bénéficie également puisque l’ap­pli­ca­tion de notre exemple peut être dotée d’une interface con­vi­viale. Grâce à ses boutons, les uti­li­sa­teurs peuvent commander le com­por­te­ment du programme (ici, le calcul d’iti­né­raires) de façon simple et variable et choisir parmi plusieurs options en toute sim­pli­cité.

Comme le Context de l’ap­pli­ca­tion de na­vi­ga­tion interagit uni­que­ment avec une interface (du fait de l’en­cap­su­lage des al­go­rithmes), il ne dépend pas de l’im­plé­men­ta­tion concrète des dif­fé­rents al­go­rithmes. Si par la suite, les al­go­rithmes sont modifiés ou de nouvelles stra­té­gies sont in­tro­duites, le code du Context n’aura pas à être modifié. À titre d’exemple, on pourrait fa­ci­le­ment et ra­pi­de­ment compléter le calcul des iti­né­raires avec des Con­cre­teS­tra­te­gies sup­plé­men­taires pour des trajets en avion, en bateau ou en train. Les nouvelles stra­té­gies devront sim­ple­ment im­plé­men­ter l’interface Strategy cor­rec­te­ment.

Une autre ca­rac­té­ris­tique avan­ta­geuse des patrons Strategy permet de faciliter la pro­gram­ma­tion complexe des logiciels orientés objet. Ces patrons per­met­tent en effet de concevoir des (modules) logiciels réu­ti­li­sables dont le dé­ve­lop­pe­ment est considéré comme par­ti­cu­liè­re­ment exigeant. Par exemple, les classes de Context ap­pa­ren­tées pour­raient également utiliser les stra­té­gies ex­ter­na­li­sées pour le calcul des iti­né­raires via l’interface et n’auraient plus à les im­plé­men­ter per­son­nel­le­ment.

Malgré ses nombreux avantages, le patron Strategy comporte également quelques in­con­vé­nients. Du fait de sa structure plus complexe, la con­cep­tion de logiciel peut générer des re­don­dances et des inef­fi­ca­ci­tés dans la com­mu­ni­ca­tion interne. Dans certains cas, l’interface Strategy générique – res­pon­sable de l’im­plé­men­ta­tion si­mul­ta­née de tous les al­go­rithmes – peut ainsi être sur­di­men­sion­née.

Un exemple : après avoir créé et ini­tia­lisé certains pa­ra­mètres, le Context les transmet à l’interface générique et aux méthodes qui y sont définies. Cependant, la dernière stratégie im­plé­men­tée n’a pas né­ces­sai­re­ment besoin de tous les pa­ra­mètres Context com­mu­ni­qués et ne les traite pas. Dans le patron Strategy, l’interface mise à dis­po­si­tion n’est donc pas toujours utilisée de façon optimale et il n’est pas toujours possible d’éviter un effort de com­mu­ni­ca­tion accru avec des trans­ferts de données superflus.

Lors de l’im­plé­men­ta­tion, il existe par ailleurs une dé­pen­dance interne étroite entre le client et les stra­té­gies. Comme le client fait un choix et appelle la stratégie concrète à l’aide d’une commande de dé­clen­che­ment (dans notre exemple, le calcul des iti­né­raires pour les piétons), il doit connaître les Con­cre­teS­tra­te­gies. Par con­sé­quent, ce patron de con­cep­tion ne devrait être utilisé que si les chan­ge­ments de stratégie et de com­por­te­ment sont es­sen­tiels ou fon­da­men­taux pour l’uti­li­sa­tion et le fonc­tion­ne­ment d’un logiciel.

Il est possible de con­tour­ner ou de compenser en partie les in­con­vé­nients présentés. Dans un Strategy pattern, le nombre d’instances d’objet peut être con­si­dé­rable, mais il peut souvent être réduit par une im­plé­men­ta­tion dans un Flyweight pattern. Une telle mesure aura également des effets bé­né­fiques sur l’ef­fi­ca­cité et l’uti­li­sa­tion de la mémoire par une ap­pli­ca­tion.

Dans quel cas le Strategy pattern est-il utilisé ?

En tant que patron de con­cep­tion fon­da­men­tal, le Strategy pattern ne se limite pas à un domaine d’uti­li­sa­tion par­ti­cu­lier dans le dé­ve­lop­pe­ment logiciel. L’uti­li­sa­tion de ce patron de con­cep­tion sera davantage dé­ter­mi­née par la nature de la pro­blé­ma­tique. Ce modèle de con­cep­tion est idéal pour tous les logiciels devant résoudre des tâches et des problèmes avec flexi­bi­lité et en proposant des options et des mo­di­fi­ca­tions de com­por­te­ment.

Le patron de con­cep­tion Strategy est par exemple utilisé par les pro­grammes offrant dif­fé­rents formats de stockage pour les fichiers ou diverses fonc­tion­na­li­tés de tri et de recherche. Dans le domaine de la com­pres­sion de données, on utilise également des pro­grammes im­plé­men­tant dif­fé­rents al­go­rithmes de com­pres­sion sur la base de ce modèle de con­cep­tion. De cette manière, vous pouvez par exemple convertir dif­fé­rentes vidéos dans le format de fichier peu vo­lu­mi­neux de votre choix ou re­con­ver­tir des fichiers com­pres­sés (par exemple des fichiers ZIP ou RAR) dans leur format d’origine avec des stra­té­gies de dé­com­pres­sion spé­ci­fiques. L’en­re­gis­tre­ment d’un document ou d’une image dans dif­fé­rents formats de fichiers constitue un autre exemple.

Ce patron de con­cep­tion participe également au dé­ve­lop­pe­ment et à l’im­plé­men­ta­tion des logiciels de jeu qui doivent par exemple réagir de façon flexible à des si­tua­tions de jeu chan­geantes pendant l’exécution. Il est ainsi possible d’en­re­gis­trer dif­fé­rents per­son­nages, des équi­pe­ments spé­ci­fiques, un modèle de com­por­te­ment ou dif­fé­rents mou­ve­ments d’un per­son­nage sous la forme de Con­cre­teS­tra­te­gies.

Les logiciels de commande cons­ti­tuent un autre domaine d’ap­pli­ca­tion du Strategy pattern. En échan­geant des Con­cre­teS­tra­te­gies, il est possible d’adapter sans dif­fi­culté des ensembles de calculs à des ca­té­go­ries pro­fes­sion­nelles, des pays et des régions. Par ailleurs, les pro­grammes trans­po­sant des données dans dif­fé­rents gra­phiques (par ex. sous forme de dia­grammes linéaires, cir­cu­laires ou à barres) utilisent aussi des patrons Strategy.

On trouve des ap­pli­ca­tions plus spé­ci­fiques des Strategy patterns dans la bi­blio­thèque standard Java (Java API) et dans les Java GUI-Toolkits (par ex. AWT, Swing et SWT) qui utilisent un ges­tion­naire de mise en page dans le dé­ve­lop­pe­ment et la gé­né­ra­tion des in­ter­faces uti­li­sa­teurs. Ce ges­tion­naire peut im­plé­men­ter dif­fé­rentes stra­té­gies pour l’or­ga­ni­sa­tion des com­po­sants lors du dé­ve­lop­pe­ment de l’interface. D’autres ap­pli­ca­tions des patrons de con­cep­tion Strategy peuvent être trouvées dans les systèmes de bases de données, les pilotes de pé­ri­phé­riques et les pro­grammes de serveurs.

Aperçu des prin­ci­pales ca­rac­té­ris­tiques du Strategy pattern

Au sein de la vaste palette de patrons de con­cep­tion, le patron de con­cep­tion Stratégie se démarque par les ca­rac­té­ris­tiques suivantes :

  • orienté com­por­te­ment (les modes et les mo­di­fi­ca­tions de com­por­te­ment sont plus fa­ci­le­ment pro­gram­mables et im­plé­men­tables ; les mo­di­fi­ca­tions sont également possibles pendant l’exécution d’un programme)
  • orienté ef­fi­ca­cité (les ex­ter­na­li­sa­tions sim­pli­fient et op­ti­mi­sent le code et son entretien)
  • orienté vers l’avenir (les mo­di­fi­ca­tions et les op­ti­mi­sa­tions peuvent fa­ci­le­ment être réalisées à moyen et à long terme)
  • vise la mo­du­la­rité (favorisée par le système modulaire et l’in­dé­pen­dance des objets et des classes)
  • vise la réu­ti­li­sa­bi­lité (par ex. l’uti­li­sa­tion multiple des stra­té­gies)
  • vise une con­vi­via­lité, une con­trô­la­bi­lité et une con­fi­gu­ra­bi­lité des logiciels op­ti­mi­sées
  • nécessite des con­nais­sances préa­lables en con­cep­tion (que peut-on ex­ter­na­li­ser, comment et à quel endroit des classes de stratégie ?)
En résumé

Dans la pro­gram­ma­tion orientée objet, les Strategy patterns per­met­tent un dé­ve­lop­pe­ment logiciel efficace et éco­no­mique grâce à des solutions sur mesure. Les po­ten­tielles mo­di­fi­ca­tions et amé­lio­ra­tions futures sont préparées de façon optimale dès la phase de con­cep­tion. De façon générale, ce système axé sur la flexi­bi­lité et le dynamisme peut être mieux commandé et contrôlé. Les erreurs et les in­co­hé­rences peuvent être plus ra­pi­de­ment corrigées. Des com­po­sants réu­ti­li­sables et rem­pla­çables per­met­tent d’éco­no­mi­ser des coûts de dé­ve­lop­pe­ment, en par­ti­cu­lier dans les projets complexes avec une pers­pec­tive à long terme. Il convient toutefois de trouver le bon équilibre. Il n’est pas rare que les modèles de con­cep­tion soient utilisés de façon trop par­ci­mo­nieuse ou trop fré­quem­ment.

Aller au menu principal