Reputation: 5289
I've been making a library for a framework that I'm using in Java: I have hundreds of LOC used purely for providing many constructors to the users, but each time, with one less argument (because a default value can be used). That leads to a lot of repeated code, and a lot of repeated JavaDoc. And, most of all, a lot of maintenance whenever a little thing changes.
I feel like there could exist some library out there, similar to Lombok, that would allow me to annotate the constructors in such a way that it would automatically generate the constructors and their associated JavaDoc. Something like:
/** Some description. */
public class Example {
/** Alternate1 description. */
public SomeObject alternate1;
/** Alternate2 description. */
public SomeOtherObject alternate2;
/** Attribute1 description. */
public int attribute1 = 1;
/** Attribute2 description. */
public int attribute2 = 2;
@InjectClassDoc
@InjectAttributesDoc
public Example (@ConstructorRequiredVariant SomeObject obj,
@ConstructorOrderedCascade int attribute1,
@ConstructorOrderedCascade int attribute2) {
doSomething();
}
@InjectClassDoc
@InjectAttributesDoc
public Example (@ConstructorRequiredVariant SomeOtherObject obj,
@ConstructorOrderedCascade int attribute1,
@ConstructorOrderedCascade int attribute2) {
doSomething();
}
// getters and setters
}
That would generate something like:
/** Some description. */
public class Example {
/** Alternate1 description. */
public SomeObject someObject;
/** Alternate2 description. */
public SomeOtherObject someOtherObject;
/** Attribute1 description. */
public int attribute1 = 1;
/** Attribute2 description. */
public int attribute2 = 2;
/**
* Some description.
* @param obj Alternate1 description.
* @param attribute1 Attribute1 description.
* @param attribute2 Attribute2 description.
*/
public Example (final SomeObject obj,
final int attribute1,
final int attribute2) {
this(obj, attribute1);
setAttribute2(attribute2);
}
/**
* Some description.
* @param obj Alternate1 description.
* @param attribute1 Attribute1 description.
*/
public Example (final SomeObject obj,
final int attribute1) {
this(obj);
setAttribute1(attribute1);
}
/**
* Some description.
* @param obj Alternate1 description.
*/
public Example (final SomeObject obj) {
setSomeObject(obj);
doSomething();
}
/**
* Some description.
* @param obj Alternate2 description.
* @param attribute1 Attribute1 description.
* @param attribute2 Attribute2 description.
*/
public Example (final SomeOtherObject obj,
final int attribute1,
final int attribute2) {
this(obj, attribute1);
setAttribute2(attribute2);
}
/**
* Some description.
* @param obj Alternate2 description.
* @param attribute1 Attribute1 description.
*/
public Example (final SomeOtherObject obj,
final int attribute1) {
this(obj);
setAttribute1(attribute1);
}
/**
* Some description.
* @param obj Alternate2 description.
*/
public Example (final SomeOtherObject obj) {
setSomeOtherObject(obj);
doSomething();
}
// getters and setters
}
This is just to give you a better idea of where I'm coming from with all of this.
Here is an excerpt of the source of my problem (link to the actual repo's source):
/** Some description that is reused on all constructors seen by the users. */
public class PieWidget extends RadialGroup {
/** Used internally for the shared properties among constructors of RadialWidgets. */
protected void constructorsCommon(TextureRegion whitePixel) {
this.whitePixel = whitePixel;
}
/** Used internally for the shared properties among constructors of RadialWidgets. */
protected PieWidget(final TextureRegion whitePixel, float preferredRadius) {
super(preferredRadius);
constructorsCommon(whitePixel);
}
/** Used internally for the shared properties among constructors of RadialWidgets. */
protected PieWidget(final TextureRegion whitePixel,
float preferredRadius, float innerRadiusPercent) {
super(preferredRadius, innerRadiusPercent);
constructorsCommon(whitePixel);
}
/** Used internally for the shared properties among constructors of RadialWidgets. */
protected PieWidget(final TextureRegion whitePixel,
float preferredRadius, float innerRadiusPercent, float startDegreesOffset) {
super(preferredRadius, innerRadiusPercent, startDegreesOffset);
constructorsCommon(whitePixel);
}
/** Used internally for the shared properties among constructors of RadialWidgets. */
protected PieWidget(final TextureRegion whitePixel,
float preferredRadius, float innerRadiusPercent,
float startDegreesOffset, float totalDegreesDrawn) {
super(preferredRadius, innerRadiusPercent, startDegreesOffset, totalDegreesDrawn);
constructorsCommon(whitePixel);
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param style defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
*/
public PieWidget(final TextureRegion whitePixel,
PieWidgetStyle style, float preferredRadius) {
this(whitePixel, preferredRadius);
setStyle(style);
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param style defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
*/
public PieWidget(final TextureRegion whitePixel,
PieWidgetStyle style, float preferredRadius,
float innerRadiusPercent) {
this(whitePixel, preferredRadius, innerRadiusPercent);
setStyle(style);
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param style defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
*/
public PieWidget(final TextureRegion whitePixel,
PieWidgetStyle style, float preferredRadius,
float innerRadiusPercent, float startDegreesOffset) {
this(whitePixel, preferredRadius, innerRadiusPercent, startDegreesOffset);
setStyle(style);
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param style defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
* @param totalDegreesDrawn the {@link #totalDegreesDrawn} that defines how
* many degrees the widget will span, starting from
* its {@link #startDegreesOffset}.
*/
public PieWidget(final TextureRegion whitePixel,
PieWidgetStyle style, float preferredRadius,
float innerRadiusPercent, float startDegreesOffset, float totalDegreesDrawn) {
this(whitePixel, preferredRadius, innerRadiusPercent, startDegreesOffset, totalDegreesDrawn);
setStyle(style);
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, float preferredRadius) {
this(whitePixel, preferredRadius);
setStyle(skin.get(PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, float preferredRadius,
float innerRadiusPercent) {
this(whitePixel, preferredRadius, innerRadiusPercent);
setStyle(skin.get(PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, float preferredRadius,
float innerRadiusPercent, float startDegreesOffset) {
this(whitePixel, preferredRadius, innerRadiusPercent, startDegreesOffset);
setStyle(skin.get(PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
* @param totalDegreesDrawn the {@link #totalDegreesDrawn} that defines how
* many degrees the widget will span, starting from
* its {@link #startDegreesOffset}.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, float preferredRadius,
float innerRadiusPercent, float startDegreesOffset, float totalDegreesDrawn) {
this(whitePixel, preferredRadius, innerRadiusPercent, startDegreesOffset, totalDegreesDrawn);
setStyle(skin.get(PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param style the name of the style to be extracted from the skin.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, String style, float preferredRadius) {
this(whitePixel, preferredRadius);
setStyle(skin.get(style, PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param style the name of the style to be extracted from the skin.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, String style, float preferredRadius,
float innerRadiusPercent) {
this(whitePixel, preferredRadius, innerRadiusPercent);
setStyle(skin.get(style, PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param style the name of the style to be extracted from the skin.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, String style, float preferredRadius,
float innerRadiusPercent, float startDegreesOffset) {
this(whitePixel, preferredRadius, innerRadiusPercent, startDegreesOffset);
setStyle(skin.get(style, PieWidgetStyle.class));
}
/**
* See {@link PieWidget} for a description.
*
* @param whitePixel a 1x1 white pixel.
* @param skin defines the way the widget looks like.
* @param style the name of the style to be extracted from the skin.
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
* @param totalDegreesDrawn the {@link #totalDegreesDrawn} that defines how
* many degrees the widget will span, starting from
* its {@link #startDegreesOffset}.
*/
public PieWidget(final TextureRegion whitePixel,
Skin skin, String style, float preferredRadius,
float innerRadiusPercent, float startDegreesOffset, float totalDegreesDrawn) {
this(whitePixel, preferredRadius, innerRadiusPercent, startDegreesOffset, totalDegreesDrawn);
setStyle(skin.get(style, PieWidgetStyle.class));
}
// ... then the actual methods start
}
The parent class:
public class RadialGroup extends WidgetGroup {
/** Used internally for the shared properties among constructors of RadialWidgets. */
protected void constructorsCommon() {
setTouchable(Touchable.childrenOnly);
}
/**
* See {@link RadialGroup} for a description.
*
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
*/
public RadialGroup(float preferredRadius) {
setPreferredRadius(preferredRadius);
constructorsCommon();
}
/**
* See {@link RadialGroup} for a description.
*
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
*/
public RadialGroup(float preferredRadius, float innerRadiusPercent) {
this(preferredRadius);
setInnerRadiusPercent(innerRadiusPercent);
}
/**
* See {@link RadialGroup} for a description.
*
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
*/
public RadialGroup(float preferredRadius, float innerRadiusPercent,
float startDegreesOffset) {
this(preferredRadius, innerRadiusPercent);
setStartDegreesOffset(startDegreesOffset);
}
/**
* See {@link RadialGroup} for a description.
*
* @param preferredRadius the {@link #preferredRadius} that defines the
* size of the widget.
* @param innerRadiusPercent the {@link #innerRadiusPercent} that defines
* the percentage of the radius that is cut off,
* starting from the center of the widget.
* @param startDegreesOffset the {@link #startDegreesOffset} that defines
* how far from the origin the drawing begins.
* @param totalDegreesDrawn the {@link #totalDegreesDrawn} that defines how
* many degrees the widget will span, starting from
* its {@link #startDegreesOffset}.
*/
public RadialGroup(float preferredRadius, float innerRadiusPercent,
float startDegreesOffset, float totalDegreesDrawn) {
this(preferredRadius, innerRadiusPercent, startDegreesOffset);
setTotalDegreesDrawn(totalDegreesDrawn);
}
// ...
}
The structure is rather simple:
constructorsCommon()
method used to do common operations for any constructor used.setter
associated with a single field of the class, and then pass the call to the other constructor which had one less attribute defined.I'm wondering if there is a way to reduce that amount of copy-pasted code and documentation out there. Ideally, I would end up with only 3 constructors, instead of 12.
Upvotes: 0
Views: 87
Reputation: 51142
To avoid writing exponentially many constructors when a class has several optional fields with default values, you can use the Builder pattern. For example:
public class Example {
private int a;
private String b;
private char c;
private boolean d;
private Example(int a, String b, char c, boolean d) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
}
public static class Builder {
// required fields
private int a;
private String b;
// optional fields with default values
private char c = 'c';
private boolean d = false;
public Builder(int a, String b) {
this.a = a;
this.b = b;
}
public Builder c(char c) {
this.c = c;
return this;
}
public Builder d(boolean d) {
this.d = d;
return this;
}
public Example build() {
return new Example(a, b, c, d);
}
}
}
Then you can use it like new Example.Builder(23, "foo").c('y').d(false).build()
to create a new object with every field given an initial value, or omit some of the chained methods to use the default values for those fields.
Upvotes: 1