Aplicando o MVC com o JavaFx

Sou novo no padrão de design GUI world / OO e quero usar o padrão MVC para meu aplicativo GUI, li um pequeno tutorial sobre padrão MVC, o modelo conterá os dados, a visualização conterá o elemento visual e o O controlador amarrará entre a vista e o modelo.

Eu tenho um modo de exibição que contém um nó ListView e o ListView será preenchido com nomes, de uma class de pessoa (modelo). Mas estou um pouco confuso sobre uma coisa.

O que eu quero saber é se o carregamento dos dados de um arquivo é de responsabilidade do Controller ou do Model ?? E o ObservableList dos nomes: deveria ser armazenado no Controlador ou no Modelo?

Existem muitas variações diferentes desse padrão. Em particular, “MVC” no contexto de uma aplicação da Web é interpretado de forma um pouco diferente de “MVC” no contexto de uma aplicação de cliente grosso (por exemplo, desktop) (porque um aplicativo da web tem que ficar no topo do ciclo de solicitação-resposta). Esta é apenas uma abordagem para implementar o MVC no contexto de um aplicativo cliente thick, usando o JavaFX.

Sua class Person não é realmente o modelo, a menos que você tenha um aplicativo muito simples: normalmente, isso é o que chamamos de object de domínio, e o modelo conterá referências a ele, junto com outros dados. Em um contexto restrito, como quando você está apenas pensando sobre o ListView , você pode pensar na Person como seu modelo de dados (ela modela os dados em cada elemento do ListView ), mas no contexto mais amplo do aplicativo, há mais dados e estado a considerar.

Se você estiver exibindo um ListView os dados necessários, no mínimo, serão um ObservableList . Você também pode querer uma propriedade como currentPerson , que pode representar o item selecionado na lista.

Se a única visualização que você tem for o ListView , criar uma class separada para armazenar isso seria um exagero, mas qualquer aplicativo real geralmente acabará com várias visualizações. Neste ponto, ter os dados compartilhados em um modelo torna-se uma maneira muito útil para diferentes controladores se comunicarem entre si.

Então, por exemplo, você pode ter algo assim:

 public class DataModel { private final ObservableList personList = FXCollections.observableArrayList(); private final ObjectProperty currentPerson = new SimpleObjectPropery<>(null); public ObjectProperty currentPersonProperty() { return currentPerson ; } public final Person getCurrentPerson() { return currentPerson().get(); } public final void setCurrentPerson(Person person) { currentPerson().set(person); } public ObservableList getPersonList() { return personList ; } } 

Agora você pode ter um controlador para o display ListView que se parece com isso:

 public class ListController { @FXML private ListView listView ; private DataModel model ; public void initModel(DataModel model) { // ensure model is only set once: if (this.model != null) { throw new IllegalStateException("Model can only be initialized once"); } this.model = model ; listView.setItems(model.getPersonList()); listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSelection, newSelection) -> model.setCurrentPerson(newSelection)); model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> { if (newPerson == null) { listView.getSelectionModel().clearSelection(); } else { listView.getSelectionModel().select(newPerson); } }); } } 

Esse controlador basicamente apenas liga os dados exibidos na lista aos dados no modelo e garante que o currentPerson do modelo seja sempre o item selecionado na visualização de lista.

Agora você pode ter outra visualização, digamos, um editor, com três campos de texto para as propriedades firstName , lastName e email de uma pessoa. Seu controlador pode se parecer com:

 public class EditorController { @FXML private TextField firstNameField ; @FXML private TextField lastNameField ; @FXML private TextField emailField ; private DataModel model ; public void initModel(DataModel model) { if (this.model != null) { throw new IllegalStateException("Model can only be initialized once"); } this.model = model ; model.currentPersonProperty().addListener((obs, oldPerson, newPerson) -> { if (oldPerson != null) { firstNameField.textProperty().unbindBidirectional(oldPerson.firstNameProperty()); lastNameField.textProperty().unbindBidirectional(oldPerson.lastNameProperty()); emailField.textProperty().unbindBidirectional(oldPerson.emailProperty()); } if (newPerson == null) { firstNameField.setText(""); lastNameField.setText(""); emailField.setText(""); } else { firstNameField.textProperty().bindBidirectional(newPerson.firstNameProperty()); lastNameField.textProperty().bindBidirectional(newPerson.lastNameProperty()); emailField.textProperty().bindBidirectional(newPerson.emailProperty()); } }); } } 

Agora, se você configurar as coisas para que ambos os controladores compartilhem o mesmo modelo, o editor editará o item atualmente selecionado na lista.

Carregar e salvar dados deve ser feito através do modelo. Às vezes, você pode include isso em uma class à qual o modelo tem uma referência (permitindo alternar facilmente entre um carregador de dados baseado em arquivo e um carregador de dados do database ou uma implementação que acesse um serviço da Web, por exemplo). No caso simples, você pode fazer

 public class DataModel { // other code as before... public void loadData(File file) throws IOException { // load data from file and store in personList... } public void saveData(File file) throws IOException { // save contents of personList to file ... } } 

Então você pode ter um controlador que fornece access a essa funcionalidade:

 public class MenuController { private DataModel model ; @FXML private MenuBar menuBar ; public void initModel(DataModel model) { if (this.model != null) { throw new IllegalStateException("Model can only be initialized once"); } this.model = model ; } @FXML public void load() { FileChooser chooser = new FileChooser(); File file = chooser.showOpenDialog(menuBar.getScene().getWindow()); if (file != null) { try { model.loadData(file); } catch (IOException exc) { // handle exception... } } } @FXML public void save() { // similar to load... } } 

Agora você pode facilmente montar um aplicativo:

 public class ContactApp extends Application { @Override public void start(Stage primaryStage) throws Exception { BorderPane root = new BorderPane(); FXMLLoader listLoader = new FXMLLoader(getClass().getResource("list.fxml")); root.setCenter(listLoader.load()); ListController listController = listLoader.getController(); FXMLLoader editorLoader = new FXMLLoader(getClass().getResource("editor.fxml")); root.setRight(editorLoader.load()); EditorController editorController = editorLoader.getController(); FXMLLoader menuLoader = new FXMLLoader(getClass().getResource("menu.fxml")); root.setTop(menuLoader.load()); MenuController menuController = menuLoader.getController(); DataModel model = new DataModel(); listController.initModel(model); editorController.initModel(model); menuController.initModel(model); Scene scene = new Scene(root, 800, 600); primaryStage.setScene(scene); primaryStage.show(); } } 

Como eu disse, há muitas variações desse padrão (e isso é provavelmente mais uma variação do modelo-visão-apresentador, ou “visão passiva”), mas essa é uma abordagem (uma que eu basicamente prefiro). É um pouco mais natural fornecer o modelo para os controladores através de seu construtor, mas é muito mais difícil definir a class do controlador com um atributo fx:controller . Esse padrão também se presta fortemente a estruturas de injeção de dependência.

Atualização: o código completo para este exemplo está aqui .

O que eu quero saber é que, se o carregamento dos dados de um arquivo é de responsabilidade do Controlador Ou do modelo?

Para mim, o modelo é responsável apenas por trazer as estruturas de dados requeridas que representam a lógica de negócios do aplicativo.

A ação de carregar esses dados de qualquer fonte deve ser feita pela Camada do Controlador. Você também pode usar o padrão de repository , que pode ajudá-lo a abstrair o tipo de fonte quando estiver acessando os dados da exibição. Com isso implementado você não deve se importar se a implementação do Repositório está carregando os dados do arquivo, sql, nosql, webservice …

E o ObservableList dos nomes será armazenado no controlador ou no modelo?

Para mim, o ObservableList é parte da View. É o tipo de estrutura de dados que você pode ligar aos controles javafx. Assim, por exemplo, um ObservableList poderia ser preenchido com Strings a partir do modelo, mas a referência ObservableList deveria ser um atributo de alguma class do View. No Javafx, é muito agradável vincular controles javafx a propriedades observáveis ​​apoiadas por objects de domínio do modelo.

Você também pode dar uma olhada no conceito viewmodel . Para mim, um bean JavaFx suportado por um POJO poderia ser considerado como um modelo de visualização, você poderia vê-lo como um object de modelo pronto para ser apresentado na visualização. Por exemplo, se sua visualização precisar mostrar algum valor total calculado a partir de dois atributos de modelo, esse valor total poderia ser um atributo do modelo de visualização. Esse atributo não seria persistido e seria calculado a qualquer momento em que você mostrasse a exibição.