Hi.
On Thu, 08 Aug 2013 10:03:36 +0200, Luc Maisonobe wrote:
Le 08/08/2013 01:13, Gilles a écrit :
On Wed, 7 Aug 2013 18:04:39 -0400, Evan Ward wrote:
Hi,
As a grateful user of the library, I would prefer the Fluent API
and
immutability. I have implemented this pattern in some of my own
projects
and it makes the API very easy to use.
Most of the time, when the interface is small, inheritance isn't
really
necessary and I just implement the withXxx methods directly. A one
line
'return new ...' isn't hard to maintain with IDE support for
renames and
add/remove parameters.
When inheritance is part of the API you can parametrize the base
class
and add an abstract create method, like so:
abstract class Parent<T extends Parent> implements Interface{
protected abstract T create(...);
public T withXxx(x){
return create(x, ...);
}
}
class Child extends Parent<Child> {
protected T create(...){
return new Child(...);
}
}
Did I understand correctly that the base class "Parent" is assumed
to
have complete knowledge of all its subclass "Child": the signature
of
"create" must contain the arguments for fields declared in "Child"?
I don't think so.
In this way, it does not; what I inferred from the partial code above
was
that there would only be _one_ "create" method. But:
1. All "create" methods must be overridden at the next level of the
class
hierarchy (this is the duplication I was referring to in the first
post).
2. When a parameter is added somewhere in the hierarchy, all the
classes
below must be touched too.
And, we must note, that the duplication still does not ensure "real"
immutability unless all the passed arguments are themselves immutable.
But:
1. We do not have control over them; e.g. in the case of the optimizers
the "ConvergenceChecker" interface could possibly be implemented by
a
non-thread-safe class (I gave one example of such a thing a few
weeks
ago when a user wanted to track the optimizer's search path)
2. Some users _complained_ (among other things :-) that we should not
force immutability of some input (model function and Jacobian IIRC)
because in some use-cases, it would be unnecessarily costly.
Consequently, if (when creating a new instance) we assign a reference
passed to the fluent method, we cannot warrant thread-safety anymore;
which in turn poses the question of whether this false sense of
security
warrants the increased code duplication and complexity (compare your
code
below with the same but without the constructors and "create" methods:
even a toy example is already cut by more than half).
Then if we really want to aim at thread-safety, I think that the
approach
of mandating a "copy()" interface to all participating classes, would
be
a serious contender to just making all fields "final". Let's also
recall
that immutability is not a goal but a means (the goal being
thread-safety).
I still think that the three "tools" mentioned in the subject line do
not
play along very well, unfortunately.
I was, and still am, a proponent of immutability but IMO this
discussion
indicates that it should not be enforced at all cost. In particular, in
small and non-layered objects, it is easy to ensure thread-safety
(through
"final") but the objects that most benefit from a fluent API are big
(with
many configuration combinations), and their primary usage does not
necessarily benefit from transparent instantiation.
Given all these considerations, in the first steps for moving some CM
codes
to fluent APIs, I would aim for simplicity and _less_ code (if just to
be
able to make adjustments more quickly).
Then, from "simple" code, we can more easily experiment (and compare,
with
"diff") the merits and drawbacks of various routes towards
thread-safety.
OK?
Gilles
The following code displays:
original: 3 1.0
changed : 2 1.0
This means that despite withI() was defined only in the base class
and
despite this base class doesn't know anything about the Derived
class,
the withI call was properly executed and the modified object was
really
a Derived instance.
The drawback is that the user had to do the cast from BAse to Derived
when calling withI in the statement:
Derived changed = (Derived) original.withI(2);
I tried a few tricks with the templates to prevent this, but did not
succeed. I'm not sure it is a big problem, though.
interface IChanger<T extends IChanger<T>> {
public T withI(int i);
}
interface DChanger<T extends DChanger<T>> {
public T withD(double d);
}
class Base implements IChanger<Base> {
int i;
public Base(int i) { this.i = i; }
public Base create (int i) { return new Base(i); }
public Base withI(int i) { return create(i); }
public int getI() { return i; }
}
class Derived extends Base implements DChanger<Derived> {
double d;
public Derived(int i, double d) { super(i); this.d = d; }
public Derived create(int i, double d) { return new Derived(i,
d); }
public Derived create(int i) { return new Derived(i,
d); }
public Derived withD(double d) { return create(i, d); }
public double getD() { return d; }
}
Derived original = new Derived(3, 1.0);
Derived changed = (Derived) original.withI(2);
System.out.println("original: " + original.getI() + " " +
original.getD());
System.out.println("changed : " + changed.getI() + " " +
changed.getD());
best regards,
Luc
>
> [...]
---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
For additional commands, e-mail: dev-h...@commons.apache.org