Pattern Strategy
Définissez une famille d'algorithmes, encapsulez chacun dans une fonction, et échangez-les à l'exécution.
Le Problème
Vous développez un checkout e-commerce. Les clients ont différentes réductions : les utilisateurs réguliers paient plein tarif, les membres VIP ont 20% de réduction, les codes promo donnent 15%.
L'approche naïve :
function calculatePrice(price: number, customerType: string): number {
if (customerType === "regular") return price;
if (customerType === "vip") return price * 0.8;
if (customerType === "promo") return price * 0.85;
// ... grossit avec chaque nouveau type de réduction
}
Chaque nouvelle réduction = modifier cette fonction. Ça devient un cauchemar de maintenance.
La Solution
Chaque réduction devient une fonction autonome. Choisissez la bonne par sa clé à l'exécution :
import { createStrategies } from "@pithos/core/eidos/strategy/strategy";
const pricing = createStrategies({
regular: (price: number) => price,
vip: (price: number) => price * 0.8,
promo: (price: number) => price * 0.85,
});
pricing.execute("vip", 100); // 80
Nouvelle réduction ? Ajoutez une ligne. Pas de conditions à mettre à jour.
Analogie
Une app GPS avec plusieurs options d'itinéraire : le plus rapide, le plus court, sans péages, panoramique. Chacun est un algorithme séparé, mais l'interface de navigation ne se soucie pas de celui qui est actif — elle appelle juste "calculer l'itinéraire" sur la stratégie sélectionnée.
Démo
- strategies.ts
- Usage
/**
* Pricing strategies using Pithos Strategy pattern.
*
* This is the core of the Strategy pattern:
* each discount rule is a standalone function, selected by key at runtime.
* No interface, no concrete classes, no context — just functions in a record.
*/
import { createStrategies } from "@pithos/core/eidos/strategy/strategy";
import type { PricingInput, PricingResult, Product } from "./types";
/** Calculate subtotal from products */
function calculateSubtotal(products: Product[]): number {
return products.reduce((sum, p) => sum + p.basePrice * p.quantity, 0);
}
/** Apply a discount percentage to a subtotal */
function applyDiscount(subtotal: number, percent: number): PricingResult {
const discount = subtotal * (percent / 100);
return {
subtotal,
discount,
total: subtotal - discount,
discountPercent: percent,
};
}
export const pricingStrategies = createStrategies({
/** Regular pricing — no discount */
regular: ({ products }: PricingInput) =>
applyDiscount(calculateSubtotal(products), 0),
/** VIP pricing — 20% off everything */
vip: ({ products }: PricingInput) =>
applyDiscount(calculateSubtotal(products), 20),
/** Promo pricing — 15% off */
promo: ({ products }: PricingInput) =>
applyDiscount(calculateSubtotal(products), 15),
/** Bulk pricing — 25% off for orders over $300 */
bulk: ({ products }: PricingInput) => {
const subtotal = calculateSubtotal(products);
return applyDiscount(subtotal, subtotal >= 300 ? 25 : 0);
},
});
import { useState, useMemo, useCallback } from "react";
import { pricingStrategies } from "@/lib/strategies";
import { INITIAL_PRODUCTS } from "@/data/products";
import type { PricingStrategyKey, Product } from "@/lib/types";
export function usePricingCalculator() {
const [strategy, setStrategy] = useState<PricingStrategyKey>("regular");
const [products, setProducts] = useState<Product[]>(INITIAL_PRODUCTS);
const result = useMemo(
() => pricingStrategies.execute(strategy, { products }),
[strategy, products],
);
const updateQuantity = useCallback((index: number, quantity: number) => {
setProducts((prev) =>
prev.map((p, i) => (i === index ? { ...p, quantity: Math.max(0, quantity) } : p)),
);
}, []);
return { strategy, setStrategy, products, result, updateQuantity };
}
Quand l'Utiliser
- Plusieurs algorithmes pour la même tâche, sélectionnés à l'exécution
- Vous voulez ajouter de nouvelles variantes sans modifier le code existant
- Logique conditionnelle complexe qui choisit entre des opérations similaires
API
- createStrategies — Construire un résolveur de stratégies à partir de fonctions nommées
- safeStrategy — Wrapper les stratégies pour retourner
Resultau lieu de throw - withFallback — Chaîner une stratégie principale avec un backup
- withValidation — Valider l'input avant exécution