GRASP — General Responsibility Assignment Software Patterns — est un ensemble de neuf principes introduits par Craig Larman dans « Applying UML and Patterns » (1997). Alors que SOLID vous indique comment structurer les relations de classes individuelles, GRASP répond à une question plus fondamentale à laquelle chaque développeur est confronté au moment de la conception : quel objet doit être responsable de ce comportement ? GRASP fournit des heuristiques nommées pour répondre à cette question systématiquement plutôt que par intuition. Les modèles ne sont pas des algorithmes : ce sont des lentilles qui rendent le processus de raisonnement explicite et communicable.

Les modèles GRASP sont des heuristiques de conception et non des règles strictes. Plusieurs modèles fonctionnent ensemble sur la même décision de conception : par exemple, un contrôleur satisfait simultanément aux modèles Contrôleur et Créateur. La valeur de GRASP ne réside pas dans l'application de chaque modèle isolément, mais dans le partage d'un vocabulaire pour les discussions sur l'attribution des responsabilités.
Les neuf modèles GRASP en un coup d'œil
Cliquez sur chaque carte pour un aperçu rapide - des exemples détaillés suivent ci-dessous
Expert en information : le modèle le plus utilisé

Information Expert est le modèle GRASP le plus fréquemment appliqué et la source de la question de conception la plus courante : « Où dois-je placer cette méthode ? La réponse : dans la classe qui contient les données dont la méthode a besoin. Si le calcul du total d’une commande nécessite de connaître les éléments de campagne, la classe Order (qui contient les éléments) doit calculer son propre total – et non un service externe qui accède à l’intérieur de Order pour extraire les articles.

Cliquez sur les lignes en surbrillance pour voir Information Expert appliqué correctement et violé
java
1
// ── VIOLATION: behavior separated from the data it needs ────────
2
class OrderPricingService {
3
  Money calculateTotal(Order order) {
4
    return order.getItems().stream()                      // reaching into Order
5
      .map(i -> i.getPrice().multiply(i.getQuantity()))   // using Order's data
6
      .reduce(Money.ZERO, Money::add);
7
  }
8
}
9
10
// ── FIX: assign responsibility to the class with the information ─
11
class Order {
12
  private final List<OrderLineItem> items;
13
14
  Money total() {
15
    return items.stream()
16
      .map(OrderLineItem::subtotal)  // delegate to each item's expert
17
      .reduce(Money.ZERO, Money::add);
18
  }
19
}
20
21
class OrderLineItem {
22
  private final Money price;
23
  private final int quantity;
24
  Money subtotal() { return price.multiply(quantity); }  // expert for its own data
25
}
Contrôleur : séparation des événements système de la logique de domaine

Le modèle Controller répond : qui doit gérer un événement système (une action utilisateur, un message entrant, un déclencheur planifié) ? Pas les objets de domaine – ils devraient ignorer les mécanismes de livraison. Pas l’interface utilisateur – elle doit ignorer la logique métier. Le Controller est une classe non-UI qui reçoit des événements et délègue aux objets de domaine. Il coordonne mais ne calcule pas. En pratique : un contrôleur REST est un contrôleur GRASP. Il en va de même pour un consommateur de messages, un gestionnaire de commandes CLI ou un planificateur de travaux par lots : tous sont des contrôleurs pour différents mécanismes de livraison.

Cliquez sur les lignes en surbrillance pour voir le modèle du contrôleur et ses limites
java
1
// ── GRASP Controller: delegates, does not compute ───────────────
2
@RestController
3
class PlaceOrderController {
4
  private final PlaceOrderService service;
5
6
  @PostMapping("/orders")
7
  ResponseEntity<OrderResponse> place(@Valid @RequestBody PlaceOrderRequest req) {
8
    // Controller responsibilities: receive, validate format, map, delegate
9
    PlaceOrderCommand cmd = req.toCommand();     // map input
10
    Order order = service.placeOrder(cmd);       // delegate ALL logic
11
    return ResponseEntity.ok(OrderResponse.from(order)); // map output
12
    // NO business logic here — zero domain rules in the controller
13
  }
14
}
15
16
// ── Same domain, different controller (Kafka consumer) ──────────
17
@KafkaListener(topics = "order-requests")
18
class PlaceOrderEventController {
19
  private final PlaceOrderService service;
20
  void handle(PlaceOrderEvent event) {
21
    service.placeOrder(event.toCommand());  // same service, different entry point
22
  }
23
}
Polymorphisme : remplacer les conditions par des types

Le modèle de polymorphisme aborde la variation comportementale basée sur le type. Lorsque vous voyez un commutateur ou if/else sur un champ de type, cela indique généralement que le modèle de polymorphisme s'applique : chaque branche du conditionnel doit être un sous-type et le comportement doit être distribué par le système de types - et non par l'appelant. Ceci est directement lié à OCP : le commutateur grandit avec chaque nouveau type ; le polymorphisme signifie que l'ajout d'un nouveau type ajoute une nouvelle classe sans toucher au code existant.

Cliquez sur les lignes en surbrillance pour voir comment le polymorphisme remplace un commutateur de type
java
1
// ── VIOLATION: type switch that grows with every new discount ────
2
Money applyDiscount(Order order, DiscountType type) {
3
  return switch (type) {
4
    case PERCENTAGE -> order.total().multiply(0.9);
5
    case FIXED      -> order.total().subtract(Money.of(10));
6
    case BOGO       -> order.total().multiply(0.5);  // added later — modified method
7
  };
8
}
9
10
// ── FIX: each type encapsulates its own behavior ─────────────────
11
interface DiscountPolicy { Money apply(Money total); }
12
13
record PercentageDiscount(double rate) implements DiscountPolicy {
14
  public Money apply(Money total) { return total.multiply(1 - rate); }
15
}
16
record FixedDiscount(Money amount) implements DiscountPolicy {
17
  public Money apply(Money total) { return total.subtract(amount); }
18
}
19
record BogoDiscount() implements DiscountPolicy {
20
  public Money apply(Money total) { return total.multiply(0.5); }
21
}
22
23
// Caller — never changes when new discount types are added:
24
Money discounted = policy.apply(order.total());
Variations protégées : la racine de tous les modèles

Larman a qualifié les variations protégées de « principe le plus fondamental dans la conception de logiciels » – et il est difficile de prétendre le contraire. Chaque modèle de conception, chaque style architectural, chaque abstraction existe pour protéger le code stable des changements volatils. L'application pratique : identifiez les points d'instabilité de votre système (API externes, règles métier qui changent fréquemment, technologie de base de données, bibliothèques tierces) et enveloppez chacun dans une interface stable. Le code qui dépend de l'interface est protégé — il ne changera pas lorsque l'instabilité changera.

Cliquez sur chaque carte pour voir une application de variations protégées dans un système réel
Fabrication pure : quand les objets de domaine ne suffisent pas

Pure Fabrication résout la tension entre une cohésion élevée et la nécessité de placer les problèmes d’infrastructure quelque part. Pensez à la persistance : l’entité Order ne doit pas savoir comment se sauvegarder dans une base de données – cela violerait le SRP et introduirait une infrastructure dans le domaine. Mais la logique de la persistance doit vivre quelque part. Un référentiel est une pure fabrication : une classe inventée à des fins de conception qui n'a pas d'équivalent direct dans le domaine du problème. Il existe pour donner au comportement de persistance un foyer à haute cohésion et à faible couplage. Autres exemples : Mapper, Logger, EventPublisher, CacheManager. Tous sont de pures fabrications – inventées pour la conception, pas pour le domaine.

Les modèles GRASP correspondent aux modèles de conception du GoF – la connexion rend les deux plus clairs
Modèle SAISIRModèle(s) GoF correspondant(s)Ce que ça fait en pratique
Expert en informations— (principe, pas un modèle)La méthode vit dans la classe avec les données
CréateurMéthode d'usine, usine abstraiteCréation d'objets proche du contexte d'utilisation
ContrôleurFaçade, CommandePoint d'entrée unique pour les événements système
Couplage faibleFaçade, Médiateur, AdaptateurRéduit la surface de dépendance entre les modules
Haute cohésion— (principe, pas un modèle)Comportement associé dans une classe
PolymorphismeStratégie, État, CommandementRépartition basée sur le type au lieu de conditions
Fabrication pureRéférentiel, mappeur, enregistreurClasse inventée pour le comportement des infrastructures
IndirectionAdaptateur, proxy, décorateur, pontL'objet intermédiaire absorbe le couplage
Variantes protégéesTous les modèles — c'est la racineInterface stable autour du point de changement volatil
À retenir : GRASP propose neuf heuristiques nommées pour les décisions d'attribution de responsabilités les plus courantes dans la conception orientée objet. Information Expert maintient le comportement proche des données. Le contrôleur sépare la livraison du domaine. Le polymorphisme remplace les conditions par des types. Pure Fabrication donne à l’infrastructure une maison propre. Les variations protégées sont le principe unificateur derrière chaque abstraction et chaque modèle de conception.
Et ensuite : SOLID et GRASP régissent la manière dont les responsabilités sont attribuées au sein et entre les classes. La leçon suivante revient plus loin sur trois heuristiques transversales – KISS, DRY et YAGNI – qui régissent le budget global de complexité d'une conception et explique le principe de moindre surprise qui les relie.