Améliorez votre code java avec le pattern LEP

Pour ce premier article je vais vous présenter un pattern simple, mais puissant que j’ai nommée LEP pour List Encapsulation Pattern. L’idée consiste à encapsuler les listes de données dans des classes dédiées pour simplifier la compréhension et la réutilisabilité du code.

Pourquoi un Pattern ?

Martin Folwer explique sur son site que les patterns sont plus populaires lorsqu’on leur donne un nom sympa. En donnant le nom LEP à ce pattern, nous pouvons mieux communiquer sur celui-ci et encourager son adoption.

Illustration du Problème

Pour comprendre l’importance du LEP, considérons un exemple concret. Imaginons que nous ayons une application qui nécessite de trouver l’article le moins cher de tous.
Dans un premier temps, nous pouvons implémenter cette fonctionnalité de la manière suivante :

public record Item(double price) {
}

public interface ItemRepository {
    List<Item> findAll();
}

public class ItemService {
    
    private final ItemRepository repository;
    
    public ItemService(ItemService repository) {
        this.repository = repository;
    }

    public Optional<Item> findCheapest() {
        return findCheapest(repository.findAll());
    }

    private Optional<Item> findCheapest(List<Item> items) {
        return items.stream().min(Comparator.comparing(Item::price));//Ici on recherche l'article le moins cher de la liste
    }
}

Pour l’instant le problème ne saute pas aux yeux : le code est concis, clair et testable.

Évolution du code avec le besoin

Imaginons maintenant que l’on veuille gérer un panier d’achats contenant des articles. Cette nouvelle notion peut être représentée par une nouvelle classe appelée ShoppingCart :

public record ShoppingCart(List<Item> items) {
}

Si on souhaite trouver l’article le moins cher de ce panier, il faut ajouter une nouvelle méthode findCheapestItem() sur la classe ShoppingCart. En essayant de réutiliser la méthode private Optional<Item> findCheapest(List<Item> items) de la classe ItemService, on obtient le code suivant :

public record ShoppingCart(List<Item> items) {

    public Optional<Item> findCheapestItem() {
        return new ItemService(null).findCheapest(items);
    }
}

On se rend compte que le code n’est pas clair. Le constructeur de ItemService a besoin d’un ItemRepository, mais la méthode findCheapest que nous souhaitons appeler n’en a pas besoin. On passe donc un objet ItemRepository à null pour instancier l’objet ItemService. Tout cela pour simplement appeler la méthode findCheapest.

Tous ces efforts pour un résultat décevant peut nous pousser à dupliquer le code :

public record ShoppingCart(List<Item> items) {

    public Optional<Item> findCheapestItem() {
        return items.stream().min(Comparator.comparing(Item::price));
    }
}

Étant donné qu’il ne s’agit que d’une seule ligne de code, la duplication ne sera pas même pas détectée par les outils d’analyse de code. Pourtant, il y a bien un problème de conception.

Solution avec le Pattern LEP

Le LEP propose une solution simple et efficace à ce problème. En encapsulant la liste d’articles dans une classe dédiée, le code devient plus modulaire, plus réutilisable et plus facile à maintenir.
Voici comment nous pouvons réécrire notre exemple en utilisant le LEP :

public record Item(double price) {
}

//Cette nouvelle classe encapsule la liste des articles et expose une méthode de recherche de l'article le moins cher
public record Items(List<Item> items) {

    public Optional<Item> findCheapest() {
        return items.stream().min(Comparator.comparing(Item::getPrice));
    }
}

//Le panier d'articles se compose maintenant des Items à la place d'une List<Item>
public record ShoppingCart(Items items) {

    public Optional<Item> findCheapestItem() {
        return items.findCheapest();//L'appel vers le code existant est simplifé
    }
}

public interface ItemRepository {
    Items findAll();//Le recherche retourne maintenant des Items au lieu d'une List<Item>
}

public class ItemService {

    private final ItemRepository repository;

    public ItemService(ItemService repository) {
        this.repository = repository;
    }

    public Optional<Item> findCheapest() {
        return repository.findAll().findCheapest();//La recherche appelle le code existant
    }
}

Avantages du Pattern LEP

En adoptant le LEP, nous bénéficions donc de plusieurs avantages :

À noter qu’il est toujours possible de faire facilement des conversions entre Items et List<Items> :

Conclusion

Le List Encapsulation Pattern est donc une pratique simple, mais puissante pour améliorer notre code Java. En encapsulant les listes dans des classes dédiées, nous pouvons rendre notre code plus modulaire, plus réutilisable et plus facile à maintenir.

Le pattern peut être introduit progressivement dans votre projet donc essayez-le !