La réutilisation de code est un pratique consistant à utiliser du code existant plutôt que d'écrire du nouveau code. https://en.wikipedia.org/wiki/Code_reuse C'est en en général une façon de pratiquer l'heuristique [[DRY - Don’t repeat yourself]]. Il existe différentes manières de réutiliser du code [[Glossaire#Encapsuler (programmation)|encapsulé]] à plusieurs niveaux: - [[Glossaire#Variable|variable]] / [[Glossaire#Constante|constante]] : réutiliser le résultat d'un calcul ou la valeur d'une expression - [[Glossaire#Fonction|fonction]] / [[Glossaire#Méthode|méthode]] : réutiliser une séquence d'instructions (paramétrable ou non) - [[Glossaire#Objet|objet]] / [[Glossaire#Classe|classe]] / [[Glossaire#Type énuméré|enumération]] / [[Glossaire#Record (java)|record]] : réutiliser un ensemble de données et de comportements cohérents - par [[Glossaire#Polymorphisme|polymorphisme]] - par [[Glossaire#Composition|composition]]/[[Glossaire#Délégation|délégation]] - par [[Glossaire#héritage de type|héritage de type]] - par [[Glossaire#Héritage d’implémentation|héritage d'implémentation]] - à l'aide des [[Glossaire#Type générique|types génériques]] - [[Glossaire#Bibliothèque|bibliothèques]] / [[Glossaire#Dépendance|dépendances]] : des modules de code écrits en interne ou par des [[Glossaire#Tierce-partie (dépendance)|tierces-parties]]. En général on peut les importer à l'aide de [[Glossaire#Gestionnaire de dépendances|gestionnaires de dépendances]] tels que [[Glossaire#Maven|Maven]] ou [[Glossaire#Gradle|gradle]] ## Délégation La délégation consiste à transférer un comportement à une autre classe. ```java public class A { private B b = new B(); public void methodA() { b.methodB(); } } ``` https://en.wikipedia.org/wiki/Delegation_pattern ### Délégation hybride Permet à une classe d'utiliser les fonctionnalités d'une autre classe sans utiliser d'héritage. En passant l'appelant en paramètre de la classe appelée. (ex. `b.methodB( this );`) ```java public class A { private B b = new B(); public void methodA() { b.methodB( this ); } } ``` > [!example]- Exemple > ```java > public class Carre implements Forme { > private final float cote; > private final String name; > private final AireFormatter formatter= new SquareCentimeterAireFormatter(); > > public Carre(float cote) { > this.cote = cote; > this.name = "Carré"; > } > > @Override > public float aire() { > return cote * cote; > } > > @Override > public String infos() { > // La responsabilité de formatter est déléguée à un autre objet > return formatter.infos(this,name); > } > } > ``` > > L'interface `AireFormatter` décrit la signature des méthodes des types qui la réalisent. > > ```java > public interface AireFormatter { > String infos(Forme forme, String label); > } > ``` > > Il est possible de changer la façon de formater l'aire en créant des implémentations différentes de l'interface `AireFormatter` > > ```java > public class SquareCentimeterAireFormatter implements AireFormatter { > @Override > public String infos(Forme forme, String name) { > return name + ", aire : " + (forme.aire() * 100) + "cm^2"; > } > } > ``` > > ```java > public class SquareMeterAireFormatter implements AireFormatter { > @Override > public String infos(Forme forme, String label) { > return label + ", aire : " + (forme.aire()) + "m^2"; > } > } > ``` ### Délégation par composition ou par aggrégation ? #### Composition Permet à une classe de définir des **comportements** et **attributs** de façon **modulaire**. L'objet `B` auquel on délègue un comportement est instancié par l'appelant `A`. L'instance de la classe `B` est détruite quand il n'existe plus de références de l'instance de la classe `A` . ```java public class A { private B b = new B(); public A() { } } ``` #### Aggrégation Permet à des instances de **réutiliser** des objets. > [!tip] L'aggrégation rend possible **l'injection de dépendances** ```java public class A { private B b; public A( B b ) { this.b = b; } } public class C { private B b = new B(); public C() { A a = new A( this.b ); } } ``` Une fois qu'il n'y a plus de références à une instance particulière de la classe `A`, son instance de classe `B` ne sera pas détruite. Dans cet exemple, `A` et `C` doivent être garbage collectés avant que `B` ne soit détruit. ## Réutiliser du code par héritage d'implémentation L’héritage d’implémentation permet de créer une nouvelle classe en **étendant** les **fonctionnalités** d’une classe existante. La classe qui est étendue est appelée **classe de base**, et la classe qui l’étend est appelée **classe dérivée**. La classe dérivée **hérite** de **l’implémentation** de la classe de base. L’héritage permet donc d’étendre une classe existante sans avoir à modifier son code. ### Classe abstraite On peut utiliser une [[3.4 - Héritages en Java#Classe abstraite|classe abstraite]] pour définir des attributs et des comportements tout en obligeant celle-ci à être étendue. ## Héritage ou Composition/Délégation ? https://en.wikipedia.org/wiki/Composition_over_inheritance ### Avantages Favoriser la composition à l'héritage (d'implémentation) est un principe de conception qui donne à la conception **une plus grande flexibilité**. Il est plus naturel de construire des classes de domaine à partir de divers composants que d'essayer de trouver des points communs entre eux et d'en créer une hiérarchie. Par exemple, une pédale d'accélérateur et un volant partagent très peu de traits communs, mais les deux sont des composants essentiels dans une voiture. Ce qu'ils peuvent faire et comment ils peuvent être utilisés au profit de la voiture sont facilement définis. La composition offre également un domaine plus stable à long terme, car elle est moins sujette aux particularités singulières des membres de la famille. > [!tip] En d'autres termes, il est préférable de composer **ce qu'un objet peut faire** (has-a) que d'étendre **ce qu'il est** (is-a). La conception initiale est **simplifiée** en identifiant les comportements des objets système dans **des interfaces séparées** au lieu de créer une relation hiérarchique pour distribuer les comportements entre les classes de domaine via l'héritage. Cette approche s'adapte plus facilement aux futurs changements d'exigences qui nécessiteraient autrement une restructuration complète des classes d'une hierarchie dans le modèle d'héritage. De plus, il évite les problèmes souvent associés à des changements relativement mineurs à un modèle basé sur l'héritage qui comprend plusieurs parentés de classes. La relation de composition est plus flexible car elle peut être **modifiée au moment de l'exécution**, tandis que les relations de sous-typage sont **statiques** et doivent être recompilées dans de nombreux langages. ### Inconvénients Un inconvénient commun de l'utilisation de la composition au lieu de l'héritage est que les méthodes fournies par des composants individuels peuvent devoir être implémentées dans le type dérivé, même s'il ne s'agit que de méthodes de délégation En revanche, l'héritage ne nécessite pas que toutes les méthodes de la classe de base soient réimplémentées au sein de la classe dérivée. Au contraire, la classe dérivée n'a besoin que d'implémenter (override) les méthodes ayant un comportement différent des méthodes de classe de base. Cela peut nécessiter beaucoup moins d'efforts de programmation si la classe de base contient de nombreuses méthodes fournissant un comportement par défaut et que seules quelques-unes d'entre elles doivent être remplacées dans la classe dérivée. > [!tip] Certains inconvénients peuvent être mitigés à l'aide des méthodes par défaut dans les interfaces