Dificultad para hacer esta clase abierta-cerrada

8

Aquí está mi problema: quiero leer la entrada de diferentes dispositivos HID, como un gamepad, carreras, joystick, etc. Casi cualquier controlador de juegos. El problema es que todos tienen diferentes entradas.

El gamepad tiene botones, interruptores y palos, mientras que el pozo de carreras podría tener una palanca de cambios. Logré abstraer todos estos componentes diferentes en solo 3, así que en lugar de tener una clase base con todas las combinaciones posibles:

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

Ahora puedo tener:

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

Al principio estaba contento con esto, ya que parecía cubrir todos los tipos posibles de entrada y, por lo tanto, mi clase puede permanecer cerrada mientras está abierta a la extensión a través de todas las implementaciones concretas, ya que todo se puede abstraer a solo 3 tipos de entrada.

PERO entonces pensé para mí mismo ¿qué pasa si solo estoy retrasando lo inevitable? ¿Qué pasa si algún día tendré que agregar otro campo a mi Deviceclase? ¡No es compatible con algo como un trackball!

¿Hay alguna manera de que pueda probar esta clase en el futuro? A mi modo de ver, terminaría con algo como esto:

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

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

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

Y tendría que seguir agregando propiedades a mi clase base cada vez que haya algo nuevo que implementar.

Mihai Bratulescu
fuente
La única forma de proteger completamente esta clase de futuro es almacenar pares clave-valor como cadenas y pasar valores alrededor de sus clases con algún tipo de protocolo sin tipo, como Protocol Buffers. Pero HID es más específico que eso. ¿En qué plataforma estás? Hay varias bibliotecas auxiliares que pueden facilitar este proceso; Aquí hay uno para .NET Framework: github.com/mikeobrien/HidLibrary . O este: github.com/jcoenraadts/hid-sharp
Robert Harvey
Esta es una excelente pregunta de ingeniería de software, y no tengo idea de por qué recibió un voto negativo por ello.
Doc Brown
@RobertHarvey Cuando dijiste pares clave-valor, algo comenzó a tener sentido en mi mente, pero ¿qué quieres decir con protocolo sin tipo? Estoy en la plataforma UWP.
Mihai Bratulescu
Un protocolo sin tipo es uno que funciona completamente en pares de cadenas, como el que usa una URL: parameter1=value1&parameter2=value2etc.
Robert Harvey,

Respuestas:

7

Estoy bastante seguro de que esto se puede hacer introduciendo un concepto como un resumen InputChannely dispositivos que tengan una lista configurable de canales de entrada. Un canal de entrada tendrá un nombre, un tipo, tal vez algunos metadatos, y debe ser capaz de producir algún "estado" que se ajuste a ese tipo. Puede haber canales predefinidos como un botón, un hacha o un interruptor, o algún canal nuevo que no conozca actualmente (pero podría agregarse más adelante al introducir una nueva InputChannelclase secundaria).

De esta manera, un dispositivo se convertirá en una especie de metamodelo, y también necesitará una forma de administrar los estados del dispositivo, que deben corresponder a la lista de canales de entrada del dispositivo.

Sin embargo, ese tipo de enfoque genérico tiene un cierto riesgo de ingeniería excesiva, también conocido como efecto de plataforma interna . Por ejemplo, puede que no sea fácil agregar una funcionalidad específica a un dispositivo genérico, o eventos específicos, o interacciones entre diferentes canales de entrada. También puede ser más difícil de usar y comprender para un usuario de su biblioteca genérica de dispositivos.

Tenga en cuenta que no siempre es beneficioso crear la solución más abstracta posible. Los requisitos cambiantes en el hardware generalmente requieren mucho más esfuerzo para implementarse en el hardware en sí que en el software correspondiente, por lo que a menudo es mejor apegarse a una solución más específica en el software y cambiar el software cuando sea necesario.

Doc Brown
fuente
Un InputChannelpuede funcionar, todo lo que necesito hacer es actualizar su estado y aumentar un onChangedEvent. Pero puede que tengas razón sobre el exceso de ingeniería ... Voy a tener esto en cuenta. ¿Crees que es aceptable agregar otro campo a una clase de vez en cuando?
Mihai Bratulescu
1
@MihaiBratulescu: depende del tipo de software que escriba: algún marco de juego para usted o su equipo, donde conozca los casos de uso y pueda reaccionar en consecuencia cuando cambien los requisitos, o algún marco genérico como Qt o el marco .Net que será utilizado por cientos de miles de desarrolladores en situaciones imprevisibles. Para este último, una solución muy genérica y SÓLIDA es mucho más importante que para el primero.
Doc Brown
el letrero más adelante: su próxima parada, Twilight Zone : "El soporte es MUY limitado para esta biblioteca. Es casi imposible solucionar problemas con tantos dispositivos y configuraciones". Esto es de las referencias hidLibrary @RobertHarvey . En voz alta insinuando lo que uno está
atrapado en
3

La idea detrás del principio abierto-cerrado es que es menos probable que rompa la funcionalidad existente si implementa una nueva funcionalidad a través de la herencia en lugar de la modificación de una clase existente. Y puedes hacerlo, heredando de tu Hid. En un año y un par de meses, puede crear un Hid2020 que hereda de Hid y agrega soporte para el trackball que se inventará en el cuarto trimestre de 2019. Después de la invención y popularización del detector de compresión en 2023, puede crear una clase Hid2024 que desciende de Hid2019.

Ese sería el enfoque defensivo. Pero también sería un poco descuidado desde una perspectiva de diseño limpio. En su caso, no perdería el sueño por violar O y simplemente cambiaría la clase base a medida que cambia el mundo a su alrededor. No parece que la implementación de trackball o cualquier otro tipo de control nuevo afecte la forma en que maneja presionar botones o cambiar cambios de estado ahora.

Martin Maat
fuente
Lo mismo para el segundo párrafo. El diseño coherente es la cosa. Y hablando de coherente; O / C ya ha cumplido su propósito aquí, simplemente teniendo en cuenta que O / C hace que uno se dé cuenta de que la clase se abrirá (modificará) con demasiada frecuencia probablemente debido a un diseño inadecuado. OMI, esta es la razón de ser del Principio Abierto / Cerrado.
radarbob