Reputation: 5399
One more day and one more struggle with generics.
I have set of Control
objects with the following inheritance tree:
BaseControl
|_SimpleControl
|_MultipleControl
|_AutocompleteControl
|_SelectControl
For each of non-abstract object in this tree I'd like to provide builder so these objects can be created easily. Here is what I have so far:
BaseControlBuilder:
public abstract class BaseControlBuilder<C extends BaseControl, B extends BaseControlBuilder<C, B>> {
protected C control;
private B builder;
BaseControlBuilder() {
control = createObj();
builder = getThis();
}
public C build() { return control; }
protected abstract C createObj();
protected abstract B getThis();
}
SimpleControlBuilder:
public class SimpleControlBuilder<C extends SimpleControl, B extends SimpleControlBuilder<C, B>>
extends BaseControlBuilder<SimpleControl, SimpleControlBuilder<C, B>> {
public SimpleControlBuilder(final String id, final String caption,
final InputType controlType) {
super();
control.setId(id);
control.setCaption(caption);
control.setType(controlType);
}
public SimpleControlBuilder(final InputType controlType) {
this("", "", controlType);
}
public SimpleControlBuilder(final Enum<?> en, final InputType controlType) {
this(en.name(), en.toString(), controlType);
}
public SimpleControlBuilder<C, B> disabled() {
control.setDisabled(true);
return this;
}
@Override
protected SimpleControl createObj() {
return new SimpleControl();
}
@Override
protected SimpleControlBuilder<C, B> getThis() {
return this;
}
}
MultipleControlBuilder:
abstract class MultipleControlBuilder<C extends MultipleControl, B extends MultipleControlBuilder<C, B>>
extends SimpleControlBuilder<MultipleControl, MultipleControlBuilder<C, B>> {
MultipleControlBuilder(final InputType type) {
super(type);
}
MultipleControlBuilder(final String id, final String caption,
final InputType type) {
super(id, caption, type);
}
MultipleControlBuilder(final Enum<?> en, final InputType type) {
super(en, type);
}
public MultipleControlBuilder<C, B> multiple() {
((MultipleControl) control).setMultiple(true);
return this;
}
}
AutocompleteControlBuilder:
public class AutocompleteControlBuilder<C extends AutocompleteControl, B extends AutocompleteControlBuilder<C, B>>
extends MultipleControlBuilder<AutocompleteControl, AutocompleteControlBuilder<C, B>> {
public AutocompleteControlBuilder(final String url,
final AutocompleteType autocompleteType) {
this("", "", url, autocompleteType);
}
public AutocompleteControlBuilder(final String id,
final String caption, final String url,
final AutocompleteType autocompleteType) {
super(id, caption, InputType.AUTOCOMPLETE);
((AutocompleteControl) control).setAutocompleteUrl(url);
((AutocompleteControl) control).setAutocompleteType(autocompleteType);
}
public AutocompleteControlBuilder(final Enum<?> en, final String url,
final AutocompleteType autocompleteType) {
this(en.name(), en.toString(), url, autocompleteType);
}
@Override
protected AutocompleteControl createObj() {
return new AutocompleteControl();
}
@Override
protected AutocompleteControlBuilder<C, B> getThis() {
return this;
}
}
But surprisingly I have got some unexpected results.
For instance, in the following code I have to cast control
to MultipleControl
to call setter despite the fact that C extends MultipleControl
...
Moreover, the following build()
method call: new AutocompleteControlBuilder<AutocompleteControl, AutocompleteControlBuilder>("url", AutocompleteType.STANDARD).build());
returns SimpleControl
instead of AutocompleteControl
which doesn't make sense, cause I explicitly provided type parameter.
And the last straw is that conciseness and clear code I'm trying to achieve are killed by ugly new AutocompleteControlBuilder<AutocompleteControl, AutocompleteControlBuilder>
constructor call. Could anybody point me to the best practices of solving this problem?
Upvotes: 2
Views: 332
Reputation: 81568
Okay, in order to set this up correctly, you should make some changes:
public class SimpleControlBuilder<C extends SimpleControl, B extends SimpleControlBuilder<C, B>>
extends BaseControlBuilder<SimpleControl, SimpleControlBuilder<C, B>> { // this should extend with the extension classes
public SimpleControlBuilder(final String id, final String caption,
final InputType controlType) {
super();
control.setId(id);
control.setCaption(caption);
control.setType(controlType);
}
public SimpleControlBuilder(final InputType controlType) {
this("", "", controlType);
}
public SimpleControlBuilder(final Enum<?> en, final InputType controlType) {
this(en.name(), en.toString(), controlType);
}
public SimpleControlBuilder<C, B> disabled() { // this should return B
control.setDisabled(true);
return this;
}
@Override
protected SimpleControl createObj() { // this should return C
return new SimpleControl();
}
@Override
protected SimpleControlBuilder<C, B> getThis() { // this should return B
return this;
}
}
So that means
public abstract class SimpleControlBuilder<C extends SimpleControl, B extends SimpleControlBuilder<C, B>>
extends BaseControlBuilder<C, B> {
public SimpleControlBuilder(final String id, final String caption,
final InputType controlType) {
super();
control.setId(id);
control.setCaption(caption);
control.setType(controlType);
}
public SimpleControlBuilder(final InputType controlType) {
this("", "", controlType);
}
public SimpleControlBuilder(final Enum<?> en, final InputType controlType) {
this(en.name(), en.toString(), controlType);
}
public B disabled() {
control.setDisabled(true);
return getThis();
}
}
And
abstract class MultipleControlBuilder<C extends MultipleControl, B extends MultipleControlBuilder<C, B>>
extends SimpleControlBuilder<C, B> {
MultipleControlBuilder(final InputType type) {
super(type);
}
MultipleControlBuilder(final String id, final String caption,
final InputType type) {
super(id, caption, type);
}
MultipleControlBuilder(final Enum<?> en, final InputType type) {
super(en, type);
}
public B multiple() {
control.setMultiple(true);
return getThis();
}
}
And
public abstract class AutocompleteControlBuilder<C extends AutocompleteControl, B extends AutocompleteControlBuilder<C, B>>
extends MultipleControlBuilder<C, B>> {
public AutocompleteControlBuilder(final String url,
final AutocompleteType autocompleteType) {
this("", "", url, autocompleteType);
}
public AutocompleteControlBuilder(final String id,
final String caption, final String url,
final AutocompleteType autocompleteType) {
super(id, caption, InputType.AUTOCOMPLETE);
control.setAutocompleteUrl(url);
control.setAutocompleteType(autocompleteType);
}
public AutocompleteControlBuilder(final Enum<?> en, final String url,
final AutocompleteType autocompleteType) {
this(en.name(), en.toString(), url, autocompleteType);
}
}
This works if MultipleControl extends SimpleControl
and AutocompleteControl extends MultipleControl
, and you have concrete extensions of SimpleControl
that can return themselves with getThis()
with concretized parameters.
public class SomeControlBuilder extends MultipleControlBuilder<SomeControl, SomeControlBuilder> {
public SomeControlBuilder(final InputType type) {
super(type);
}
public SomeControlBuilder(final String id, final String caption,
final InputType type) {
super(id, caption, type);
}
public SomeControlBuilder(final Enum<?> en, final InputType type) {
super(en, type);
}
@Override
protected SomeControlBuilder getThis() {
return this;
}
@Override
protected SomeControl createObj() {
return new SomeControl();
}
}
Upvotes: 1