Специальные атрибуты / свойства вместо получателя / установщика в Java, чтобы избежать кода шаблона

Введение

Я работаю над проектом с открытым исходным кодом Treez, в котором я организовываю так называемые «атомы» в виде дерева. . Эти атомы иногда имеют множество атрибутов, и эти атрибуты изменяются либо с помощью действий пользователя в древовидном представлении, либо с помощью API в редакторе кода Eclipse.

Сами атрибуты моих атомов представлены повторно используемыми «AttributeAtoms». Они содержат фактическое значение атрибута и предоставляют дополнительные функции, такие как проверка (другие возможные термины для «Atom» могут быть «виджет», «компонент», «свойство» или «узел дерева»).

Вопрос (ы)

Раньше я предоставлял пару геттер / сеттер для каждого из моих атрибутов Atom. Это большая дополнительная работа, и это увеличивает размер моих классов Atom (см. Примеры кода ниже). Сейчас я ищу альтернативное решение, которое

  • делает меньше работы по созданию новых атомов (и меньше работы по их обслуживанию).
  • позволяет избежать "избыточного" кода котельной плиты геттера / сеттера.

Ниже я опишу несколько вариантов. Какой из вариантов вы бы использовали? Есть ли у вас предложения, как улучшить эти параметры? Вы знаете еще другие варианты?

Пример кода геттера / сеттера

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

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

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

Похожие статьи

Рассмотренные варианты

А. Автоматически сгенерированные геттеры / сеттеры с IDE

Eclipse предоставляет возможность автоматически генерировать геттеры / сеттеры.

  • Не работает для моих AttributeAtoms, поскольку код получателя / установщика выглядит немного иначе.
  • Не избегает лишнего «избыточного» кода.

Если я решу сохранить геттеры / сеттеры, я могу попытаться создать что-то подобное для своих AttributeAtoms. Также см. Этот пост о (не работающем) автоматическом создании получателя / установщика для свойств JavaFx: http://www.eclipse.org/forums/index.php/t/781816/

Б. Аннотации для генерации геттеров / сеттеров (Project Lombok)

Lombok предоставляет возможность использовать аннотации для автоматического создания геттеров и сеттеров.

  • Не работает для моих AttributeAtoms
  • Я пробовал использовать Lombok с Eclipse. Автозавершение кода в редакторе работало, но я получал предупреждения «метод не найден». Возможно, мне придется потратить еще немного времени, чтобы заставить Ломбока работать с классическими атрибутами.
  • Также см. Безопасно ли использовать Project Lombok?

Если я решу использовать аннотации для определения геттеров / сеттеров, возможно, удастся расширить Lombok для работы с моими AttributeAtoms.

С. Один универсальный метод получения / установки для всех атрибутов

Я мог бы использовать одну пару геттер / сеттер для всех атрибутов Atom, таких как

Object get(String attributeName)
void set(String attriuteName, Object value)
  • Безопасность типов можно улучшить, передав дополнительные аргументы типа.
  • Однако завершение кода для моего Atom будет предлагать только один метод получения / установки, и пользователь не увидит, какие атрибуты доступны. (Возможно, это можно было бы решить, используя перечисления вместо строк для идентификации атрибутов. Но эти перечисления необходимо каким-то образом создать. Также см. Следующий вариант.)

Д. Пользовательский редактор Eclipse и обработка кода

Возможно, я мог бы написать дополнительный плагин Eclipse для моего проекта с открытым исходным кодом, который «разрешает доступ к частным атрибутам», предлагая соответствующие поддельные методы для завершения кода. Перед компиляцией исходного кода пользователя необходимо выполнить ложные вызовы типа

myAtom.setMyAttribue(newValue);

будет переведен в код реально существующего обобщенного геттера (вариант C):

myAtom.set("myAttribute", newValue);

Э. Общедоступные атрибуты

Если я сделаю свои атрибуты Atom общедоступными, мне не понадобится код геттеров / сеттеров в каждом Atom. Вместо этого повторно используемые 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<String> myAttribute;
    

Пример кода Atom

Вот пример класса Atom. Если вы прокрутите вниз, вы найдете много строк кода, которые используются геттерами / сеттерами. Это уродливо, правда?

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

}

person Stefan    schedule 11.07.2015    source источник


Ответы (3)


Для варианта E, используя модификатор «final», вы можете предотвратить замену полностью нового AttributeAtom, при этом все еще разрешая получение / настройку:

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

Тогда будет разрешено:

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

Но то, о чем вы беспокоитесь, не будет:

myAtom.myAttribute = completelyDifferentAttribute
person Cookie Monster    schedule 12.07.2015
comment
Что ж, у этого был бы недостаток, заключающийся в том, что я не могу воссоздать атрибут из класса, но, возможно, мне это действительно не нужно. Если предложений больше не будет, я приму их как ответ через несколько дней. - person Stefan; 12.07.2015

В конце концов я решил использовать новый паттерн, расширяющий вариант E «двойным обертыванием»:

  • Существует интерфейс «Атрибут», который предоставляет методы «T get ()» и «set (T value)».
  • Другой интерфейс «AttributeWrapper» наследуется от «Attribute». Он предоставляет дополнительные методы setAttribute и getAttribute для обмена обернутым атрибутом.
  • Методы получения / установки AttributeWrapper перенаправляются на завернутый атрибут.
  • Если «AttributeWrapper» передается как «Атрибут» во внешний мир, видны только методы «get» и «set». Это обеспечивает некоторую (псевдо) инкапсуляцию. (Эта инкапсуляция может быть почти такой же хорошей, как инкапсуляция с модификатором private. В любом случае модификатор private можно обойти с помощью отражения.)
  • Те, кто знает, что мои атрибуты на самом деле являются AttributeWrappers, могут преобразовывать атрибуты в AttributeWrappers и обменивать завернутые атрибуты. Это также позволяет мне создавать и обмениваться моими общедоступными атрибутами final не только в конструкторе, но и в любом методе моих классов Atom. (Размещение всех определений атрибутов непосредственно в области атрибутов или в конструкторе сделало бы мой код некрасивым и, следовательно, трудным для чтения.)
  • Существует класс «Wrap», который обеспечивает реализацию AttributeWrapper по умолчанию.
  • Абстрактный класс AttributeAtom является базовым классом для всех моих атомов атрибутов. Он реализует атрибут, а также предоставляет вспомогательный метод «wrap». Этот метод оборачивает AttributeAtom в родительский AttributeWrapper, который передается методу.
  • Окончательный рабочий процесс

    1. Declare a public final Attribute myAttribute and immediately assign a Wrap to it.
    2. Создайте фактический атрибут newAttribute в методе инициализации.
    3. Назначьте новый атрибут как содержимое соответствующего Wrap с помощью вспомогательного метода wrap.
  • Все это станет более понятным с помощью примера кода:

Окончательное использование

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

Использование атрибута и оболочки для определения класса MyAtom

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

}

Интерфейс 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 <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

}
person Stefan    schedule 27.07.2015

Спустя несколько лет хочу добавить, что полностью перешел на JavaScript. Кажется, это лучший язык для моего случая использования.

person Stefan    schedule 03.12.2020