No subject
Nir Lisker
nlisker at gmail.com
Tue Jan 18 19:25:50 UTC 2022
Hi,
In JavaFX we have the following exported interface:
public interface ObsevableValue<T> {
void addListener(InvalidationListener listener);
void removeListener(InvalidationListener listener);
void addListener(ChangeListener<? super T> listener);
void removeListener(ChangeListener<? super T> listener);
}
and many exported classes that implement it in the following way:
public ObjectBinding<T> extends ObjectExpression<T> implements
ObsevableValue<T> {
private ExpressionHelper<T> helper = null;
@Override
public void addListener(InvalidationListener listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
@Override
public void addListener(ChangeListener<? super T> listener) {
helper = ExpressionHelper.addListener(helper, this, listener);
}
@Override
public void removeListener(ChangeListener<? super T> listener) {
helper = ExpressionHelper.removeListener(helper, listener);
}
Where ExpressionHelper is some internal helper class.
I'm looking at removing the code duplication by extracting this behavior
and having all classes use the same code. My first Idea was to create a
non-exported (internal) interface that has a default implementation to the
methods and have the clases implement them:
public interface ObservableValueHelper<T> extends ObservableValue<T> {
ExpressionHelper<T> getHelper();
void setHelper(ExpressionHelper<T> helper);
@Override
public default void addListener(InvalidationListener listener) {
var expressionHelper = helperSupplier().get();
setHelper(ExpressionHelper.addListener(getHelper(), this,
listener));
}
@Override
public default void removeListener(InvalidationListener listener) {
setHelper(ExpressionHelper.removeListener(getHelper(), listener));
}
@Override
public default void addListener(ChangeListener<? super T> listener) {
setHelper(ExpressionHelper.addListener(getHelper(), this,
listener));
}
@Override
public default void removeListener(ChangeListener<? super T> listener) {
setHelper(ExpressionHelper.removeListener(getHelper(), listener));
}
}
and now:
public class ObjectBinding<T> extends ObjectExpression<T> implements
ObservableValueHelper<T> {
@Override
public ExpressionHelper<T> getHelper() {
return helper;
}
@Override
public void setHelper(ExpressionHelper<T> helper) {
this.helper = helper;
}
}
but this can't work because the new public methods will be exposed API. I'm
also only saving half the work because now I need to declare 2 methods to
compensate, but I can envision a case where the interface has more than 4
methods.
I feel like I am stuck between several rocks and hard places:
* If I extract the behavior to an interface like I showed, I can't also
extract the field because interfaces can only hold static fields.
* If I extract the behavior to a class, I can extract the field too, but I
can't extend it since I'm already extending another class.
* If I use the interface approach with the get/set abstract methods, I have
to declare them as public. I can't reduce their visibility in the
implementing class, and I can't not export them.
* Composition instead of inheritance isn't viable because I have to provide
implementation for the abstract methods somewhere in the hierarchy.
I'm not advocating for multiple inheritance or new visibility modifiers
(though here there is a broader issue where library designers want a
"protected but not public API" kind of visibilit), but I would like to not
need to copy-paste the same code everywhere, whatever the solution might
be. Is there a solution?
- Nir
More information about the amber-dev
mailing list