¿Qué es mejor: un grupo de captadores o 1 método con un parámetro de cadena de selección?

15

Nuestro dominio del conocimiento involucra a personas que caminan sobre una placa de registro de presión con los pies descalzos. Hacemos reconocimiento de imagen que da como resultado objetos de la clase 'Pie', si se reconoce un pie humano en los datos del sensor.

Hay varios cálculos que deben realizarse en los datos del pie.

Ahora, qué API sería mejor:

class Foot : public RecognizedObject  { 
  MaxPressureFrame getMaxPressureFrame();
  FootAxis getFootAxis();
  AnatomicalZones getAnatomicalZones();

  // + similar getters for other calculations

  // ...
}

O:

class Foot : public RecognizedObject {
  virtual CalculationBase getCalculation(QString aName);

  // ...
}

Ahora, hay muchas ventajas y desventajas que se me ocurren, pero realmente no puedo decidir cuáles son las más importantes. Tenga en cuenta que esta es una aplicación de usuario final, no una biblioteca de software que vendemos.

¿Algún consejo?

Algunos profesionales para el primer enfoque podrían ser:

  • BESO: todo es muy concreto. La API, pero también la implementación.
  • valores de retorno fuertemente tipados.
  • heredar de esta clase es infalible. Nada puede ser anulado, solo agregado.
  • La API está muy cerrada, no entra nada, nada se puede anular, por lo que menos puede salir mal.

Algunas desventajas:

  • El número de captadores crecerá, ya que cada nuevo cálculo que inventamos se agrega a la lista
  • Es más probable que la API cambie, y si se introducen cambios de última hora, necesitamos una nueva versión de API, un Foot2.
  • en caso de reutilización de la clase en otros proyectos, es posible que no necesitemos todos los cálculos

Algunos profesionales para el segundo enfoque:

  • mas flexible
  • es menos probable que la API cambie (suponiendo que tengamos la abstracción correcta, si no, cambiar costará más)

Algunas desventajas:

  • mecanografiado libremente. Necesita lanzamientos en cada llamada.
  • el parámetro de cadena: tengo malos sentimientos al respecto (ramificación en valores de cadena ...)
  • No existe un caso / requisito de uso actual que exija una flexibilidad adicional, pero podría existir en el futuro.
  • la API impone restricciones: cada cálculo debe derivarse de una clase base. Obtendrá un cálculo mediante este método 1, y pasar parámetros adicionales será imposible, a menos que ideemos una forma aún más dinámica y súper flexible de pasar parámetros que aumente aún más la complejidad.
Bgie
fuente
55
Podrías hacer un enumy encender sus valores. Aún así, creo que la segunda opción es malvada, porque se desvía de KISS.
Vorac
Es más difícil encontrar todos los usos de un cálculo específico si utiliza un parámetro de cadena para activarlo. Difícil de ser 100% los encontraste todos.
Konrad Morawski
Toma lo mejor de dos mundos y escribe una fachada con un montón de captores invocando getCalculation().
nalply
1
¡Las enumeraciones son definitivamente mejores que las cuerdas! No había pensado en esto. Restringen la API y evitan el abuso del parámetro de cadena (como concat de tokens y otra basura) Así que supongo que la comparación es entre la opción 1 y la opción 2 con enum en lugar de cadena.
Bgie

Respuestas:

6

Creo que en primer enfoque valdrá la pena. Las cadenas mágicas pueden crear los siguientes problemas: errores de mecanografía, mal uso, seguridad de tipo de retorno no trivial, falta de finalización del código, código poco claro (¿tenía esta versión esa característica? Supongo que lo descubriremos en tiempo de ejecución). El uso de enumeraciones resolverá algunos de esos problemas, pero veamos los inconvenientes que planteó:

  • El número de captadores crecerá, ya que cada nuevo cálculo que inventamos se agrega a la lista

Es cierto que puede ser molesto, sin embargo, mantiene las cosas agradables y estrictas, y le brinda la finalización del código en cualquier parte de su proyecto en cualquier IDE moderno, con buenos comentarios de encabezado que son mucho más útiles que las enumeraciones.

  • Es más probable que la API cambie, y si se introducen cambios de última hora, necesitamos una nueva versión de API, un Foot2.

Es cierto, pero en realidad es un gran profesional;) ​​puede definir interfaces para API parciales, y luego no necesita volver a compilar la clase dependiente que no se ve afectada por las API más nuevas (por lo que no es necesario para Foot2). Eso permite un mejor desacoplamiento, la dependencia ahora es de la interfaz y no de la implementación. Además, si cambia una interfaz existente, tendrá un error de compilación en clases dependientes, lo cual es excelente para evitar el código obsoleto.

  • en caso de reutilización de la clase en otros proyectos, es posible que no necesitemos todos los cálculos

No veo cómo el uso de cuerdas mágicas o enumeraciones ayudará con eso ... Si entiendo correctamente, o incluye el código en la clase Foot o lo divide en algunas clases más pequeñas, y eso vale para ambas opciones

usuario116462
fuente
Me gusta la idea de API parciales con interfaces, parece a prueba de futuro. Iré con ese. Gracias. Si se vuelve demasiado abarrotado (el pie implementa demasiadas interfaces), el uso de varias clases de adaptadores pequeños sería aún más flexible: si existen varias variaciones de pie con diferentes API (como pie, pie humano, pie de perro, versión de pie humano2) podría haber un adaptador pequeño para que cada uno permita que un widget GUI funcione con todos ellos ...
Bgie
Una ventaja de usar un selector de comandos es que uno puede tener una implementación que recibe un comando que no entiende, llamar a un método auxiliar estático proporcionado con la interfaz. Si el comando representa algo que se puede hacer con casi todas las implementaciones utilizando un enfoque de propósito general, pero que algunas implementaciones podrían hacer por mejores medios [considere, por ejemplo IEnumerable<T>.Count], dicho enfoque puede permitir que el código disfrute de los beneficios de rendimiento de los nuevos características de interfaz cuando se utilizan implementaciones que las admiten, pero siguen siendo compatibles con implementaciones antiguas
supercat
12

Recomendaría la opción 3: dejar en claro que los cálculos no son una parte intrínseca de la abstracción de a Foot, sino que funcionan con ella. Luego puede dividir Footy los cálculos en clases separadas, así:

class Foot : public RecognizedObject {
public:
    // Rather low-level API to access all characteristics that might be needed by a calculation
};

class MaxPressureFrame {
public:
    MaxPressureFrame(const Foot& aFoot); // Performs the calculation based on the information in aFoot
    //API for accessing the results of the calculation
};

// Similar classes for other calculations

De esta manera, aún tiene un tipeo fuerte de su cálculo, y puede agregar nuevos sin afectar el código existente (a menos que haya cometido un error grave en la cantidad de información expuesta por Foot).

Bart van Ingen Schenau
fuente
Definitivamente mejor que las opciones 1 y 2. Esto está a solo un paso de utilizar el patrón de diseño de la estrategia.
Brian
44
Esto puede ser apropiado. Esto podría ser exagerado. Depende de cuán complejos sean los cálculos. ¿Vas a agregar una MaxPressureFrameStrategy y una MaxPressureFrameStrategyFactory? Y tiende a convertir el pie en un objeto anémico.
user949300
Aunque esta es una buena alternativa, revertir las dependencias no funcionaría en nuestro caso. El pie también debe actuar como algún tipo de mediador, ya que algunos cálculos deben recalcularse si otro cambia (debido a que el usuario cambió un parámetro más o menos).
Bgie
@Bgie: Con tales dependencias entre los cálculos, estoy de acuerdo con @ user116462 en que la primera opción es la mejor. "
Bart van Ingen Schenau