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

介绍

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

我的Atoms本身的属性由可重用的“AttributeAtoms”表示 。 那些包含实际属性值并提供诸如validation之类的附加function(“Atom”的其他可能术语可能是“小部件”,“bean”,“属性”或“树节点”)。

问题(S)

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

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

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

Getter / Setter代码示例

private AttributeAtom myAttribute = new FilePathAttributeAtom("myAttribtue"); public String getMyAttribute() { return myAttribute.getValue(); } public void setMyAttribute(String value) { this.myAtrribute.setValue(value); } 

相关文章

  • java有类似于C#属性的东西吗?
  • (没有)Java中的属性?
  • http://blog.netopyr.com/2011/05/19/creating-javafx-properties/
  • http://www.eclipse.org/forums/index.php/t/781816/
  • 为什么要使用getter和setter?
  • 使用getter和setter的私有属性有什么好处?

考虑的选择

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

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

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

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

B.生成getter / setter的注释(Project Lombok)

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

  • 对我的AttributeAtoms都不起作用
  • 我尝试将Lombok与Eclipse一起使用。 编辑器中的代码完成工作但我得到了“找不到方法”的警告。 我可能需要花更多的时间让Lombok为经典属性工作。
  • 另请参阅使用Project Lombok是否安全?

如果我决定使用Annotations来定义getter / setter,那么可以扩展Lombok以适用于我的AttributeAtoms。

  • 此处已存在扩展Lombok for JavaFx属性的请求: https ://groups.google.com/forum/#!searchin/project-lombok/getter$20and$20setter$20for$20properties/project-lombok/Ik6phxDXHVU/zzDkC2MpmvgJ

  • 以下是如何使用自定义转换扩展Lambok的简介: http ://notatube.blogspot.de/2010/12/project-lombok-creating-custom.html

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

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

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

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

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

 myAtom.setMyAttribue(newValue); 

将被转换为真正存在的通用getter的代码(选项C):

 myAtom.set("myAttribute", newValue); 

E.公共属性

如果我公开我的Atom属性,我不需要每个Atom中的getter / setter代码。 相反,可重用的AttributeAtoms将提供get / set方法。 例如,这种用法看起来像这样

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

代替

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

一些缺点:

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

     myAtom.myAttribute = completelyDifferentAttribute 

有什么策略来改善这个吗?

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

     private *publicMethodAccess* AttributeAtom myAttribute; 

Atom代码示例

这是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 xLabel; private ModelPath xRange; private AttributeAtom yLabel; private AttributeAtom firstFamilyLabel; private ModelPath firstFamilyRange; private AttributeAtom secondFamilyLabel; private ModelPath secondFamilyRange; private AttributeAtom probeName; private ModelPath sweepOutputModel; private ModelPath firstProbeTable; private AttributeAtom probeColumnIndex; private AttributeAtom 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,同时仍允许获取/设置:

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

然后将允许以下内容:

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

但你担心的事情不会是:

 myAtom.myAttribute = completelyDifferentAttribute 

我最终决定使用一个新的模式,它扩展了选项E的“双层包装”:

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

    1. 声明一个公共最终属性myAttribute并立即为其分配一个Wrap。
    2. 在初始化方法中创建实际的Attribute newAttribute。
    3. 使用辅助方法“wrap”将新属性指定为相应Wrap的内容。
  • 使用一些示例代码可以更清楚地了解所有这些:

最终用法

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

使用Attribute和Wrap作为MyAtom的类定义

 public class MyAtom { //#region ATTRIBUTES public final Attribute myFilePathAttribute = new Wrap<>(); //#end region //... //#region METHODS private init(){ //create a new AttributeAtom //(FilePath inherits from AttributeAtom) 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 wrap) { Wrap wrapper = (Wrap) wrap; wrapper.setAttribute(this); } 

接口属性

 package org.treez.core.attribute; /** * Represents an attribute * * @param  */ public interface Attribute { /** * Returns the attribute value * * @return */ T get(); /** * Sets the attribute value * * @param value */ void set(T value); } 

接口AttributeWrapper

 package org.treez.core.attribute; /** * Wraps a replaceable attribute. The methods are "hidden" if this AttributeWrapper is passed as its parent interface * Attribute * * @param  */ public interface AttributeWrapper extends Attribute { /** * Sets the wrapped Attribute * * @param attribute */ void setAttribute(Attribute attribute); /** * Returns the wrapped Attribute * * @return */ Attribute getAttribute(); } 

换行:AttributeWrapper的实现

 package org.treez.core.attribute; import java.util.Objects; /** * Default implementation of the AttributeWrapper interface */ public class Wrap implements AttributeWrapper { //#region ATTRIBUTES private Attribute wrappedAttribute; //#end region //#region CONSTRUCTORS /** * Constructor */ public Wrap() {} /** * Constructor with wrapped attribute * * @param wrappedAttribute */ public Wrap(Attribute 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 getAttribute() { return wrappedAttribute; } @Override public void setAttribute(Attribute wrappedAttribute) { this.wrappedAttribute = wrappedAttribute; } //#end region }