Procurando alguns conselhos de design OO

12

Estou desenvolvendo um aplicativo que será usado para abrir e fechar válvulas em um ambiente industrial e pensei em algo simples como este: -

public static void ValveController
{
    public static void OpenValve(string valveName)
    {
        // Implementation to open the valve
    }

    public static void CloseValve(string valveName)
    {
        // Implementation to close the valve
    }
}

(A implementação gravaria alguns bytes de dados na porta serial para controlar a válvula - um "endereço" derivado do nome da válvula e um "1" ou "0" para abrir ou fechar a válvula).

Outro desenvolvedor perguntou se deveríamos criar uma classe separada para cada válvula física, da qual existem dezenas. Concordo que seria melhor escrever código como PlasmaValve.Open()antes ValveController.OpenValve("plasma"), mas isso é um exagero?

Além disso, eu estava imaginando a melhor forma de lidar com o design com alguns requisitos futuros hipotéticos em mente: -

  1. Somos solicitados a apoiar um novo tipo de válvula que exija valores diferentes para abrir e fechar (não 0 e 1).
  2. Somos solicitados a apoiar uma válvula que pode ser ajustada para qualquer posição de 0 a 100, em vez de simplesmente "aberta" ou "fechada".

Normalmente eu usaria herança para esse tipo de coisa, mas recentemente comecei a entender "composição sobre herança" e me pergunto se há uma solução mais simples usando composição?

Andrew Stephens
fonte
2
Eu criaria uma classe de válvula genérica que possui um identificador para a válvula específica (não uma string, talvez uma enumeração) e qualquer informação necessária para controlar o fluxo dentro dos métodos OpenValve / CloseValve. Como alternativa, você pode tornar a classe de válvulas abstrata e fazer implementações separadas para cada uma delas, onde a válvula de abrir / fechar chama a lógica dentro da classe de válvula especificada para o evento em que diferentes válvulas têm diferentes mecanismos de abertura / fechamento. O mecanismo comum seria definido na classe base.
Jimmy Hoffa
2
Não se preocupe com requisitos futuros hipotéticos. YAGNI.
Pd15
3
@pdr YAGNI é uma lâmina de dois gumes, concordo que vale a pena segui-la em geral, mas, ao extremo, pode-se dizer que fazer qualquer coisa para ajudar a manutenção ou legibilidade futura está violando a YAGNI, por isso acho o escopo da YAGNI muito ambíguo para muitos. Dito isto, muitas pessoas reconhecem onde usar o YAGNI e onde jogá-lo, porque explicar o futuro poupará muita dor. Eu só acho que é preciso ter cuidado ao sugerir que as pessoas sigam o YAGNI quando você não sabe onde elas vão pousar nesse espectro.
Jimmy Hoffa
2
Cara, 'composição sobre herança' é superestimada. Eu criaria uma classe / interface abstrata da Valve e as subclassificaria em PlasmaValve. E então eu me certificaria de que meu ValveController funcionasse com Valve (s), não me importando com qual subclasse eles são exatamente.
MrFox 15/10/12
2
@suslik: Absolutamente. Também vi um excelente código chamado espaguete por pessoas que não entendem os princípios do SOLID. Nós poderíamos continuar para sempre com isso. O que quero dizer é que já vi mais problemas causados ​​por descartar princípios estabelecidos (nascidos de muitos anos de experiência) imediatamente do que vi causados ​​por excesso de aderência. Mas concordo que os dois extremos são perigosos.
Pd15

Respostas:

12

Se cada instância do objeto válvula executasse o mesmo código que este ValveController, parece que várias instâncias de uma única classe seriam o caminho certo a seguir. Nesse caso, apenas configure qual válvula controla (e como) no construtor do objeto da válvula.

No entanto, se cada controle de válvula precisar de um código diferente para ser executado, e o atual ValveController estiver executando uma declaração de chave gigante que faz coisas diferentes dependendo do tipo de válvula, você reimplementou mal o polimorfismo. Nesse caso, reescreva-o em várias classes com uma base comum (se isso fizer sentido) e deixe o princípio de responsabilidade única ser o seu guia de design.

pedregoso
fonte
1
+1 por mencionar instruções de opção baseadas em tipo como cheiro de código. Frequentemente vejo esses tipos de instruções de switch em que o desenvolvedor afirma que estava apenas seguindo o KISS. Exemplo perfeito de como os princípios de design pode ser pervertido heh
Jimmy Hoffa
2
Várias instâncias também podem facilitar o vínculo de válvulas em uma sequência, permitindo que você modele a tubulação real da planta como um gráfico direcionado em seu código. Você também pode adicionar lógica de negócios às classes, caso precise fazer algo como abrir uma válvula quando outra fechar para evitar o aumento da pressão ou fechar todas as válvulas a jusante para não obter o efeito "golpe de aríete" quando a válvula é aberta novamente.
TMN
1

Minha queixa principal é usar strings para o parâmetro de identificação da válvula.

Pelo menos, crie uma Valveclasse que possua um getAddressna forma que a implementação subjacente precisa e passe-a para a, ValveControllerassegurando que você não possa criar válvulas inexistentes. Dessa forma, você não precisará lidar com cadeias de caracteres erradas em cada método de abrir e fechar.

Depende de você criar métodos de conveniência que chamam de abertura e fechamento ValveController, mas, para ser honesto, manteria toda a comunicação com a porta serial (incluindo a codificação) em uma única classe que outras classes chamarão quando necessário. Isso significa que, quando você precisar migrar para um novo controlador, precisará modificar apenas uma classe.

Se você gosta de testar, também deve criar ValveControllerum singleton para poder zombar dele (ou criar uma máquina de treinamento para os operadores).

catraca arrepiante
fonte
Eu nunca vi alguém recomendar um singleton para testar antes - geralmente acontece o contrário.
Kazark 18/10/12
francamente a Singleton é mais para evitar a estática e assim a comunicação pode ser sincronizado
roquete aberração