Profitez du modèle de conception de stratégie / Blogs / Perficient

Les modèles de conception jouent un rôle essentiel dans la création de solutions d’application à la fois maintenables et évolutives. Le modèle de conception de stratégie est idéal pour les scénarios qui nécessitent une sélection d’exécution parmi les différents algorithmes disponibles. Dans ce blog, nous expliquerons comment implémenter le modèle de conception de stratégie dans un service AEM OSGi, améliorant ainsi la flexibilité et la gérabilité de votre code.
Qu’est-ce que le modèle de conception de stratégie ?
Le Strategy Design Pattern entre dans la catégorie des modèles de conception comportementale qui définit une famille d’algorithmes, encapsule chacun d’eux et les rend interchangeables. Ce modèle permet à l’algorithme de varier indépendamment des clients qui l’utilisent. Il est particulièrement utile pour les scénarios dans lesquels plusieurs méthodes peuvent être appliquées pour atteindre un objectif spécifique, et la méthode à utiliser peut être sélectionnée au moment de l’exécution.
Vous pouvez en savoir plus sur le modèle de stratégie en général ici.
Diagramme UML du modèle de conception de stratégie
Voici un diagramme UML qui illustre le modèle de conception de stratégie :
- Stratégie: L’interface qui définit le comportement commun à toutes les stratégies.
- ConreteImplementation1, ConreteImplementation2, ConreteImplementation3: Implémentations concrètes de l’interface Stratégie.
- Contexte: conserve une référence à un objet Strategy et l’utilise pour exécuter la stratégie.
Existe-t-il des cas d’utilisation dans AEM
Alors, existe-t-il un scénario dans lequel nous pouvons l’utiliser dans AEM ? Oui! Il existe de nombreux cas d’utilisation de solutions AEM dans lesquels nous avons plusieurs stratégies et la stratégie appropriée est choisie au moment de l’exécution. Nous utilisons généralement des instructions conditionnelles et des instructions switch dans de tels scénarios. Voici quelques exemples où le modèle de stratégie peut être appliqué.
- Comportements spécifiques au modèle: Différentes stratégies pour un composant lorsqu’il a un comportement différent selon différents modèles.
- Transformation du contenu: Différentes stratégies pour transformer du contenu HTML, JSON ou XML.
- Comportements spécifiques à la marque : Différentes stratégies pour les sites de marques individuelles dans des environnements multi-locataires lorsqu’ils partagent certaines bases de code et scénarios communs.
- Implémentations spécifiques au canal : Différents canaux pour envoyer des notifications tels que le courrier électronique et le Web.
Avantages de l’utilisation du modèle de conception de stratégie
La mise en œuvre du modèle de conception de stratégie dans les services AEM OSGi offre de nombreux avantages, qui s’alignent bien sur les principes SOLID et d’autres bonnes pratiques en matière de développement logiciel.
Six avantages
- Adaptabilité et évolutivité: Le Strategy Design Pattern facilite l’ajout de nouveaux algorithmes sans altérer le code existant, améliorant ainsi l’extensibilité du système. Lorsqu’un nouvel algorithme est requis, il peut être intégré de manière transparente en créant une nouvelle classe de stratégie, laissant ainsi le contexte et les autres stratégies inchangés.
- Adhésion aux principes SOLID: Le modèle de conception de stratégie s’aligne sur les principes SOLID en garantissant que chaque classe de stratégie adhère au principe de responsabilité unique (SRP), encapsulant un algorithme spécifique pour une compréhension, des tests et une maintenance plus faciles. Il prend en charge le principe ouvert/fermé (OCP) en permettant d’ajouter de nouvelles stratégies sans modifier le code existant. Le modèle garantit la conformité au principe de substitution de Liskov (LSP), permettant une utilisation interchangeable des implémentations de stratégie sans altérer les fonctionnalités. Il adhère au principe de ségrégation d’interface (ISP) en permettant aux clients d’interagir avec l’interface de stratégie, réduisant ainsi les dépendances inutiles. Enfin, il respecte le principe d’inversion de dépendance (DIP) en faisant dépendre le contexte d’abstractions plutôt que d’implémentations concrètes, favorisant ainsi un code flexible et découplé.
- Facilité de test: Étant donné que chaque stratégie est une implémentation distincte et ne dépend pas directement d’autres implémentations, elle peut être testée unitairement indépendamment des autres stratégies et du contexte. Cela conduit à des tests plus gérables et compréhensibles.
- Réutilisabilité du code: Si nécessaire, les stratégies peuvent être réutilisées dans différentes parties de l’application ou même dans différents projets. Cela réduit la redondance et encourage la réutilisation de code bien testé.
- Maintenabilité: La séparation claire des différents algorithmes dans leurs propres classes signifie que toute modification ou ajout aux algorithmes n’a pas d’impact sur le reste de la base de code. Cela rend le système plus maintenable.
- Meilleure lisibilité du code: En encapsulant la logique de chaque algorithme dans sa propre classe d’implémentation, le Strategy Design Pattern rend le code plus lisible. Les développeurs peuvent rapidement comprendre l’objectif de chaque stratégie en examinant l’interface de la stratégie et ses implémentations.
Mise en œuvre de stratégie typique pour AEM
Dans AEM, nous pouvons utiliser des cours réguliers pour la mise en œuvre de stratégies, mais avec OSGi, nous obtenons une meilleure expérience de gestion des stratégies. Une implémentation typique du modèle de conception de stratégie dans AEM consistera à :
- Contexte stratégique: Détient toutes les stratégies pour le client.
- Interface de stratégie: Fournit une interface générique pour différentes stratégies.
- Implémentations OSGi pour les stratégies: Contient la logique des algorithmes individuels pour chaque stratégie.
Étapes de mise en œuvre
Étape 1 : Définir l’interface de stratégie
Tout d’abord, créez une interface Java qui définit le comportement commun à toutes les stratégies.
public interface StrategyService { /** * Name of the strategy. * @return Strategy name. */ String getName(); /** * Executes the strategy based on criteria. * @param strategyBean Dummy Strategy bean object consists of the fields required for strategy execution. * @return Strategy Result bean. You can replace it with result object according to your need. */ StrategyResult execute(StrategyBean strategyBean); /** * Method for executing isFit operation if given strategy is fit for scenario. * @param strategyBean Strategy Object consisting of params needed for strategy is fit operation. * @return true if given strategy fits the criteria. */ boolean isFit(StrategyBean strategyBean); }
Les commentaires sont explicites ici, mais approfondissons tout de même ce que chaque méthode entend faire.
Chaîne getName()
Nom de la stratégie. Le nom doit être explicite. Ainsi, lorsque quelqu’un se faufile dans le code, il doit savoir quel scénario vise une stratégie.
StrategyResult exécuter (StrategyBean stratégieBean);
Cette méthode exécute la logique de l’opération requise au moment de l’exécution. StrategyResult Bean représenté ici est juste pour référence. Vous pouvez le mettre à jour en fonction de votre opération.
StrategyBean se compose des paramètres nécessaires à l’exécution de l’opération et encore une fois, c’est également juste pour référence, vous pouvez exécuter la même chose avec votre propre objet.
boolean isFit(StrategyBean stratégieBean);
Cette méthode permet de décider si la stratégie donnée est adaptée ou non au scénario. StrategyBean fournira les paramètres nécessaires pour exécuter l’opération.
Étape 2 : Mettre en œuvre des stratégies concrètes
Créez plusieurs implémentations de l’interface StrategyService. Chaque classe implémentera les méthodes différemment.
Ici, à titre de référence, créons deux implémentations concrètes pour StrategyService, à savoir FirstStrategyServiceImpl et SecondStrategyServiceImpl.
@Component(service = StrategyService.class, immediate = true, property = { "strategy.type=FirstStrategy" }) @Slf4j public class FirstStrategyServiceImpl implements StrategyService { @Override public String getName() { return "FirstStrategy"; } @Override public StrategyResult execute(StrategyBean strategyBean) { log.info("Executing First Strategy Service Implementation...."); //Implement Logic return new StrategyResult(); } @Override public boolean isFit(StrategyBean strategyBean) { //Implement Logic return new Random().nextBoolean(); } }
Et notre implémentation du deuxième service ici ressemblera à comme ça:
@Component(service = StrategyService.class, immediate = true, property = { "strategy.type=SecondStrategy" }) @Slf4j public class SecondStrategyServiceImpl implements StrategyService { @Override public String getName() { return "SecondStrategy"; } @Override public StrategyResult execute(StrategyBean strategyBean) { log.info("Executing Second Strategy Service Implementation...."); //Implement Logic return new StrategyResult(); } @Override public boolean isFit(StrategyBean strategyBean) { //Implement Logic return new Random().nextBoolean(); } }
Ici, nous n’avons aucune logique. Nous donnons simplement des implémentations factices. Vous pouvez ajouter votre logique opérationnelle dans exécuter et estFit méthode.
Étape 3 : implémenter la classe de service contextuel
Notre implémentation de service contextuel contiendra toutes les stratégies avec une liaison dynamique et ressemblera à ceci.
@Component(service = StrategyContextService.class, immediate = true) @Slf4j public class StrategyContextServiceImpl implements StrategyContextService { private final Map<String, StrategyService> strategies = new HashMap<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) protected void bindStrategy(final StrategyService strategy) { strategies.put(strategy.getName(), strategy); } protected void unbindStrategy(final StrategyService strategy) { strategies.remove(strategy.getName()); } public Map<String, StrategyService> getStrategies() { return strategies; } @Override public StrategyService getApplicableStrategy(final StrategyBean strategyBean) { return strategies .values() .stream() .filter(strategyService -> strategyService.isFit(strategyBean)) .findFirst() .orElse(null); } @Override public StrategyResult executeApplicableStrategy(final StrategyBean strategyBean) { final var strategyService = getApplicableStrategy(strategyBean); if (strategyService != null) { return strategyService.execute(strategyBean); } else { // Run Default Strategy Or Update Logic Accordingly return strategies.get("default").execute(strategyBean); } } @Override public Collection<StrategyService> getAvailableStrategies() { return strategies.values(); } @Override public Map<String, StrategyService> getAvailableStrategiesMap() { return strategies; } }
Ici, vous pouvez voir que nous lions dynamiquement des instances d’implémentations de StrategyService. Nous n’avons donc pas à nous soucier du changement dans cette classe de contexte chaque fois qu’une nouvelle stratégie est mise en œuvre. Le contexte ici liera dynamiquement les mêmes et mettra à jour les stratégies disponibles.
Nous fournissons ici des méthodes pour obtenir la stratégie, exécuter les stratégies et obtenir toutes les stratégies disponibles.
L’avantage que nous avons est que nous n’avons pas à nous soucier des changements nécessaires pour des stratégies supplémentaires. Lorsqu’une nouvelle stratégie est nécessaire, nous avons simplement besoin de sa classe de mise en œuvre et nous n’avons pas à nous soucier de son impact sur les autres. Si la logique isFit ne provoque pas de conflit avec les autres, nous n’avons pas à nous soucier des autres services. Cet isolement nous donne confiance pour les solutions complexes, car nous avons ici la flexibilité de ne pas nous soucier de l’impact des fonctionnalités nouvellement ajoutées.
Étape 4 : Utiliser cette implémentation
Puisqu’il s’agit d’une implémentation OSGi, vous pouvez l’utiliser dans n’importe quel service OSGi, servlet, flux de travail, planificateurs, tâches Sling, modèles Sling, etc.
Vous pouvez soit l’utiliser directement, soit créer un service wrapper autour de cela si un post-traitement supplémentaire des résultats de la stratégie est nécessaire.
Notre exemple de service client et de modèle de fronde ressemblera à ceci :
@Component(service = StrategyConsumer.class, immediate = true) public class StrategyConsumer { @Reference private StrategyContextService strategyContext; public StrategyResult performTask() { final StrategyBean strategyBean = new StrategyBean(); //Set strategy object based on data StrategyResult strategyResult = strategyContext.executeApplicableStrategy(strategyBean); //Do Post Processing return strategyResult; } public StrategyResult performTask(final StrategyBean strategyBean) { StrategyResult strategyResult = strategyContext.executeApplicableStrategy(strategyBean); //Do Post Processing return strategyResult; } }
La méthode peut être mise à jour pour s’adapter à une opération logique pour l’ajout de données dans le bean stratégique si nécessaire.
Un exemple de modèle de fronde qui utilisera le service consommateur ou se connectera directement au service contextuel ressemblera à ceci :
@Model(adaptables = SlingHttpServletRequest.class) public class StrategyConsumerModel { @OSGiService private StrategyConsumer strategyConsumer; @PostConstruct public void init(){ StrategyResult strategyResult = strategyConsumer.performTask(); //do processing if needed // OR StrategyBean strategyBean = new StrategyBean(); strategyResult = strategyConsumer.performTask(strategyBean); } }
Et vous êtes prêt maintenant.
Il ne s’agissait que d’une implémentation factice, mais vous pouvez la modifier en fonction des besoins de votre entreprise.
En introduction, des cas d’usage ont déjà été évoqués. Vous pouvez l’utiliser pour mettre en œuvre de tels scénarios. Des cas d’utilisation supplémentaires peuvent être n’importe quelle tâche pour laquelle vous devez sélectionner l’implémentation de manière dynamique au moment de l’exécution en fonction de scénarios.
Pensées finales
Avec l’implémentation ci-dessus, nous pouvons voir que le modèle de conception de stratégie est bien adapté pour gérer et étendre des algorithmes complexes d’une manière propre et modulaire, qui adhère également aux meilleures pratiques, notamment aux principes SOLID. Il favorise la flexibilité, l’évolutivité, la maintenabilité et la facilité de essai. Au fur et à mesure que vous développez des solutions AEM, envisagez de l’intégrer, cela améliorera considérablement la qualité et la réutilisabilité du code, garantissant ainsi que les exigences métier évolutives pourront être facilement satisfaites.
Apprenez-en davantage sur les trucs et astuces des développeurs AEM en lisant notre blog Adobe!
Source link