Le pattern MVC et SWING

Le modèle MVC est l’un des modèles de conception que j’ai le plus de mal à comprendre dans la «vraie vie SWING». J’ai parcouru pas mal de messages sur ce site qui traitent du modèle, mais je n’ai toujours pas l’impression de comprendre comment tirer parti de ce modèle dans mon application (Java SWING).

Disons que j’ai un JFrame qui contient une table, quelques champs de texte et quelques boutons. J’utiliserais probablement un TableModel pour “ponter” la JTable avec un modèle de données sous-jacent. Cependant, toutes les fonctions responsables de la suppression des champs, de la validation des champs, du locking des champs et des actions des boutons iront généralement directement dans JFrame. Cependant, cela ne mélange-t-il pas le contrôleur et la vue du motif?

Autant que je sache, je parviens à implémenter le modèle MVC correctement lorsque je regarde la JTable (et le modèle), mais les choses s’embrouillent quand je regarde l’ensemble de JFrame dans son ensemble.

J’aimerais vraiment savoir comment les autres s’y prennent. Comment allez-vous lorsque vous devez afficher un tableau, quelques champs et quelques boutons pour un utilisateur (en utilisant le modèle MVC)?

Un livre que je vous recommande fortement pour MVC en swing serait “Head First Design Patterns” de Freeman et Freeman. Ils ont une explication très complète de MVC.

Bref résumé

  1. Vous êtes l’utilisateur – vous interagissez avec la vue. La vue est votre fenêtre sur le modèle. Lorsque vous faites quelque chose à la vue (comme cliquer sur le bouton Lecture), la vue indique au contrôleur ce que vous avez fait. C’est le travail du contrôleur de gérer cela.

  2. Le contrôleur demande au modèle de changer son état. Le contrôleur prend vos actions et les interprète. Si vous cliquez sur un bouton, le contrôleur doit déterminer ce que cela signifie et comment le modèle doit être manipulé en fonction de cette action.

  3. Le contrôleur peut également demander à changer de vue. Lorsque le contrôleur reçoit une action de la vue, il peut être nécessaire de dire à la vue de modifier en conséquence. Par exemple, le contrôleur peut activer ou désactiver certains boutons ou éléments de menu dans l’interface.

  4. Le modèle notifie la vue lorsque son état a changé. Lorsque quelque chose change dans le modèle, en fonction d’une action que vous avez prise (comme cliquer sur un bouton) ou d’une autre modification interne (comme la chanson suivante de la liste de lecture a démarré), le modèle indique que son état a changé.

  5. La vue demande au modèle l’état. La vue obtient l’état affiché directement à partir du modèle. Par exemple, lorsque le modèle notifie l’affichage d’une nouvelle chanson, la vue demande le nom du morceau au modèle et l’affiche. La vue peut également demander au modèle un état résultant du fait que le contrôleur demande une modification de la vue.

entrer la description de l'image ici Source (Au cas où vous vous demanderiez ce qu’est un “contrôleur crémeux”, pensez à un biscuit Oreo, le contrôleur étant le centre crémeux, la vue étant le biscuit supérieur et le modèle étant le biscuit de fond.)

Euh, au cas où vous seriez intéressé, vous pourriez télécharger une chanson assez amusante sur le modèle MVC d’ ici !

Un problème que vous pouvez rencontrer avec la programmation Swing implique la fusion des threads SwingWorker et EventDispatch avec le pattern MVC. Selon votre programme, votre vue ou votre contrôleur peut devoir étendre SwingWorker et remplacer la méthode doInBackground() où une logique gourmande en ressources est placée. Cela peut être facilement fusionné avec le modèle MVC typique et est typique des applications Swing.

EDIT # 1 :

De plus, il est important de considérer MVC comme une sorte de composite de différents modèles. Par exemple, votre modèle pourrait être implémenté à l’aide du modèle Observer (nécessitant que la vue soit enregistrée en tant qu’observateur sur le modèle) alors que votre contrôleur pourrait utiliser le modèle de stratégie.

EDIT # 2 :

Je voudrais aussi répondre spécifiquement à votre question. Vous devriez afficher vos boutons de table, etc. dans la vue, ce qui implémenterait évidemment un ActionListener. Dans votre méthode actionPerformed() , vous détectez l’événement et l’envoyez à une méthode associée dans le contrôleur (n’oubliez pas que la vue contient une référence au contrôleur). Ainsi, lorsqu’un bouton est cliqué, l’événement est détecté par la vue, envoyé à la méthode du contrôleur, le contrôleur peut directement demander à la vue de désactiver le bouton ou quelque chose. Ensuite, le contrôleur va interagir avec le modèle et le modifier (qui aura principalement des méthodes de lecture et de définition, et d’autres pour enregistrer et notifier les observateurs, etc.). Dès que le modèle sera modifié, il lancera une mise à jour sur les observateurs enregistrés (ce sera la vue dans votre cas). Par conséquent, la vue va maintenant se mettre à jour.

Je n’aime pas l’idée que la vue soit celle qui est notifiée par le modèle lorsque ses données changent. Je déléguerais cette fonctionnalité au contrôleur. Dans ce cas, si vous modifiez la logique de l’application, vous n’avez pas besoin d’interférer avec le code de la vue. La tâche de la vue est uniquement pour les composants d’application + la mise en page rien de plus rien de moins. La mise en page est déjà une tâche prolixe, pourquoi la laisser interférer avec la logique des applications?

Mon idée de MVC (avec laquelle je travaille actuellement, tout va bien) est la suivante:

  1. La vue est la plus stupide des trois. Il ne sait rien du contrôleur et du modèle. Son souci ne concerne que la prostétie et la disposition des composants du swing.
  2. Le modèle est également stupide, mais pas aussi stupide que la vue. Il exécute les fonctionnalités suivantes.
    • une. quand un de ses setter est appelé par le contrôleur, il enverra une notification à ses auditeurs / observateurs (comme je l’ai dit, je délèguerais ce rôle au contrôleur). Je préfère SwingPropertyChangeSupport pour y parvenir, car il est déjà optimisé à cette fin.
    • b. fonctionnalité d’interaction de firebase database.
  3. Un contrôleur très intelligent. Connaît très bien la vue et le modèle. Le contrôleur a deux fonctionnalités:
    • une. Il définit l’action que la vue exécutera lorsque l’utilisateur interagit avec elle.
    • b. Il écoute le modèle. Comme ce que j’ai dit, lorsque le composeur du modèle est appelé, le modèle envoie une notification au contrôleur. C’est le travail du contrôleur d’interpréter cette notification. Il peut être nécessaire de refléter le changement de vue.

Exemple de code

La vue :

Comme je l’ai dit, la création de la vue est déjà verbeuse alors créez votre propre implémentation 🙂

 interface View{ JTextField getTxtFirstName(); JTextField getTxtLastName(); JTextField getTxtAddress(); } 

C’est idéal pour interfacer les trois à des fins de testabilité. J’ai seulement fourni mon implémentation de modèle et de contrôleur.

Le modèle :

 public class MyImplementationOfModel implements Model{ ... private SwingPropertyChangeSupport propChangeFirer; private Ssortingng address; private Ssortingng firstName; private Ssortingng lastName; public MyImplementationOfModel() { propChangeFirer = new SwingPropertyChangeSupport(this); } public void addListener(PropertyChangeListener prop) { propChangeFirer.addPropertyChangeListener(prop); } public void setAddress(Ssortingng address){ Ssortingng oldVal = this.address; this.address = address; //after executing this, the controller will be notified that the new address has been set. Its then the controller's //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this propChangeFirer.firePropertyChange("address", oldVal, address); } ... //some other setters for other properties & code for database interaction ... } 

Le controlle :

 public class MyImplementationOfController implements PropertyChangeListener, Controller{ private View view; private Model model; public MyImplementationOfController(View view, Model model){ this.view = view; this.model = model; //register the controller as the listener of the model this.model.addListener(this); setUpViewEvents(); } //code for setting the actions to be performed when the user interacts to the view. private void setUpViewEvents(){ view.getBtnClear().setAction(new AbstractAction("Clear") { @Override public void actionPerformed(ActionEvent arg0) { model.setFirstName(""); model.setLastName(""); model.setAddress(""); } }); view.getBtnSave().setAction(new AbstractAction("Save") { @Override public void actionPerformed(ActionEvent arg0) { ... //validate etc. ... model.setFirstName(view.getTxtFName().getText()); model.setLastName(view.getTxtLName().getText()); model.setAddress(view.getTxtAddress().getText()); model.save(); } }); } public void propertyChange(PropertyChangeEvent evt){ Ssortingng propName = evt.getPropertyName(); Object newVal = evt.getNewValue(); if("address".equalsIgnoreCase(propName)){ view.getTxtAddress().setText((Ssortingng)newVal); } //else if property (name) that fired the change event is first name property //else if property (name) that fired the change event is last name property } } 

Le principal, où le MVC est configuré:

 public class Main{ public static void main(Ssortingng[] args){ View view = new YourImplementationOfView(); Model model = new MyImplementationOfModel(); ... //create jframe //frame.add(view.getUI()); ... //make sure the view and model is fully initialized before letting the controller control them. Controller controller = new MyImplementationOfController(view, model); ... //frame.setVisible(true); ... } } 

Le modèle MVC est un modèle de la façon dont une interface utilisateur peut être structurée. Par conséquent, il définit les 3 éléments Model, View, Controller:

  • Modèle Un modèle est une abstraction de quelque chose qui est présenté à l’utilisateur. En swing, vous avez une différenciation des modèles d’interface graphique et des modèles de données. Les modèles d’interface utilisateur abstraite l’état d’un composant d’ interface utilisateur tel que ButtonModel . Les données modélisent des données structurées abstraites que l’interface utilisateur présente à l’utilisateur comme TableModel .
  • Affichage La vue est un composant d’interface utilisateur chargé de présenter les données à l’utilisateur. Ainsi, il est responsable de tous les problèmes dépendants de l’ interface utilisateur tels que la mise en page, le dessin, etc. Par exemple, JTable .
  • Contrôleur Un contrôleur encapsule le code de l’application qui est exécuté afin d’interagir avec l’utilisateur (mouvement de la souris, clic de la souris, pression sur une touche, etc.). Les contrôleurs peuvent avoir besoin de données pour leur exécution et ils produisent des résultats. Ils lisent leurs entrées à partir de modèles et mettent à jour les modèles à la suite de l’exécution. Ils pourraient également restructurer l’interface utilisateur (par exemple, remplacer des composants d’interface utilisateur ou afficher une nouvelle vue complète). Cependant, ils ne doivent pas connaître les composants de l’interface utilisateur, car vous pouvez encapsuler la restructuration dans une interface distincte que le contrôleur appelle uniquement. En swing, un contrôleur est normalement implémenté par ActionListener ou Action .

Exemple

  • Rouge = modèle
  • Vert = vue
  • Bleu = contrôleur

entrer la description de l'image ici

Lorsque l’utilisateur clique sur le bouton, il appelle ActionListener . L’ ActionListener ne dépend que d’autres modèles. Il utilise certains modèles comme entrée et d’autres comme résultat ou sortie. C’est comme des arguments de méthode et des valeurs de retour. Les modèles avertissent l’interface utilisateur lorsqu’ils sont mis à jour. Il n’est donc pas nécessaire que la logique du contrôleur connaisse le composant d’interface utilisateur. Les objects du modèle ne connaissent pas l’interface utilisateur. La notification est effectuée par un motif d’observateur. Ainsi, les objects du modèle ne savent que si quelqu’un souhaite recevoir une notification si le modèle change.

En java swing, certains composants implémentent également un modèle et un contrôleur. Par exemple, javax.swing.Action . Il implémente un modèle d’interface utilisateur (propriétés: activation, petite icône, nom, etc.) et est un contrôleur car il étend ActionListener .

Une explication détaillée, exemple d’application et code source : https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/ .

Bases MVC en moins de 240 lignes:

 public class Main { public static void main(Ssortingng[] args) { JFrame mainFrame = new JFrame("MVC example"); mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); mainFrame.setSize(640, 300); mainFrame.setLocationRelativeTo(null); PersonService personService = new PersonServiceMock(); DefaultListModel searchResultListModel = new DefaultListModel(); DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel(); searchResultSelectionModel .setSelectionMode(ListSelectionModel.SINGLE_SELECTION); Document searchInput = new PlainDocument(); PersonDetailsAction personDetailsAction = new PersonDetailsAction( searchResultSelectionModel, searchResultListModel); personDetailsAction.putValue(Action.NAME, "Person Details"); Action searchPersonAction = new SearchPersonAction(searchInput, searchResultListModel, personService); searchPersonAction.putValue(Action.NAME, "Search"); Container contentPane = mainFrame.getContentPane(); JPanel searchInputPanel = new JPanel(); searchInputPanel.setLayout(new BorderLayout()); JTextField searchField = new JTextField(searchInput, null, 0); searchInputPanel.add(searchField, BorderLayout.CENTER); searchField.addActionListener(searchPersonAction); JButton searchButton = new JButton(searchPersonAction); searchInputPanel.add(searchButton, BorderLayout.EAST); JList searchResultList = new JList(); searchResultList.setModel(searchResultListModel); searchResultList.setSelectionModel(searchResultSelectionModel); JPanel searchResultPanel = new JPanel(); searchResultPanel.setLayout(new BorderLayout()); JScrollPane scrollableSearchResult = new JScrollPane(searchResultList); searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER); JPanel selectionOptionsPanel = new JPanel(); JButton showPersonDetailsButton = new JButton(personDetailsAction); selectionOptionsPanel.add(showPersonDetailsButton); contentPane.add(searchInputPanel, BorderLayout.NORTH); contentPane.add(searchResultPanel, BorderLayout.CENTER); contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH); mainFrame.setVisible(true); } } class PersonDetailsAction extends AbstractAction { private static final long serialVersionUID = -8816163868526676625L; private ListSelectionModel personSelectionModel; private DefaultListModel personListModel; public PersonDetailsAction(ListSelectionModel personSelectionModel, DefaultListModel personListModel) { boolean unsupportedSelectionMode = personSelectionModel .getSelectionMode() != ListSelectionModel.SINGLE_SELECTION; if (unsupportedSelectionMode) { throw new IllegalArgumentException( "PersonDetailAction can only handle single list selections. " + "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION"); } this.personSelectionModel = personSelectionModel; this.personListModel = personListModel; personSelectionModel .addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { ListSelectionModel listSelectionModel = (ListSelectionModel) e .getSource(); updateEnablement(listSelectionModel); } }); updateEnablement(personSelectionModel); } public void actionPerformed(ActionEvent e) { int selectionIndex = personSelectionModel.getMinSelectionIndex(); PersonElementModel personElementModel = (PersonElementModel) personListModel .get(selectionIndex); Person person = personElementModel.getPerson(); Ssortingng personDetials = createPersonDetails(person); JOptionPane.showMessageDialog(null, personDetials); } private Ssortingng createPersonDetails(Person person) { return person.getId() + ": " + person.getFirstName() + " " + person.getLastName(); } private void updateEnablement(ListSelectionModel listSelectionModel) { boolean emptySelection = listSelectionModel.isSelectionEmpty(); setEnabled(!emptySelection); } } class SearchPersonAction extends AbstractAction { private static final long serialVersionUID = 4083406832930707444L; private Document searchInput; private DefaultListModel searchResult; private PersonService personService; public SearchPersonAction(Document searchInput, DefaultListModel searchResult, PersonService personService) { this.searchInput = searchInput; this.searchResult = searchResult; this.personService = personService; } public void actionPerformed(ActionEvent e) { Ssortingng searchSsortingng = getSearchSsortingng(); List matchedPersons = personService.searchPersons(searchSsortingng); searchResult.clear(); for (Person person : matchedPersons) { Object elementModel = new PersonElementModel(person); searchResult.addElement(elementModel); } } private Ssortingng getSearchSsortingng() { try { return searchInput.getText(0, searchInput.getLength()); } catch (BadLocationException e) { return null; } } } class PersonElementModel { private Person person; public PersonElementModel(Person person) { this.person = person; } public Person getPerson() { return person; } @Override public Ssortingng toSsortingng() { return person.getFirstName() + ", " + person.getLastName(); } } interface PersonService { List searchPersons(Ssortingng searchSsortingng); } class Person { private int id; private Ssortingng firstName; private Ssortingng lastName; public Person(int id, Ssortingng firstName, Ssortingng lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } public int getId() { return id; } public Ssortingng getFirstName() { return firstName; } public Ssortingng getLastName() { return lastName; } } class PersonServiceMock implements PersonService { private List personDB; public PersonServiceMock() { personDB = new ArrayList(); personDB.add(new Person(1, "Graham", "Parrish")); personDB.add(new Person(2, "Daniel", "Hendrix")); personDB.add(new Person(3, "Rachel", "Holman")); personDB.add(new Person(4, "Sarah", "Todd")); personDB.add(new Person(5, "Talon", "Wolf")); personDB.add(new Person(6, "Josephine", "Dunn")); personDB.add(new Person(7, "Benjamin", "Hebert")); personDB.add(new Person(8, "Lacota", "Browning ")); personDB.add(new Person(9, "Sydney", "Ayers")); personDB.add(new Person(10, "Dustin", "Stephens")); personDB.add(new Person(11, "Cara", "Moss")); personDB.add(new Person(12, "Teegan", "Dillard")); personDB.add(new Person(13, "Dai", "Yates")); personDB.add(new Person(14, "Nora", "Garza")); } public List searchPersons(Ssortingng searchSsortingng) { List matches = new ArrayList(); if (searchSsortingng == null) { return matches; } for (Person person : personDB) { if (person.getFirstName().contains(searchSsortingng) || person.getLastName().contains(searchSsortingng)) { matches.add(person); } } return matches; } } 

Vous pouvez créer un modèle dans une classe Java simple distincte et un contrôleur dans une autre.

Ensuite, vous pouvez avoir des composants Swing en plus. JTable serait l’une des vues (et le modèle de table ferait de facto partie de la vue – cela ne se traduirait que du “modèle partagé” en JTable ).

Chaque fois que la table est éditée, son modèle de table indique au “contrôleur principal” de mettre à jour quelque chose. Cependant, le contrôleur ne doit rien savoir sur la table. L’appel devrait donc ressembler davantage à: updateCustomer(customer, newValue) , pas updateCustomer(row, column, newValue) .

Ajoutez une interface d’écoute (observateur) pour le modèle partagé. Certains composants (par exemple, votre table) pourraient l’implémenter directement. Un autre observateur pourrait être le contrôleur qui coordonne la disponibilité des boutons, etc.


C’est une façon de le faire, mais bien sûr, vous pouvez la simplifier ou l’étendre si c’est exagéré pour votre cas d’utilisation.

Vous pouvez fusionner le contrôleur avec le modèle et avoir les mêmes mises à jour de processus de classe et maintenir la disponibilité des composants. Vous pouvez même faire du “modèle partagé” un object TableModel (même si ce n’est pas seulement utilisé par la table, je vous recommande au moins de fournir une API plus conviviale qui ne fuit pas les abstractions de la table)

D’autre part, vous pouvez disposer d’interfaces complexes pour les mises à jour ( CustomerUpdateListener , OrderCancellationListener , OrderCancellationListener ) et d’un contrôleur (ou médiateur) dédié uniquement pour la coordination des différentes vues.

Cela dépend de la complexité de votre problème.

Pour une séparation correcte, vous auriez généralement une classe de contrôleur à laquelle la classe Frame déléguerait. Il existe différentes manières de définir les relations entre les classes – vous pouvez implémenter un contrôleur et l’étendre avec votre classe de vue principale ou utiliser une classe de contrôleur autonome que Frame appelle lorsque des événements se produisent. La vue reçoit généralement des événements du contrôleur en implémentant une interface d’écouteur.

Parfois, une ou plusieurs parties du modèle MVC sont sortingviales, ou tellement «minces», qu’elles ajoutent une complexité inutile pour les séparer. Si votre contrôleur est plein d’appels d’une ligne, le faire dans une classe distincte peut finir par masquer le comportement sous-jacent. Par exemple, si tous les événements que vous gérez sont liés à un modèle de table et constituent des opérations simples d’ajout et de suppression, vous pouvez choisir d’implémenter toutes les fonctions de manipulation de table dans ce modèle (ainsi que les rappels nécessaires pour l’afficher dans le JTable). Ce n’est pas vrai MVC, mais cela évite d’append de la complexité là où cela n’est pas nécessaire.

Cependant, implémentez-le, rappelez-vous à JavaDoc vos classes, méthodes et packages pour que les composants et leurs relations soient correctement décrits!

J’ai trouvé des articles intéressants sur la mise en œuvre de modèles MVC, qui pourraient résoudre votre problème.

Si vous développez un programme avec une interface graphique , le modèle mvc est presque là mais flou.

La détection du code du modèle, de la vue et du contrôleur est difficile et ne constitue généralement pas seulement une tâche de refactorisation.

Vous savez que vous l’avez lorsque votre code est réutilisable. Si vous avez correctement implémenté MVC, vous devriez pouvoir facilement implémenter une interface TUI, une interface de ligne de commande, une carte RWD ou une première conception mobile avec les mêmes fonctionnalités. Il est facile de le voir faire que de le faire en fait, en plus sur un code existant.

En fait, les interactions entre le modèle, la vue et le contrôleur se produisent à l’aide d’autres modèles d’isolement (en tant qu’observateur ou auditeur).

Je suppose que cet article l’explique en détail, du modèle direct non MVC (comme vous le ferez sur un Q & D ) à l’implémentation finale réutilisable:

http://www.austintek.com/mvc/