有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

反射“特殊属性/属性”,而不是Java中的getter/setter,以避免锅炉板代码

简介

我正在做一个开源项目Treez,我在树视图中组织所谓的“原子”。这些原子有时具有许多属性,这些属性可以通过树状视图中的用户操作或Eclipse代码编辑器中的API进行修改

原子本身的属性由可重用的“AttributeAtoms”表示。这些属性包含实际属性值,并提供验证等附加功能(“Atom”的其他可能术语可能是“widget”、“bean”、“property”或“tree node”)

问题

过去,我为每个Atom属性提供了一个getter/setter对。这是一项大量的额外工作,它会扩大我的Atom类的大小(参见下面的代码示例)。现在,我正在寻找一种替代解决方案

  • 使创建新原子的工作量减少(维护原子的工作量也减少)
  • 避免“冗余”getter/setter锅炉板代码

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

Getter/Setter代码示例

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

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

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

相关文章

考虑过的选项

A.使用IDE自动生成的getter/setter

Eclipse提供了自动生成getter/setter的可能性

  • 不适用于我的AttributeAtoms,因为getter/setter代码看起来略有不同
  • 不会避免额外的“冗余”代码

如果我决定保留getter/setter,我可以尝试为属性域创建类似的东西。另请参阅这篇关于JavaFx属性的自动getter/setter创建(不起作用)的文章: http://www.eclipse.org/forums/index.php/t/781816/

B.用于生成getter/setter的注释(Lombok项目)

Lombok提供了使用注释自动生成getter和setter的可能性

  • 这两种方法都不适用于我的属性域
  • 我尝试在Eclipse中使用Lombok。编辑器中的代码完成工作正常,但我收到了“未找到方法”警告。我可能需要投入更多的时间来让龙目巨人为经典属性工作
  • 另见Is it safe to use Project Lombok?

如果我决定使用注释来定义getter/setter,那么就有可能扩展Lombok来处理我的AttributeAtoms

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

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

Object get(String attributeName)
void set(String attriuteName, Object value)
  • 通过传递额外的类型参数,可以提高类型安全性
  • 然而,Atom的代码完成只会建议使用单一的getter/setter,用户不会看到哪些属性可用。(也许可以通过使用枚举而不是字符串来标识属性来解决这个问题。但是需要以某种方式创建这些枚举。另请参见下一个选项。)

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

也许我可以为我的开源项目编写一个额外的Eclipse插件,通过建议相应的伪代码完成方法“允许访问私有属性”。在编译用户源代码之前,假调用

myAtom.setMyAttribue(newValue);

将转换为实际存在的通用getter的代码(选项C):

myAtom.set("myAttribute", newValue);

E.公共属性

如果将Atom属性公开,则不需要getter/sette每个原子中的r代码。相反,可重用的AttributeAtoms将提供get/set方法。例如,用法如下所示

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

而不是

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

一些缺点:

  • 用户需要习惯这种“非常规方法”。Java用户可能期望setMyAttribute(newValue),而C#用户可能期望myAtom.myAttribute = newValue
  • 可以交换我不允许的整个属性TOM

    myAtom.myAttribute = completelyDifferentAttribute
    

有什么改进的策略吗

  • 是否有一种方法允许访问属性的方法,而不允许交换属性本身?我需要一个新的访问修饰符,如

    private *publicMethodAccess* AttributeAtom<String> myAttribute;
    

原子代码示例

下面是一个示例Atom类。如果滚动到底部,您将发现getter/setter使用的许多代码行。这很难看,不是吗

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

}

共 (3) 个答案

  1. # 1 楼答案

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

  2. # 2 楼答案

    对于选项E,通过使用“final”修饰符,可以防止替换一个全新的AttributeAtom,同时仍然允许获取/设置:

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

    然后将允许以下情况:

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

    但你担心的不是:

    myAtom.myAttribute = completelyDifferentAttribute
    
  3. # 3 楼答案

    我最终决定使用一种新的模式,它通过“双重包装”扩展选项E:

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

      1. 声明公共最终属性myAttribute,并立即为其指定换行符
      2. 在初始化方法中创建实际属性newAttribute
      3. 通过使用助手方法“Wrap”,将新属性指定为相应Wrap的内容
    • 所有这些都将通过一些示例代码变得更加清晰:

    最终用法

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

    在MyAtom的类定义中使用Attribute和Wrap

    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中“包裹”

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