【问题标题】:Using POJOs as model layer in JavaFX application在 JavaFX 应用程序中使用 POJO 作为模型层
【发布时间】:2016-01-31 20:58:48
【问题描述】:

我正在创建简单的 JavaFX 应用程序。我希望我的模型层完全独立于 JavaFX - 没有 StringPropertyIntegerProperty 等作为字段。我希望它是 POJO。这样做的主要原因是我希望它是可序列化的。 我创建了DataRepository - 简单的类似 CRUD 的接口和它的一些实现,所以我可以随时更改存储数据的位置 - XML 文件、SQLite 数据库或其他任何东西。我还必须以某种方式将我的数据存储与 JavaFX 连接起来(以在 TableView 中显示其内容),因此我决定创建我的 ObservableList 实现,它包装了我的存储库。我的问题是 - 还有其他方法吗? ObservableList 包含大约 30 个实现方法,看起来我做错了什么。

我的(简化的)模型:

public class Movie implements Serializable {

    private String title;
    private String director;

    public Movie() {

    }

    public Movie(String title, String director) {
        this.title = title;
        this.director = director;
    }

    // Getters and setters, equals etc...
}

电影存储库:

public interface MovieRepository {

    public void add(Movie movie);

    public void remove(String title);

    public void remove(int index);

    public Movie get(String title);

    public Movie get(int index);

    public List<Movie> getAll();
}

主视图的控制器:

public class MainController {

    @FXML
    private TableView<Movie> movieTable;
    @FXML
    private TableColumn<Movie, String> movieTitleColumn;
    @FXML
    private Label titleLabel;

    private MovieRepository movies = new DBMovieRepository(); //MovieRepository implementation which uses SQLite DB to store data
    private MainApp app;

    @FXML
    private void initialize() {
        movieTable.setItems(new ObservableMovies(movies));
        // ObservableMovies is my implementation of ObservableList
        // It basically wraps methods from MovieRepository 
        // and notifies listeners
        showMovieDetails(null);

        movieTitleColumn.setCellValueFactory(cellData -> new ReadOnlyStringWrapper(cellData.getValue().getTitle()));
        movieTable.getSelectionModel().selectedItemProperty()
        .addListener((observable, oldValue, newValue) -> showMovieDetails(newValue));
    }

    private void showMovieDetails(Movie movie) {
        if(movie != null) {
            titleLabel.setText(movie.getTitle());
        } else {
            titleLabel.setText("");
        }
    }

    @FXML
    private void handleNew() {
        Movie movie = new Movie();
        app.showNewMovieDialog(movie);
        movieTable.getItems().add(movie);
    }

    public void setApp(MainApp app) {
        this.app = app;
    }
}

【问题讨论】:

  • 为什么不将您的属性标记为transient?然后你只需要将它们包装在你的值周围,它将是 JavaFX 兼容和Serializable。我也不明白,为什么你要实现自己的ObversableListFXCollections 中有一些方法可以从普通的 java.util.Collections 创建那些。但是如果你真的需要实现它,你可以从ObservableListBase继承。

标签: java javafx javafx-8


【解决方案1】:

您在这里有几个选项(也许更多),这些选项在本网站的其他问题中都有介绍。不过,为方便起见,我也会在这里总结一下。

1.使用 JavaFX 属性并使类可序列化

您可以使用自定义序列化表单来执行此操作。创建 JavaFX 属性 transient 并实现 readObjectwriteObject 以存储它们包装的值:

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Objects;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Movie implements Serializable {

    private transient StringProperty title = new SimpleStringProperty();
    private transient StringProperty director = new SimpleStringProperty();

    public Movie() {

    }

    public Movie(String title, String director) {
        setTitle(title);
        setDirector(director);
    }



    @Override
    public int hashCode() {
        return Objects.hash(getDirector(), getTitle());
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;

        Movie other = (Movie) obj;
        return Objects.equals(getTitle(), other.getTitle()) 
                && Objects.equals(getDirector(), other.getDirector());

    }

    public final StringProperty titleProperty() {
        return this.title;
    }

    public final String getTitle() {
        return this.titleProperty().get();
    }

    public final void setTitle(final String title) {
        this.titleProperty().set(title);
    }

    public final StringProperty directorProperty() {
        return this.director;
    }

    public final String getDirector() {
        return this.directorProperty().get();
    }

    public final void setDirector(final String director) {
        this.directorProperty().set(director);
    }

    private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
        s.defaultReadObject();
        title = new SimpleStringProperty((String) s.readObject());
        director = new SimpleStringProperty((String) s.readObject());
    }

    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();
        s.writeObject(getTitle());
        s.writeObject(getDirector());
    }

} 

2。使用带有“绑定属性”的 POJO。

详情请参阅JavaBean wrapping with JavaFX Properties。简而言之:

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Movie {

    private String title ;
    private String director ;
    private final PropertyChangeSupport propertySupport ;

    public Movie(String title, String director) {
        this.title = title ;
        this.director = director ;
        this.propertySupport = new PropertyChangeSupport(this);
    }

    public Movie() {
        this("", "");
    }

    public String getTitle() {
        return title ;
    }

    public String setTitle(String title) {
        String oldTitle = this.title ;
        this.title = title ;
        propertySupport.firePropertyChange("title", oldTitle, title);
    }

    // similarly for director...

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        propertySupport.addPropertyChangeListener(listener);
    }

    // hashCode and equals...
}

如果想要将存储库包装为可观察列表,请使用使用可观察列表的存储库实现来包装它:

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;


public class ObservableMovieRepository implements MovieRepository {

    private final MovieRepository repository ;
    private final ObservableList<Movie> movieList;


    public ObservableMovieRepository(MovieRepository repository) {
        this.repository = repository ;
        this.movieList = FXCollections.observableArrayList(repository.getAll());
    }

    @Override
    public void add(Movie movie) {
        repository.add(movie);
        movieList.add(movie);
    }

    @Override
    public void remove(String title) {
        Movie movie = get(title);
        repository.remove(title);
        movieList.remove(title);
    }

    @Override
    public void remove(int index) {
        repository.remove(index);
        movieList.remove(index);
    }

    @Override
    public Movie get(String title) {
        return repository.get(title);
    }

    @Override
    public Movie get(int index) {
        return movieList.get(index);
    }

    @Override
    public ObservableList<Movie> getAll() {
        return movieList ;
    }

}

这使用标准的ObservableList 实现,该实现在创建时复制现有列表,并且该实现使该列表与包装存储库中的列表保持同步。现在你的 UI 代码可以做

ObservableMovieRepository movies = new ObservableMovieRepository(new DBMovieRepository());

// ...

movieTable.setItems(movies.getAll());

有了上面的Movie 类,你就可以了

movieTitleColumn.setCellValueFactory(cellData -> cellData.getValue().titleProperty());

如果你使用POJO版本你可以这样做

movieTitleColumn.setCellValueFactory(cellData -> {
    try {
        return new JavaBeanStringPropertyBuilder()
            .bean(cellData.getValue())
            .name("title")
            .build();
    } catch (Exception e) { throw new RuntimeException(e); }
}

【讨论】:

  • 我希望我的 DataRepository 成为一个“黑匣子”——当被问及时,它只是吐出应用程序所需的数据,但其余应用程序不应该知道数据是如何存储的(如我之前说过,它可以从 XML 或数据库或其他任何东西中读取)。所以我假设我必须有一些层将我的数据源与 gui/controller 连接起来,ObservableList 看起来不错。
  • 您可能需要发布一些代码来说明您的意思。我仍然不明白你为什么不直接使用提供的实现。
  • 好的,我将尝试提出一些最小的工作示例,并将其发布在编辑问题中
【解决方案2】:

这里似乎有多个问题,所以我不确定我是否理解正确,但我会尝试将其拆分一下。

我希望我的模型层完全独立于 JavaFX - 不 StringProperty、IntegerProperty 等作为字段。我希望它是 POJO。

您可以将您的属性标记为transient。然后,您只需将它们包装在您的值周围,它将既符合 JavaFX 又符合Serializable。您只需要将更改传播回您的支持属性。

我还必须以某种方式将我的数据存储与 JavaFX 连接起来(以显示 它的内容在 TableView 中),所以我决定创建我的实现 ObservableList 包装了我的存储库。我的问题是 - 有吗 还有什么办法吗?

这方面的信息非常有限,我真的不知道,为什么你需要创建自己的 ObservableList 实现,但要保持它的 POJO,你可以在你的 bean 中维护简单的 java.util.Collections 并提供瞬态 @ 987654325@s,您可以在创建时通过将 java.util.Lists 包装在 POJO 中来创建它。您可以在 FXCollections 实用程序类中找到这些方法。

ObservableList 包含大约 30 个实现方法,看起来 好像我做错了什么。

如果你真的需要实现它,你可以从ObservableListBase继承。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-07
    • 1970-01-01
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 2020-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多