La tarea es configurar una pieza de hardware dentro del dispositivo, de acuerdo con algunas especificaciones de entrada. Esto debe lograrse de la siguiente manera:
1) Recopile la información de configuración. Esto puede suceder en diferentes momentos y lugares. Por ejemplo, el módulo A y el módulo B pueden solicitar (en diferentes momentos) algunos recursos de mi módulo. Esos 'recursos' son en realidad la configuración.
2) Una vez que está claro que no se realizarán más solicitudes, debe enviarse al hardware un comando de inicio que proporcione un resumen de los recursos solicitados.
3) Solo después de eso, puede (y debe) realizarse una configuración detallada de dichos recursos.
4) Además, solo después de 2), se puede (y debe) enrutar los recursos seleccionados a las personas que llaman declaradas.
Una causa común de errores, incluso para mí, que escribí la cosa, es confundir esta orden. ¿Qué convenciones de nombres, diseños o mecanismos puedo emplear para que alguien que vea el código por primera vez pueda utilizar la interfaz?
fuente
discovery
ohandshake
?Respuestas:
Es un rediseño, pero puede evitar el uso indebido de muchas API pero no tener disponible ningún método que no deba llamarse.
Por ejemplo, en lugar de
first you init, then you start, then you stop
Su constructor
init
es un objeto que se puede iniciar ystart
crea una sesión que se puede detener.Por supuesto, si tiene una restricción para una sesión a la vez, debe manejar el caso en el que alguien intenta crear una con una ya activa.
Ahora aplique esa técnica a su propio caso.
fuente
zlib
yjpeglib
son dos ejemplos que siguen este patrón para la inicialización. Aún así, se necesitan muchas documentaciones para enseñar el concepto a los desarrolladores.Puede hacer que el método de inicio devuelva un objeto que es un parámetro requerido a la configuración:
Incluso si su
MySession
es solo una estructura vacía, esto impondrá a través de la seguridad de tipo que noConfigure()
se puede llamar a ningún método antes del inicio.fuente
module->GetResource()->Configure(nullptr)
?a, b, c, d
, entonces puedo comenzara
, y usarloMySession
para intentar usarlob
como un objeto ya iniciado, mientras que en realidad no lo es.Basándose en la respuesta de Cashcow: ¿por qué tiene que presentar un nuevo objeto a la persona que llama, cuando puede presentar una nueva interfaz? Rebrand-Pattern:
También puede dejar que ITerminateable implemente IRunnable, si una sesión se puede ejecutar varias veces.
Su objeto:
De esta forma, solo puede llamar a los métodos correctos, ya que solo tiene la interfaz IStartable al principio y obtendrá el método run () solo accesible cuando haya llamado a start (); Desde el exterior, parece un patrón con múltiples clases y objetos, pero la clase subyacente sigue siendo una clase, a la que siempre se hace referencia.
fuente
Hay muchos enfoques válidos para resolver su problema. Basile Starynkevitch propuso un enfoque de "burocracia cero" que lo deja con una interfaz simple y confía en que el programador use adecuadamente la interfaz. Si bien me gusta este enfoque, presentaré otro que tiene más eingineering pero permite que el compilador detecte algunos errores.
Identificar los diferentes estados de su dispositivo puede estar en, como
Uninitialised
,Started
,Configured
y así sucesivamente. La lista tiene que ser finita.Para cada estado, defina
struct
la información adicional necesaria relevante para ese estado, por ejemploDeviceUninitialised
,DeviceStarted
etc.Empaque todos los tratamientos en un objeto
DeviceStrategy
donde los métodos usan estructuras definidas en 2. como entradas y salidas. Por lo tanto, puede tener unDeviceStarted DeviceStrategy::start (DeviceUninitalised dev)
método (o cualquiera que sea el equivalente según las convenciones de su proyecto).Con este enfoque, un programa válido debe llamar a algunos métodos en la secuencia aplicada por los prototipos de métodos.
Los diversos estados son objetos no relacionados, esto se debe al principio de sustitución. Si le resulta útil que estas estructuras compartan un ancestro común, recuerde que el patrón de visitante se puede utilizar para recuperar el tipo concreto de la instancia de una clase abstracta.
Mientras describí en 3. una
DeviceStrategy
clase única , hay situaciones en las que es posible que desee dividir la funcionalidad que proporciona en varias clases.Para resumirlos, los puntos clave del diseño que describí son:
Debido al principio de sustitución, los objetos que representan estados de dispositivo deben ser distintos y no tener relaciones de herencia especiales.
Empaque los tratamientos del dispositivo en objetos de estrategia en lugar de en los objetos que representan los dispositivos mismos, de modo que cada dispositivo o estado del dispositivo se vea solo a sí mismo, y la estrategia los vea a todos y exprese posibles transiciones entre ellos.
Juraría que vi una vez una descripción de la implementación de un cliente telnet siguiendo estas líneas, pero no pude encontrarla nuevamente. ¡Habría sido una referencia muy útil!
¹: Para esto, siga su intuición o encuentre las clases de equivalencia de métodos en su implementación real para la relación “método₁ ~ método₂ iff. es válido usarlos en el mismo objeto ", suponiendo que tenga un gran objeto que encapsule todos los tratamientos en su dispositivo. Ambos métodos de enumerar estados dan resultados fantásticos.
fuente
Usa un patrón de construcción.
Tenga un objeto que tenga métodos para todas las operaciones que mencionó anteriormente. Sin embargo, no realiza estas operaciones de inmediato. Solo recuerda cada operación para más tarde. Debido a que las operaciones no se ejecutan de inmediato, el orden en que las pasa al constructor no importa.
Después de definir todas las operaciones en el constructor, llama a un
execute
método. Cuando se llama a este método, realiza todos los pasos que enumeró anteriormente en el orden correcto con las operaciones que almacenó anteriormente. Este método también es un buen lugar para realizar algunas comprobaciones de cordura que abarcan toda la operación (como intentar configurar un recurso que aún no estaba configurado) antes de escribirlas en el hardware. Esto podría salvarlo de dañar el hardware con una configuración sin sentido (en caso de que su hardware sea susceptible a esto).fuente
Solo necesita documentar correctamente cómo se usa la interfaz y dar un ejemplo tutorial.
También puede tener una variante de biblioteca de depuración que realiza algunas comprobaciones de tiempo de ejecución.
Tal vez la definición y documentación correctamente algunas convenciones de nombres (por ejemplo
preconfigure*
,startup*
,postconfigure*
,run*
....)Por cierto, muchas interfaces existentes siguen un patrón similar (por ejemplo, kits de herramientas X11).
fuente
Este es de hecho un tipo de error común e insidioso, porque los compiladores solo pueden imponer condiciones de sintaxis, mientras que usted necesita que sus programas cliente sean "gramaticalmente" correctos.
Desafortunadamente, las convenciones de nomenclatura son casi completamente ineficaces contra este tipo de error. Si realmente desea alentar a las personas a no hacer cosas no gramaticales, debe pasar un objeto de comando de algún tipo que debe inicializarse con valores para las condiciones previas, de modo que no puedan realizar los pasos fuera de orden.
fuente
Al usar este patrón, está seguro de que cualquier implementador se ejecutará en este orden exacto. Puede ir un paso más allá y crear una ExecutorFactory que construirá Ejecutores con rutas de ejecución personalizadas.
fuente
step1(); step2(); step3();
. El objetivo del generador de pasos es proporcionar una API que exponga algunos pasos y aplicar la secuencia en la que se los llama. No debería evitar que un programador haga otras cosas entre pasos.