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 :
- Modularité : La logique de traitement est encapsulée dans des classes dédiées, ce qui rend le code plus modulaire et plus facile à comprendre.
- Réutilisabilité : Les fonctionnalités encapsulées peuvent être réutilisées facilement dans d’autres parties de l’application.
- Maintenabilité : En supprimant les dépendances inutiles, le code devient plus facile à tester et donc à maintenir.
À noter qu’il est toujours possible de faire facilement des conversions entre Items
et List<Items>
:
List<Item> itemList = items.items();
Items items = new Items(itemList);
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 !