Статьи

Класс Bean для программирования на JavaFX

В моей предыдущей статье Bean Class для Java-программирования я представил, как я буду кодировать данные или класс модели. Этот стиль класса бинов широко используется в таких средах, как Hibernate, Java Server Faces, CDI и других. Существует один вариант этого класса, о котором вам нужно знать, и это класс бина JavaFX.

JavaFX — это современная среда графического интерфейса, которая является преемницей Swing, точно так же, как она была преемницей AWT. Одна важная характеристика JavaFX заключается в том, что наблюдаемый шаблон запекается в его бобах, как и предполагалось. Когда бин JavaFX связан или связан с элементом управления JavaFX, таким как текстовое поле, изменения в элементе управления могут автоматически обновлять бин, а изменения в бине обновляют элемент управления. Это важная функция в шаблоне Model View Controller, которую продвигает JavaFX.

Bean-компонент JavaFX, при правильном построении, может быть заменой Java-бина в большинстве случаев. Ожидается, что он будет придерживаться всех обязательных, дополнительных и даже бонусных функций, которые я описал в предыдущей статье. Давайте начнем с обзора полей из этой статьи.

private String isbn;
private String title;
private String author;
private String publisher;
private int pages;

Я добавил дополнительное поле, чтобы сделать этот компонент немного более интересным. Поле будет представлять дату публикации книги.

private LocalDate datePublished;

Чтобы сделать этот компонент видимым, мы должны изменить способ объявления этих полей. JavaFX представил семейство классов-оболочек, называемых классами свойств. Эти классы свойств существуют для примитивных типов, String, List, Map, Object, Set и перехвата всех для любого другого типа класса. Эти классы доступны как для чтения / записи, так и для чтения. Они также входят в абстрактные и конкретные классы. Абстрактный класс — это тип свойства (LHS), а конкретный класс — экземпляр (RHS). Вот таблица классов свойств чтения / записи.

Abstract / LHS Type Concrete / RHS Type
----------------------------------------------
BooleanProperty      SimpleBooleanProperty
DoubleProperty       SimpleDoubleProperty
FloatProperty        SimpleFloatProperty
IntegerProperty      SimpleIntegerProperty
ListProperty<E>      SimpleListProperty<E>
LongProperty         SimpleLongProperty
MapProperty<K,V>     SimpleMapProperty<K,V>
ObjectProperty<T>    SimpleObjectProperty<T>
SetProperty<E>       SimpleSetProperty<E>
StringProperty       SimpleStringProperty

Абстрактные классы свойств определяют необходимые методы свойства в общем виде. Конкретный класс может переопределять общие методы, но не добавлять никаких дополнительных методов. JavaFX имеет другие конкретные классы свойств, которые могут отображаться справа. Конкретные классы расширяют абстрактный класс. Это пример чистого наследования.

Это означает, что нам нужно переписать поля следующим образом.

private final StringProperty isbn;
private final StringProperty title;
private final StringProperty author;
private final StringProperty publisher;
private final IntegerProperty pages;
private final ObjectProperty<LocalDate> datePublished;

Следующее изменение, которое мы сделаем из оригинального компонента, это то, что теперь у нас должен быть конструктор. Эти поля свойств являются пустыми ссылками на абстрактные классы, поэтому вы должны создавать их экземпляры. Если вы не планируете использовать конструктор не по умолчанию, тогда все, что вам нужно, это что-то вроде этого:

public BookFX() {
    this.isbn = new SimpleStringProperty("");
    this.title = new SimpleStringProperty("");
    this.author = new SimpleStringProperty("");
    this.publisher = new SimpleStringProperty("");
    this.pages = new SimpleIntegerProperty(0);
    this.datePublished = new SimpleObjectProperty<>(LocalDate.now());
}

Каждая из ссылок на абстрактный класс создается с помощью конкретного класса. Все конкретные классы принимают соответствующее значение базового типа в своем конструкторе.

Если нам нужен конструктор по умолчанию и не по умолчанию, их можно записать так:

public BookFX() {
    this("", "", "", "", 0, LocalDate.now());
}

public BookFX(final String isbn, final String title, final String author, final String publisher, final int pages, final LocalDate datePublished) {
    this.isbn = new SimpleStringProperty(isbn);
    this.title = new SimpleStringProperty(title);
    this.author = new SimpleStringProperty(author);
    this.publisher = new SimpleStringProperty(publisher);
    this.pages = new SimpleIntegerProperty(pages);
    this.datePublished = new SimpleObjectProperty<>(datePublished);
}

Конструктор по умолчанию использует способность Java вызывать конструктор для вызова другого конструктора путем перегрузки. Конструктор не по умолчанию теперь можно использовать для создания экземпляра объекта, и в то же время этот класс имеет требуемый конструктор по умолчанию.

Сеттеры и геттеры также должны быть изменены. Предполагается, что эти методы получают или устанавливают значение внутри объекта свойства, а не само свойство. Это легко сделать, так как классы свойств имеют метод get и set для обработки этого. Вот сеттеры и геттеры.

public final String getIsbn() {
    return isbn.get();
}
public void setIsbn(final String isbn) {
    this.isbn.set(isbn);
}
public final String getTitle() {
    return title.get();
}
public void setTitle(final String title) {
    this.title.set(title);
}
public String getAuthor() {
    return author.get();
}
public void setAuthor(final String author) {
    this.author.set(author);
}
public final String getPublisher() {
    return publisher.get();
}
public void setPublisher(final String publisher) {
    this.publisher.set(publisher);
}
public final int getPages() {
    return pages.get();
}
public void setPages(final int pages) {
    this.pages.set(pages);
}
public final LocalDate getDatePublished() {
    return datePublished.get();
}
public void setDatePublished(final LocalDate datePublished) {
    this.datePublished.set(datePublished);
}

С этим синтаксисом у нас теперь есть замена для стандартных Java-бинов, как описано в моей первой статье. Это еще не завершено. Мы используем классы свойств для достижения наблюдаемого шаблона. Для этого элементы управления JavaFX должны быть связаны или связаны с отдельными полями. Элементам управления нужна ссылка на объект свойства. Требуется только эквивалент получателя. Соглашения об именах не существует, потому что мы записываем имя метода при выполнении привязки. Наиболее распространенный подход к именованию — добавить слово Property к имени переменной. Вот добытчики для свойств.

public final StringProperty isbnProperty() {
    return isbn;
}
public final StringProperty titleProperty() {
    return title;
}
public final StringProperty authorProperty() {
    return author;
}
public final StringProperty publisherProperty() {
    return publisher;
}
public final IntegerProperty pagesProperty() {
    return pages;
}
public final ObjectProperty<LocalDate> datePublishedProprty() {
    return datePublished;
}

Для свойства нет установщика, потому что после создания и привязки к элементу управления его нельзя заменять. Только его значение может быть изменено.

Методы hashCode, equals, toString и compareTo необходимо изменить, чтобы использовать методы get и set свойств.

@Override
public String toString() {
    return "Book{" + "isbn=" + isbn.get() + ", title=" + title.get() + ", author=" + author.get() + ", publisher=" + publisher.get() + ", pages=" + pages.get() + ", datePublished=" + datePublished.get() + '}';
}

@Override
public int hashCode() {
    int hash = 7;
    hash = 61 * hash + Objects.hashCode(this.isbn.get());
    hash = 61 * hash + Objects.hashCode(this.title.get());
    hash = 61 * hash + Objects.hashCode(this.author.get());
    hash = 61 * hash + Objects.hashCode(this.publisher.get());
    hash = 61 * hash + this.pages.get();
    hash = 61 * hash + Objects.hashCode(this.datePublished.get());

    return hash;
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    final BookFX other = (BookFX) obj;
    if (this.pages.get() != other.pages.get()) {
        return false;
    }
    if (!Objects.equals(this.isbn.get(), other.isbn.get())) {
        return false;
    }
    if (!Objects.equals(this.title.get(), other.title.get())) {
        return false;
    }
    if (!Objects.equals(this.author.get(), other.author.get())) {
        return false;
    }
    if (!Objects.equals(this.publisher.get(), other.publisher.get())) {
        return false;
    }
    if (!Objects.equals(this.datePublished.get(), other.datePublished.get())) {
        return false;
    }
    return true;
}

@Override
public int compareTo(BookFX book) {
    return this.title.get().compareTo(book.title.get());
}

Единственное изменение, которое необходимо внести в наш класс Comparator, — объявить компоненты как компоненты JavaFX, а не как компоненты Java.

public class BookPageComparatorFX implements Comparator<BookFX> {
    @Override
    public int compare(BookFX book1, BookFX book2) {
        return book1.getPages() - book2.getPages();
    }
}

Соберите все вместе, вот как выглядит наш компонент JavaFX.

import java.io.Serializable;
import java.time.LocalDate;
import java.util.Objects;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class BookFX implements Serializable, Comparable<BookFX> {

    private final StringProperty isbn;
    private final StringProperty title;
    private final StringProperty author;
    private final StringProperty publisher;
    private final IntegerProperty pages;
    private final ObjectProperty<LocalDate> datePublished;

    /**
     * Default constructor
     */
    public BookFX() {
        this("", "", "", "", 0, LocalDate.now());
    }

    /**
     * Non-default constructor
     *
     * @param isbn
     * @param title
     * @param author
     * @param publisher
     * @param pages
     * @param datePublished
     */
    public BookFX(final String isbn, final String title, final String author, final String publisher, final int pages, final LocalDate datePublished) {
        this.isbn = new SimpleStringProperty(isbn);
        this.title = new SimpleStringProperty(title);
        this.author = new SimpleStringProperty(author);
        this.publisher = new SimpleStringProperty(publisher);
        this.pages = new SimpleIntegerProperty(pages);
        this.datePublished = new SimpleObjectProperty<>(datePublished);
    }

    public final String getIsbn() {
        return isbn.get();
    }

    public void setIsbn(final String isbn) {
        this.isbn.set(isbn);
    }

    public final StringProperty isbnProperty() {
        return isbn;
    }

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

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

    public final StringProperty titleProperty() {
        return title;
    }

    public String getAuthor() {
        return author.get();
    }

    public void setAuthor(final String author) {
        this.author.set(author);
    }

    public final StringProperty authorProperty() {
        return author;
    }

    public final String getPublisher() {
        return publisher.get();
    }

    public void setPublisher(final String publisher) {
        this.publisher.set(publisher);
    }

    public final StringProperty publisherProperty() {
        return publisher;
    }

    public final int getPages() {
        return pages.get();
    }

    public void setPages(final int pages) {
        this.pages.set(pages);
    }

    public final IntegerProperty pagesProperty() {
        return pages;
    }

    public final LocalDate getDatePublished() {
        return datePublished.get();
    }

    public void setDatePublished(final LocalDate datePublished) {
        this.datePublished.set(datePublished);
    }

    public final ObjectProperty<LocalDate> datePublishedProprty() {
        return datePublished;
    }

    @Override
    public String toString() {
        return "Book{" + "isbn=" + isbn.get() + ", title=" + title.get() + ", author=" + author.get() + ", publisher=" + publisher.get() + ", pages=" + pages.get() + ", datePublished=" + datePublished.get() + '}';
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 61 * hash + Objects.hashCode(this.isbn.get());
        hash = 61 * hash + Objects.hashCode(this.title.get());
        hash = 61 * hash + Objects.hashCode(this.author.get());
        hash = 61 * hash + Objects.hashCode(this.publisher.get());
        hash = 61 * hash + this.pages.get();
        hash = 61 * hash + Objects.hashCode(this.datePublished.get());

        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final BookFX other = (BookFX) obj;
        if (this.pages.get() != other.pages.get()) {
            return false;
        }
        if (!Objects.equals(this.isbn.get(), other.isbn.get())) {
            return false;
        }
        if (!Objects.equals(this.title.get(), other.title.get())) {
            return false;
        }
        if (!Objects.equals(this.author.get(), other.author.get())) {
            return false;
        }
        if (!Objects.equals(this.publisher.get(), other.publisher.get())) {
            return false;
        }
        if (!Objects.equals(this.datePublished.get(), other.datePublished.get())) {
            return false;
        }
        return true;
    }

    /**
     * Natural comparison method for Title
     *
     * @param book
     * @return negative value, 0, or positive value
     */
    @Override
    public int compareTo(BookFX book) {
        return this.title.get().compareTo(book.title.get());
    }
}

Программа тестирования для этого bean-компонента практически идентична исходному BeanTester, но с добавлением LocalDate и использованием его в функции Comparator.

// Required for Arrays.sort
import java.time.LocalDate;
import java.util.Arrays;
// Required by the Comparator function
import static java.util.Comparator.comparing;

public class BeanTesterFX {

    /**
     * Here is where I am testing my comparisons
     *
     */
    public void perform() {
        // Lets create four books
        BookFX b0 = new BookFX("200", "Xenon", "Hamilton", "Harcourt", 99, LocalDate.of(2017, 2, 4));
        BookFX b1 = new BookFX("500", "Boron", "Bradbury", "Prentice", 108, LocalDate.of(2018, 5, 23));
        BookFX b2 = new BookFX("300", "Radon", "Heinlein", "Thompson", 98, LocalDate.of(2015, 8, 30));
        BookFX b3 = new BookFX("404", "Argon", "Campbell", "Hachette", 102, LocalDate.of(2012, 11, 15));

        // Using Comparable to compare two books
        System.out.println("Value returned by Comparable");
        System.out.println(b0.getTitle() + " compared to " + b1.getTitle() + ": "
                + b0.compareTo(b1));
        System.out.println();

        // Using Comparator to compare two books
        System.out.println("Value returned by Comparator");
        BookPageComparatorFX bookPageComparatorFX = new BookPageComparatorFX();
        System.out.println(b0.getPages() + " compared to " + b1.getPages() + ": "
                + bookPageComparatorFX.compare(b0, b1));
        System.out.println();

        // Create an array we can sort
        BookFX[] myBooks = new BookFX[4];
        myBooks[0] = b0;
        myBooks[1] = b1;
        myBooks[2] = b2;
        myBooks[3] = b3;
        System.out.println("Unsorted");
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable Interface on Title");
        Arrays.sort(myBooks); // uses the Comparable compareTo in the bean
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable Object on Pages");
        Arrays.sort(myBooks, bookPageComparatorFX); // uses the Comparator object
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable lambda expression on Publishers");
        Arrays.sort(myBooks, (s1, s2) -> {
            return s1.getPublisher().compareTo(s2.getPublisher());
        }); // uses the Comparator lambda
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable lambda functions based on ISBN");
        Arrays.sort(myBooks, comparing(BookFX::getIsbn)); // Comparable function
        displayBooks(myBooks);

        System.out.println("Sorted with Comparable lambda functions based on Date Published");
        Arrays.sort(myBooks, comparing(BookFX::getDatePublished)); // Comparable function
        displayBooks(myBooks);

    }

    /**
     * Print the contents of each Book object in the array
     *
     * @param theBooks
     */
    private void displayBooks(BookFX[] theBooks) {
        for (BookFX b : theBooks) {
            System.out.print(b.getIsbn() + "\t");
            System.out.print(b.getTitle() + "\t");
            System.out.print(b.getAuthor() + "\t");
            System.out.print(b.getPublisher() + "\t");
            System.out.println(b.getPages() + "\t");
            System.out.println(b.getDatePublished());
        }
        System.out.println();
    }

    /**
     * Where it all begins
     *
     * @param args
     */
    public static void main(String[] args) {
        BeanTesterFX bt = new BeanTesterFX();
        bt.perform();
        System.exit(0);
    }
}

JavaFX требует использования компонентов JavaFX, как описано здесь. Они требуются только в том случае, если вы намереваетесь воспользоваться преимуществами привязки к элементам управления. Если привязка не требуется, тогда следует использовать обычный Java-бин.

Подводя итог этим двум статьям, если вы не используете JavaFX, вам следует использовать класс bean-компонентов Java для данных и моделей. В приложении JavaFX вы должны использовать компонент JavaFX всякий раз, когда требуется привязка.