MVC: Model-View-Controller

J'ai déssiné au tableau le diagramme suivant:

Ce patron de conception nous dit que pour avoir un bon GUI il faut découpler la logique de l'application, le traitment des entrées (user input) et la rétroaction (feedback) visuelle. De cette séparation on obtient les trois grandes composantes proposées par le patron : le modèle, le controleur et la vue.

Model

Le model s'occupe de gérer l'information et d'avertir les acteurs intéressés lorsque cette information change. Dans cet exemple, le modèle gère une pile (stack) et il averti ceux qui se sont enregistrés comme obervateur (Observer) lorsqu'il y a des changements

public class MyStack extends Observable {
	private LinkedList stack;
	int size;
	int count;
	
	public MyStack(int size) {
		this.size = size;
		count = 0;
		stack = new LinkedList();
	}
	
	public void push(Object o) {
		if(count < size) {
			stack.addFirst(o);
			count++;
			setChanged();
			notifyObservers("push");
		}
	}
	
	public Object pop() {
		if(count > 0) {
			Object o = stack.removeFirst();
			count--;
			setChanged();
			notifyObservers("pop");
			return o;
		}
		
		return null;
	}

	public int getSize() {
		return count;
	}
}

View

La vue doit avoir tout ce qui lui faut pour se dessiner correctement à l'écran. Elle écoute pour des changements dans le modèle, et se met à jour.

Il faut faire attention quand donne une référence au controlleur à la vue, car le contrôleur peut changer. Il faut donc seulement appeler les méthodes publiées (celle qui ne changeront pas). Idéalement, la vue n'a pas de référence vers le contrôleur.

Comme mentionné lors de la présentation, dans cet implantation, il y a un couplage de contrôle. En regardant la méthode update(), on voit que la mise à jour de la vue dépends de l'avertissement du modèle. Dans une bonne implantation, c'est la vue qui doit ce mettre a jour en allant regarder ce qui a changé dans le modèle. Elle ne reçoit qu'un avertissement que le modèle à changé et aucune autre information supplémentaire.

Pour corriger le couplage de contrôle, il faudrait ajouter une référence vers le modèle dans la classe PileOfBoxes et modifier la méthode update.

public class PileOfBoxes extends JPanel implements Observer {
	
	JLabel[] labels;
	int maxSize;
	int count;

	/*
	 *il faut ajouter une référence au modèle afin d'obtenir l'état de celui-ci après un changement.
	 *MyStack stack;
	 */
	
	public PileOfBoxes(int maxSize) {
		setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
		this.maxSize = maxSize;
		count = 0;
		labels = new JLabel[maxSize];
		for(int i=0; i 0) {
			count--;
			labels[count].setVisible(false);
		}
	}
	
	public void update(Observable o, Object arg) {
		String modification = (String) arg;
		
		if(modification.equals("push")) {
			addBox();
		} else if(modification.equals("pop")) {
			removeBox();
		}

		/*
		 *Couplage de contrôle
		 *Il faut que la vue découvre par elle même en quoi le modèle a changé de façon à se mettre à jour par elle même.
		 *Par exemple, on pourrait regarder la grandeur de la pile pour découvrir s'il y a eu un ajout ou un retrair.
		 */
	}
}

Controller

Le contrôleur permet à l'usager d'interagir avec l'application. Il écoute le input de l'usager et mets à jour le modèle.

Si la vue est très sophistiquée, le contrôleur pourrait avoir une référence vers celle-ci afin de gérer le input du client. (Par exemple, si on a un rectangle comme vue et on veut savoir si l'usager clique sur ce rectangle, alors le contrôleur doit avoir une référence vers la vue).

public class BoxPileController implements ActionListener {
	private MyStack model;
	
	public BoxPileController(MyStack model, PileOfBoxes view) {
		this.model = model;
	}

	public void actionPerformed(ActionEvent e) {
		String action = ((JButton) e.getSource()).getText();
		
		if(action.equals("add")) {
			model.push("box");
		} else if(action.equals("remove")) {
			model.pop();
		}
	}
}

Quelque remarque sur l'implantion

Puisque le MVC est aussi un patron architectural (de haut niveau), il est souvent difficile de prédire comment celui-ci se manifestera en code. L'important, c'est de réduire le plus possible les dépendances entre ces trois entités.

Dans une implantation donnée, il peut y avoir plusieurs manifestation du MVC. Dans mon exemple, il y a la manifestation suivante:

Puisque j'utilise swing, il y a une deuxième manifestation du MVC: Les JPanels, JButtons, etc. on tous leur propre implantation du MVC dans swing.

Pour exécuter mon code, télécharger le fichier demo.zip, décompresser le dans un répertoire quelconque (en gardant la structure de fichier interne). Le main se trouve dans le package editor. C'est la classe Application qu'il faut lancer.