Switch vs Polimorfismo

4

Eu sei que este é um problema clássico, mas é tão difícil escolher o caminho certo ou o melhor. Deixe-me apresentar uma versão simplificada do código.

Design 1 usando o switch:

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

 }

Design 2 com 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);
  }
 }

Agora, para as informações adicionais. Existem quatro doenças (quatro cores). É um requisito fixo para o jogo sem necessidade real de um design onde seja fácil refatorar se precisarmos adicionar uma nova doença.

O problema é que eu ouvi tantas coisas contraditórias como switch é um cheiro de código tentar usar o padrão de comando (polimorfismo). Não crie classes inúteis apenas para acessar o polimorfismo (favorecer comutador ou comando?). Favorecer a composição sobre a herança (trocar ganha porque a cor está incluída na doença). Eu não estou acostumado com o padrão Command, mas não parece se encaixar nas minhas necessidades.

Eu sei que no grande esquema das coisas eu alcanço as mesmas coisas com os dois designs, mas este é um projeto onde eu tento me aperfeiçoar no design. Então, eu gostaria de ter opiniões e conselhos sobre o que devo preferir quando me deparo com esse tipo de problema ...

Obrigado.

    
por Tirke 15.04.2017 / 17:56
fonte

5 respostas

17

switch, ou big if elseif blocks são geralmente considerados um cheiro de código. ou seja, provavelmente há uma solução melhor.

No seu caso, eu teria algo como ...

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

No seu caso, "switch vs. polymorphism" é a pergunta errada. Em grande medida, a única diferença entre várias doenças parece ser a cor. Portanto, se você tiver informações que dependem da cor, poderá usar um mapa ou usar um valor de enumeração para cada cor e usar matrizes indexadas por esse valor de enumeração.

    
por 16.04.2017 / 10:32
fonte
0

Quais são seus requisitos? Esse código tem vida útil curta? Você precisará adicionar novas doenças / cores? As outras pessoas vão ler o seu código ou este é apenas um projeto em que você está trabalhando sozinho? Quão crítico é o design deste aspecto do seu sistema para o quadro geral?

Se você está empenhado em usar padrões OO aqui, considere também:

  • o padrão de visitantes
  • padrão de modelo

Ambos deixam a implementação subordinada às classes, mas também permitem que uma quantidade significativa de controle seja centralizada. Então, você poderia escrevê-lo no caminho usando um desses padrões, e mudar (sem trocadilhos) outro facilmente se você não gostar?

    
por 15.04.2017 / 18:21
fonte
0

O maior cheiro de código no switch é devido ao teste. Você quebra o O em princípios SOLID - Open Closed Principle .

Por quê? Você tem uma classe que contém o comutador. Você testa a classe e fica bom. Agora, mais tarde, os requisitos mudam e você precisa de um novo caso. Mas você testou essa aula! Não deve ser mais manipulado. Ele deve estar aberto para extensões, mas fechado para modificação (seu segundo exemplo).

O segundo exemplo é melhor porque promove classes menores que têm SRP, que são mais fáceis de testar (ou criar por meio de desenvolvimento orientado a testes) e, uma vez testadas, você tem certeza de que elas funcionam e podem ser reutilizadas. Para citar o tio Bob da página vinculada:

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.

    
por 15.04.2017 / 20:15
fonte
0

A coisa com ambos desings que você propôs é que você tem responsabilidades mistas.

Em um design "bom", a Cidade e o Nível de Doença seriam dissociados uns dos outros, de modo que o gerenciamento dos níveis de doenças fosse realizado separadamente do resto os dados na classe City.

Tecnicamente, já que nível de doença não é nada além de um contador em relação a um conjunto relativamente fixo de inteiros, você poderia tecnicamente implementar isso usando um array int simples como este:

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

A sua cidade pode ter esta aparência:

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

Este design atualmente é menos que ideal, pois ainda mistura preocupações com o nível da cidade, mas sem conhecer os requisitos exatos (ou regras do jogo), eu os aumentei.

Além disso, assumi as regras de Pandemic para regras sobre como aumentar e diminuir o nível da doença na cidade. Pode ser que o seu jogo em particular tenha regras diferentes ou que essas regras sejam mais flexíveis. Ajuste conforme necessário;)

PS - para a pandemia, cada cidade pode ter apenas cubos de uma única doença de cor, então toda a classe DiseaseLevel é superenvolvida e um único marcador de doença seria suficiente perfeitamente.

    
por 20.04.2017 / 09:50
fonte