Do properties of properties make sense?

后端 未结 1 905
既然无缘
既然无缘 2021-01-25 06:56

Because this is a question about design I\'ll start by saying what i have and what i want.

I have a design that uses composition. A Cell object hol

相关标签:
1条回答
  • 2021-01-25 07:07

    Using just the standard JavaFX API you can leverage the Bindings.selectXXX methods to observe a "property of a property".

    So for example:

    import javafx.beans.binding.Bindings;
    import javafx.beans.property.IntegerProperty;
    import javafx.beans.property.ObjectProperty;
    import javafx.beans.property.SimpleIntegerProperty;
    import javafx.beans.property.SimpleObjectProperty;
    import javafx.scene.paint.Color;
    
    public class Cell {
    
        private final ObjectProperty<Shape> shape = new SimpleObjectProperty<>(new Shape());
    
    
        public final ObjectProperty<Shape> shapeProperty() {
            return this.shape;
        }
    
    
    
    
        public final Cell.Shape getShape() {
            return this.shapeProperty().get();
        }
    
    
    
    
        public final void setShape(final Cell.Shape shape) {
            this.shapeProperty().set(shape);
        }
    
    
        public static class Shape {
    
            private final IntegerProperty size = new SimpleIntegerProperty(0);
            private final ObjectProperty<Color> color = new SimpleObjectProperty<>(Color.BLACK);
            public final IntegerProperty sizeProperty() {
                return this.size;
            }
    
            public final int getSize() {
                return this.sizeProperty().get();
            }
    
            public final void setSize(final int size) {
                this.sizeProperty().set(size);
            }
    
            public final ObjectProperty<Color> colorProperty() {
                return this.color;
            }
    
            public final javafx.scene.paint.Color getColor() {
                return this.colorProperty().get();
            }
    
            public final void setColor(final javafx.scene.paint.Color color) {
                this.colorProperty().set(color);
            }
    
        }
    
    
        public static void main(String[] args) {
            Cell cell = new Cell();
            Bindings.selectInteger(cell.shapeProperty(), "size").addListener(
                    (obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
            cell.getShape().setSize(10);
            cell.setShape(new Shape());
            Shape s = new Shape();
            s.setSize(20);
            cell.setShape(s);
        }
    
    }
    

    Will produce the (desired) output

    Size changed from 0 to 10
    Size changed from 10 to 0
    Size changed from 0 to 20
    

    This API has a bit of a legacy feel to it, in that it relies on passing the property name as a string, and consequently is not typesafe and cannot be checked at compile time. Additionally, if any of the intermediate properties are null (e.g. if cel.getShape() returns null in this example), the bindings generate annoying and verbose warning messages (even though this is supposed to be a supported use case).

    Tomas Mikula has a more modern implementation in his ReactFX library, see this post for a description. Using ReactFX, you would do:

    public static void main(String[] args) {
        Cell cell = new Cell();
        Var<Number> size = Val.selectVar(cell.shapeProperty(), Shape::sizeProperty);
        size.addListener(
                (obs, oldSize, newSize) -> System.out.println("Size changed from "+oldSize+" to "+newSize));
    
        cell.getShape().setSize(10);
        cell.setShape(new Shape());
        Shape s = new Shape();
        s.setSize(20);
        cell.setShape(s);
    }
    

    Finally, if you are creating a list of cells, you can create an ObservableList specifying an extractor. The extractor is a function mapping each element in the list (each Cell) to an array of Observables. If any of those Observables changes, the list fires an update event. So you could do

    ObservableList<Cell> cellList = 
        FXCollections.observableArrayList(cell -> new Observable[] {Bindings.selectInteger(cell.shapeProperty(), "size")});
    

    using the standard API, or

    ObservableList<Cell> cellList = 
        FXCollections.observableArrayList(cell -> new Observable[] {Val.selectVar(cell.shapeProperty(), Shape::sizeProperty)});
    

    using ReactFX. Then just add a ListChangeListener to the list, and it will be notified if the size changes (or if the shape changes to a new shape with a different size). You can add as many observables that are properties (or properties of properties) of the cell in the returned array as you need.

    0 讨论(0)
提交回复
热议问题