Hello world!
I tried to make a very simple MVC in JavaFX. My model is a Pessoa
class that has nome
and idade
(the idade
field in case it does not get used). Two text fields represent two views. For simplicity both show the same thing: the name of the person. If you edit one of the names and der Enter , the change is reflected in the template and per table in both text fields again.
Questions:
Is this code in MVC?
Who is the controller?
Should not there be a class for the model named "Template"?
Should not there be a class for the controller called "Controller"?
What other criticisms can be made of this MVC attempt?
App.java
package piovezan.mvcdesktop;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class App extends Application {
private final Pessoa pessoa = new Pessoa("Mario", 39);
public static void main( String[] args ) {
launch(args);
}
@Override
public void start(Stage stage) {
atribuirTituloAoPalco("Hello World!", stage);
TextField visao1 = criarTextField(pessoa.getNome(), e -> pessoa.setNome(((TextField)e.getSource()).getText()));
TextField visao2 = criarTextField(pessoa.getNome(), e -> pessoa.setNome(((TextField)e.getSource()).getText()));
pessoa.adicionarObservadorDoNome(nome -> visao1.setText(nome));
pessoa.adicionarObservadorDoNome(nome -> visao2.setText(nome));
FlowPane root = new FlowPane(Orientation.VERTICAL);
root.getChildren().add(visao1);
root.getChildren().add(visao2);
stage.setScene(new Scene(root, 300, 250));
stage.setOnCloseRequest(v -> pessoa.removerObservadores());
stage.show();
}
private void atribuirTituloAoPalco(String titulo, Stage stage) {
stage.setTitle(titulo);
}
private TextField criarTextField(String texto, EventHandler<ActionEvent> eventHandler) {
TextField textField = new TextField();
textField.setText(texto);
textField.setOnAction(eventHandler);
return textField;
}
}
Person.java
package piovezan.mvcdesktop;
import java.util.HashSet;
import java.util.Set;
/**
* É responsabilidade do implementador da classe escolher tipos imutáveis para os campos que serão observados
* por cada observador (exemplo: String para nome, Integer para idade) caso contrário o observável irá "vazar"
* estado para o observador.
*/
public class Pessoa {
private String nome;
private int idade;
private final Set<Observador<String>> observadoresDoNome = new HashSet<>();
private final Set<Observador<Integer>> observadoresDaIdade = new HashSet<>();
public Pessoa(String nome, int idade) {
this.nome = nome;
this.idade = idade;
}
public void setNome(String nome) {
this.nome = nome;
notificarQueNomeMudou();
}
public void setIdade(int idade) {
this.idade = idade;
notificarQueIdadeMudou();
}
public String getNome() {
return nome;
}
public int getIdade() {
return idade;
}
public void adicionarObservadorDoNome(Observador<String> observador) {
observadoresDoNome.add(observador);
}
public void adicionarObservadorDaIdade(Observador<Integer> observador) {
observadoresDaIdade.add(observador);
}
public void notificarQueNomeMudou() {
for (Observador<String> observador: observadoresDoNome) {
observador.notificar(nome);
}
}
public void notificarQueIdadeMudou() {
for (Observador<Integer> observador: observadoresDaIdade) {
observador.notificar(idade);
}
}
public void removerObservadores() {
observadoresDoNome.clear();
observadoresDaIdade.clear();
}
}
Observer.java
package piovezan.mvcdesktop;
@FunctionalInterface
public interface Observador<T> {
void notificar(T observavel);
}
EDIT:
Looking at the answer and comparing an example of MVC with FXML I found another example without FXML that came in handy. I have based on it, however without using start()
objects of JavaFX, and I now have the following code:
App.java
package piovezan.mvcdesktop;
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;
public class App extends Application {
public static void main( String[] args ) {
launch(args);
}
@Override
public void start(Stage stage) {
Pessoa pessoa = new Pessoa("Mario", 39);
Controlador controlador1 = new Controlador(pessoa);
Visao visao1 = new Visao(pessoa, controlador1);
Controlador controlador2 = new Controlador(pessoa);
Visao visao2 = new Visao(pessoa, controlador2);
FlowPane root = new FlowPane(Orientation.VERTICAL);
root.getChildren().add(visao1.comoComponente());
root.getChildren().add(visao2.comoComponente());
atribuirTituloAoPalco("Hello World!", stage);
stage.setScene(new Scene(root, 300, 250));
stage.setOnCloseRequest(v -> pessoa.removerObservadores());
stage.show();
}
private void atribuirTituloAoPalco(String titulo, Stage stage) {
stage.setTitle(titulo);
}
}
Controller.java
package piovezan.mvcdesktop;
public class Controlador {
private final Pessoa pessoa;
public Controlador(Pessoa pessoa) {
this.pessoa = pessoa;
}
public void atualizar(String nome) {
pessoa.setNome(nome);
}
}
Visao.java
package piovezan.mvcdesktop;
import javafx.scene.control.TextField;
import javafx.scene.layout.FlowPane;
public class Visao {
private FlowPane visao;
private TextField campoNome;
private Pessoa pessoa;
private Controlador controlador;
public Visao(Pessoa pessoa, Controlador controlador) {
this.pessoa = pessoa;
this.controlador = controlador;
criarEConfigurarPane();
criarEDisporControles();
atualizarControladorAPartirDeListeners();
observarModeloEAtualizarControles();
}
private void criarEConfigurarPane() {
visao = new FlowPane();
}
private void criarEDisporControles() {
campoNome = new TextField();
campoNome.setText(pessoa.getNome());
visao.getChildren().add(campoNome);
}
private void atualizarControladorAPartirDeListeners() {
campoNome.setOnAction(v -> controlador.atualizar(campoNome.getText()));
}
private void observarModeloEAtualizarControles() {
pessoa.adicionarObservadorDoNome(nome -> campoNome.setText(nome));
}
public FlowPane comoComponente() {
return visao;
}
}
Is it better? Now Vision and Controller are classes and the *Property
method has been simplified to just link them to each other and to the Model.
By this code I conclude that it will cost a bit by default to enter the head because I start with the Model that is disconnected from the others, then I go to the Vision class that is the beginning of the flow, I pass through the Controller and I return the coding of View. It is rather non-linear development to follow this line.
I also noticed that the start()
class was completely expendable, are you sure?
In time, should the model be observable as a rule?