Dans le développement logiciel, une approche initiale pour gérer des logiques conditionnelles peut impliquer l'utilisation extensive de structures if-else. Cependant, cette méthode devient rapidement problématique à mesure que les exigences évoluent, violant des principes de conception fondamentaux tels que le principe Ouvert/Fermé et le principe de responsabilité unique. Cet article explore l'application du patron Factory pour restructurer et améliorer la distribution de récompenses dans un système.
Considérons un scénario de système de loterie où différents types de récompenses doivent être distribués : coupons, biens physiques et cartes cadeaux tierces. Une implémentation directe utilisant if-else pour gérer ces différents types de récompenses peut entraîner un code difficile à maintenir et à faire évoluer.
Code Initial (Approche if-else)
L'exemple suivant illustre une approche initiale utilisant de multiples conditions if-else pourr gérer la distribution de récompenses.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import java.util.Map;
// Supposons que ces classes existent et gèrent la logique de distribution spécifique
// class CouponService { public CouponResult sendCoupon(String uId, String awardNumber, String bizId) {...} }
// class GoodsService { public Boolean deliverGoods(DeliverReq deliverReq) {...} }
// class IQiYiCardService { public void grantToken(String mobile, String awardNumber) {...} }
// Classes de requête et de réponse (simplifiées pour l'exemple)
class AwardReq {
private Integer awardType;
private String uId;
private String awardNumber;
private String bizId;
private Map<string string=""> extMap;
public Integer getAwardType() { return awardType; }
public String getuId() { return uId; }
public String getAwardNumber() { return awardNumber; }
public String getBizId() { return bizId; }
public Map<string string=""> getExtMap() { return extMap; }
}
class AwardRes {
private String code;
private String message;
public AwardRes(String code, String message) { this.code = code; this.message = message; }
}
class CouponResult {
private String code;
private String info;
public String getCode() { return code; }
public String getInfo() { return info; }
}
class DeliverReq {
// ... champs pour la livraison de biens physiques
public void setUserName(String name) {}
public void setUserPhone(String phone) {}
public void setSku(String sku) {}
public void setOrderId(String orderId) {}
public void setConsigneeUserName(String name) {}
public void setConsigneeUserPhone(String phone) {}
public void setConsigneeUserAddress(String address) {}
}
public class PrizeController {
private Logger logger = LoggerFactory.getLogger(PrizeController.class);
public AwardRes awardToUser(AwardReq req) {
String reqJson = JSON.toJSONString(req);
AwardRes awardRes = null;
try {
logger.info("Début de la distribution de la récompense {} avec req: {}", req.getuId(), reqJson);
if (req.getAwardType() == 1) { // Coupon
CouponService couponService = new CouponService();
CouponResult couponResult = couponService.sendCoupon(req.getuId(), req.getAwardNumber(), req.getBizId());
if ("0000".equals(couponResult.getCode())) {
awardRes = new AwardRes("0000", "Distribution réussie");
} else {
awardRes = new AwardRes("0001", couponResult.getInfo());
}
} else if (req.getAwardType() == 2) { // Bien physique
GoodsService goodsService = new GoodsService();
DeliverReq deliverReq = new DeliverReq();
deliverReq.setUserName(queryUserName(req.getuId()));
deliverReq.setUserPhone(queryUserPhoneNumber(req.getuId()));
deliverReq.setSku(req.getAwardNumber());
deliverReq.setOrderId(req.getBizId());
deliverReq.setConsigneeUserName(req.getExtMap().get("consigneeUserName"));
deliverReq.setConsigneeUserPhone(req.getExtMap().get("consigneeUserPhone"));
deliverReq.setConsigneeUserAddress(req.getExtMap().get("consigneeUserAddress"));
Boolean isSuccess = goodsService.deliverGoods(deliverReq);
if (isSuccess) {
awardRes = new AwardRes("0000", "Distribution réussie");
} else {
awardRes = new AwardRes("0001", "Échec de la distribution");
}
} else if (req.getAwardType() == 3) { // Carte tierce partie
String bindMobileNumber = queryUserPhoneNumber(req.getuId());
IQiYiCardService iQiYiCardService = new IQiYiCardService();
iQiYiCardService.grantToken(bindMobileNumber, req.getAwardNumber());
awardRes = new AwardRes("0000", "Distribution réussie");
} else {
awardRes = new AwardRes("0002", "Type de récompense non supporté");
}
logger.info("Distribution de la récompense terminée pour {}.", req.getuId());
} catch (Exception e) {
logger.error("Échec de la distribution de la récompense {} avec req: {}", req.getuId(), reqJson, e);
awardRes = new AwardRes("0001", e.getMessage());
}
return awardRes;
}
// Données fictives pour l'exemple
private String queryUserName(String uId) { return "UtilisateurExemple"; }
private String queryUserPhoneNumber(String uId) { return "15200101232"; }
}
</string></string>
Cette approche présente plusieurs inconvénients :
- Violation du Principe Ouvert/Fermé : Toute modification ou ajout de nouveau type de récompense nécessite de modifier la classe
PrizeControllerexistante. - Violation du Principe de Responsabilité Unique : La classe
PrizeControllergère la logique de distribution pour plusieurs types de récompenses, rendant la classe surchargée. - Difficulté de Maintenance : La maintenance et le débogage deviennent complexes avec l'augmentation du nombre de conditions
if-else.
Introduction au Patron Factory
Le patron Factory vise à encapsuler la logique de création d'objets. Au lieu que le client instancie directement les objets concrets, il interagit avec une usine qui se charge de créer l'objet approprié en fonction des paramètres fournis. Cela permet de découpler le client de la connaissance des implémentations concrètes.
Structure du Code avec le Patron Factory
1. Interface Commune pour les Récompenses
Nous définissons une interface commune pour toutes les opérations de distribution de récompenses.
import java.util.Map;
public interface RewardDistributionService {
/**
* Distribue une récompense à un utilisateur.
* @param userId L'identifiant de l'utilisateur.
* @param rewardId L'identifiant de la récompense.
* @param transactionId L'identifiant de la transaction métier.
* @param extraParameters Paramètres supplémentaires (ex: adresse de livraison).
* @throws Exception En cas d'erreur lors de la distribution.
*/
void distribute(String userId, String rewardId, String transactionId, Map<string string=""> extraParameters) throws Exception;
}
</string>
2. Implémentations Spécifiques pour Chaque Type de Récompense
a) Distribution de Coupons
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
public class CouponDistributionService implements RewardDistributionService {
private Logger logger = LoggerFactory.getLogger(CouponDistributionService.class);
private CouponService couponService = new CouponService(); // Service réel pour les coupons
@Override
public void distribute(String userId, String rewardId, String transactionId, Map<string string=""> extraParameters) throws Exception {
logger.info("Distribution de coupon => UserID: {}, RewardID: {}, TransactionID: {}, Extras: {}",
userId, rewardId, transactionId, JSON.toJSONString(extraParameters));
CouponResult result = couponService.sendCoupon(userId, rewardId, transactionId);
logger.info("Résultat de la distribution de coupon: {}", JSON.toJSONString(result));
if (!"0000".equals(result.getCode())) {
throw new RuntimeException(result.getInfo());
}
}
}
</string>
b) Distribution de Biens Physiques
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
public class PhysicalGoodsDistributionService implements RewardDistributionService {
private Logger logger = LoggerFactory.getLogger(PhysicalGoodsDistributionService.class);
private GoodsService goodsService = new GoodsService(); // Service réel pour les biens physiques
@Override
public void distribute(String userId, String rewardId, String transactionId, Map<string string=""> extraParameters) throws Exception {
logger.info("Distribution de bien physique => UserID: {}, RewardID: {}, TransactionID: {}, Extras: {}",
userId, rewardId, transactionId, JSON.toJSONString(extraParameters));
DeliverReq deliverReq = new DeliverReq();
deliverReq.setUserName(fetchUserName(userId)); // Récupérer le nom de l'utilisateur
deliverReq.setUserPhone(fetchUserPhoneNumber(userId)); // Récupérer le numéro de téléphone
deliverReq.setSku(rewardId);
deliverReq.setOrderId(transactionId);
deliverReq.setConsigneeUserName(extraParameters.get("consigneeUserName"));
deliverReq.setConsigneeUserPhone(extraParameters.get("consigneeUserPhone"));
deliverReq.setConsigneeUserAddress(extraParameters.get("consigneeUserAddress"));
Boolean success = goodsService.deliverGoods(deliverReq);
logger.info("Résultat de la distribution de bien physique: {}", success);
if (!success) {
throw new RuntimeException("Échec de la distribution du bien physique");
}
}
// Méthodes fictives pour récupérer les informations utilisateur
private String fetchUserName(String userId) { return "NomUtilisateur"; }
private String fetchUserPhoneNumber(String userId) { return "15200101232"; }
}
</string>
c) Distribution de Cartes Tierces (Exemple : Carte d'Abonnement IQiYi)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
public class ThirdPartyCardDistributionService implements RewardDistributionService {
private Logger logger = LoggerFactory.getLogger(ThirdPartyCardDistributionService.class);
private IQiYiCardService iQiYiCardService = new IQiYiCardService(); // Service réel pour les cartes tierces
@Override
public void distribute(String userId, String rewardId, String transactionId, Map<string string=""> extraParameters) throws Exception {
String userMobile = fetchUserMobile(userId); // Récupérer le mobile de l'utilisateur
iQiYiCardService.grantToken(userMobile, transactionId); // Utilisation de transactionId comme ID de carte/token
logger.info("Distribution de carte tierce partie => UserID: {}, RewardID: {}, TransactionID: {}, Extras: {}",
userId, rewardId, transactionId, JSON.toJSONString(extraParameters));
logger.info("Résultat de la distribution de carte tierce partie: success");
}
// Méthode fictive pour récupérer le numéro de mobile
private String fetchUserMobile(String userId) { return "15200101232"; }
}
</string>
Chaque implémentation se concentre sur un seul type de récompense, améliorant la maintenabilité et l'extensibilité. L'ajout d'un nouveau type de récompense n'implique que la création d'une nouvelle classe implémentant l'interface RewardDistributionService.
3. L'Usine (Factory)
La classe Factory est responsable de la création des instances de service de distribution appropriées en fonction d'un type donné.
import java.util.HashMap;
import java.util.Map;
public class RewardDistributionFactory {
private static final Map<integer rewarddistributionservice=""> services = new HashMap<>();
static {
// Initialisation des services disponibles
services.put(1, new CouponDistributionService()); // 1: Coupon
services.put(2, new PhysicalGoodsDistributionService()); // 2: Bien Physique
services.put(3, new ThirdPartyCardDistributionService());// 3: Carte Tierce
}
/**
* Renvoie une instance de service de distribution de récompense.
* @param rewardType Le type de récompense (ex: 1 pour coupon, 2 pour bien physique).
* @return Une instance de RewardDistributionService.
* @throws IllegalArgumentException Si le type de récompense n'est pas supporté.
*/
public RewardDistributionService getService(Integer rewardType) {
if (rewardType == null) {
throw new IllegalArgumentException("Le type de récompense ne peut pas être nul.");
}
RewardDistributionService service = services.get(rewardType);
if (service == null) {
throw new IllegalArgumentException("Type de récompense non supporté: " + rewardType);
}
return service;
}
// Optionnel: Méthode alternative utilisant if/else ou switch si une map n'est pas souhaitée
public RewardDistributionService getServiceAlternative(Integer rewardType) {
if (rewardType == null) {
throw new IllegalArgumentException("Le type de récompense ne peut pas être nul.");
}
switch (rewardType) {
case 1: return new CouponDistributionService();
case 2: return new PhysicalGoodsDistributionService();
case 3: return new ThirdPartyCardDistributionService();
default: throw new IllegalArgumentException("Type de récompense non supporté: " + rewardType);
}
}
}
</integer>
L'usine utilise une Map pour associer un type de récompense (représenté par un entier) à l'instance de service correspondante. Cette approche est plus flexible et facilement extensible qu'une série de if-else ou switch.
Utilisation du Patron Factory
Voici comment le client (par exemple, le PrizeController) utiliserait l'usine pour distribuer des récompenses :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.HashMap;
public class PrizeControllerRevised {
private Logger logger = LoggerFactory.getLogger(PrizeControllerRevised.class);
private RewardDistributionFactory rewardFactory = new RewardDistributionFactory();
public AwardRes awardToUser(AwardReq req) {
String reqJson = JSON.toJSONString(req);
AwardRes awardRes;
try {
logger.info("Début de la distribution de la récompense {} avec req: {}", req.getuId(), reqJson);
RewardDistributionService distributionService = rewardFactory.getService(req.getAwardType());
distributionService.distribute(req.getuId(), req.getAwardNumber(), req.getBizId(), req.getExtMap());
awardRes = new AwardRes("0000", "Distribution réussie");
logger.info("Distribution de la récompense terminée pour {}.", req.getuId());
} catch (IllegalArgumentException e) {
logger.error("Erreur de configuration du type de récompense {} pour l'utilisateur {}: {}", req.getAwardType(), req.getuId(), e.getMessage());
awardRes = new AwardRes("0002", e.getMessage());
} catch (Exception e) {
logger.error("Échec de la distribution de la récompense {} avec req: {}", req.getuId(), reqJson, e);
awardRes = new AwardRes("0001", e.getMessage());
}
return awardRes;
}
// Les classes AwardReq, AwardRes, CouponResult, DeliverReq sont les mêmes que dans l'exemple précédent.
// Les classes CouponService, GoodsService, IQiYiCardService sont supposées exister.
}
// Classes de support pour l'exemple PrizeControllerRevised (identiques à l'exemple précédent)
class AwardReq { /* ... */ }
class AwardRes { public AwardRes(String code, String message) {} }
class CouponResult { public String getCode() { return "0000"; } public String getInfo() { return ""; } }
class DeliverReq { /* ... */ }
class CouponService { public CouponResult sendCoupon(String uId, String awardNumber, String bizId) { return new CouponResult(); } }
class GoodsService { public Boolean deliverGoods(DeliverReq deliverReq) { return true; } }
class IQiYiCardService { public void grantToken(String mobile, String awardNumber) {} }
Conclusion
Le patron Factory améliore significativement la structure du code en encapsulant la création d'objets et en réduisant le couplage entre le client et les implémentations concrètes. L'approche favorise la maintenabilité, l'extensibilité et le respect des principes de conception logicielle, rendant le système plus robuste face aux évolutions futures des exigences.
En résumé, pour maîtriser le patron Factory dans ce contexte :
- L'usine gère la création des récompenses.
- Coupons, biens physiques et cartes tierces sont des exemples concrets.
- L'usine utilise une interface commune pour interagir avec les différentes implémentations.
- Chaque implémentation gère sa logique spécifique sous le contrôle de l'usine.