"Attributs/propriétés spéciaux" au lieu de getter/setter en Java pour éviter le code de la plaque de chaudière


Intro

Je travaille sur un projet open source Treez où j'organise ce qu'on appelle des "atomes" dans une vue arborescente. Ces atomes ont parfois beaucoup d'attributset ces attributs sont modifiés soit par des actions utilisateur dans une arborescence, soit par une API dans un éditeur de code Eclipse.

Les attributs de mes atomes eux-mêmes sont représentés par "AttributeAtoms"réutilisables. Ceux-ci contiennent la valeur d'attribut réelle et fournir des fonctionnalités supplémentaires comme la validation (d'autres termes possibles pour "Atom" peuvent être "widget", "bean", "propriété" ou "nœud d'arbre").

Question(s)

Dans le passé, j'ai fourni une paire getter/setter pour chacun de mes attributs Atom. C'est beaucoup de travail supplémentaire et cela fait exploser la taille de mes classes Atom (voir les exemples de code ci-dessous). Maintenant, je cherche une solution alternative qui

  • fait moins de travail pour créer de nouveaux Atomes (et moins de travail pour maintenir ils).
  • évite le code de plaque de chaudière getter/setter "redondant".

Je vais décrire quelques options ci-dessous. Laquelle des options utiliseriez-vous? Avez-vous des suggestions sur la façon d'améliorer ces options? Savez-vous encore d'autres options?

Exemple de code Getter / Setter

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

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

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

Autre article

Options envisagées

A. Getters/setters générés automatiquement avec ID

Eclipse offre la possibilité de générer automatiquement des getters / setters.

  • Ne fonctionne pas pour mes AttributeAtoms car le code gettter/setter semble légèrement différent.
  • N'évite pas le code "redondant" supplémentaire.

Si je décide de garder les getters/setters, je pourrais essayer de créer quelque chose de similaire pour mes AttributeAtoms. Voir aussi ce post sur la création automatique de getter/setter (ne fonctionne pas) pour JavaFX Propriété: http://www.eclipse.org/forums/index.php/t/781816/

B. Annotations pour la génération de getters /setters (Projet Lombok)

Lombok offre la possibilité d'utiliser des annotations pour la génération automatique de getters et setters.

  • Ne fonctionne ni pour mes AttributeAtoms
  • J'ai essayé d'utiliser Lombok avec Eclipse. L'achèvement du code dans l'éditeur a fonctionné mais j'ai reçu des avertissements "méthode introuvable". Je pourrais avoir besoin d'investir un peu plus de temps pour faire travailler Lombok pour les attributs classiques.
  • Voir aussi Est-il sûr d'utiliser le projet Lombok?

Si je décide d'utiliser des annotations pour définir des getters/setters, il pourrait être possible d'étendre Lombok pour fonctionner pour mes AttributeAtoms.

C. Un getter/setter généralisé pour tous les attributs

Je pourrais utiliser une seule paire getter / setter pour tous les attributs Atom comme

Object get(String attributeName)
void set(String attriuteName, Object value)
  • La sécurité de type pourrait être améliorée en passant des arguments de type supplémentaires.
  • Cependant, l'achèvement du code pour mon atome ne suggérerait que le seul getter/setter et un utilisateur ne verrait pas quels attributs sont disponibles. (Peut-être que cela pourrait être résolu en utilisant des énumérations au lieu de chaînes pour identifier les attributs. Mais ces énumérations doivent être créées d'une manière ou d'une autre. Voir aussi l'option suivante.)

D. Éditeur Eclipse personnalisé et traitement du code

Peut-être que je pourrais écrire un plugin Eclipse supplémentaire pour mon projet open source qui "permet d'accéder aux attributs privés" en suggérant de fausses méthodes correspondantes pour l'achèvement du code. Avant de compiler le code source de l'utilisateur, de faux appels comme

myAtom.setMyAttribue(newValue);

Serait traduit en code pour le getter généralisé réellement existant (option C):

myAtom.set("myAttribute", newValue);

E. Attributs publics

Si je rends mes attributs Atom publics, je n'ai pas besoin de code getters/setter dans chacun Atome. Au lieu de cela, les AttributeAtoms réutilisables fourniraient des méthodes get/set. L'utilisation ressemblerait par exemple à ceci

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

Au Lieu de

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

Quelques inconvénients:

  • , les Utilisateurs doivent s'habituer à cette "approche non conventionnelle". Les utilisateurs Java peuvent s'attendre à setMyAttribute(newValue) et les utilisateurs C# peuvent s'attendre à myAtom.myAttribute = newValue.
  • Il est possible de échanger l'ensemble AttributeAtom, ce que je ne veux pas autoriser :

    myAtom.myAttribute = completelyDifferentAttribute
    

Tout stratégies pour améliorer cela?

  • Existe-t-il un moyen d'autoriser l'accès aux méthodes d'un attribut tout en ne permettant pas d'échanger l'attribut lui-même? J'aurais besoin d'un nouveau modificateur d'accès comme

    private *publicMethodAccess* AttributeAtom<String> myAttribute;
    

Atome exemple de code

Voici un exemple de classe Atom. Si vous faites défiler vers le bas, vous trouverez de nombreuses lignes de code consommées par les getters/setters. C'est laid, n'est-ce pas?

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

}
Author: Community, 2015-07-11

2 answers

Pour l'option E, en utilisant le modificateur" final", vous pouvez empêcher un tout nouvel AttributeAtom d'être échangé, tout en permettant toujours getting / setting:

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

Alors ce qui suit sera autorisé:

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

Mais la chose qui vous inquiète ne sera pas:

myAtom.myAttribute = completelyDifferentAttribute
 2
Author: Cookie Monster, 2015-07-12 09:37:36

J'ai finalement décidé d'utiliser un nouveau modèle qui s'étend de l'option E avec un "double enveloppe":

  • , Il est une interface "Attribut", qui fournit les méthodes "T get () et set(T value)"
  • Une autre interface "AttributeWrapper" hérite de "Attribute". Il fournit les méthodes supplémentaires "setAttribute" et "getAttribute" pour échanger un attribut encapsulé.
  • Les méthodes get/set de AttributeWrapper sont redirigées vers l'attribut encapsulé.
  • Si un "AttributeWrapper "est passé comme" Attribut "au monde extérieur, seules les méthodes" get "et" set " sont visibles. Cela fournit une certaine (pseudo) encapsulation. (Cette encapsulation peut être presque aussi bonne que l'encapsulation avec un modificateur privé. Le modificateur privé peut être contourné avec réflexion de toute façon.)
  • Ceux qui connaissent le fait que mes attributs sont en fait AttributeWrappers sont capables de convertir les attributs en AttributeWrappers et d'échanger les attributs encapsulés. Cela aussi me permet de créer et d'échanger mes attributs finaux publics non seulement dans le constructeur mais aussi dans n'importe quelle méthode de mes classes Atom. (Mettre toutes les définitions d'attribut directement dans la région d'attribut ou dans le constructeur rendrait mon code moche et donc difficile à lire.)
  • Il existe une classe "Wrap" qui fournit une implémentation par défaut de l'AttributeWrapper.
  • La classe abstraite AttributeAtom est la classe de base pour tous mes atomes d'attribut. Il implémente Attribut et aussi fournit une méthode d'aide "wrap". Cette méthode encapsule AttributeAtom dans un AttributeWrapper parent qui est passé à la méthode.
  • Le flux de travail final est

    1. Déclare un attribut final public myAttribute et lui attribue immédiatement un Wrap.
    2. Crée l'attribut newAttribute dans une méthode d'initialisation.
    3. Attribuez le nouvel attribut comme contenu du Wrap correspondant en utilisant la méthode d'assistance "wrap".
  • Tout ça va être plus clair avec un exemple de code:

En Utilisation Finale

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

Utilisation de l'attribut et de l'enveloppe pour la définition de classe de 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
}

Méthode d'aide "envelopper" dans 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);
}

Attribut d'interface

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

}

Interface 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: L'implémentation de 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

}
 1
Author: Stefan, 2015-07-27 20:48:46