java - 重复的 Java 构造函数(重构或生成)

标签 java refactoring code-generation documentation-generation

有问题

我一直在为我在 Java 中使用的框架创建一个库:我有数百个 LOC,纯粹用于向用户提供许多构造函数,但每次都少一个参数(因为默认值可以使用)。这会导致大量重复的代码,以及大量重复的 JavaDoc。而且,最重要的是,只要有一点变化,就需要进行大量的维护。

<小时/>

解决方案示例

我觉得可能存在一些类似于 Lombok 的库,它允许我以自动生成构造函数及其关联的 JavaDoc 的方式注释构造函数。像这样的东西:

/** 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

}

这会生成如下内容:

/** 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

}
<小时/>

问题的现实示例

这只是为了让您更好地了解我从哪里来完成这一切。

以下是我的问题根源的摘录 (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
}

父类:

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);
    }

    // ...
}

结构相当简单:

  • 有一个 constructorsCommon() 方法,用于对所使用的任何构造函数执行常见操作。
  • 构造函数本身也非常简单:它们使用与类的单个字段关联的setter,然后将调用传递给另一个少定义了一个属性的构造函数。
  • 该类有 3 组构造函数:一组用于提供 Skin 来实例化该类的每种不同方式,并且每组都被复制 4 次(一组用于为所有可设置的公共(public)属性)这 3 种方法)。

我想知道是否有一种方法可以减少复制粘贴的代码和文档的数量。理想情况下,我最终只会有 3 个构造函数,而不是 12 个。

最佳答案

当一个类有多个带有默认值的可选字段时,为了避免编写指数级数量的构造函数,您可以使用 Builder pattern 。例如:

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);
        }
    }
}

然后你可以像 new Example.Builder(23, "foo").c('y').d(false).build() 一样使用它来创建一个新对象给定初始值的字段,或省略一些链接方法以使用这些字段的默认值。

关于java - 重复的 Java 构造函数(重构或生成),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59338296/

相关文章:

java - 使用 smartgwt 防止用户留下未保存的表单

java - 使用 JWTSigner.sign() 生成的 token 表示 jwt.io 调试器中的签名无效

javascript - 这些语句可以写得更简洁吗?

java - 始终避免在 Java 中输入输出参数?

.net - T4 vs CodeDom vs 奥斯陆

c# - View 模型类生成

java - 在 IntelliJ 插件构建期间运行代码生成器

Java/JAXB : Unmarshall XML elements with same name but different attribute values to different class members

java - 在不使用 OR 运算符的情况下检查 String 是否包含 Java 中的多个值?

c - c包括,防止冗余代码