Semaine 12

Activité 1

Patron de conception Command

Le patron de conception Command est un patron de type comportemental qui permet d’encapsuler dans des objets des opérations (ou commande) qui peuvent être invoqué sur demande au moment requis dans un contexte donné (un objet particulier). Dans les IHM, le patron de conception Command est particulièrement utilisé pour encapsuler des opérations faites par les utilisateurs, qui aurait besoin d’être mise en place plus tard, à plusieurs reprises ou bien annulé. Ainsi, les fameux « annuler » (undo) et « rétablir » (redo) sont implémentés à l’aide de ce patron de conception.

Le patron de conception Command se compose donc d’une description abstraite de la commande sous forme d’une interface, les implémentations concrètes des commandes et finalement l’objet invocateur, qui appelle la commande en lui passant ou non les paramètres nécessaires. L’image suivante présente le diagramme UML du patron :

diagramme UML

Exemple du patron avec une IHM

Afin d’illustrer l’utilisation du patron de conception Command, nous utiliserons celui-ci afin d’implémenter les options annuler et refaire dans l’IHM de l’exercice précédent portant sur le Clavardage (exercice sur le patron Observer). Voici un aperçu de l’IHM en question :

IHMExempleCommand

Pour ce faire, nous devons d’abord créer une interface qui représentera le concept abstrait de la Command :

public interface Command {
    
    public void annuler();
    
    public void refaire();
    
}

Ensuite, il est nécessaire de créer l’implémentation de cette Command, où sera annulé ou refait l’objet de la Command, soit l’écriture du texte dans la fenêtre de clavardage :

import javax.swing.JTextPane;

/**
 *
 * @author cgouin
 */
public class ChatCommand implements Command {
    
    protected String texte;  
    protected JTextPane textPane;

    public ChatCommand(String texte, JTextPane textPane) {
        this.texte = texte;
        this.textPane = textPane;
    }
    
    public void ecrire() {
        textPane.setText(textPane.getText() + "
" + getTexte());
    }

    public String getTexte() {
        return texte;
    }

    public void setTexte(String texte) {
        this.texte = texte;
    }

    @Override
    public void annuler() {                
        
        // Aller chercher la chaine de caractères originale avant l'écriture du texte et du saut de ligne 
        String texteAnnule = textPane.getText().substring(0, textPane.getText().length() - texte.length() - 1);
        textPane.setText(texteAnnule);        
    }

    @Override
    public void refaire() {
        ecrire();
    }
    
}

Ensuite, il reste à intégrer le patron de conception Command à la fenêtre JFrame. Pour ce faire, nous utilisons deux piles: la première qui contiendra les commandes mises en place et prêtes à être annulées et la seconde pile qui contiendra les commandes à refaire. Notons que l’application du patron de conception Observer n’est pas touché par la mise en place de ce second patron. Nous avons également ajouté des ActionListeners sur les menus des items Annulé et Refaire.

import java.util.ArrayList;
import java.util.Stack;

/**
 *
 * @author Charles
 */
public class ChatJFrame extends javax.swing.JFrame implements ClavardageListener {
    
    ArrayList<ClavardageListener> listeners = new ArrayList<>();
    
    Stack<Command> commands = new Stack<>();
    Stack<Command> refaireCommands = new Stack<>();
    
    
    /**
     * Creates new form NewJFrame
     */
    public ChatJFrame() {
        initComponents();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTextPane1 = new javax.swing.JTextPane();
        jTextField1 = new javax.swing.JTextField();
        jButton1 = new javax.swing.JButton();
        jLabel1 = new javax.swing.JLabel();
        jMenuBar1 = new javax.swing.JMenuBar();
        jMenu2 = new javax.swing.JMenu();
        jMenuItem1 = new javax.swing.JMenuItem();
        jMenuItem2 = new javax.swing.JMenuItem();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        jTextPane1.setEditable(false);
        jScrollPane1.setViewportView(jTextPane1);

        jButton1.setText("Envoyer");
        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButton1ActionPerformed(evt);
            }
        });

        jLabel1.setText("<html><b>Conversation en ligne</b></html>");

        jMenu2.setText("Édition");

        jMenuItem1.setText("Annuler");
        jMenuItem1.setEnabled(false);
        jMenuItem1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItem1ActionPerformed(evt);
            }
        });
        jMenu2.add(jMenuItem1);

        jMenuItem2.setText("Refaire");
        jMenuItem2.setEnabled(false);
        jMenuItem2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jMenuItem2ActionPerformed(evt);
            }
        });
        jMenu2.add(jMenuItem2);

        jMenuBar1.add(jMenu2);

        setJMenuBar(jMenuBar1);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(50, 50, 50)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 299, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addGroup(layout.createSequentialGroup()
                                .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 219, javax.swing.GroupLayout.PREFERRED_SIZE)
                                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                                .addComponent(jButton1))))
                    .addGroup(layout.createSequentialGroup()
                        .addGap(139, 139, 139)
                        .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap(64, Short.MAX_VALUE))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(jLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 193, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(jButton1))
                .addGap(23, 23, 23))
        );

        pack();
    }// </editor-fold>                        

    private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
        
        // Créer et ajouter la command à la Pile.
        ChatCommand command = new ChatCommand(jTextField1.getText(), jTextPane1);
        commands.push(command); 
        
        // Ecrire le texte dans le chat
        command.ecrire();
        
        fireMessage(jTextField1.getText());
        
        // Si c'est la première fois que l'on créer une Command, rendre l'option Annulé disponible
        if(!jMenuItem1.isEnabled()) {
            jMenuItem1.setEnabled(true);
        }
        
    }                                        

    private void jMenuItem1ActionPerformed(java.awt.event.ActionEvent evt) {                                           
        //Annuler la commande en prenant le premier item de la pile de commands
        Command command = commands.pop();
        command.annuler();
        
        //Mettre la command annulée dans la pile de command à refaire
        refaireCommands.push(command);
        
        //S'il n'y a plus de command, rendre l'option inaccessible
        if(commands.empty()) {
            jMenuItem1.setEnabled(false);
        }
        
        //rende l'option refaire disponible
        jMenuItem2.setEnabled(true);
    }                                          

    private void jMenuItem2ActionPerformed(java.awt.event.ActionEvent evt) {                                           
        
        // Refaire la command qui a été annulée
        Command command = refaireCommands.pop();
        command.refaire();
        
        // Remettre dans la liste des items à annuler
        commands.push(command);
        
        //S'il n'y a plus de command à refaire, rendre l'option inaccessible
        if(commands.empty()) {
            jMenuItem2.setEnabled(false);
        }
        
        jMenuItem1.setEnabled(true);
        
        
        
    }                                          


    // Variables declaration - do not modify                     
    private javax.swing.JButton jButton1;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JMenu jMenu2;
    private javax.swing.JMenuBar jMenuBar1;
    private javax.swing.JMenuItem jMenuItem1;
    private javax.swing.JMenuItem jMenuItem2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTextField jTextField1;
    private javax.swing.JTextPane jTextPane1;
    // End of variables declaration                   

    @Override
    public void nouveauMessage(String message) {
        
        //Créer une nouvelle command
        ChatCommand command = new ChatCommand("< " + message, jTextPane1);
        command.ecrire();
        
        commands.push(command);
        
        // Si c'est la première fois que l'on créer une Command, rendre l'option Annulé disponible
        if(!jMenuItem1.isEnabled()) {
            jMenuItem1.setEnabled(true);
        }
    }
    
    public synchronized void addListener(ClavardageListener listener) {
        
        listeners.add(listener);
        
    }
       
    public synchronized void removeListener(ClavardageListener listener) {
        
        listeners.remove(listener);
    }
    
    public synchronized void fireMessage(String message) {
        for(ClavardageListener listener : listeners) {
            listener.nouveauMessage(message);
        }
    }
    
}