1

With knockout.js computed observables it is possible to define a JavaScript observable that depends on other observables, using a functional expression, e.g.

var computedObservable = ko.computed(function(){ 
    return firtName() + lastName();
});

If one of the referenced observables (e.g. firstName) changes, the computed observable is automatically updated. The referenced observables are defined by the given functional expression and do not have to be explicitly specified.

How can I implement something similar in Java?

(Well, there are JavaFx Bindings, but they require the explicit registration of the referenced properties/observables.)

I distinguish two cases:

a) The given lambda expression is analyzed with Java to automatically find out which referenced observables have to be considered. (The referenced observables might reference further observables in a dependency chain.) Expected example code:

ComputedObservable<String> computedObservable = new ComputedObservable<>(() -> {
    return firstName.get() + lastName.get();
}); 

b) I have to manually register each referenced observable to be able to observe them. I would like to avoid this case. With other words I do not want to have code like

ComputedObservable<String> computedObservable = new CoputedObservable<>();
computedObservable.registerReference(firstName);
computedObservable.registerReference(lastName);
computedObservable.setExpresion(() -> {
        return firstName.get() + lastName.get();
});

or (using JavaFx Bindings)

 DoubleBinding db = new DoubleBinding() { 
        {
            super.bind(a, b, c, d);
        }

        @Override
        protected double computeValue() {
            return (a.get() * b.get()) + (c.get() * d.get());
        }
 };

Is case a) possible at all and if yes, is there an existing Java implementation/library for doing so?

Edit: There is another strategy:

c) Use specialized operators to define the functional expression. Those operators implicitly register the referenced observables. So there is no need for an extra manual registration step. (The functional expression will be limited to the available operators and advanced functionality might require to implement new helping operators.)

StringBinding fullName = firstName.concat(secondName);
DoubleBinding result = a.multiply(b).add(c.multiply(d)); 

Related questions:

Community
  • 1
  • 1
Stefan
  • 10,010
  • 7
  • 61
  • 117
  • 2
    Remember that just because something might make sense in Javascript doesn't mean you should attempt to bring it to Java. – Kayaman May 16 '16 at 13:23
  • Are your doubts already reflected in the Cons that I mentioned in my answer with the "registry-strategy"? Or are there further points that would make the approach of knockout.js useless/not suited for Java? – Stefan May 17 '16 at 06:33
  • Well, my main point was that where Javascript supports natively something like that, there is no similar mechanism in Java. That means you'll be fighting to be able to write Java in a Javascript way. That's often a sign that you're doing something wrong. Like saving non-relational data in a RDBMS. – Kayaman May 17 '16 at 06:44
  • I did this in C# a long time ago. Just ported it to Kotlin. Still interested? – HelloWorld Jul 31 '18 at 23:32
  • I am more into JavaScript programming now. Since ES5... even the ko observables are not required any more...in principle..., because the attributes can be made observable them self using getters/setters. This is for example utilized by vue.js: https://vuejs.org/v2/guide/reactivity.html. Nevertheless, your port might be interesting for other users of the JVM. – Stefan Aug 01 '18 at 06:37

3 Answers3

1

Even though you explicitly excluded JavaFX bindings, I think they are the closest you can get without intricate reflection (or even bytecode) hacking.

Your snippet is a model use case for Bindings::concat:

ObservableValue<String> firstName = ...;
ObservableValue<String> lastName = ...;
StringExpression name = Bindings.concat(firstName, " ", lastName);

Bindings defines loads of useful atomics but it is conceivable that you have more advanced needs like a*b + c*d. Utility methods to the rescue!

NumberBinding result = MyBindings.multiplyAndAdd(a, b, c, d);

with

public NumberBinding multiplyAndAdd(
        ObservableNumberValue a, ObservableNumberValue b,
        ObservableNumberValue c, ObservableNumberValue d) {
    return Bindings.add(Bindings.multiply(a, b), Bindings.multiply(c, d));
}

Shouldn't that address your requirements?

PS: Your link for JavaFX bindings points to the documentation of version 2. You should make sure to search for "JavaFX 8" when researching JavaFX.

Nicolai Parlog
  • 47,972
  • 24
  • 125
  • 255
  • So the JavaFx bindings in combination with their specialized operators seem to be the best I can get. The specialized operators implicitly register the referenced observables and define the functional expression. DoubleBinding result = a.multiply(b).add(c.multiply(d)); (I updated the JavaFx link to reference version 8 of the documentation.) – Stefan May 16 '16 at 15:30
0

Maybe following strategy would also work, using a static registry for all observables:

  • Do a first evaluation of the given lambda. While the lambda expression is executed, the get methods of the referenced observables have a side effekt: they tell the registry that they have been called.
  • Retrieve the list of referenced observables from the registry.
  • Reset the registry.
  • Attach listeners to the referenced observables to define the current update cycle.
  • When re-evaluating the expression also recreate the update cycle.

A similar work flow for knockout computed observables is explained here in more detail:

http://knockoutjs.com/documentation/computed-dependency-tracking.html

Cons for this strategy might be (are there further ones?):

  • A performance loss due to the registration overhead and update cycle refreshing.
  • Might not work well with multi-threading (JavaScript typically runs in a single thread)
  • The single registry has to be available for (known by) all observables. (In JavaScript I once had the issue that for a test framework a second instance of knockout had been used. The test code observables did not play together with the observables in the tested code until I injected a reference to the knockout instance of the test code.)

Pros:

  • The definition of computed observables would be more intuitive and light wight: No need to use/learn specialized operators since all the information that is required is already included in the functional expression.
Stefan
  • 10,010
  • 7
  • 61
  • 117
0

I asked myself the same question and then I thougt, why not just implementing it. Here is a very first draft (not threadsafe yet), which proves that it is not that hard. I just wrote that down in an hour, but maybe if I have time I will improve it.

https://github.com/t-oster/knockout4j

Thomas Oster
  • 421
  • 2
  • 10