Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Design Patterns en Java
  3. Reconcevoir du code existant
Extrait - Design Patterns en Java Descriptions et solutions illustrées en UML 2 et Java (5e édition) - Les 23 modèles de conception
Extraits du livre
Design Patterns en Java Descriptions et solutions illustrées en UML 2 et Java (5e édition) - Les 23 modèles de conception Revenir à la page d'achat du livre

Reconcevoir du code existant

Mise en œuvre des patterns pour reconcevoir du code

Le but de ce chapitre est de montrer comment mettre en œuvre les design patterns en reconcevant du code existant dont l’élaboration s’est faite en dehors des patterns. Les neuf patterns qui seront mis en œuvre font partie des vingt-trois modèles que nous avons introduits dans les chapitres précédents.

Les codes à reconcevoir se présentent sous la forme d’exemples que nous allons étudier tout au long de ce chapitre.

Chaque exemple est dédié à un pattern et se présente en deux parties :

  • Une première partie qui présente l’exemple sans mettre en œuvre le pattern et qui, bien souvent, présente de piètres qualités de conception par objets.

  • Une seconde partie qui montre comment l’exemple de la première partie a été réécrit en utilisant le pattern correspondant, aboutissant ainsi à une version plus respectueuse des qualités de la conception par objets (notamment en termes de lisibilité, de modularité et de capacité d’extension).

Il convient de noter une exception pour les patterns Composite et Visitor pour lesquels nous avons élaboré une réécriture mettant en œuvre les deux patterns simultanément.

Composite et Visitor

1. L’exemple initial

Le code de l’exemple est constitué de deux classes : la classe Employee qui représente des employés et la classe Subsidiary qui représente des sociétés.

Le code de la classe Employee, précédé de celui de l’énumération EmployeeType se trouve ci-dessous. La classe introduit les accesseurs en lecture et en écriture des attributs wage et employeeType (de type EmployeeType). Elle offre également la fonction isStaffEmployee qui indique si un employé fait partie du staff (au sens de l’équipe de direction) ainsi que la fonction isAdministrativeEmployee qui précise s’il est un employé administratif.

public enum EmployeeType { 
 
   STAFF, ADMINISTRATIVE, TECHNICAL 
} 
 
public class Employee { 
   protected long wage; 
   protected EmployeeType employeeType; 
 
   public Employee(long wage, EmployeeType employeeType) { 
      this.wage = wage; 
      this.employeeType = employeeType; 
   } 
 
   public long getWage() { 
      return wage; 
   } 
 
   public EmployeeType getEmployeeType() { 
      return employeeType; 
   } 
 
   public void setWage(long wage) { 
      this.wage = wage; 
   } 
 
   public void setEmployeeType(EmployeeType employeeType) { 
      this.employeeType = employeeType; 
   } 
 
   public boolean isStaffEmployee() { 
      return employeeType == EmployeeType.STAFF; 
   } 
 
   public boolean isAdministrativeEmployee() { 
      return employeeType == EmployeeType.ADMINISTRATIVE; 
   } 
} 

Nous examinons maintenant la classe Subsidiary dont le code est fourni à la suite. Cette classe...

Template Method

1. L’exemple initial

L’exemple initial est constitué par la classe abstraite GraphicalObject et ses deux sous-classes concrètes Ellipse et TextZone. Elle représente un objet graphique abstrait qui factorise les propriétés communes. En l’occurrence, elle introduit les propriétés communes aux ellipses et aux zones de texte, à savoir leur position, leur dimension, leur initialisation et la méthode de dessin drawFrame qui dessine un cadre autour de l’objet.

Les deux sous-classes concrètes introduisent leurs propriétés et leur méthode spécifique de dessin. Dans les méthodes de dessin, l’action est simulée par une impression à l’écran des données.

Le code des trois classes se trouve ci-dessous.

public abstract class GraphicalObject { 
    protected int xPos,yPos,width,height;  

    public GraphicalObject(int xPos, int yPos, int width, int height) { 
        this.xPos = xPos; 
        this.yPos = yPos; 
        this.width = width; 
        this.height = height; 
    } 
 
    protected void drawFrame() { 
        System.out.println("Drawing rectangle xPos= " + xPos + " yPos= " +  
         yPos + " Width= " + width + " Height = " + height); 
    } 
    public abstract void drawFramedObject(); 
}
public class Ellipse extends GraphicalObject { 
 
    protected int lineThickness;...

Iterator

1. L’exemple initial

L’exemple choisi pour illustrer la mise en œuvre du pattern Iterator est constitué d’une liste chaînée de chaînes de caractères dont les nœuds sont décrits par la classe Node qui détient deux attributs :

  • sa valeur (attribut value) ;

  • le nœud suivant, s’il existe (attribut next).

Le code de la classe Node est le suivant :

class Node   { 
    protected String value; 
    protected Node next; 
 
    public Node(String value) { 
        this.value = value; 
        this.next = null; 
    } 
 
    public String getValue() { 
        return value; 
    } 
 
    public Node getNext() { 
        return next; 
    } 
 
    public void setNext(Node next) { 
        this.next = next; 
    } 
 
    public boolean hasNext() { 
        return next != null; 
    } 
}  

La classe LinkedList introduit la tête de la liste chaînée (attribut head) et les méthodes suivantes :

  • addNewNode qui ajoute un nœud à la liste.

  • getNumberOfNodes qui renvoie le nombre de nœuds présents dans la liste.

  • getValue qui retourne la valeur d’un nœud en fonction de son indice.

La classe LinkedList est décrite par le code suivant :

class LinkedList   { 
    protected Node head = null; 
    public LinkedList() { 
    } 
 
    public void addNewNode(String value) { 
        if (head == null) head = new Node(value); 
        else { 
            Node lastNode...

Chain of Responsibility

1. L’exemple initial

Nous choisissons pour l’exemple initial le traitement des demandes de crédit à la consommation ou de crédit immobilier au sein d’un établissement bancaire.

Chaque demande de crédit est introduite auprès du conseiller bancaire du client. Celui-ci la traite de la façon suivante :

  • Il accorde le crédit à la consommation si son montant ne dépasse pas le plafond qui lui a été attribué. À défaut, il transmet la demande à son directeur d’agence.

  • Il ne peut pas accorder de crédit immobilier, il doit transmettre la demande au directeur de l’agence.

Le directeur de l’agence bancaire traite les demandes ainsi :

  • Le crédit à la consommation est accordé si son montant ne dépasse pas le plafond qui lui a été attribué sinon il transmet la demande à la direction de la banque.

  • Le crédit immobilier est accordé à la double condition que son montant ne dépasse pas le plafond et que sa durée ne dépasse pas la durée limite, ce plafond et cette durée limite ayant été attribués au directeur. À défaut, il transmet la demande à la direction de la banque.

Enfin, les demandes sont traitées comme suit par la direction de la banque. Quel que soit le type de crédit, il n’est accordé que si son montant ne dépasse pas le seuil fixé pour ce type de crédit par le conseil d’administration de la banque.

Nous étudions le code mettant en œuvre l’ensemble de ces mécanismes.

Les demandes de crédit à la consommation sont décrites par la classe ConsumerLoanRequest, les demandes de crédit immobilier sont décrites par la classe HomeLoanRequest. Le code de ces deux classes se trouve ci-dessous. La classe ConsumerLoanRequest introduit le montant du prêt. La classe HomeLoanRequest introduit le montant et la durée du prêt.

public class ConsumerLoanRequest { 
    protected int amount; 
 
    public ConsumerLoanRequest(int amount) { 
        super(); 
        this.amount...

State

1. L’exemple initial

Le code suivant est celui de l’énumération StateEnum  et de la classe Queue. Cette dernière gère une structure de données de type file, à savoir qui ajoute un élément au sommet de la structure et enlève un élément à la base de celle-ci. Son attribut state typé par l’énumération StateEnum contient la valeur de l’état courant de la file : vide (EMPTYSTATE), pleine (FULLSTATE) ou ni vide ni pleine (NORMALSTATE).

La méthode enqueue ajoute un élément si la file n’est pas pleine. Dans ce cas, elle met à jour l’état après l’ajout. Si la variable top est égale à MAXSIZE alors la file est pleine.

La méthode dequeue retire un élément si la file n’est pas vide. Cette méthode provoque un décalage des valeurs présentes dans la file. Ensuite, elle met à jour l’état après le retrait. Si la variable top est égale à 0 alors la file est vide.

enum StateEnum  { 
    EMPTYSTATE, NORMALSTATE, FULLSTATE 
} 
 
public class Queue { 
    protected final static int MAXSIZE = 10; 
    protected int buffer[] = new int[MAXSIZE]; 
    protected int top = 0; 
    protected StateEnum state = StateEnum.EMPTYSTATE; 
 
 
    public void enqueue(int value) { 
        if (state != StateEnum.FULLSTATE) { 
            buffer[top] = value; 
            top++; 
            if (top == MAXSIZE) state = StateEnum.FULLSTATE; 
            else state = StateEnum.NORMALSTATE; 
        } 
    } 
 
    public Integer dequeue() { 
        if (state != StateEnum.EMPTYSTATE) { 
            int...

Observer

1. L’exemple initial

L’exemple est un objet graphique qui lorsque l’une de ses dimensions est modifiée, invoque un ensemble de méthodes au travers d’un mécanisme de callback. Cet objet graphique est un rectangle qui est décrit par la classe éponyme.

Le mécanisme de callback est mis en œuvre de la façon suivante :

  • L’interface Callback introduit la signature de la méthode update devant être invoquée lors de tout changement.

  • Une liste d’objets réalisant cette interface est transmise comme paramètre des méthodes setHeight et setWidth. Ces deux méthodes invoquent la méthode update des objets de ces listes au travers d’un appel à la méthode notifyCallBack.

L’ensemble du code présentant l’interface Callback et la classe Rectangle se trouve à la suite. Il est précédé du code de l’énumération DimensionEnum qui sert à indiquer une dimension.

enum DimensionEnum  { 
    HEIGHT, WIDTH 
} 
 
public interface Callback { 
    void update(DimensionEnum dimension, Integer newValue); 
} 
 
import java.util.ArrayList; 
import java.util.List; 
 
public class Rectangle { 
    protected int height, width; 
 
    public Rectangle(int height, int width) { 
        super(); 
        this.height = height; 
        this.width = width; 
    } 
 
    public int getHeight() { 
        return height; 
    } 
 
    public int getWidth() { 
        return width; 
    } 
 
    public void notifyCallBack(DimensionEnum dimension, Integer newValue,
        List<Callback> dimensionCallbacks) { 
        for (Callback dimensionCallback: dimensionCallbacks) { ...

Decorator

1. L’exemple initial

L’exemple initial est fondé sur une alternative à la mise en œuvre classique du pattern Observer. Il s’agit d’une réalisation de ce pattern sur une classe existante dont la modification des surclasses n’est pas souhaitée ou s’avère impossible. La gestion des observateurs est introduite dans une sous-classe de la classe observée.

Dans notre exemple, la classe dont nous souhaitons observer les changements d’états est la classe Rectangle dont le code se trouve à la suite.

public class Rectangle { 
    protected int height, width; 
 
    public Rectangle(int height, int width) { 
        super(); 
        this.height = height; 
        this.width = width; 
    } 
 
    public int getHeight() { 
        return height; 
    } 
 
    public int getWidth() { 
        return width; 
    } 
 
    public void setHeight(int height) { 
        this.height = height; 
    } 
 
    public void setWidth(int width) { 
        this.width = width; 
    } 
} 

La gestion des observateurs est introduite dans la sous-classe ObservableRectangle de la classe Rectangle par des mécanismes d’enregistrement et d’invocation de ces observateurs lors d’une modification de l’état de l’objet. Par ailleurs, ObservableRectangle redéfinit les méthodes setHeight et setWidth de la classe Rectangle afin que ces deux méthodes appellent la méthode notifyObserver qui invoque la méthode update des observateurs. Le code de ObservableRectangle est précédé de celui de l’énumération DimensionEnum toujours utilisée pour préciser la dimension.

enum DimensionEnum  { ...

Command

1. L’exemple initial

L’exemple choisi pour illustrer le pattern Command est basé sur un éditeur d’objets graphiques qui sont des objets UML, en l’occurrence des classes et des méthodes. Il prend en charge la possibilité d’ajouter des arguments (paramètres) à une méthode ou d’ajouter des propriétés à une classe.

La classe UML est représentée par la classe ClassGraphicalObject. La méthode UML est représentée par la classe MethodGraphicalObject qui introduit le nombre d’arguments.

Le code de ces deux classes est donné ci-dessous :

public class MethodGraphicalObject { 
    protected int numberArguments = 0; 
    public static final int MAXARGUMENTS = 10; 
 
    public MethodGraphicalObject() { 
    } 
 
    public int getNumberArguments() { 
        return numberArguments; 
    } 
 
    public void setNumberArguments(int numberArguments) { 
        this.numberArguments = numberArguments; 
    } 
 
} 
 
public class ClassGraphicalObject { 
    protected int numberProperties = 0; 
    public static final int MAXPROPERTIES = 20; 
 
    public ClassGraphicalObject() { 
    } 
 
    public int getNumberProperties() { 
        return numberProperties; 
    } 
 
    public void setNumberProperties(int numberProperties) { 
        this.numberProperties = numberProperties; 
    } 
}  

Augmenter d’une unité le nombre d’arguments ou de propriétés se fait en appuyant sur la touche up du clavier, diminuer d’une unité se fait en appuyant sur le touche down. Les objets en charge de l’interface utilisateur invoquent la gestion centralisée des commandes...