Méthodes pour factoriser du code - Héritage et agrégation avec délégation
Table of Contents
Session 2 - Test écrit - 8 juin 2015 - 16h30-17h45 - 0h20'
Un test écrit de vingt minutes clôturera la seconde session, consacrée à la factorisation en utilisant l'héritage. En préparation, étudier la correction du TD2 et celle du TP2.
Session 3 - Travaux dirigés - 8 juin 2015 - 16h30-17h45 - 0h55'
Les exercices qui suivent concernent une architecture en couches et passent en revue différentes méthodes de factorisation du code pour de telles architectures :
- héritage avec approche ascendante,
- héritage avec approche descendante,
- héritage multiple (en Java 8 uniquement),
- agrégation avec délégation.
Parmi ces quatre méthodes, celles recourant à l'héritage ne sont pas nouvelles : elles ont été étudiées pendant la seconde session. La seule nouvelle n'utilise pas l'héritage, mais l'agrégation avec délégation.
On cherche à programmer un agent cherchant à envoyer un message (sur un réseau) en utilisant un canal de communication, chargé de l'émission du message sur le réseau. Ce processus possède deux degrés de liberté, ou deux points de variation :
- la décomposition du message à envoyer en plusieurs fragments à émettre,
- le protocole de communication utilisé par le canal.
On suppose par la suite que deux techniques de décomposition sont utilisées :
- décomposition d'un message de taille quelconque en des fragments de taille limitée,
- pas de décomposition, le message étant simplement encapsulé pour le transport sur le réseau.
De même, on suppose que deux protocoles peuvent être utilisés, qu'on appellera "Protocole 1" et "Protocole 2" simplement. De ces deux hypothèses, il résulte qu'il est possible d'obtenir quatre combinaisons.
Découpage | Encapsulation | |
Protocole 1 | X | X |
Protocole 2 | X | X |
Quatre combinaisons d'implémentations
Dans la suite, on modélise la communication sur le réseau par une sortie sur la console, préfixée par le nom du protocole.
public void emettre(String msg) { System.out.println("protocole X : " + msg); }
On implémente ainsi l'encapsulation et le découpage des messages.
// Encapsulation public void envoyer(String msg) { this.emettre(msg); } // Découpage public void envoyer(String msg) { int TAILLE = 5; StringBuilder m = new StringBuilder(msg); int q = msg.length() / TAILLE; int r = msg.length() % TAILLE; for(int j = 0; j < q; j++){ this.emettre(m.substring(j * TAILLE, (j+1) * TAILLE)); } this.emettre(m.substring(q * TAILLE, q * TAILLE + r)); }
Dans la suite on abrégera le code de la première méthode en ENC et celui de la seconde en DEC. Enfin, pour chaque classe implémentée, on indique dans son nom les choix réalisés :
- DecoupantMessages et EncapsulantMessages pour le découpage et l'encapsulation respectivement,
- Protocole1 et Protocole2 pour les protocoles 1 et 2 respectivement.
Pour illustrer ce type d'architecture, prenons à titre d'exemple Internet. Les messages y sont décomposés en paquets dont la taille maximale est fixée par le MTU (Maximum Transmission Unit). Deux protocoles peuvent servir au transport des messages : UDP (User Datagram Protocol en mode non connecté, comme le courrier postal, utilisé pour le streaming par exemple) et TCP (Transmission Control Protocol en mode connecté, comme le téléphone, utilisé pour les pages Web).
Hiérarchie d'interfaces
Définir dans le paquet td3
- une interface Agent déclarant la méthode void envoyer(String msg),
- une interface Canal déclarant la méthode void emettre(String msg),
- une interface AgentCommuniquant héritant des deux interfaces précédentes.
Hiérarchie d'interfaces - Un agent communiquant
interface Agent { ...................................................................... } interface Canal { ...................................................................... } interface AgentCommuniquant ............................................ ......................................................................
Héritage - Approche ascendante
On réalise les quatre combinaisons en factorisant la couche basse, suivant une approche ascendante pour l'héritage.
Réaliser le schéma suivant dans le paquet td3.ascendant.
Approche ascendante - Factorisation de la couche basse
class CanalOutProtocole1 ............................................... { ...................................................................... ...................................................................... ...................................................................... ...................................................................... } class CanalOutProtocole2 ............................................... { ...................................................................... ...................................................................... ...................................................................... ...................................................................... } class AgentDecoupantMessagesPourProtocole1 ............................. ...................................................................... { @Override public void envoyer(String msg) { .............................................................. ... } } class AgentEncapsulantMessagesPourProtocole1 ........................... ...................................................................... { @Override public void envoyer(String msg) { .............................................................. ... } } class AgentDecoupantMessagesPourProtocole2 ............................. ...................................................................... { @Override public void envoyer(String msg) { .............................................................. ... } } class AgentEncapsulantMessagesPourProtocole2 ........................... ...................................................................... { @Override public void envoyer(String msg) { .............................................................. } }
Héritage - Approche descendante
On réalise les quatre combinaisons en factorisant la couche haute, suivant une approche descendante pour l'héritage.
Réaliser le schéma suivant dans le paquet td3.descendant.
Approche descendante - Factorisation de la couche haute
........................................................................ class AgentDecoupantMessages ......................................... { @Override public void envoyer(String msg) { .............................................................. } } ........................................................................ class AgentEncapsulantMessages ....................................... { @Override public void envoyer(String msg) { .............................................................. } } class AgentDecoupantMessagesPourProtocole1 ............................. ...................................................................... { @Override public void emettre(String msg) { .............................................................. } } class AgentDecoupantMessagesPourProtocole2 ............................. ...................................................................... { @Override public void emettre(String msg) { .............................................................. } } class AgentEncapsulantMessagesPourProtocole1 ........................... ...................................................................... { @Override public void emettre(String msg) { .............................................................. } } class AgentEncapsulantMessagesPourProtocole2 ........................... ...................................................................... { @Override public void emettre(String msg) { .............................................................. } }
Héritage multiple (Java 8 seulement)
On réalise les quatre combinaisons en factorisant la couche haute et celle basse.
Réaliser le schéma suivant dans le paquet td3.heritageMultiple.
Héritage multiple - Factorisation des deux couches
interface AgentDecoupantMessages ....................................... ...................................................................... { @Override default public void envoyer(String msg) { .............................................................. } } interface AgentEncapsulantMessages ..................................... ...................................................................... { @Override default public void envoyer(String msg) { .............................................................. } } class AgentDecoupantMessagesPourProtocole1 ............................. ...................................................................... { ...................................................................... } class AgentDecoupantMessagesPourProtocole2 ............................. ...................................................................... { ...................................................................... } class AgentEncapsulantMessagesPourProtocole1 ........................... ...................................................................... { ...................................................................... } class AgentEncapsulantMessagesPourProtocole2 ........................... ...................................................................... { ...................................................................... }
Agrégation avec délégation
On réalise les quatre combinaisons en factorisant la couche haute et celle basse.
Réaliser le schéma suivant dans le paquet td3.agregation. Suivre les commentaires ci-dessous.
Agrégation avec délégation - Factorisation des deux couches
class AgentDecoupantMessages ........................................... ...................................................................... { // Attribut de type Canal et constructeur associé ...................................................................... ...................................................................... ...................................................................... ...................................................................... ...................................................................... @Override public void envoyer(String msg) { .............................................................. } @Override public void emettre(String msg) { // Délégation au canal .............................................................. } } class AgentEncapsulantMessages ......................................... ...................................................................... { // Attribut de type Canal et constructeur associé ...................................................................... ...................................................................... ...................................................................... ...................................................................... ...................................................................... @Override public void envoyer(String msg) { .............................................................. } @Override public void emettre(String msg) { // Délégation au canal .............................................................. } }
Test
Dans les trois paquets contenant les implémentations utilisant l'héritage, ajouter une classe de test, définie ainsi, puis tester les implémentations.
class Test { public static void main(String[] args) { AgentCommuniquant a = new AgentDecoupantMessagesPourProtocole1(); a.envoyer("nul n'est censé ignorer les principes de modularité."); Agent b = new AgentDecoupantMessagesPourProtocole2(); b.envoyer("nul n'est censé ignorer les principes de modularité."); a = new AgentEncapsulantMessagesPourProtocole1(); a.envoyer("nul n'est censé ignorer les principes de modularité."); b = new AgentEncapsulantMessagesPourProtocole2(); b.envoyer("nul n'est censé ignorer les principes de modularité."); } }
Dans le paquet td3.agregation, réliser le même test.
class Test { public static void main(String[] args) { AgentCommuniquant a = .................................. ...................................................... a.envoyer("nul n'est censé ignorer les principes de modularité."); Agent b = .............................................. ...................................................... b.envoyer("nul n'est censé ignorer les principes de modularité."); a = .................................................... ...................................................... a.envoyer("nul n'est censé ignorer les principes de modularité."); b = .................................................... ...................................................... b.envoyer("nul n'est censé ignorer les principes de modularité."); } }