I’m pulling out this as a side track from RFC: Form binding with signals - #2 by SimonMartinelli.
When talking about signals, I’ve recently been using constructs like this:
add(new TextField() {{
bindValue(someSignal);
}});
This is an anonymous inner class with an initializer block which is thus run like a constructor would be run for a regular inner class. It’s roughly similar to this conventional code block:
var myField = new TextField();
myField.bindValue(someSignal);
I know that this pattern has been abused previously and is nowadays considered an anti-pattern. But I also see that it fits very well into the way of thinking that we want to encourage with signals.
First, you’re not supposed to directly use component instances after they have been created when using signal. Instead, state changes should be applied to signals and incremental updates applied using signal bindings. This pattern means that there’s not even a variable that you might accidentally use outside of the context where the component is initially configured (though you can still “leak” it by doing weird things in the initializer). An additional benefit is that you avoid some redundant code by not repeating myField. 10 times if you are applying 10 different settings to that component. The flip side is that you don’t have a natural way of “naming” the instance to more clearly define its purpose.
Second, the whole point of signals is to have declarative configuration for your components. One potential benefit of this is the same as with declarative markup languages: the structure of the component hierarchy can be apparent from the structure of the code. It’s relatively obvious that these two representations have the same structure. Reading is also quite natural since the code starts from the outside.
<div class="outer">
<div class="middle">
<span>Inner</span>
</div>
</div>
add(new Div() {{
setClassName("outer");
add(new Div() {{
setClassName("middle");
add(new Span() {{
setText("Inner");
}});
}});
}});
The regular Java approach does on the other hand lose this structure and makes you set up inner components first so that you have them available when setting up the outer components.
var inner = new Span();
inner.setText("Inner");
var middle = new Div();
middle.setClassName("middle");
middle.add(inner);
var outer = new Div();
outer.setClassName("outer");
outer.add(middle);
add(outer);
I’ve heard multiple arguments against the pattern. Those objections are indeed valid for general use but I would claim that they mostly don’t apply for the particular way of using that I’m considering.
- Developers are not used to reading code using this structure. Sure, but the same could also be said about Java features such as lambdas or records when they were initially introduced. Some Spring annotations are unfamiliar if you’re used to CDI. jOOQ and JPA look different from each other. And so on. But we certainly have a “weirdness budget” that we need to be conscious of.
- There’s some overhead from having more classes. Sure, but Java today deals with lots of classes much better than in the old times when classes were in their own separate “PermGen” area in memory that got full after a couple of redeploys. There’s also a lightweight class behind each lambda or method reference but most developers today use those without similar concerns.
- Might cause memory leaks with and implicit “outer this” reference that is captured in the inner class. Sure, but that’s not an issue when an outer component class defines its child components since those children should no longer be used when the parent is no longer used. Lambdas can also capture “outer this” references and that’s rarely mentioned as a reason avoid lambdas.
- Some static code analysers complain about this pattern. Yes, that’s an actual problem.
- Some code formatters put the second
{on a line of its own with an additional indentation level. This makes sense if the class also has other members but it only leads to visual noise in this case.
For now, using the {{ pattern specifically in the context of declarative-like component configuration together with signals is an experiment from my side. It’s probably the least bad way of getting a DSL-like structure for component trees within the constraints of the Java language. We will eventually have to decide whether we want to have that as the official recommendation that we’re using in e.g. documentation and various official examples.
Before we get to making that decision, I would like to hear if there are any additional arguments, for or against, that would be useful to consider?