Patron de conception: La fabrique
Les sources de cet article sont disponibles sur GitHub.
Présentation
Cet article est le premier d’une série touchant aux patrons de conception. Les design patterns sont aux développeurs ce que les outils sont aux menuisiers, alors il est primordial de les comprendre en détails afin de se doter d’un coffre à outils complet et de savoir lequel peut faire le travail face à un obstacle dans la conception du code.
Que diriez-vous à un menuisier qui visse à l’aide d’un marteau?
Les patrons de conception ont été mis au point afin de répondre à des problèmes spécifiques et réccurants dans les projets de développement logiciel. Plus vous les connaissez bien, plus vous trouverez la solution à vos problèmes rapidement.
La fabrique (Factory Pattern)
Une fabrique, ou factory, est un des patrons de conception les plus utilisés par les développeurs. Il permet notamment d’obtenir une instance de classe concrète sans se soucier de l’implémentation de cette dernière. Dans un exemple typique, la fonction de création d’instance de la fabrique ne contient qu’un seul paramètre qui peut être choisi par l’utilisateur, défini dans une configuration au niveau applicatif, ou reçu dans une itération.
1 2 |
ConcreteObject obj1 = Factory.makeInstance(ConcreteObjectType.ONE); obj1.doSomething(); |
Le problème
Lors d’un projet web, vous devez implémenter les validations des entités passées en dans le corps de chaque requête. Vous disposez de plusieurs types d’entité et votre méthode de validation est exécutée en amont de la requête. Vous pourriez être tempté d’implémenter une fonction isValid()
dans l’interface commune des entités, leur délégant ainsi la responsabilité de se valider elles-mêmes.
1 2 3 4 5 6 7 8 9 10 |
/** * Intercepteur de requêtes exécuté avant chaque requête. * * @param entity L'entité provenant de la requête * @return Vrai: Poursuivre l'exécution * Faux: Stopper l'exécution */ public boolean requestInterceptor(Entity entity) { return entity != null ? entity.isValid() : false; } |
Bien que cela puisse sembler une solution efficace, elle apporte un second problème. Dans les entités complexes, et dépendant du langage, elles peuvent vite devenir assez volumineuses (en SLOC). On pense notamment au accesseurs, mutateurs, fonctions de comparaison lors du tri, la surcharge des fonctions hashCode()
et equals()
, et souvent bien plus. Lui laisser la responsabilité de se valider elle-même brise le principe de responsabilité unique de la conception de logiciels.
La solution
Une solution simple à ce problème est d’encapsuler la logique de validation des entités dans des validateurs distincts. Par exemple, pour valider l’entité User
, nous aurions un UserValidator
implémentant une interface Validator
déclarant une fonction validate(User userToValidate)
.
1 2 3 |
public interface Validator<T extends Entity> { boolean validate(T entityToValidate); } |
1 2 3 4 5 6 7 8 9 10 11 |
public class UserValidator implements Validator<User> { @Override public boolean validate(User entityToValidate) { return entityToValidate != null && entityToValidate.getFirstName() != null && entityToValidate.getLastName() != null && entityToValidate.getFirstName().trim().length() > 0 && entityToValidate.getLastName().trim().length() > 0; } } |
À ce point, nous avons une instance concrète à créer selon un paramètre, le type de l’entité, et nous n’avons pas à nous soucier de son implémentation puisque nous exécuterons la fonction déclarée au niveau de l’interface. C’est ici que la fabrique prend tout son sens:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class ValidatorFactory { @SuppressWarnings("unchecked") public static <T extends Entity> Validator<T> makeInstance(T entityType) { Validator<T> validatorReturned = null; if (entityType instanceof User) { validatorReturned = (Validator<T>) new UserValidator(); } else if (entityType instanceof Book) { validatorReturned = (Validator<T>) new BookValidator(); } return validatorReturned; } } |
À l’aide des types génériques, nous pouvons nous assurer que nous obtiendrons une instance de classe qui sera celle représentant un des sous types de Entity
. Puis, à l’aide d’une condition comparant le type de l’entité, nous implémentons la création des validateurs. Pour valider l’entité dans le contexte de l’exemple ci-dessus, l’appel à la fonction de validation se ferait comme ceci:
1 2 3 4 5 6 7 8 9 10 11 12 |
/** * Intercepteur de requêtes exécuté avant chaque requête. * * @param entity L'entité provenant de la requête * @return Vrai: Poursuivre l'exécution * Faux: Stopper l'exécution */ public boolean requestInterceptor(Entity entity) { return entity != null && ValidatorFactory .makeInstance(entity) .validate(entity); } |
Conclusion
En somme, la fabrique est un outil puissant pour réduire le couplage entre les classes et elle peut être utilisée lors de phases de réingénérie des applications obsolètes afin d’en améliorer l’architecture. Elle peut aussi être utilisée de pair avec un conteneur d’injection de dépendances afin d’éviter de coupler la couche métier avec le framework et ainsi soustraire les annotations de votre code.
N’hésitez pas à donner votre opinion dans les commentaires ci-dessous!
Code source
Les sources de cet article sont disponibles sur GitHub.
1 |
git clone http://github.com/EzoQC/FactoryPattern.git |
Commentaires
1 commentaire
I could not refrain from commenting. Very well written!
Laisser un commentaire