"Special attributes/properties" 而不是 Java 中的 getter/setter 以避免样板代码

"Special attributes/properties" instead of getter/setter in Java to avoid boiler plate code

简介

我正在开发一个开源项目 Treez,我在该项目中以树视图组织所谓的 "Atoms"。这些 Atom 有时有 很多属性 并且这些属性可以通过树视图中的用户操作或通过 API一个 Eclipse 代码编辑器。

我的原子本身的属性由可重用"AttributeAtoms"表示。那些保存实际属性值并提供附加功能,如验证("Atom" 的其他可能术语可能是 "widget"、"bean"、"property" 或 "tree node")。

问题

过去我为每个 Atom 属性提供了一个 getter/setter 对。这是很多额外的工作,它会增加我的 Atom classes 的大小(请参阅下面的代码示例)。现在我正在寻找替代解决方案

我将在下面描述几个选项。您会使用哪个选项?您对如何改进这些选项有什么建议吗?你知道更多的选择吗?

Getter/Setter 代码示例

    private AttributeAtom<String> myAttribute = new FilePathAttributeAtom("myAttribtue");

    public String getMyAttribute() {
        return myAttribute.getValue();
    }    

    public void setMyAttribute(String value) {
        this.myAtrribute.setValue(value);
    }

相关文章

已考虑的选项

一个。使用 IDE

自动生成 getters/setters

Eclipse 提供了自动生成 getters/setters.

的可能性

如果我决定保留 getters/setters,我可以尝试为我的 AttributeAtoms 创建类似的东西。另请参阅此 post 关于 JavaFx 属性的(不工作)自动 getter/setter 创建: http://www.eclipse.org/forums/index.php/t/781816/

乙。生成注释 getters/setters (Project Lombok)

Lombok 提供了使用注解自动生成 getters 和 setter 的可能性。

如果我决定使用注释来定义 getters/setters,则可以扩展 Lombok 来为我的 AttributeAtoms 工作。

C。一个通用的 getter/setter 用于所有属性

我可以对所有 Atom 属性使用单个 getter/setter 对,例如

Object get(String attributeName)
void set(String attriuteName, Object value)

D.自定义 Eclipse 编辑器和代码处理

也许我可以为我的开源项目编写一个额外的 Eclipse 插件,"allows to access private attributes" 通过建议相应的伪造代码完成方法。在编译用户源代码之前,像

这样的假调用
myAtom.setMyAttribue(newValue);

将被翻译成真正存在的通用代码 getter(选项 C):

myAtom.set("myAttribute", newValue);

E. Public 属性

如果我将我的 Atom 属性设置为 public,我就不需要在每个 Atom 中添加 getters/setter 代码。相反,可重用的 AttributeAtoms 将提供 get/set 方法。例如,用法看起来像这样

myAtom.myAttribute.get();
myAtom.myAttribute.set(newValue);

而不是

myAtom.getMyAttribute();
myAtom.setMyAttribute(newValue);

一些缺点:

有什么改进策略吗?

Atom 代码示例

这里有一个 Atom 的例子 class。如果滚动到底部,您会发现 getters/setters 使用了很多代码行。这很丑,不是吗?

package org.treez.results.atom.probe;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.eclipse.swt.graphics.Image;
import org.treez.core.atom.attribute.AttributeRoot;
import org.treez.core.atom.attribute.ModelPath;
import org.treez.core.atom.attribute.ModelPathSelectionType;
import org.treez.core.atom.attribute.Section;
import org.treez.core.atom.attribute.base.AttributeAtom;
import org.treez.core.atom.variablerange.VariableRange;
import org.treez.core.data.column.ColumnType;
import org.treez.data.column.Columns;
import org.treez.data.output.OutputAtom;
import org.treez.data.table.Table;
import org.treez.results.Activator;

/**
 * Collects data from a sweep and puts it in a single (probe-) table. That table can easier be used to produce plots
 * than the distributed sweep results.
 */
public class SweepProbe extends AbstractProbe {

    /**
     * Logger for this class
     */
    @SuppressWarnings("unused")
    private static Logger sysLog = Logger.getLogger(SweepProbe.class);

    //#region ATTRIBUTES

    private AttributeAtom<String> xLabel;

    private ModelPath xRange;

    private AttributeAtom<String> yLabel;

    private AttributeAtom<String> firstFamilyLabel;

    private ModelPath firstFamilyRange;

    private AttributeAtom<String> secondFamilyLabel;

    private ModelPath secondFamilyRange;

    private AttributeAtom<String> probeName;

    private ModelPath sweepOutputModel;

    private ModelPath firstProbeTable;

    private AttributeAtom<String> probeColumnIndex;

    private AttributeAtom<String> probeRowIndex;

    //#end region

    //#region CONSTRUCTORS

    /**
     * Constructor
     *
     * @param name
     */
    public SweepProbe(String name) {
        super(name);
        createPropertyModel();
    }

    //#end region

    //#region METHODS

    /**
     * Creates the model for the property control
     */
    private void createPropertyModel() {

        //root
        AttributeRoot root = new AttributeRoot("root");

        //page
        org.treez.core.atom.attribute.Page page = root.createPage("page");

        //x section
        Section xSection = page.createSection("xSection", "X");
        xSection.createSectionAction("action", "Run probe", () -> execute(treeViewRefreshable));

        xLabel = xSection.createTextField("xLabel", "Label for x-Axis", "x");

        xRange = xSection.createModelPath("xRange", "Range for x-Axis", "", VariableRange.class, this);
        xRange.setSelectionType(ModelPathSelectionType.FLAT);
        xRange.setValue("root.studies.sweep.threshold");

        //y section
        Section ySection = page.createSection("ySection", "Y");
        yLabel = ySection.createTextField("yLabel", "Label for y-Axis", "y");

        //first family section
        Section firstFamilySection = page.createSection("firstFamily", "First family");
        firstFamilySection.setExpanded(false);

        firstFamilyLabel = firstFamilySection.createTextField("firstFamilyLabel", "Label for first family", "family1");

        firstFamilyRange = firstFamilySection.createModelPath("firstFamilyRange", "Range for first family", "",
                VariableRange.class, this);

        //second family section
        Section secondFamilySection = page.createSection("secondFamily", "Second family");
        secondFamilySection.setExpanded(false);

        secondFamilyLabel = secondFamilySection.createTextField("secondFamilyLabel", "Label for second family",
                "family2");

        secondFamilyRange = secondFamilySection.createModelPath("secondFamilyRange", "Range for second family", "",
                VariableRange.class, this);

        //probe section
        Section probeSection = page.createSection("probe", "Probe");

        probeName = probeSection.createTextField("propeName", "Name", "MyProbe");

        sweepOutputModel = probeSection.createModelPath("sweepOutput", "SweepOutput", "", OutputAtom.class, this);

        firstProbeTable = probeSection.createModelPath("tablePath", sweepOutputModel, Table.class);
        firstProbeTable.setLabel("First probe table");

        probeColumnIndex = probeSection.createTextField("probeColumnIndex", "Column index", "0");

        probeRowIndex = probeSection.createTextField("probeColumnIndex", "Row index", "0");

        setModel(root);

    }

    /**
     * Provides an image to represent this atom
     */
    @Override
    public Image provideBaseImage() {
        Image baseImage = Activator.getImage("sweep.png");
        return baseImage;
    }

    //#region CREATE TABLE COLUMNS

    /**
     * Creates the required columns for the given table
     *
     * @param table
     */
    @Override
    protected void createTableColumns(Table table) {
        //TODO

    }

    //#end region

    //#region COLLECT PROBE DATA

    @Override
    protected void collectProbeDataAndFillTable() {
        // TODO Auto-generated method stub

    }

    //#end region

    //#end region

    //#region ACCESSORS

    //#region X LABEL

    /**
     * @return
     */
    public String getXLabel() {
        return xLabel.getValue();
    }

    /**
     * @param label
     */
    public void setXLabel(String label) {
        xLabel.setValue(label);
    }

    //#end region

    //#region X RANGE

    /**
     * @return
     */
    public String getXRange() {
        return xRange.getValue();
    }

    /**
     * @param range
     */
    public void setXRange(String range) {
        xRange.setValue(range);
    }

    //#end region

    //#region Y LABEL

    /**
     * @return
     */
    public String getYLabel() {
        return yLabel.getValue();
    }

    /**
     * @param label
     */
    public void setYLabel(String label) {
        yLabel.setValue(label);
    }

    //#end region

    //#region FIRST FAMILY LABEL

    /**
     * @return
     */
    public String getFirstFamilyLabel() {
        return firstFamilyLabel.getValue();
    }

    /**
     * @param label
     */
    public void setFirstFamilyLabel(String label) {
        firstFamilyLabel.setValue(label);
    }

    //#end region

    //#region FIRST FAMILY RANGE

    /**
     * @return
     */
    public String getFirstFamilyRange() {
        return firstFamilyRange.getValue();
    }

    /**
     * @param range
     */
    public void setFirstFamilyRange(String range) {
        firstFamilyRange.setValue(range);
    }

    //#end region

    //#region SECOND FAMILY LABEL

    /**
     * @return
     */
    public String getSecondFamilyLabel() {
        return secondFamilyLabel.getValue();
    }

    /**
     * @param label
     */
    public void setSecondFamilyLabel(String label) {
        secondFamilyLabel.setValue(label);
    }

    //#end region

    //#region SECOND  FAMILY RANGE

    /**
     * @return
     */
    public String getSecondFamilyRange() {
        return secondFamilyRange.getValue();
    }

    /**
     * @param range
     */
    public void setSecondFamilyRange(String range) {
        secondFamilyRange.setValue(range);
    }

    //#end region

    //#region PROBE

    /**
     * @return
     */
    public String getProbeName() {
        return probeName.getValue();
    }

    /**
     * @param name
     */
    public void setProbeName(String name) {
        probeName.setValue(name);
    }

    //#end region

    //#region SWEEP OUTPUT MODEL

    /**
     * @return
     */
    public String getSweepOutputModelName() {
        return sweepOutputModel.getValue();
    }

    /**
     * @param sweepOutputModel
     */
    public void setSweepOutputModelName(String sweepOutputModel) {
        this.sweepOutputModel.setValue(sweepOutputModel);
    }

    //#end region

    //#region PROBE TABLE

    /**
     * @return
     */
    public String getFirstProbeTable() {
        return firstProbeTable.getValue();
    }

    /**
     * @param firstProbeTable
     */
    public void setFirstProbeTable(String firstProbeTable) {
        this.firstProbeTable.setValue(firstProbeTable);
    }

    //#end region

    //#region COLUMN INDEX

    /**
     * @return
     */
    public String getProbeColumnIndex() {
        return probeColumnIndex.getValue();
    }

    /**
     * @param index
     */
    public void setProbeColumnIndex(String index) {
        probeColumnIndex.setValue(index);
    }

    //#end region

    //#region ROW INDEX

    /**
     * @return
     */
    public String getProbeRowIndex() {
        return probeRowIndex.getValue();
    }

    /**
     * @param index
     */
    public void setProbeRowIndex(String index) {
        probeRowIndex.setValue(index);
    }

    //#end region

    //#end region

}

对于选项 E,通过使用 "final" 修饰符,您可以防止换入一个全新的 AttributeAtom,同时仍然允许 getting/setting:

public final AttributeAtom<String> myAttribute = new FilePathAttributeAtom("myAttribtue");

然后将允许以下内容:

myAtom.myAttribute.get();
myAtom.myAttribute.set(newValue)

但你担心的事情不会是:

myAtom.myAttribute = completelyDifferentAttribute

我最终决定使用一种新模式,它用 "double wrapping":

扩展选项 E
  • 有一个接口 "Attribute" 提供方法 "T get()" 和 "set(T value)"
  • 另一个接口 "AttributeWrapper" 继承自 "Attribute"。它提供了额外的方法 "setAttribute" 和 "getAttribute" 来交换包装的属性。
  • AttributeWrapper 的方法 get/set 被重定向到包装的属性。
  • 如果 "AttributeWrapper" 作为 "Attribute" 传递给外界,只有方法 "get" 和 "set" 可见。这提供了一些(伪)封装。 (这种封装可能几乎与使用 private 修饰符的封装一样好。无论如何,private 修饰符都可以通过反射绕过。)
  • 那些知道我的属性实际上是 AttributeWrappers 的人能够将属性转换为 AttributeWrappers 并交换包装的属性。这也允许我不仅在构造函数中而且在 Atom classes 的任何方法中创建和交换我的 public final 属性。 (将所有属性定义直接放在属性区域或构造函数中会使我的代码难看,因此难以阅读。)
  • 有一个 class "Wrap" 提供了 AttributeWrapper 的默认实现。
  • 抽象 class AttributeAtom 是我所有属性原子的基础 class。它实现了 Attribute,还提供了一个辅助方法 "wrap"。该方法将 AttributeAtom 包装在传递给该方法的父 AttributeWrapper 中。
  • 最终的工作流程是

    1. 声明一个 public 最终属性 myAttribute 并立即为其分配一个 Wrap。
    2. 在初始化方法中创建实际的属性 newAttribute。
    3. 使用辅助方法"wrap".
    4. 将新的Attribute指定为相应Wrap的内容
  • 所有这些都将通过一些示例代码变得更加清晰:

最终使用

MyAtom myAtom = new MyAtom();
String defaultPath = myAtom.myFilePathAttribute.get();
myAtom.myFilePathAttribute.set("D:/newpath.txt")

MyAtomclass 定义的属性和包装的使用

public class MyAtom {

    //#region ATTRIBUTES

    public final Attribute<String> myFilePathAttribute = new Wrap<>();

    //#end region

    //...

    //#region METHODS

    private init(){

        //create a new AttributeAtom 
        //(FilePath inherits from AttributeAtom<String>)
        FilePath filePath = new FilePath("C:/defaultpath.txt");

        //set the new AttributeAtom as content of the corresponding Wrap myFilePathAttribute
        filePath.wrap(myFilePathAttribute);

    }

    //#end region
}

AttributeAtom

中的辅助方法"wrap"
/**
 * Wraps this attribute in the AttributeWrapper that is given as Attribute
 *
 * @param wrap
 */
public void wrap(Attribute<T> wrap) {
    Wrap<T> wrapper = (Wrap<T>) wrap;
    wrapper.setAttribute(this);
}

接口属性

package org.treez.core.attribute;

/**
 * Represents an attribute
 *
 * @param <T>
 */
public interface Attribute<T> {

    /**
     * Returns the attribute value
     *
     * @return
     */
    T get();

    /**
     * Sets the attribute value
     *
     * @param value
     */
    void set(T value);

}

接口属性包装器

package org.treez.core.attribute;

/**
 * Wraps a replaceable attribute. The methods are "hidden" if this AttributeWrapper is passed as its parent interface
 * Attribute
 *
 * @param <T>
 */
public interface AttributeWrapper<T> extends Attribute<T> {

    /**
     * Sets the wrapped Attribute
     *
     * @param attribute
     */
    void setAttribute(Attribute<T> attribute);

    /**
     * Returns the wrapped Attribute
     * 
     * @return
     */
    Attribute<T> getAttribute();

}

Wrap: AttributeWrapper

的实现
package org.treez.core.attribute;

import java.util.Objects;

/**
 * Default implementation of the AttributeWrapper interface
 */
public class Wrap<T> implements AttributeWrapper<T> {

    //#region ATTRIBUTES

    private Attribute<T> wrappedAttribute;

    //#end region

    //#region CONSTRUCTORS

    /**
     * Constructor
     */
    public Wrap() {}

    /**
     * Constructor with wrapped attribute
     *
     * @param wrappedAttribute
     */
    public Wrap(Attribute<T> wrappedAttribute) {
        this.wrappedAttribute = wrappedAttribute;
    }

    //#end region

    //#region ACCESSORS

    @Override
    public T get() {
        Objects.requireNonNull(wrappedAttribute, "Wrapped attribute must be set before calling this method.");
        T value = wrappedAttribute.get();
        return value;
    }

    @Override
    public void set(T value) {
        Objects.requireNonNull(wrappedAttribute, "Wrapped attribute must be set before calling this method.");
        wrappedAttribute.set(value);
    }

    @Override
    public Attribute<T> getAttribute() {
        return wrappedAttribute;
    }

    @Override
    public void setAttribute(Attribute<T> wrappedAttribute) {
        this.wrappedAttribute = wrappedAttribute;
    }

    //#end region

}

几年后我想补充一点,我完全切换到 JavaScript。 对于我的用例来说,这似乎是更好的语言。