Colocación del oyente que se adhiere al patrón MVC tradicional (no mediador)

Estoy implementando un progtwig dentro de Swing, y he leído la implementación de Nirmal de este patrón en Swing , que parece mostrar un manejo bastante elegante de todo el concepto de “separación de responsabilidades”.

Sin embargo, como estoy desarrollando un progtwig más complicado que el publicado por Nirml, que consiste en un solo contenedor JFrame, busco orientación sobre cómo implementar MVC correctamente.

Mi progtwig consistirá en sub-contenedores y tal. Tengo curiosidad sobre cómo se supone que el Controlador debe implementar la lógica detrás de definir y asignar todos los oyentes de la Vista … o si el controlador que define los oyentes para cada componente de la Vista es incluso práctico.

Parece que necesitaría un método en el contenedor de nivel superior de View para permitir que el controlador invoque la vista para agregar un Listener al componente en cuestión. y entonces necesitaría una cadena de métodos, cada uno pasando al oyente desde el contenedor de nivel superior, al contenedor inmediato que contiene el componente … culminando con el contenedor invocando addActionListener () en él.

¿Es esta la manera correcta de manejar oyentes en MVC?

Está definiendo a todos los oyentes de cada componente en la Vista por el Controlador obligatorio en MVC, o una práctica útil? Esto también implicaría que creo métodos en el contenedor de nivel superior (Vista) para darle al Controlador una forma de asignar oyentes a cada componente en sub-contenedores.

Bien, primero lo primero, Swing ya implementa una forma de MVC, aunque en forma de VC-M. Esto significa que no debes intentar restringir Swing a un MVC puro directamente, ya que estarás muy decepcionado y pasarás mucho tiempo intentando hacer hacks donde no deberían estar.

En su lugar, puede ajustar un MVC alrededor de Swing, lo que le permite trabajar alrededor de la API.

En mi opinión, un controlador no necesita saber, ni debería importar, cómo se implementan la vista o el modelo, pero solo debería importar cómo puede funcionar con ellos (he tenido demasiados desarrolladores que se apropian de los componentes de la interfaz de usuario). y hacer cosas para / con ellos que no deberían tener, y romper la API cuando hemos cambiado la implementación. Es mejor ocultar ese tipo de detalles)

En este sentido, puede pensar en una vista como entidad autónoma: tiene controles y hace cosas independientes del controlador. El controlador no se preocupa por los detalles de la implementación. Lo que sí le importa es obtener información y saber cuándo se ha producido algún evento, descrito en el contrato. No debería importar cómo se generó.

Por ejemplo, supongamos que tiene una vista de inicio de sesión. El controlador solo quiere saber el nombre de usuario y la contraseña que el usuario ingresó y cuándo debe validar esa información.

Supongamos que implementa la vista / controlador para exponer los JPasswordField JTextField y JPasswordField para comenzar, pero más adelante, sus usuarios desean que la selección del nombre de usuario esté restringida a una lista específica (posiblemente proporcionada por el modelo). Ahora tiene detalles de implementación bloqueados en su controlador que ya no son aplicables y tiene que cambiar o crear manualmente un MVC nuevo para este nuevo caso de uso.

¿Qué pasaría si, en cambio, simplemente declarara que la vista tiene un getter para el nombre de usuario y contraseña y algún tipo de detector de eventos que le diría al controlador cuándo el usuario quería que se verificaran las credenciales? Bueno, ahora solo tendrías que proporcionar una nueva vista, sin necesidad de modificar el controlador. Al controlador no le importará CÓMO se generan estos valores.

En cuanto al aspecto más importante de tu pregunta.

Mi progtwig consistirá en sub-contenedores y tal. Tengo curiosidad sobre cómo se supone que el Controlador debe implementar la lógica detrás de la definición y asignación de todos los oyentes de la Vista … o si el controlador que define los oyentes para cada componente de la Vista es incluso práctico.

Parece que necesitaría un método en el contenedor de nivel superior de View para permitir que el controlador invoque la vista para agregar un Listener al componente en cuestión. y entonces necesitaría una cadena de métodos, cada uno pasando al oyente desde el contenedor de nivel superior, al contenedor inmediato que contiene el componente … culminando con el contenedor invocando addActionListener () en él.

¿Es esta la manera correcta de manejar oyentes en MVC?

La respuesta general es, no, no es la forma correcta.

Cada subvista se convertiría en su propio MVC, centrándose en sus propios requisitos. El MVC principal puede usar eventos u otras funcionalidades proporcionadas por el MVC hijo para realizar actualizaciones o incluso modificar los estados de otros MVC secundarios.

Lo importante para recordar aquí, es que una vista puede actuar como controlador para otras vistas, aunque puede elegir tener una serie de controladores que la vista podría gestionar.

Imagina algo así como un “mago”. Tiene una serie de pasos que recostackn diversa información del usuario, cada paso debe ser válido antes de que pueda pasar al siguiente paso.

Ahora, es posible que tengas la tentación de integrar la navegación en esto directamente, pero una mejor idea sería separar los detalles de navegación como su propio MVC.

El asistente, cuando se le preguntaba, le presentaba un paso al usuario, el usuario completaba la información, posiblemente desencadenando eventos. Estos eventos permitirían a la MVC de navegación decidir si el usuario puede pasar al siguiente paso o al paso anterior.

Los dos MVC serían controlados por un tercer MVC “maestro” que ayudaría a administrar los estados (escuchando eventos del asistente y actualizando el estado de la navegación)

Probemos un ejemplo con una pregunta que se pregunta mucho por aquí, ¡un cuestionario!

Un cuestionario tiene preguntas, cada pregunta tiene un aviso, una respuesta correcta, una serie de respuestas posibles y también queremos almacenar la respuesta resultante del usuario.

La API de la prueba

Entonces, a continuación tenemos el esquema básico del cuestionario MVC, tenemos una pregunta, que es administrada por un modelo, hay un controlador y una vista y una serie de observadores (oyentes)

Los contratos (interfaces)

 public interface Question { public String getPrompt(); public String getCorrectAnswer(); public String getUserAnswer(); public String[] getOptions(); public boolean isCorrect(); } /** * This is a deliberate choice to separate the update functionality * No one but the model should ever actually -apply- the answer to the * question */ public interface MutableQuestion extends Question { public void setUserAnswer(String userAnswer); } public interface QuizModel { public void addQuizObserver(QuizModelObserver observer); public void removeQuizObserver(QuizModelObserver observer); public Question getNextQuestion(); public Question getCurrentQuestion(); public int size(); public int getScore(); public void setUserAnswerFor(Question question, String answer); } public interface QuizModelObserver { public void didStartQuiz(QuizModel quiz); public void didCompleteQuiz(QuizModel quiz); public void questionWasAnswered(QuizModel model, Question question); } public interface QuizView extends View { public void setQuestion(Question question); public boolean hasAnswer(); public String getUserAnswer(); public void addQuizObserver(QuizViewObserver observer); public void removeQuizObserver(QuizViewObserver observer); } public interface QuizViewObserver { public void userDidChangeAnswer(QuizView view); } public interface QuizController { public QuizModel getModel(); // This is the model public QuizView getView(); public void askNextQuestion(); } 

Yo, personalmente, trabajo según el principio de “código para la interfaz (no implementación)”, también deliberadamente me he pasado de la raya con la idea de demostrar el punto.

Si miras de cerca, notarás que ni la vista ni el modelo realmente tienen alguna relación entre ellos. Todo esto se controla a través del controlador

Una de las cosas que he hecho aquí es proporcionarle al controlador una askNextQuestion , porque el controlador no sabe cuándo debería ocurrir (puede pensar en usar el userDidChangeAnswer , pero eso significaría que el usuario solo obtiene un solo bash). para responder a la pregunta, tipo de media)

La implementación

Ahora, normalmente, me gustaría tener algunas implementaciones abstract para completar la funcionalidad “común”, lo he omitido en su mayor parte y he ido directamente a la implementación predeterminada, esto se hace principalmente con fines de demostración.

 public class DefaultQuestion implements MutableQuestion { private final String prompt; private final String correctAnswer; private String userAnswer; private final String[] options; public DefaultQuestion(String prompt, String correctAnswer, String... options) { this.prompt = prompt; this.correctAnswer = correctAnswer; this.options = options; } @Override public String getPrompt() { return prompt; } @Override public String getCorrectAnswer() { return correctAnswer; } @Override public String getUserAnswer() { return userAnswer; } @Override public String[] getOptions() { List list = new ArrayList<>(Arrays.asList(options)); Collections.shuffle(list); return list.toArray(new String[list.size()]); } public void setUserAnswer(String userAnswer) { this.userAnswer = userAnswer; } @Override public boolean isCorrect() { return getCorrectAnswer().equals(getUserAnswer()); } } public abstract class AbstractQuizModel implements QuizModel { private List observers; public AbstractQuizModel() { observers = new ArrayList<>(25); } @Override public void addQuizObserver(QuizModelObserver observer) { observers.add(observer); } @Override public void removeQuizObserver(QuizModelObserver observer) { observers.remove(observer); } protected void fireDidStartQuiz() { for (QuizModelObserver observer : observers) { observer.didStartQuiz(this); } } protected void fireDidCompleteQuiz() { for (QuizModelObserver observer : observers) { observer.didCompleteQuiz(this); } } protected void fireQuestionWasAnswered(Question question) { for (QuizModelObserver observer : observers) { observer.questionWasAnswered(this, question); } } } public class DefaultQuizModel extends AbstractQuizModel { private List questions; private Iterator iterator; private MutableQuestion currentQuestion; private boolean completed; private int score; public DefaultQuizModel() { questions = new ArrayList<>(50); } public void add(MutableQuestion question) { questions.add(question); } public void remove(MutableQuestion question) { questions.remove(question); } @Override public Question getNextQuestion() { if (!completed && iterator == null) { iterator = questions.iterator(); fireDidStartQuiz(); } if (iterator.hasNext()) { currentQuestion = iterator.next(); } else { completed = true; iterator = null; currentQuestion = null; fireDidCompleteQuiz(); } return currentQuestion; } @Override public Question getCurrentQuestion() { return currentQuestion; } @Override public int size() { return questions.size(); } @Override public int getScore() { return score; } @Override public void setUserAnswerFor(Question question, String answer) { if (question instanceof MutableQuestion) { ((MutableQuestion) question).setUserAnswer(answer); if (question.isCorrect()) { score++; } fireQuestionWasAnswered(question); } } } public class DefaultQuizController implements QuizController { private QuizModel model; private QuizView view; public DefaultQuizController(QuizModel model, QuizView view) { this.model = model; this.view = view; } @Override public QuizModel getModel() { return model; } @Override public QuizView getView() { return view; } @Override public void askNextQuestion() { Question question = getModel().getCurrentQuestion(); if (question != null) { String answer = getView().getUserAnswer(); getModel().setUserAnswerFor(question, answer); } question = getModel().getNextQuestion(); getView().setQuestion(question); } } public class DefaultQuizViewPane extends JPanel implements QuizView { private final JLabel question; private final JPanel optionsPane; private final ButtonGroup bg; private final List options; private String userAnswer; private final List observers; private final AnswerActionListener answerActionListener; private final GridBagConstraints optionsGbc; protected DefaultQuizViewPane() { setBorder(new EmptyBorder(4, 4, 4, 4)); question = new JLabel(); optionsPane = new JPanel(new GridBagLayout()); optionsPane.setBorder(new EmptyBorder(4, 4, 4, 4)); answerActionListener = new AnswerActionListener(); optionsGbc = new GridBagConstraints(); optionsGbc.gridwidth = GridBagConstraints.REMAINDER; optionsGbc.weightx = 1; optionsGbc.anchor = GridBagConstraints.WEST; options = new ArrayList<>(25); bg = new ButtonGroup(); observers = new ArrayList<>(25); setLayout(new BorderLayout()); add(question, BorderLayout.NORTH); add(optionsPane); } protected void reset() { question.setText(null); for (JRadioButton rb : options) { rb.removeActionListener(answerActionListener); bg.remove(rb); optionsPane.remove(rb); } options.clear(); } @Override public void setQuestion(Question question) { reset(); if (question != null) { this.question.setText(question.getPrompt()); for (String option : question.getOptions()) { JRadioButton rb = makeRadioButtonFor(option); options.add(rb); optionsPane.add(rb, optionsGbc); } optionsPane.revalidate(); revalidate(); repaint(); } } @Override public void addQuizObserver(QuizViewObserver observer) { observers.add(observer); } @Override public void removeQuizObserver(QuizViewObserver observer) { observers.remove(observer); } protected void fireUserDidChangeAnswer() { for (QuizViewObserver observer : observers) { observer.userDidChangeAnswer(this); } } protected JRadioButton makeRadioButtonFor(String option) { JRadioButton btn = new JRadioButton(option); btn.addActionListener(answerActionListener); bg.add(btn); return btn; } @Override public boolean hasAnswer() { return userAnswer != null; } @Override public String getUserAnswer() { return userAnswer; } @Override public JComponent getViewComponent() { return this; } protected class AnswerActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { userAnswer = e.getActionCommand(); fireUserDidChangeAnswer(); } } } 

Realmente no hay nada lujoso aquí. Lo único que tiene un gran interés aquí es cómo el controlador gestiona los eventos entre el modelo y la vista

La API de navegación

La API de navegación es bastante básica. Le permite controlar si el usuario puede navegar al elemento siguiente o anterior (si las acciones deberían estar disponibles para el usuario) y desactivar cualquiera de las acciones en cualquier momento

(Una vez más, me he centrado en un diseño simple, de manera realista, sería bueno tener cierto control sobre la modificación del estado del modelo para cambiar en qué direcciones puede funcionar la navegación, pero lo dejé a propósito para mantener es simple)

Los contratos (interfaces)

 public enum NavigationDirection { NEXT, PREVIOUS; } public interface NavigationModel { public boolean canNavigate(NavigationDirection direction); public void addObserver(NavigationModelObserver observer); public void removeObserver(NavigationModelObserver observer); public void next(); public void previous(); } public interface NavigationModelObserver { public void next(NavigationModel view); public void previous(NavigationModel view); } public interface NavigationController { public NavigationView getView(); public NavigationModel getModel(); public void setDirectionEnabled(NavigationDirection navigationDirection, boolean b); } public interface NavigationView extends View { public void setNavigatable(NavigationDirection direction, boolean navigtable); public void setDirectionEnabled(NavigationDirection direction, boolean enabled); public void addObserver(NavigationViewObserver observer); public void removeObserver(NavigationViewObserver observer); } public interface NavigationViewObserver { public void next(NavigationView view); public void previous(NavigationView view); } 

La implementación

 public static class DefaultNavigationModel implements NavigationModel { private Set navigatableDirections; private List observers; public DefaultNavigationModel() { this(true, true); } public DefaultNavigationModel(boolean canNavigateNext, boolean canNavigatePrevious) { navigatableDirections = new HashSet<>(2); observers = new ArrayList<>(25); setCanNavigate(NavigationDirection.NEXT, canNavigateNext); setCanNavigate(NavigationDirection.PREVIOUS, canNavigatePrevious); } public void setCanNavigate(NavigationDirection direction, boolean canNavigate) { if (canNavigate) { navigatableDirections.add(direction); } else { navigatableDirections.remove(direction); } } @Override public boolean canNavigate(NavigationDirection direction) { return navigatableDirections.contains(direction); } @Override public void addObserver(NavigationModelObserver observer) { observers.add(observer); } @Override public void removeObserver(NavigationModelObserver observer) { observers.remove(observer); } protected void fireMoveNext() { for (NavigationModelObserver observer : observers) { observer.next(this); } } protected void fireMovePrevious() { for (NavigationModelObserver observer : observers) { observer.previous(this); } } @Override public void next() { fireMoveNext(); } @Override public void previous() { fireMovePrevious(); } } public static class DefaultNavigationController implements NavigationController { private final NavigationModel model; private final NavigationView view; public DefaultNavigationController(NavigationModel model, NavigationView view) { this.model = model; this.view = view; view.setNavigatable(NavigationDirection.NEXT, model.canNavigate(NavigationDirection.NEXT)); view.setNavigatable(NavigationDirection.PREVIOUS, model.canNavigate(NavigationDirection.PREVIOUS)); view.addObserver(new NavigationViewObserver() { @Override public void next(NavigationView view) { if (getModel().canNavigate(NavigationDirection.NEXT)) { getModel().next(); } } @Override public void previous(NavigationView view) { if (getModel().canNavigate(NavigationDirection.PREVIOUS)) { getModel().previous(); } } }); } @Override public NavigationView getView() { return view; } @Override public NavigationModel getModel() { return model; } @Override public void setDirectionEnabled(NavigationDirection navigationDirection, boolean enabled) { getView().setDirectionEnabled(navigationDirection, enabled); } } public static class DefaultNavigationViewPane extends JPanel implements NavigationView { private final List observers; private final JButton btnNext; private final JButton btnPrevious; public DefaultNavigationViewPane() { btnNext = new JButton("Next >"); btnNext.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { fireMoveNext(); } }); btnPrevious = new JButton("< Previous"); btnPrevious.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { fireMovePrevious(); } }); setLayout(new FlowLayout(FlowLayout.RIGHT)); add(btnPrevious); add(btnNext); observers = new ArrayList<>(); } @Override public void addObserver(NavigationViewObserver observer) { observers.add(observer); } @Override public void removeObserver(NavigationViewObserver observer) { observers.remove(observer); } protected void fireMoveNext() { for (NavigationViewObserver observer : observers) { observer.next(this); } } protected void fireMovePrevious() { for (NavigationViewObserver observer : observers) { observer.previous(this); } } @Override public JComponent getViewComponent() { return this; } @Override public void setNavigatable(NavigationDirection direction, boolean navigtable) { switch (direction) { case NEXT: btnNext.setVisible(navigtable); break; case PREVIOUS: btnPrevious.setVisible(navigtable); break; } } @Override public void setDirectionEnabled(NavigationDirection direction, boolean enabled) { switch (direction) { case NEXT: btnNext.setEnabled(enabled); break; case PREVIOUS: btnPrevious.setEnabled(enabled); break; } } } 

The Quiz Master

Ahora bien, estas son dos API distintas, no tienen nada en común, entonces, necesitamos algún tipo de controlador para unirlas

Los contratos (las interfaces)

 public interface QuizMasterController { public QuizController getQuizController(); public NavigationController getNavigationController(); public QuizMasterView getView(); } public interface QuizMasterView extends View { public NavigationController getNavigationController(); public QuizController getQuizController(); public void showScoreView(int score, int size); public void showQuestionAndAnswerView(); } 

De acuerdo, entonces probablemente se esté haciendo la pregunta obvia, ¿dónde está la modelo? Bueno, no necesita uno, es solo un puente entre las API de navegación y prueba, no gestiona ningún dato propio …

Las implementaciones

 public class DefaultQuizMasterController implements QuizMasterController { private QuizController quizController; private NavigationController navController; private QuizMasterView view; public DefaultQuizMasterController(QuizController quizController, NavigationController navController) { this.quizController = quizController; this.navController = navController; view = new DefaultQuizMasterViewPane(quizController, navController); // Setup the initial state quizController.askNextQuestion(); navController.getModel().addObserver(new NavigationModelObserver() { @Override public void next(NavigationModel view) { getQuizController().askNextQuestion(); getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, false); } @Override public void previous(NavigationModel view) { // NOOP } }); quizController.getView().addQuizObserver(new QuizViewObserver() { @Override public void userDidChangeAnswer(WizeQuiz.QuizView view) { getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, true); } }); quizController.getModel().addQuizObserver(new QuizModelObserver() { @Override public void didStartQuiz(QuizModel quiz) { getView().showQuestionAndAnswerView(); } @Override public void didCompleteQuiz(QuizModel quiz) { getView().showScoreView(quiz.getScore(), quiz.size()); getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, false); } @Override public void questionWasAnswered(QuizModel model, Question question) { } }); navController.setDirectionEnabled(NavigationDirection.NEXT, false); } @Override public QuizController getQuizController() { return quizController; } @Override public NavigationController getNavigationController() { return navController; } @Override public QuizMasterView getView() { return view; } } public class DefaultQuizMasterViewPane extends JPanel implements QuizMasterView { private QuizController quizController; private NavigationController navController; private QuestionAndAnswerView qaView; private ScoreView scoreView; private CardLayout cardLayout; public DefaultQuizMasterViewPane(QuizController quizController, NavigationController navController) { this.quizController = quizController; this.navController = navController; quizController.getModel().addQuizObserver(new QuizModelObserver() { @Override public void didStartQuiz(QuizModel quiz) { } @Override public void didCompleteQuiz(QuizModel quiz) { } @Override public void questionWasAnswered(QuizModel model, Question question) { qaView.updateScore(); } }); scoreView = new ScoreView(); qaView = new QuestionAndAnswerView(); qaView.updateScore(); cardLayout = new CardLayout(); setLayout(cardLayout); add(qaView, "view.qa"); add(scoreView, "view.score"); } @Override public JComponent getViewComponent() { return this; } @Override public NavigationController getNavigationController() { return navController; } @Override public QuizController getQuizController() { return quizController; } @Override public void showScoreView(int score, int size) { scoreView.updateScore(); cardLayout.show(this, "view.score"); } @Override public void showQuestionAndAnswerView() { cardLayout.show(this, "view.qa"); } protected class QuestionAndAnswerView extends JPanel { private JLabel score; public QuestionAndAnswerView() { setLayout(new BorderLayout()); add(getQuizController().getView().getViewComponent()); JPanel south = new JPanel(new BorderLayout()); south.add(getNavigationController().getView().getViewComponent(), BorderLayout.SOUTH); score = new JLabel(); score.setHorizontalAlignment(JLabel.RIGHT); south.add(score, BorderLayout.NORTH); add(south, BorderLayout.SOUTH); } protected void updateScore() { score.setText(getQuizController().getModel().getScore() + "/" + getQuizController().getModel().size()); } } protected class ScoreView extends JPanel { private JLabel score; public ScoreView() { setLayout(new GridBagLayout()); score = new JLabel("You scored:"); add(score); } protected void updateScore() { score.setText("You scored: " + getQuizController().getModel().getScore() + "/" + getQuizController().getModel().size()); } } } 

Ahora, la implementación es interesante, en realidad tiene dos “estados” o “vistas”, la vista de “preguntas y respuestas” y la “vista de puntaje”. Esto, de nuevo, es deliberado, porque realmente no quería OTRO MVC. La vista Preguntas y respuestas ya está administrando dos MVC de cualquier forma: P

Básicamente, lo que hace es monitorear la API del cuestionario para cuando el usuario cambie la respuesta a una pregunta, luego le dice a la API de navegación que puede pasar a la siguiente pregunta. También supervisa los eventos de inicio y finalizados, presentando la vista requerida para esos estados.

También está monitoreando la API de navegación para eventos de navegación. En este ejemplo, solo podemos movernos en una sola dirección e incluso si la API de navegación se configuró para hacer lo contrario, la API de prueba no proporciona esa funcionalidad

Ponlo junto

Ahora, he decidido construir deliberadamente cada sección por separado, es posible que QuizMasterController construya la API de Navigation , ya que sabe que la API de prueba solo permite la navegación hacia adelante, igualmente podríamos cambiar la API de navegación para permitir que esos estados para ser modificado a través del modelo o el modelo modificado, todas estas son soluciones viables, solo he ido para un ejemplo directo.

 NavigationModel navigationModel = new DefaultNavigationModel(true, false); NavigationView navigationView = new DefaultNavigationViewPane(); NavigationController navigationController = new NavWiz.DefaultNavigationController(navigationModel, navigationView); DefaultQuizModel quizModel = new DefaultQuizModel(); quizModel.add(new DefaultQuestion( "Which pop duo was the first western band to play in The Peoples Republic of China?", "Wham", "Wham", "Simon and Garfunkel", "Chas and Dave", "Right Said Fred")); quizModel.add(new DefaultQuestion( "Timber selected from how many fully grown oak trees were needed to build a large 3 decker Royal Navy battle ship in the 18th century?", "3,500", "50", "500", "1,500", "3,500")); quizModel.add(new DefaultQuestion( "Speed skating originated in which country?", "Netherlands", "Russia", "Netherlands", "Canada", "Norway")); quizModel.add(new DefaultQuestion( "Off the coast of which country did the Amoco Cadiz sink?", "France", "South Africa", "France", "USA", "Spain")); quizModel.add(new DefaultQuestion( "The song 'An Englishman in New York' was about which man?", "Quentin Crisp", "Quentin Crisp", "Sting", "John Lennon", "Gordon Sumner")); QuizView quizView = new DefaultQuizViewPane(); QuizController quizController = new DefaultQuizController(quizModel, quizView); QuizMasterController quizMasterController = new DefaultQuizMasterController(quizController, navigationController); JFrame frame = new JFrame("Testing"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(quizMasterController.getView().getViewComponent()); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); 

Y finalmente terminamos con algo como …

Examen Examen Examen

Esto no es nada si no es áspero, pero está diseñado para proporcionar algunas ideas sobre cómo puede lograr complejos MVC compuestos