Switch vs Polymorphism

4

So che questo è un problema classico ma è così difficile scegliere il modo giusto o il migliore. Lascia che ti presenti una versione semplificata del codice.

Progetta 1 usando l'interruttore:

public class Disease {
  private Color color;
}

public class City {
   private String name;
   private int yellowCubes;
   private int redCubes;
   private int bluesCubes;
   private int blackCubes;

   public void setCubesOnCity(int cubesNb, Disease disease) {
       cubesNb = computed again ..
       disease.subCubes(cubesNb);
       switch(disease.getColor()) {
           case Colors.BLACK:
               blackCubes += cubesNb;
           //other cases
       }
  }

  public int getCubesOnCity(Disease disease) {
      int cubesNb = 0;
      switch(disease.getColor()){
          case Colors.BLACK:
              cubesNb = this.getBlackCubes();
          // Etc
      }
      return cubesNb;
   }

 }

Progettazione 2 con polimorfismo:

public abstract class Disease {
   abstract int getCubesSetOn(City city);
   abstract void setCubesOn(City city, int cubesNb);
}

// One of sub classes
public class BlueDisease extends Disease {

  // Always needed for UI
  public Color getColor(){return Colors.BLUE;}

  @Override
  int getCubesSetOn(City city) {
      return city.getBlueCubes();
  }

  @Override
  void setCubesOn(City city, int cubesNb) {
      int totalCubes = cubesNb + city.getBlueCubes();
      city.setBlueCubes(totalCubes);
  }
}

public class City {
   private String name;
   private int yellowCubes;
   private int redCubes;
   private int bluesCubes;
   private int blackCubes;

   public void setCubesOnCity(int cubesNb, Disease disease) {
       cubesNb = computed again ..
       disease.subCubes(cubesNb);
       disease.setCubesOn(this, cubesNb);
  }

  public int getCubesOnCity(Disease disease) {
       return disease.getCubesSetOn(this);
  }
 }

Ora per le informazioni aggiuntive. Ci sono quattro malattie (quattro colori). È un requisito fisso per il gioco senza un reale bisogno di un design in cui è facile refactoring se abbiamo bisogno di aggiungere una nuova malattia.

Il problema è che ho sentito tante cose contraddittorie come switch è un odore di codice che tenta di usare il pattern Command (polimorfismo). Non creare classi inutili solo per accedere al polimorfismo (favor switch o comando?). Favorisci la composizione sull'ereditarietà (lo switch vince perché il colore è incluso nella malattia). Non sono abituato al pattern Command ma non mi sembra adatto ai miei bisogni.

So che nel grande schema delle cose ottengo le stesse cose con i due progetti, ma questo è un progetto in cui cerco di migliorare il design. Quindi mi piacerebbe avere opinioni e consigli su cosa dovrei preferire quando incontro questo tipo di problema ...

Grazie.

    
posta Tirke 15.04.2017 - 17:56
fonte

5 risposte

17

switch, o grandi se elseif blocchi di solito sono considerati un odore di codice. cioè c'è probabilmente una soluzione migliore.

Nel tuo caso avrei qualcosa di simile ..

Dictionary<colour, int> cubeCounts;
...
cubeCounts[disease.Colour] ++;
    
risposta data 15.04.2017 - 18:51
fonte
2

Nel tuo caso, "switch vs. polymorphism" è la domanda sbagliata. In larga misura, l'unica differenza tra varie malattie sembra essere il colore. Quindi, se si dispone di informazioni che dipendono dal colore, è possibile utilizzare una mappa oppure è possibile utilizzare un valore enum per ciascun colore e utilizzare gli array indicizzati da tale valore enum.

    
risposta data 16.04.2017 - 10:32
fonte
0

Quali sono i tuoi requisiti? Questo codice ha una durata breve? Dovrai aggiungere nuove malattie / colori? Le altre persone leggeranno il tuo codice o è solo un progetto su cui stai lavorando da solo? Quanto è critica la progettazione di questo aspetto del tuo sistema per l'immagine complessiva?

Se sei deciso a utilizzare i modelli OO qui, considera anche:

  • lo schema del visitatore
  • modello modello

Entrambi questi aspetti lasciano l'implementazione a classi subordinate, ma consentono anche di centralizzare una quantità significativa di controllo. Quindi, potresti scriverlo in modo corretto usando uno di questi modelli e passare (nessun gioco di parole) a un altro facilmente se non ti piace?

    
risposta data 15.04.2017 - 18:21
fonte
0

Il più grande odore di codice nell'interruttore è dovuto al test. Rompi la O nei principi SOLID - Open Closed Principle .

Perché? Hai una classe che contiene lo switch. Metti alla prova la lezione e va bene. Ora, più tardi i requisiti cambiano e hai bisogno di un nuovo caso. Ma hai testato questa classe! Non dovrebbe essere più manipolato. Dovrebbe essere aperto per le estensioni, ma chiuso per la modifica (il tuo secondo esempio).

Il secondo esempio è migliore perché promuove classi più piccole che hanno SRP, che sono più facili da testare (o da creare tramite lo sviluppo basato su test) e una volta testate si è certi che funzionano e possono essere riutilizzate. Per citare lo zio Bob dalla pagina collegata:

Classes that must be modified to accommodate new changes are fragile, rigid, unpredictable and unreusable. By insulating the class from changes, the class becomes robust, flexible, and reusable. Also as no modifications are made to the code no bugs can be introduced, leading to code that only becomes more stable over time through testing. The ability to reuse a class that has been working for years without modification is clearly preferable to modifying the class every time requirements change.

    
risposta data 15.04.2017 - 20:15
fonte
0

La cosa con le entrambe che hai proposto è che hai delle responsabilità miste.

In un design "buono", la Città e il Livello di Malattia sarebbero disaccoppiati gli uni dagli altri in modo che la gestione dei livelli di malattia fosse eseguita separatamente dal resto del i dati nella classe City.

Tecnicamente, dal momento che Livello di malattia non è altro che un contatore su un insieme relativamente stabile di numeri interi, potresti implementarlo tecnicamente usando un semplice array int come questo:

public enum Color { BLACK, BLUE, RED, YELLOW }

public interface Disease {
    Color getColor();
}

public class DiseaseLevel {
    private final int[] diseaseLevels;
    public DiseaseLevel() {
        diseaseLevels = new int[Color.values().length];
    }

    public int getDiseaseLevel(Disease disease) {
        return diseaseLevels[disease.getColor().ordinal()];
    }

    public void setDiseaseLevel(Disease disease, int level) {
        assert level >= 0 : "Disease level can not be negative!";
        diseaseLevels[disease.getColor().ordinal()] = level;
    }
}

La tua città potrebbe essere simile a questa:

private enum InfectionResult {
    ok, Infected, OUTBREAK
}

public class City {
    private final String name;
    private final long population;

    private final Color color;
    private final DiseaseLevel diseaseLevel;

    public City(String name, long population) {
        this.name = name;
        this.population = population;
        this.diseaseLevel = new DiseaseLevel();
    }

    public String getName() { return name; }
    public long getPopulation() { return population; }

    public Color getColor() { return color; }
    public int getDiseaseLevel() {
        return diseaseLevel.getDiseaseLevel(color);
    }

    private void setDiseaseLevel(int level) {
        diseaseLevel.setDiseaseLevel(color, level)
    }

    public InfectionResult increaseDiseaseLevel(int number) {
         int newLevel = getDiseaseLevel() + number;
         if (newLevel > 3) {
             setDiseaseLevel(3);
             return InfectionResult.OUTBREAK;
         }

         setDiseaseLevel(newLevel);
         return InfectionResult.Infected;
    }

    public InfectionResult decreaseDiseaseLevel(int number) {
         int newLevel = getDiseaseLevel() - number;
         if (newLevel < 0) {
             setDiseaseLevel(0);
             return InfectionResult.ok;
         }

         setDiseaseLevel(newLevel);
         return InfectionResult.Infected;
    }
}

Questo design al momento non è l'ideale in quanto mescola ancora le preoccupazioni a livello di Città, ma senza conoscere i requisiti esatti (o le regole del gioco), li ho infornati.

Inoltre, ho assunto le regole di Pandemic per le regole sull'aumento e la riduzione del livello di malattia in una città. Potrebbe essere che il tuo particolare gioco abbia regole diverse o che quelle regole potrebbero essere più flessibili. Regola secondo necessità;)

PS: per Pandemia, ogni città può avere solo cubi di una singola malattia del colore, quindi l'intera classe DiseaseLevel è sovrastampata e un singolo contatore di malattia sarebbe sufficiente.

    
risposta data 20.04.2017 - 09:50
fonte

Leggi altre domande sui tag