Le modèle MVC et SWING
L'un des modèles de conception que je trouve le plus difficile à saisir dans "real Swing life" est le modèle MVC. J'ai été à travers un certain nombre de messages sur ce site qui discutent du modèle, mais je ne pense toujours pas que j'ai une compréhension claire de la façon de tirer parti du 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 " bridge" le JTable avec un modèle de données sous-jacent. Cependant, toutes les fonctions responsables de l'effacement des champs, de la validation des champs, du verrouillage des champs ainsi que des actions de bouton iraient généralement directement dans le JFrame. Cependant, cela ne mélange-t-il pas le contrôleur et la vue du motif?
Pour autant que je puisse voir, je parviens à implémenter le modèle MVC "correctement" en regardant le JTable (et le modèle), mais les choses deviennent boueuses quand je regarde l'ensemble du JFrame dans son ensemble.
J'aimerais vraiment entendre comment les autres vont à propos à cet égard. Comment allez-vous quand vous avez besoin d'afficher une table, quelques champs et quelques boutons à un utilisateur (en utilisant le modèle MVC)?
7 réponses
Un livre que je vous recommande fortement pour MVC dans swing serait "Head First Design Patterns" par Freeman et Freeman. Ils ont une explication très complète de MVC.
Bref Résumé
Vous êtes l'utilisateur--vous d'interagir avec la vue., La vue est votre fenêtre sur le modèle. Lorsque vous faites quelque chose à la vue (comme Cliquez sur le Bouton de lecture) puis la vue indique au contrôleur ce que vous avez fait. C'est l' travail du contrôleur à gérer que.
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, c'est le travail du contrôleur de comprendre ce que cela signifie Et comment le modèle devrait être manipulé en fonction de cette action.
Le contrôleur peut également demander l'avis du changement. lorsque le contrôleur reçoit une action de la vue, il peut être nécessaire de vue de changer. Exemple, le contrôleur pourrait permettre ou désactiver certains boutons ou éléments de menu dans l'interface.
Le modèle notifie la vue lorsque son état a changé.{[7] } quand quelque chose change dans le modèle, basé soit sur une action que vous avez prise (comme cliquer sur un bouton) ou un autre changement interne (comme le suivant chanson dans la liste de lecture a commencé), le modèle avertit la vue que son état a changé.
La vue demande au modèle d'état. la vue obtient l'état, il affiche directement à partir du modèle. Par exemple, lorsque le modèle avertit la vue qu'une nouvelle chanson a commencé à jouer, la vue demande le nom de la chanson du modèle et l'affiche. Le point de vue pourrait demandez également au modèle d'État à la suite du contrôleur en demandant un changement dans la vue.
Source (au cas où vous vous demanderiez ce qu'est un "contrôleur crémeux", pensez à un cookie Oreo, Le contrôleur étant le centre crémeux, la vue étant le biscuit supérieur et le modèle étant le biscuit inférieur.)
Euh, au cas où vous êtes intéressé, vous pouvez télécharger une chanson assez divertissante sur le modèle MVC de ici !
Un problème auquel vous pouvez faire face avec la programmation Swing consiste à fusionner le thread SwingWorker et EventDispatch avec le modèle MVC. Selon votre programme, votre vue ou votre contrôleur peut devoir étendre le SwingWorker et remplacer la méthode doInBackground()
où la logique gourmande en ressources est placer. Cela peut être facilement fusionné avec le modèle MVC typique, et est typique des applications Swing.
MODIFIER #1:
En outre, il est important de considérer MVC comme une sorte de composite de divers modèles. Par exemple, votre modèle peut être implémenté en utilisant le modèle D'Observateur (nécessitant que la vue soit enregistrée en tant qu'observateur du modèle) tandis que votre contrôleur peut utiliser le modèle de stratégie.
MODIFIER #2:
Je voudrais en outre répondez spécifiquement à votre question. Vous devez 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 (rappelez - vous-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 interagir avec et modifier le modèle (qui aura principalement des méthodes getter et setter, et d'autres pour enregistrer et notifier les observateurs et ainsi de suite). Dès que le modèle est modifié, il appellera 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 seulement pour les applications composants + mise en page rien de plus rien de moins. Layouting dans swing est déjà une tâche verbeuse, pourquoi laisser interférer avec la logique des applications?
Mon idée de MVC (que je suis actuellement travailler avec, jusqu'à présent tout va bien) est:
- la vue est la plus stupide des trois. Il ne sait rien sur le contrôleur et le modèle. Sa préoccupation est seulement la prostéthique et la mise en page des composants swing.
- le modèle est aussi stupide, mais pas aussi stupide que la vue. Il exécute les fonctionnalités suivantes.
- a. quand l'un de ses setter est appelé par le contrôleur, il déclenchera une notification à ses auditeurs / observateurs (comme je l'ai dit, Je déligerais ce rôle au contrôleur). Je préfère SwingPropertyChangeSupport pour y parvenir car il est déjà optimisé à cet effet.
- B. fonctionnalité d'interaction de base de données.
- un contrôleur très intelligent. Connaît très bien la vue et le modèle. Le contrôleur a deux fonctionnalités:
- a. Il définit l'action que la vue exécutera lorsque l'utilisateur interagira avec elle.
- B. Il écoute le modèle. Comme ce que j'ai dit, quand le setter du modèle est appelé, le modèle déclenchera 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 apporté à la vue.
Exemple De Code
La Vue:
Comme je l'ai dit, la création de la vue est déjà verbeuse, alors créez simplement votre propre implémentation:)
interface View{
JTextField getTxtFirstName();
JTextField getTxtLastName();
JTextField getTxtAddress();
}
Il est idéal pour interfacer les trois à des fins de testabilité. J'ai seulement fourni mon implémentation du modèle et du contrôleur.
Le Modèle :
public class MyImplementationOfModel implements Model{
...
private SwingPropertyChangeSupport propChangeFirer;
private String address;
private String firstName;
private String lastName;
public MyImplementationOfModel() {
propChangeFirer = new SwingPropertyChangeSupport(this);
}
public void addListener(PropertyChangeListener prop) {
propChangeFirer.addPropertyChangeListener(prop);
}
public void setAddress(String address){
String 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 Contrôleur:
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){
String propName = evt.getPropertyName();
Object newVal = evt.getNewValue();
if("address".equalsIgnoreCase(propName)){
view.getTxtAddress().setText((String)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(String[] 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 le modèle 3 éléments, Vue, Contrôleur:
- Modèle " Un modèle est une abstraction de quelque chose qui est présenté à l'utilisateur. Dans swing, vous avez une différenciation des modèles d'interface graphique et des modèles de données. Les modèles GUI abstraient l'état d'un composant d'interface utilisateur comme ButtonModel . Modèles de données données structurées abstraites que l'interface utilisateur présente à l'utilisateur comme TableModel .
- View la vue est un composant d'interface utilisateur qui est responsable de la présentation des données à l'utilisateur. Ainsi, il est responsable de tous les problèmes dépendants de l'interface utilisateur comme la mise en page, le dessin, etc. Par exemple JTable .
- Controller un controller encapsule le code d'application qui est exécuté pour une interaction de l'utilisateur (mouvement de la souris, clic de souris,pression de touche, etc.). Les contrôleurs peuvent avoir besoin d'une entrée pour leur exécution et ils produisent une sortie. Ils ont lu leurs entrée des modèles et mise à jour des modèles à la suite de l'exécution. Ils peuvent également restructurer l'interface utilisateur (par exemple, remplacer les composants de l'interface utilisateur ou afficher une nouvelle vue complète). Cependant, ils ne doivent pas connaître les compoenents de l'interface utilisateur, car vous pouvez encapsuler la restructuration dans une interface séparée que le contrôleur appelle uniquement. En balancer un contrôleur est normalement mis en œuvre par un ActionListener ou Action.
Exemple
- Rouge = modèle
- Vert = Vue
- Bleu = contrôleur
Lorsque le Button
est cliqué, il appelle la ActionListener
. Le 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 notifient 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 objets du modèle ne connaissent pas l'interface utilisateur. La notification est effectuée par un modèle d'observateur. Par conséquent, le modèle les objets savent seulement qu'il y a quelqu'un qui veut être averti si le modèle change.
Dans java swing, certains composants implémentent également un modèle et un contrôleur. Par exemple, le 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 .
A 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/.
MVC notions de base en moins de 240 lignes:
public class Main {
public static void main(String[] 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();
String personDetials = createPersonDetails(person);
JOptionPane.showMessageDialog(null, personDetials);
}
private String 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) {
String searchString = getSearchString();
List<Person> matchedPersons = personService.searchPersons(searchString);
searchResult.clear();
for (Person person : matchedPersons) {
Object elementModel = new PersonElementModel(person);
searchResult.addElement(elementModel);
}
}
private String getSearchString() {
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 String toString() {
return person.getFirstName() + ", " + person.getLastName();
}
}
interface PersonService {
List<Person> searchPersons(String searchString);
}
class Person {
private int id;
private String firstName;
private String lastName;
public Person(int id, String firstName, String lastName) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
}
public int getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
class PersonServiceMock implements PersonService {
private List<Person> personDB;
public PersonServiceMock() {
personDB = new ArrayList<Person>();
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<Person> searchPersons(String searchString) {
List<Person> matches = new ArrayList<Person>();
if (searchString == null) {
return matches;
}
for (Person person : personDB) {
if (person.getFirstName().contains(searchString)
|| person.getLastName().contains(searchString)) {
matches.add(person);
}
}
return matches;
}
}
Vous pouvez créer un modèle dans une classe Java distincte et un contrôleur dans une autre.
Ensuite, vous pouvez avoir des composants Swing en plus de cela. {[0] } serait l'une des vues (et le modèle de table de facto ferait partie de la vue - il ne se traduirait que du "modèle partagé" à 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 devrait rien savoir sur la table. Donc l'appel devrait regarder de plus comme: updateCustomer(customer, newValue)
, pas updateCustomer(row, column, newValue)
.
Ajoute 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 le 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 la même classe pour traiter les mises à jour et maintenir la disponibilité des composants. Vous pouvez même faire du "modèle partagé" un TableModel
(bien que s'il n'est pas seulement utilisé par la table, je recommanderais au moins de fournir une API plus conviviale qui ne fuit pas les abstractions de table)
D'autre part, vous pouvez avoir des interfaces complexes pour les mises à jour (CustomerUpdateListener
, OrderItemListener
, OrderCancellationListener
) et contrôleur dédié (ou médiateur) uniquement pour la coordination de différents points de vue.
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 façons de configurer 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 le cadre appelle lorsque des événements se produisent. La vue recevrait généralement des événements du contrôleur en implémentant une interface d'écoute.
Parfois, une ou plusieurs parties du modèle MVC sont trivial, ou si "mince" qu'il ajoute une complexité inutile pour les séparer. Si votre contrôleur est plein d'appels d'une ligne, l'avoir dans une classe séparée peut finir par obscurcir le comportement sous-jacent. Par exemple, si tous les événements que vous gérez sont liés à un TableModel et sont de simples opérations 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'ajouter de la complexité là où ce n'est pas nécessaire.
Quelle que soit votre implémentation, n'oubliez pas de JavaDoc vos classes, méthodes et paquets afin que les composants et leurs relations soient correctement décrits!
J'ai trouvé quelques articles intéressants sur l'implémentation de modèles MVC, ce qui pourrait résoudre votre problème.
Si vous développez un programme avec une interface graphique , MVC pattern est presque là mais floue.
La Désection du code du modèle, de la vue et du contrôleur est difficile, et n'est normalement pas seulement une tâche de refactorisation.
Vous savez que vous l'avez quand votre code est réutilisable. Si vous avez correctement implémenté MVC, il devrait être facile d'implémenter un TUI {[2] } ou un CLI ou unRWD ou unmobile first design avec la même fonctionnalité. Il est facile de le voir fait que de le faire en fait, d'ailleurs sur un code existant.
En fait, les interactions entre le modèle, la vue et le contrôleur se produisent en utilisant d'autres modèles d'isolement (en tant Qu'Observateur ou auditeur)
Je suppose que ce post l'explique en détail, du modèle direct non MVC (comme vous le ferez sur un Q & D ) à l'implémentation réutilisable finale: