Dificuldade em tornar esta aula aberta

8

Aqui está o meu problema: eu quero ler as entradas de diferentes dispositivos HID, como um gamepad, corridas bem, joystick, etc. Praticamente qualquer controlador de jogo. A questão é que todos eles têm entradas diferentes.

O gamepad possui botões, interruptores e manípulos, enquanto o poço de corrida pode ter um manípulo. Consegui abstrair todos esses componentes diferentes em apenas 3, em vez de ter uma classe base com todas as combinações possíveis:

abstract class Device
    {
    public Buttons Buttons;
    public Axes Axes;
    public Switches Switches;
    public GearSticks GearSticks;
    //many more
    }

Agora posso ter:

abstract class Device
{
public Buttons Buttons;   //on or off
public Axes Axes;         //range [-100%:100%]
public Switches Switches; //multiple states
}

No começo, fiquei feliz com isso, pois parecia cobrir todos os tipos possíveis de entrada e, portanto, minha classe pode permanecer fechada enquanto aberta à extensão através de todas as implementações concretas, pois tudo pode ser abstraído para apenas 3 tipos de entrada.

MAS então eu pensei comigo mesmo e se eu apenas estivesse atrasando o inevitável? E se um dia eu tiver que adicionar outro campo à minha Deviceturma? Não suporta algo como um trackball!

Existe uma maneira de eu poder provar essa classe no futuro? Do jeito que eu vejo, eu terminaria com algo assim:

public Device1 : Device
{
//buttons
//trackball
}

public Device2 : Device
{
//Switch
//Axis
}

public Device3 : Device
{
//trackball
//switch
}

E eu teria que continuar adicionando propriedades à minha classe base sempre que houver algo novo para implementar.

Mihai Bratulescu
fonte
A única maneira de uma classe completamente à prova de futuro é armazenar pares de valores-chave como seqüências de caracteres e passar valores por suas classes com algum tipo de protocolo sem tipo, como Protocol Buffers. Mas o HID é mais específico que isso. Em que plataforma você está? Existem várias bibliotecas auxiliares que podem facilitar esse processo; aqui está um para o .NET Framework: github.com/mikeobrien/HidLibrary . Ou este: github.com/jcoenraadts/hid-sharp
Robert Harvey
Essa é uma excelente pergunta de engenharia de software, e não tenho idéia do motivo pelo qual você recebeu um voto negativo.
Doc Brown
@RobertHarvey Quando você disse pares de valor-chave, algo começou a fazer sentido em minha mente, mas o que você quer dizer com protocolo sem tipo? Estou na plataforma UWP.
Mihai Bratulescu
Um protocolo typeless é aquele que funciona inteiramente de pares de cordas, como a que um usos URL: parameter1=value1&parameter2=value2, etc.
Robert Harvey

Respostas:

7

Tenho certeza de que isso pode ser feito introduzindo um conceito como um resumo InputChannele dispositivos com uma lista configurável de canais de entrada. Um canal de entrada terá um nome, um tipo, talvez alguns metadados, e precisará ser capaz de produzir algum "estado" que se ajuste a esse tipo. Pode haver canais predefinidos, como um botão, um machado ou um comutador, ou algum novo canal que você não conhece no momento (mas pode ser adicionado posteriormente introduzindo uma nova InputChannelclasse filho).

Dessa forma, um dispositivo se tornará algum tipo de metamodelo e você também precisará de uma maneira de gerenciar os estados do dispositivo, que devem corresponder à lista de canais de entrada do dispositivo.

No entanto, esse tipo de abordagem genérica tem um certo risco de superengenharia, também conhecido como efeito da plataforma interna . Por exemplo, pode não ser fácil adicionar funcionalidades específicas a um dispositivo genérico, eventos específicos ou interações entre diferentes canais de entrada. Também pode ser mais difícil de usar e entender para um usuário da sua biblioteca de dispositivos genéricos.

Observe que nem sempre é benéfico criar a solução mais abstrata possível. A alteração dos requisitos de hardware normalmente exige muito mais esforço para ser implementada no próprio hardware do que no software correspondente; portanto, é melhor seguir uma solução mais específica no software e alterá-lo quando necessário.

Doc Brown
fonte
Um InputChannelpode funcionar, tudo que eu preciso fazer é atualizar seu estado e aumentar um onChangedEvent. Mas você pode estar certo sobre a engenharia excedente ... Vou manter isso em mente. Você acha que adicionar outro campo a uma classe de vez em quando é aceitável?
Mihai Bratulescu
1
@ MihaiBratulescu: depende do tipo de software que você escreve - alguma estrutura de jogo para você ou sua equipe, onde você conhece os casos de uso e pode reagir de acordo quando os requisitos mudam, ou alguma estrutura genérica como Qt ou a estrutura .Net que será usado por centenas de milhares de desenvolvedores em situações imprevisíveis. Para o último, uma solução muito genérica e SOLID é muito mais importante do que para o primeiro.
Doc Brown
a placa à frente - sua próxima parada, a Twilight Zone : "O suporte é MUITO limitado para esta biblioteca. É quase impossível solucionar problemas com tantos dispositivos e configurações". Isso é da referência hidLibrary @RobertHarvey . Alto insinuando o que está em para pegar a-preensão de uma teoria do campo unificado Tar bebê
radarbob
3

A idéia por trás do princípio de aberto-fechado é que você tem menos probabilidade de quebrar a funcionalidade existente se implementar nova funcionalidade por herança, em vez de modificar uma classe existente. E você pode fazer isso, herdando do seu Hid. Em um ano e alguns meses, você pode criar um Hid2020 que herda o Hid e adiciona suporte para o trackball que será inventado no quarto trimestre de 2019. Após a invenção e popularização do detector de compressão em 2023, você pode criar uma classe Hid2024 que descende de Hid2019.

Essa seria a abordagem defensiva. Mas também seria um pouco desleixado de uma perspectiva de design limpo. No seu caso, eu não perderia o sono por violar O e apenas mudaria a classe base à medida que o mundo ao seu redor mudasse. Parece que a implementação do trackball ou qualquer outro novo tipo de controle afetará a maneira como você lida com os pressionamentos de botão ou com as alterações de status agora.

Martin Maat
fonte
Idem no segundo parágrafo. Design coerente é a coisa. E falando de coerente; O / C já cumpriu seu objetivo aqui, simplesmente considerando o O / C faz com que se perceba que a classe será aberta (modificada) com muita frequência, provavelmente devido ao design inadequado. Na IMO, essa é a razão de ser do Princípio Aberto / Fechado.
Radarbob 07/09/19