è l'interruttore (questo) antipattern o cattiva pratica in Java per Enums?

7

Al lavoro mi sono imbattuto in un problema per vedere come crescono le enumerazioni e memorizzare le logiche di business. Mentre crescevano, i costruttori sono cresciuti molto. A un certo punto ho scoperto invece di inserire il decimo parametro booleano (e che aveva già vari altri parametri rispetto al booleano) nel costruttore ed espandere tutte le inizializzazioni con un parametro aggiuntivo, ne farò un nuovo - ma non proprio usato - costruzione per scopi di leggibilità del codice.

Ecco un esempio, quindi anziché:

public enum A {
  A1(true, true, false),
  A2(true, false, true),
  A3(false, false, false);

private A(boolean firstParam, boolean secondParam, boolean thirdParam){
    ... usual constructor ...
}

... getters

Così invece ho fatto quanto segue:

public enum A {
  A1,
  A2,
  A3;

... no need for special constructor

public boolean isFirstParam(){
  switch(this){
    case A1:
    case A2:
      return true;
    default:
      return false;
  }
}

public boolean isSecondParam(){
  switch(this){
    case A1:
      return true;
    default:
      return false;
  }
}

public boolean isThirdParam(){
  switch(this){
    case A2:
      return true;
    default:
      return false;
  }
}

Finora chiunque lo abbia usato lo apprezza. Ma alcuni colleghi che non ne erano a conoscenza hanno avuto reazioni negative:

  • Sonar non è stato felice di vedere l'istruzione switch (this).
  • Non gli piaceva il concetto stesso.

La mia domanda è: c'è una vera ragione per cui non dovrei usarla? È molto più mantenibile quando hai enumerazioni complesse. Credo di non aver usato niente di speciale, semplicemente passando da enum, solo in un posto speciale. Ho ragione?

    
posta CsBalazsHungary 18.11.2015 - 15:31
fonte

2 risposte

13

L'altra alternativa è l'override dei metodi per istanza:

public enum A {
  A1{
    @Override public boolean isFirstParam(){ return true; }
    @Override public boolean isSecondParam(){ return true; }
    @Override public boolean isThirdParam(){ return false; }
  },
  A2{
    @Override public boolean isFirstParam(){ return true; }
    @Override public boolean isSecondParam(){ return false; }
    @Override public boolean isThirdParam(){ return false; }
  },
  A3{
    @Override public boolean isFirstParam(){ return false; }
    @Override public boolean isSecondParam(){ return false; }
    @Override public boolean isThirdParam(){ return false; }
  };

  public abstract boolean isFirstParam();
  public abstract boolean isSecondParam();
  public abstract boolean isThirdParam();
}

Il link ha questo da dire:

The idea of adding behavior to enum constants can be taken one step further. You can give each enum constant a different behavior for some method. One way to do this by switching on the enumeration constant. Here is an example with an enum whose constants represent the four basic arithmetic operations, and whose eval method performs the operation:

public enum Operation {
    PLUS, MINUS, TIMES, DIVIDE;

    // Do arithmetic op represented by this constant
    double eval(double x, double y){
        switch(this) {
            case PLUS:   return x + y;
            case MINUS:  return x - y;
            case TIMES:  return x * y;
            case DIVIDE: return x / y;
        }
        throw new AssertionError("Unknown op: " + this);
    }
}

This works fine, but it will not compile without the throw statement, which is not terribly pretty. Worse, you must remember to add a new case to the switch statement each time you add a new constant to Operation. If you forget, the eval method with fail, executing the aforementioned throw statement

There is another way give each enum constant a different behavior for some method that avoids these problems. You can declare the method abstract in the enum type and override it with a concrete method in each constant. Such methods are known as constant-specific methods. Here is the previous example redone using this technique:

public enum Operation {
  PLUS   { double eval(double x, double y) { return x + y; } },
  MINUS  { double eval(double x, double y) { return x - y; } },
  TIMES  { double eval(double x, double y) { return x * y; } },
  DIVIDE { double eval(double x, double y) { return x / y; } };

  // Do arithmetic op represented by this constant
  abstract double eval(double x, double y);
}

Personalmente non mi dispiace lanciare un'eccezione e gestire esplicitamente tutte le possibilità, tranne se l'interruttore si trova nella stessa enum. L'IDE mi dà un avvertimento (posso anche configurarlo per trasformarlo in un errore quindi non verrà compilato) quando non gestirai tutti i casi. Se mantieni i tuoi progetti privi di avvertimenti, cosa che cerco di fare, saprai immidamente quali file necessitano delle loro istruzioni switch aggiornate.

    
risposta data 18.11.2015 - 16:19
fonte
-1

So che questo è vecchio e ha già risposto, ma un'altra opzione potrebbe essere il modello di build.

public enum A {
  A1(new EnumBuilder().set1(true).set2(true).set3(false)),
  A2(new EnumBuilder().set1(true).set2(false).set3(true)),
  A3(new EnumBuilder().set1(false).set2(false).set3(false));

private A(EnumBuilder builder){
    param1 = builder.param1;
    // etc.
}
...
}

class EnumBuilder {
    private boolean param1, param2, param3;

    EnumBuilder set1(boolean value){
        param1 = value;
        return this;
    }

    // etc.
}
    
risposta data 23.05.2018 - 23:06
fonte

Leggi altre domande sui tag