Lorsqu’il s’agit d’architecture logicielle, il existe divers patrons de conception (Design patterns) qui peuvent être utilisés pour améliorer la structure, la flexibilité et la maintenabilité d’un système. Un tel patron est le « Chain of responsibility », qui relève de la catégorie des patrons comportementaux (behavioral pattern).
Dans cet article, nous explorerons le motif de chaîne de responsabilité en Java et comment il peut être implémenté dans vos projets logiciels.
Qu'est ce que le modèle de conception "Chaine de responsabilité"?
Le modèle de conception « chaîne de responsabilité » est un patron qui permet à une demande d’être transmise à travers une chaîne de « handlers » jusqu’à ce qu’elle soit traitée par l’un d’entre eux. Ce modèle est utile lorsque plusieurs objets peuvent traiter une demande et que le handler spécifique n’est pas connu à l’avance. Le modèle « chaîne de responsabilité » favorise un couplage lâche entre l’émetteur d’une demande et son destinataire, car l’émetteur n’a pas besoin de savoir quel objet traitera la demande.
Comment fonctionne le patron "Chaine de responsabilité?
La structure du pattern « chaîne de responsabilité » se compose d’une chaîne d’objets handler, chacun ayant son propre ensemble de règles pour traiter une requête. Lorsqu’une requête est effectuée, elle est transmise à travers la chaîne de handlers jusqu’à ce que l’un d’entre eux soit en mesure de la traiter. Si aucun des handlers ne peut traiter la requête, elle est transmise à un handler par défaut, ou simplement ignorée.
La chaîne de handlers peut être implémentée de différentes manières, telles qu’une liste chaînée, un arbre ou un graphe. Chaque handler a une référence vers le handler suivant dans la chaîne, et la requête est transmise au handler suivant s’il est incapable de la traiter.
Implémentation du patron "Chaine de responsabilité" en Java
Pour mieux comprendre le modèle chaîne de responsabilité, examinons un exemple de sa mise en œuvre en Java. Nous allons créer un programme simple qui calcule le prix total d’un panier d’achat, en tenant compte de toutes les réductions qui peuvent s’appliquer.
Etape 1: Créer l'interface du Handler
La première étape c’est de créer une interface du handler qui définit les méthodes pour traiter une demande et définir le prochain handler dans la chaîne. Dans notre exemple, nous nommerons cette interface DiscountHandler et elle comportera deux méthodes : handleDiscount() et setNextHandler().
				
					public interface DiscountHandler { 
    double handleDiscount(ShoppingCart cart); 
    void setNextHandler(DiscountHandler nextHandler); 
} 
				
			
		Etape 2: Créer les classes concrètes des Handlers
Ensuite, nous créerons des Handlers concrets qui implémentent l’interface DiscountHandler. Chaque handler aura ses propres règles pour traiter une demande et passera la demande au handler suivant s’il est incapable de la traiter.
Dans notre exemple, nous créerons trois Handlers concrets : TenPercentDiscountHandler, TwentyPercentDiscountHandler et NoDiscountHandler. Le TenPercentDiscountHandler appliquera une réduction de 10 % si le prix total du panier d’achat est supérieur à 100 $, le TwentyPercentDiscountHandler appliquera une réduction de 20 % si le prix total est supérieur à 200 $, et le NoDiscountHandler retournera simplement le prix total sans aucune réduction.
				
					public class TenPercentDiscountHandler implements DiscountHandler { 
    
    private DiscountHandler nextHandler;
    @Override 
    public double handleDiscount(ShoppingCart cart) { 
        if (cart.getTotalPrice() > 100) { 
            return cart.getTotalPrice() * 0.9; 
        } else { 
            return nextHandler.handleDiscount(cart); 
        } 
    }
    @Override 
    public void setNextHandler(DiscountHandler nextHandler) { 
        this.nextHandler = nextHandler; 
    }
} 
				
			
		
				
					public class TwentyPercentDiscountHandler implements DiscountHandler { 
    private DiscountHandler nextHandler;
    @Override 
    public double handleDiscount(ShoppingCart cart) {
        if (cart.getTotalPrice() > 200) { 
                return cart.getTotalPrice() * 0.8;   
            } else { 
                return nextHandler.handleDiscount(cart); 
            } 
        }
    @Override 
    public void setNextHandler(DiscountHandler nextHandler) { 
        this.nextHandler = nextHandler; 
    }
} 
				
			
		
				
					public class NoDiscountHandler implements DiscountHandler { 
    private DiscountHandler nextHandler;
    @Override 
    public double handleDiscount(ShoppingCart cart) { 
        return cart.getTotalPrice(); 
    }
    @Override 
    public void setNextHandler(DiscountHandler nextHandler) { 
        this.nextHandler = nextHandler; 
    }
} 
				
			
		Etape 3: Créer la classe Client
La classe Client est chargée de créer la chaîne de responsabilité et de transmettre la demande au premier handler de la chaîne.
Dans notre exemple, la classe Client s’appelle ShoppingCartCalculator et elle possède une méthode appelée calculateTotalPrice() qui prend en paramètre un objet panier d’achat et renvoie le prix total après l’application de toutes les réductions.
				
					public class ShoppingCartCalculator { 
    private DiscountHandler discountHandler;
    public ShoppingCartCalculator() { 
        // Create the chain of handlers 
        DiscountHandler tenPercentDiscountHandler = new TenPercentDiscountHandler(); 
        DiscountHandler twentyPercentDiscountHandler = new TwentyPercentDiscountHandler(); 
        DiscountHandler noDiscountHandler = new NoDiscountHandler();
        // Set the next handler in the chain 
        tenPercentDiscountHandler.setNextHandler(twentyPercentDiscountHandler); 
        twentyPercentDiscountHandler.setNextHandler(noDiscountHandler);
        // Set the first handler in the chain 
        discountHandler = tenPercentDiscountHandler; 
    }
    public double calculateTotalPrice(ShoppingCart cart) { 
        return discountHandler.handleDiscount(cart); 
    }
} 
				
			
		Etape 4: Tester le programme
Enfin, nous pouvons tester notre programme en créant un objet panier d’achat et en le passant à la méthode calculateTotalPrice() de la classe ShoppingCartCalculator.
				
					public class Main { 
    public static void main(String[] args) { 
    // Create a shopping cart with a total price of $150 
    ShoppingCart cart = new ShoppingCart(150);
    // Calculate the total price after applying discounts 
    ShoppingCartCalculator calculator = new ShoppingCartCalculator(); 
    double totalPrice = calculator.calculateTotalPrice(cart);
    // Print the total price 
    System.out.println("Total price: $" + totalPrice); }
}
// The output of the program will be:
// Total price: $135.0 
				
			
		Avantages de l'utilisation du pattern "Chaine de responsabilité"
Le patron « chaîne de responsabilité » offre plusieurs avantages, notamment :
- Souplesse : La chaîne de Handlers peut être facilement modifiée ou étendue sans affecter le code client.
- Faible couplage : L’expéditeur d’une requête n’a pas besoin de savoir quel objet la traitera, favorisant un faible couplage entre les objets.
- Scalabilité : La chaîne de Handlers peut être aussi longue ou aussi courte que nécessaire, facilitant l’ajout de nouveaux Handlers à mesure que le système évolue.
- Maintenabilité : Le pattern « chaîne de responsabilité » favorise une approche modulaire pour le traitement des requêtes, ce qui rend plus facile la maintenance et le débogage du code.
Conclusion
Le modèle de conception « chaîne de responsabilité » est un modèle utile pour gérer les demandes de manière flexible et évolutive. En implémentant ce modèle dans vos projets logiciels, vous pouvez améliorer la maintenabilité et la flexibilité de votre code, facilitant ainsi l’ajout de nouvelles fonctionnalités et la gestion de différents types de demandes.
Ainsi, la prochaine fois que vous serez confronté à une situation où plusieurs objets peuvent gérer une demande, envisagez d’utiliser ce modèle pour la traiter de manière plus efficace et organisée.
 
				 
													 
                                