Enums ou várias classes herdadas

Sep 27 2020

Estou lendo sobre OOD e me deparei com um problema de design de estacionamento. O estacionamento possui pisos com vagas de estacionamento. A classe de vagas é a seguinte:

public enum ParkingSpotType {
  HANDICAPPED, COMPACT, LARGE, MOTORBIKE, ELECTRIC
}
public abstract class ParkingSpot {
  private String number;
  private boolean free;
  private Vehicle vehicle;
  private final ParkingSpotType type;

  public boolean IsFree();

  public ParkingSpot(ParkingSpotType type) {
    this.type = type;
  }

  public boolean assignVehicle(Vehicle vehicle) {
    this.vehicle = vehicle;
    free = false;
  }

  public boolean removeVehicle() {
    this.vehicle = null;
    free = true;
  }
}

public class HandicappedSpot extends ParkingSpot {
  public HandicappedSpot() {
    super(ParkingSpotType.HANDICAPPED);
  }
}

public class CompactSpot extends ParkingSpot {
  public CompactSpot() {
    super(ParkingSpotType.COMPACT);
  }
}

public class LargeSpot extends ParkingSpot {
  public LargeSpot() {
    super(ParkingSpotType.LARGE);
  }
}

public class MotorbikeSpot extends ParkingSpot {
  public MotorbikeSpot() {
    super(ParkingSpotType.MOTORBIKE);
  }
}

public class ElectricSpot extends ParkingSpot {
  public ElectricSpot() {
    super(ParkingSpotType.ELECTRIC);
  }
}

Os parâmetros são autoexplicativos. Então, por que precisamos de classes separadas para cada valor ENUM? Qual é a vantagem disso? Pensei em ter apenas um tipo de classe ParkingSpot com um campo representando o tipo de vaga de estacionamento. Mas por que precisamos tem classes separadas para cada valor ENUM junto com o armazenamento de Enum como um campo também?

Nota: Para design completo, consulte https://www.educative.io/courses/grokking-the-object-oriented-design-interview/gxM3gRxmr8Z

Respostas

3 Flater Oct 27 2020 at 18:55

Você está correto ao dizer que agora as classes derivadas são supérfluas, porque sua única diferença é um valor enum específico, que é rotulado exatamente da mesma maneira. Você poderia omitir o enum ou as classes derivadas e nada mudaria.

No entanto, essa conclusão pode mudar quando houver mais diferenças do que apenas o valor enum.

Por exemplo, se o preço da vaga for calculado de forma diferente com base no tipo de vaga. Por ter as classes derivadas, você pode manter a lógica de cálculo de cada tipo de vaga de estacionamento separadamente e fazer com que substituam o mesmo método de base.
Comparativamente, se você tivesse apenas a classe base para trabalhar, ela teria que conter toda a lógica de cálculo.

No entanto, o exemplo atual é um pouco leve no contexto e lista de recursos futuros para decidir se a herança é garantida aqui ou não. Não devemos apenas avaliar o estado atual do código (no qual sua observação está correta), mas também levar em conta quaisquer mudanças que são planejadas / esperadas em um futuro próximo.


Dito isso, suspeito que geralmente você acabará tendo que remover as classes derivadas ou o enum. Não vejo a vantagem de implementar as mesmas informações de duas maneiras diferentes.

Ewan Sep 27 2020 at 16:50

A diferença se torna aparente se considerarmos

CompactSpot.Assign(Car.SUV)

isso deve obviamente produzir um erro de algum tipo e temos duas opções sobre como implementá-lo.

ou:

ParkingSpot.Assign(car)
{
    if(car is SUV && this.type = COMPACT)
    {
        throw CarTooLarge()
    }
    if(car != HANDICAPPED && this.type == HANDICAPPED)
    ....
}

CompactSpot : ParkingSpot
    Assign(car)
    {
        if(car is SUV)
        {
            throw CarTooLarge()
        }
        base.assign(car);
    }
}

A abordagem de herança nos permite dividir a lógica condicional entre várias classes, em vez de ter um grande bloco condicional e lógica na classe base.

Se eu consumir sua classe ParkingSpot como uma DLL binária, posso herdar um novo tipo de ParkingSpot e adicionar minha própria lógica. O que não posso fazer se você tiver enum

radarbob Oct 29 2020 at 09:20

A pergunta responde a si mesma se pensarmos em comportamento em vez de estado, e devemos. A herança é motivada por "fazer a mesma coisa, mas de maneira diferente" ou ter funcionalidade adicional para vários subtipos. Talvez os designers têm uma boa noção de que ParkingSpote Vehiclecomportamento vai expandir e evoluir. Talvez por uma questão de aprendizado, eles simplesmente incluíram subtipulação.

Embora um ENUM diferencie veículos / parkingSpots do mesmo tipo, os requisitos de funcionalidade devem ser uma consideração prioritária. Mesmo assim, um enum ainda pode ser apropriado.

Há uma necessidade de combinar veículos e vagas de estacionamento - o fato de ser um ENUM é apenas um detalhe de implementação. Portanto, uma VehicleTypepropriedade púbica parece caber. Essa propriedade está muito mais no espírito de OO do que, digamos, o código do cliente vasculhando o Reflection / metadados. O código do cliente não deve calcular o estado de outro objeto.


Mas deve haver algum serviço, digamos ParkingAllocator que primeiro procurará uma vaga livre relevante para um determinado veículo e somente se uma vaga livre de um determinado veículo estiver disponível, estacionaremos um veículo lá. Portanto, se estivermos tentando atribuir um veículo para um ponto, temos absoluta certeza de que o ponto é grande o suficiente para um veículo. Isso faz sentido?

O diagrama de caso de uso mostra o atendente do estacionamento e os atores do cliente. Eles não estão no diagrama de classes, mas fazem parte do espaço do problema.


Acho que existem requisitos ausentes ou com defeito.

  • Um veículo menor é permitido em um slot maior? As motocicletas cabem em qualquer lugar.

  • Qualquer objeto de veículo pode ser elétrico ou ter permissões para deficientes físicos ou ambos - na vida real, pelo menos - então eu vejo ElectricSlote HandicapSlotcomo atributos de uma vaga de estacionamento. Conforme mostrado, o diagrama de classes aqui é inconsistente com o conceito de veículos de diferentes tamanhos cabendo em slots de tamanhos diferentes.

  • Não há correspondência aparente entre os tipos de vagas de estacionamento e os tipos de veículos. Quais veículos exigem "grande", por exemplo? De imediato, um Dicionário pode corresponder explicitamente os locais aos veículos e / ou vice-versa. Valores inteiros de enum subjacentes também podem representar o tamanho relativo da vaga de estacionamento. E como eu disse acima, "elétrico" não é um tamanho.


Estruturas de dados!

essas coisas ...

if(car is SUV)
    {
        throw CarTooLarge()
    }
    base.assign(car);
}

desaparece se essa correspondência de tipo de veículo / tipo local for uma estrutura de dados. A Dictionaryé uma estrutura de dados. De imediato, vejo dois dicionários - veículo-> local e local-> veículo - como o núcleo de uma classe com métodos apropriados.