TP4

JO_NFP121

NFP 121 - TP4

pattern Observer / evénements / modèle MVC




EnoncéEnoncé

Question 1


la classe Concrete Subject :


	public class ConcreteSubject extends Observable {

	/** ConcreteSubject est composé d'une liste list */
	private ArrayList<String> list;

	public ConcreteSubject() {
		list = new ArrayList<String>();
	}

	public void insert(String name) {
		list.add(name);
		setChanged();
		notifyObservers(name);
	}

	public String toString() {
		return list.toString();
	}

	}
	

la classe ConcreteObserver :


	public class ConcreteObserver implements Observer {

	private Stack<Observable> senders;
	private Stack<Object> arguments;

	public ConcreteObserver() {
		senders = new Stack<Observable>();
		arguments = new Stack<Object>();
	}

	
	public void update(Observable observable, Object arg) {
		senders.push(observable);
		arguments.push(arg);
	}

	public Stack<Observable> senders() {
		return senders;
	}

	public Stack<Object> arguments() {
		return arguments;
	}
	
	}
	

la classe PatternObservateur :


	public class PatternObservateur extends junit.framework.TestCase {
    /**
     * Teste la notification.
     */
    public void testNotify() {
        /**Sujet concret (observable): list.*/
        ConcreteSubject list;
        /**Obervateur concret (observateur): observer.*/
        ConcreteObserver observer;

        list = new ConcreteSubject();           // création d'un "observé" constitué d'une liste
        observer = new ConcreteObserver();      // création d'un observateur
        list.addObserver(observer);             // ajouter cet observateur à la liste
        list.insert("il fait beau, ce matin");  // modification de cette liste, l'observateur doit
                                                // (dervrait) être notifié

        // "vérification" :
        assertFalse(observer.senders().empty());                            
        assertEquals(list, observer.senders().pop());                       
        assertEquals("il fait beau, ce matin", observer.arguments().pop()); 
    }

    /**
     * Teste la notification d'une liste avec 2 observateurs.
     */
    public void test1() {
        /**On crée la liste observable.*/
        question1.ConcreteSubject l1 = new question1.ConcreteSubject();
        /**on ajoute 2 observateurs : o1.*/
        question1.ConcreteObserver o1 = new question1.ConcreteObserver();
        /**o2.*/
        question1.ConcreteObserver o2 = new question1.ConcreteObserver();
        l1.addObserver(o1);
        l1.addObserver(o2);
        l1.insert("test");
        l1.insert(" 1 ");
        
        assertFalse(o1.senders().empty());
        assertFalse(o2.senders().empty()); 
        assertEquals(l1, o1.senders().pop()); //il y a 2 insertions donc 2 pop
        assertEquals(l1, o1.senders().pop()); 
        assertEquals(l1, o2.senders().pop());
        assertEquals(l1, o2.senders().pop()); 
        assertEquals(" 1 ", o1.arguments().pop());// le dernier element inserer est retourné le premier
        assertEquals(" 1 ", o2.arguments().pop());
        assertEquals("test", o1.arguments().pop());
        assertEquals("test", o2.arguments().pop());
        
        assertTrue(o1.senders().empty() && o1.arguments().empty());
        assertTrue(o2.senders().empty() && o2.arguments().empty());
    }

    
    /**
     * 2 listes, 1 observateur.
     */
    public void test2() {
        /**liste 1 .*/
        question1.ConcreteSubject l1 = new question1.ConcreteSubject();
        /**liste 2.*/
        question1.ConcreteSubject l2 = new question1.ConcreteSubject();
        /**observateur o.*/
        question1.ConcreteObserver o = new question1.ConcreteObserver();
        l1.addObserver(o);
        l2.addObserver(o);
        l1.insert("testA");
        l1.insert(" A ");
        l2.insert("testB");
        l2.insert(" B ");

        assertEquals(l2, o.senders().pop());
        assertEquals(l2, o.senders().pop());
        assertEquals(l1, o.senders().pop());
        assertEquals(l1, o.senders().pop());
        assertEquals(" B ", o.arguments().pop());
        assertEquals("testB", o.arguments().pop()); 
        assertEquals(" A ", o.arguments().pop());
        assertEquals("testA", o.arguments().pop());

        assertTrue(o.senders().empty() && o.arguments().empty());
    }

    /**
     * 2 listes, 2 observateurs.
     * Teste countObserver(), deleteObservers() et
     * deleteOserver(observer o)
     */
      
    public void test3() {
        question1.ConcreteSubject l1 = new question1.ConcreteSubject();
        question1.ConcreteSubject l2 = new question1.ConcreteSubject();
        question1.ConcreteObserver o1 = new question1.ConcreteObserver();
        question1.ConcreteObserver o2 = new question1.ConcreteObserver();
        l1.addObserver(o1);
        l1.addObserver(o2);
        l2.addObserver(o1);
        l2.addObserver(o2);

        assertTrue(l1.countObservers() == 2);
        assertTrue(l2.countObservers() == 2);
        l1.deleteObserver(o1);
        assertTrue(l1.countObservers() == 1);
        l1.deleteObserver(o2);
        assertTrue(l1.countObservers() == 0);
        l2.deleteObservers();
        assertTrue(l2.countObservers() == 0);

        assertTrue(o1.senders().empty());
        assertTrue(o2.senders().empty());
        assertTrue(l1.countObservers() == 0);
        assertTrue(l2.countObservers() == 0);
    }
    
	}
	




Question 2.1


la classe AppletteQuestion2 :


public class AppletteQuestion2 extends JApplet {

    private JButton boutonA = new JButton("A");
    private JButton boutonB = new JButton("B");
    private JButton boutonC = new JButton("C");
    private boolean testSouris = false; // ne pas modifier cette déclaration, 
                                        // installer le paramètre de cette applette Nom : mouse Valeur : oui,   
                                        // sa prise en compte est à la ligne 37-39

    private TextArea contenu = new TextArea(60, 80);

    /**
     * Appelée par le navigateur ou le visualiseur afin de signaler à l'Applet
     * qu'il est maintenant pris en charge par le système. Il est garanti que
     * ceci précédera le premier appel de la méthode start.
     */
    public void init() {
        JRootPane rootPane = this.getRootPane();
        rootPane.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);
        try {
            testSouris = getParameter("mouse").equals("oui"); // le paramètre issu de la page HTML
        } catch (Exception e) {
        }
        JPanel enHaut = new JPanel();
        enHaut.add(boutonA);
        enHaut.add(boutonB);
        enHaut.add(boutonC);
        setLayout(new BorderLayout(5, 5));
        add("North", enHaut);
        add("Center", contenu); // contenu sera transmis aux observateurs, voir
                                // la description des constructeurs
        if (testSouris)
            enHaut.setBackground(Color.magenta);
        else
            enHaut.setBackground(Color.blue);

        // le bouton A a 3 observateurs jbo1, jbo2 et jbo3
        boutonA.addActionListener(new JButtonObserver("jbo1", contenu));
        boutonA.addActionListener(new JButtonObserver("jbo2", contenu));
        boutonA.addActionListener(new JButtonObserver("jbo3", contenu));
        // le bouton B a 2 observateurs jbo1 et jbo2
        boutonB.addActionListener(new JButtonObserver("jbo1", contenu));
        boutonB.addActionListener(new JButtonObserver("jbo2", contenu));

        // le bouton C a 1 observateur jbo1
        boutonC.addActionListener(new JButtonObserver("jbo1", contenu));

        if (testSouris) { // à compléter en q2.2
            // le bouton A a 1 observateur jmo1
            boutonA.addMouseListener(new JMouseObserver("jmo1", contenu));
            // le bouton B a 1 observateur jmo2
            boutonB.addMouseListener(new JMouseObserver("jmo2", contenu));
            // le bouton C a 1 observateur jmo3
            boutonC.addMouseListener(new JMouseObserver("jmo3", contenu)); 
        }
    }
}

la classe JButtonObserver :


public class JButtonObserver implements ActionListener{ 

	private String nom;
	private TextArea contenu;

	/**
	 * Constructeur d'objets de classe JButtonObserver
	 * 
	 * @param nom
	 *            le nom du bouton, jbo1, jbo2, jbo3, jmo1, jmo2, jmo3
	 * @param contenu
	 *            la zone de texte de l'applette
	 */
	public JButtonObserver(String nom, TextArea contenu) {
		this.nom = nom;
		this.contenu = contenu;
	}

	/**
	 * affichage d'un message dans la zone de texte ce message est de la forme
	 * observateur this.nom : clic du bouton nom_du_bouton exemple : observateur
	 * jbo1 : clic du bouton A, voir la méthode getActionCommand()
	 * 
	 * @param e
	 *            String message = "observeur "+this.nom+" : clic du bouton "+e.getActionCommand()
	 */
	public void actionPerformed(ActionEvent e) {
        String message = "observeur "+this.nom+" : clic du bouton "+e.getActionCommand(); 
        contenu.append(message + "\n");
    } 

}




Question 2.2


la classe JMouseObserver :


public class JMouseObserver implements MouseListener{ 

	private String nom;
	private TextArea contenu;

	/**
	 * Constructeur d'objets de classe JButtonObserver
	 */
	public JMouseObserver(String nom, TextArea contenu) {
		this.nom = nom;
		this.contenu = contenu;
	}

	public void mouseClicked(MouseEvent e) {
	}

	/**
	 * affichage d'un message dans la zone de texte ce message est de la forme
	 * observateur this.nom : souris entrée en (X,Y) exemple : observateur jmo1
	 * : souris entrée en (15,20)
	 * 
	 * @param
	 */
	public void mouseEntered(MouseEvent e) {
        String message = "observeur "+this.nom+" : souris entrée en ("+e.getX()+","+ e.getY()+")";
        contenu.append(message + "\n");
    }

	public void mouseExited(MouseEvent e) {}

	public void mousePressed(MouseEvent e) {}

	public void mouseReleased(MouseEvent e) {}

}



Question 3.1


J'ai pas mal galéré pour avoir un comportement identique à l'applette de l'énoncé.

Notemment, pour la fonction soustraction et division où il fallait soustraire ou diviser par le sommet de la pile et non l'élément suivant.


la classe Vue :


public class Vue extends JPanel implements Observer {

    private JLabel etatPile;
    private PileModele<Integer> pile;
    /**La Vue s'inscrit auprès du modèle comme observateur.*/
    public Vue(PileModele<Integer> pile) {
        super();
        this.pile = pile;
        this.etatPile = new JLabel("entrez des nombres entiers");
        setLayout(new FlowLayout(FlowLayout.LEFT));
        add(etatPile);
        setBackground(Color.green);
        
        // inscription auprès du modèle comme observateur
        pile.addObserver(this);
    }
    /**Mise à jour de la vue*/
    public void update(Observable obs, Object arg) {
        etatPile.setText(pile.toString()); // ou obs.toString()
    }

}

la classe Controleur :


public class Controleur extends JPanel {

    private JButton push, add, sub, mul, div, clear;
    private PileModele<Integer> pile;
    private JTextField donnee;

    public Controleur(PileModele<Integer> pile) {
        super();
        this.pile = pile;
        this.donnee = new JTextField(8);

        this.push = new JButton("push");
        this.add = new JButton("+");
        this.sub = new JButton("-");
        this.mul = new JButton("*");
        this.div = new JButton("/");
        this.clear = new JButton("[]");

        setLayout(new GridLayout(2, 1));
        add(donnee);
        donnee.addActionListener(null /* null est à remplacer */);
        JPanel boutons = new JPanel();
        boutons.setLayout(new FlowLayout());
        boutons.add(push);  push.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent ae){
                   Controleur.this.push(); }});
        boutons.add(add);   add.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent ae){
                   Controleur.this.add(); }}); 
        boutons.add(sub);   sub.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent ae){
                   Controleur.this.sub(); }});
        boutons.add(mul);   mul.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent ae){
                   Controleur.this.mul(); }});
        boutons.add(div);   div.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent ae){
                   Controleur.this.div(); }});
        boutons.add(clear); clear.addActionListener(new ActionListener(){
                public void actionPerformed(ActionEvent ae){
                   Controleur.this.clear(); }});
        add(boutons);
        boutons.setBackground(Color.red);
        actualiserInterface();
    }
    /**
     * Actualise l'interface en fonction de la taille de la pile.
     */
    public void actualiserInterface() {
       if(pile.estVide()){
          add.setEnabled(false);
          sub.setEnabled(false);
          mul.setEnabled(false);
          div.setEnabled(false);
          clear.setEnabled(false);
          push.setEnabled(true);
       }
       else if(pile.taille()== 1){
          add.setEnabled(false);
          sub.setEnabled(false);
          mul.setEnabled(false);
          div.setEnabled(false);
          clear.setEnabled(true);
          push.setEnabled(true);
        }
        else if(pile.taille()> 1){
          add.setEnabled(true);
          sub.setEnabled(true);
          mul.setEnabled(true);
          div.setEnabled(true);
          clear.setEnabled(true);
          push.setEnabled(true);
        }
        else if(pile.estPleine()) {
          push.setEnabled(false);
          add.setEnabled(true);
          sub.setEnabled(true);
          mul.setEnabled(true);
          div.setEnabled(true);
          clear.setEnabled(true);
        }
    }

    private Integer operande() throws NumberFormatException {
        return Integer.parseInt(donnee.getText());
    }

    /**
     * push, empile les objets(integer).
     */
    public void push(){
        try{
            this.pile.empiler(operande());
        }catch(Exception e){

        }
        this.actualiserInterface();
    }
    /**add, ajoute les objets(integer) entre eux.*/
    public void add(){
        try{
            this.pile.empiler(this.pile.depiler() + this.pile.depiler());
        }catch(Exception e){
        }
        this.actualiserInterface();
        
    }
     /**sub, soustrait les objets(integer) entre eux.*/
    public void sub(){
        try{
            int o = this.pile.sommet();
            this.pile.depiler();
            this.pile.empiler(this.pile.depiler() - o);
            
        }catch(Exception e){
        }
        this.actualiserInterface();
    }
     /**mul, multiplie les objets(integer) entre eux.*/
    public void mul(){
        try{
            this.pile.empiler(this.pile.depiler() * this.pile.depiler());
        }catch(Exception e){
        }
        this.actualiserInterface();
    }
     /**div, divise les objets(integer) entre eux.*/
    public void div(){
        try{
            int o = this.pile.sommet();
            if(o != 0){
                this.pile.depiler();
                this.pile.empiler(this.pile.depiler() / o);
            }
            if(o == 0){
                //on ne fait rien
            }
        }catch(Exception e){
        }
        this.actualiserInterface();
    }
     /**clear, efface tous les objets(integer).*/
    public void clear(){
        for(int i = this.pile.taille(); i >=0 ; i--){
            try{
                this.pile.depiler();
            }catch(Exception e){
            }
        }
        this.actualiserInterface();
    }
}

la classe PileModele :


public class PileModele<T> extends Observable implements PileI<T> {
    private PileI<T> pile;
   /**Constructeur.*/
    public PileModele(PileI<T> pile) {
        this.pile = pile;
    }
    /**Empiler.
     * Ajoute des éléments à la pile.
     * @param
     *      o object à empiler.
     */
    public void empiler(T o) throws PilePleineException {
        if (estPleine()) throw new PilePleineException(o.toString());
        try{
            this.pile.empiler(o);
        }catch(Exception e){

        }finally{
            setChanged();
            notifyObservers();
        } 
    }
    /**Dépiler.
     * @return l'état de la pile avec un élément en moins.
     */
    public T depiler() throws PileVideException {
        if (estVide()) throw new PileVideException();
        
        try{
            return this.pile.depiler();
        }catch(Exception e){
            
        }finally{
            setChanged();
            notifyObservers();
        }
        return null;
    }
    /**Retourne le sommet de la pile.
     * @return le sommet.
     */
    public T sommet() throws PileVideException {
        if (estVide()) throw new PileVideException();
        return  this.pile.sommet();
    }
    /**Retourne le nombre d'éléments de la pile.
     * @return la taille.
     */
    public int taille() {
        return pile.taille();
    }
    /**Donne le nombre d'éléments maximum que peut contenir la pile.
     * @return la capacité.
     */
    public int capacite() {
        return pile.capacite();
    }
    /**Retourne vrai si la pile est vide.
     * @return vrai si vide.
     */
    public boolean estVide() {
        return pile.estVide();
    }
    /**Retourne vrai si la pile est pleine.
     * @return vrai si pleine.
     */
    public boolean estPleine() {
        return pile.estPleine();
    }
    /**Méthode toString.*/
    public String toString() {
        return pile.toString();
    }
}



Question 3.2


Au départ je n'avais pas compris le choix de mettre des boutons locaux au controleur. Le controleur gère l'interface entre le modèle et le client. Il va interpréter la requête de ce dernier pour lui envoyer la vue correspondante. Il effectue la synchronisation entre le modèle et la vue. La vue représente l'interface utilisateur, ce avec quoi il interagit. Elle n'effectue aucun traitement, elle se contente simplement d'afficher les données que lui fournit le modèle, ici la pile. Il peut tout à fait y avoir plusieurs vues qui présentent les données d'un même modèle. Au final, j'ai trouvé cet exemple très formateur .

Mvc2

Question 3.3


En intégrant la vue2 à l'applette, il n'y a en effet que peu dechangement à apporter.

Sans la première vue :


3 3 1

Avec les 2 vues :


3 3 2

Conclusion, biliographie et remarques :

Tp très intéressant bien que difficile. Je ne sais pas si j'ai réellement compris le pattern MVC...mais j'y vois plus clair désormais.

http://baptiste-wicht.developpez.com/tutoriels/conception/mvc/#LII-B


Commentaires

Ajouter un commentaire