Soy bastante pragmático, pero mi principal preocupación aquí es que puedas permitir que esto ConfigBlock
domine tus diseños de interfaz de una manera posiblemente mala. Cuando tienes algo como esto:
explicit MyGreatClass(const ConfigBlock& config);
... una interfaz más apropiada podría ser así:
MyGreatClass(int foo, float bar, const string& baz);
... en lugar de simplemente seleccionar estos foo/bar/baz
campos de forma masiva ConfigBlock
.
Diseño de interfaz perezosa
En el lado positivo, este tipo de diseño facilita el diseño de una interfaz estable para su constructor, por ejemplo, ya que si termina necesitando algo nuevo, puede cargarlo en un ConfigBlock
(posiblemente sin ningún cambio de código) y luego elija cualquier cosa nueva que necesite sin ningún tipo de cambio de interfaz, solo un cambio en la implementación de MyGreatClass
.
Por lo tanto, es tanto una ventaja como una desventaja que esto le libere de diseñar una interfaz más cuidadosamente pensada que solo acepte entradas que realmente necesita. Aplica la mentalidad de "Solo dame esta gran cantidad de datos, seleccionaré lo que necesito de ella" en lugar de algo más como "Estos parámetros precisos son lo que esta interfaz necesita para funcionar".
Así que definitivamente hay algunos profesionales aquí, pero podrían ser muy compensados por los contras.
Acoplamiento
En este escenario, todas estas clases que se construyen a partir de una ConfigBlock
instancia terminan teniendo sus dependencias de la siguiente manera:
Esto puede convertirse en un PITA, por ejemplo, si desea realizar una prueba unitaria Class2
en este diagrama de forma aislada. Es posible que tenga que simular superficialmente varias ConfigBlock
entradas que contienen los campos relevantes que Class2
le interesan para poder probarlo en una variedad de condiciones.
En cualquier tipo de contexto nuevo (ya sea prueba unitaria o proyecto completamente nuevo), cualquiera de esas clases puede llegar a ser más una carga de (re) uso, ya que terminamos teniendo que llevar siempre ConfigBlock
el viaje y configurarlo en consecuencia.
Reusabilidad / Implementabilidad / Testabilidad
En cambio, si diseña estas interfaces de manera adecuada, podemos desacoplarlas ConfigBlock
y terminar con algo como esto:
Si observa en este diagrama anterior, todas las clases se vuelven independientes (sus acoplamientos aferentes / salientes se reducen en 1).
Esto lleva a muchas más clases independientes (al menos independientes de ConfigBlock
) que pueden ser mucho más fáciles de (re) usar / probar en nuevos escenarios / proyectos.
Ahora este Client
código termina siendo el que tiene que depender de todo y ensamblarlo todo junto. La carga termina siendo transferida a este código de cliente para leer los campos apropiados de a ConfigBlock
y pasarlos a las clases apropiadas como parámetros. Sin embargo, dicho código de cliente generalmente está diseñado de manera limitada para un contexto específico, y su potencial de reutilización generalmente será nulo o cerrado de todos modos (podría ser la main
función de punto de entrada de su aplicación o algo así).
Entonces, desde un punto de vista de reutilización y prueba, puede ayudar a hacer que estas clases sean más independientes. Desde el punto de vista de la interfaz para aquellos que usan sus clases, también puede ayudar establecer explícitamente qué parámetros necesitan en lugar de solo uno masivo ConfigBlock
que modele todo el universo de campos de datos necesarios para todo.
Conclusión
En general, este tipo de diseño orientado a clases que depende de un monolito que tiene todo lo necesario tiende a tener este tipo de características. Como resultado, su aplicabilidad, capacidad de implementación, reutilización, capacidad de prueba, etc. pueden degradarse significativamente. Sin embargo, pueden simplificar el diseño de la interfaz si intentamos darle un giro positivo. Depende de usted medir esos pros y contras y decidir si las compensaciones valen la pena. Por lo general, es mucho más seguro equivocarse contra este tipo de diseño en el que está seleccionando un monolito en clases que generalmente están destinadas a modelar un diseño más general y ampliamente aplicable.
Por último, si bien no menos importante:
extern CodingBlock MyCodingBlock;
... esto es potencialmente peor (¿más sesgado?) en términos de las características descritas anteriormente que el enfoque de inyección de dependencia, ya que termina acoplando sus clases no solo a ConfigBlocks
, sino directamente a una instancia específica de la misma. Eso degrada aún más la aplicabilidad / implementabilidad / comprobabilidad.
Mi consejo general sería equivocarse al diseñar interfaces que no dependen de este tipo de monolitos para proporcionar sus parámetros, al menos para las clases más generalmente aplicables que diseñe. Y evite el enfoque global sin inyección de dependencia si puede, a menos que realmente tenga una razón muy fuerte y segura para no evitarlo.
switch
declaración o unaif
prueba de declaración contra un valor leído de los archivos de configuración.Si. Es mejor centralizar las constantes y valores de tiempo de ejecución y el código para leerlos.
Esto es malo: la mayoría de sus constructores no necesitarán la mayoría de los valores. En cambio, cree interfaces para todo lo que no es trivial de construir:
código antiguo (su propuesta):
nuevo código:
crear una instancia de MyGreatClass:
Aquí,
current_config_block
hay una instancia de suConfigBlock
clase (la que contiene todos sus valores) y laMyGreatClass
clase recibe unaGreatClassData
instancia. En otras palabras, solo pase a los constructores los datos que necesitan, y agregue facilidadesConfigBlock
para crear esos datos.Este código sugiere que tendrá una instancia global de CodingBlock. No haga eso: normalmente debe tener una instancia declarada globalmente, en cualquier punto de entrada que use su aplicación (función principal, DllMain, etc.) y pasarla como un argumento donde lo necesite (pero como se explicó anteriormente, no debe pasar toda la clase, solo exponga las interfaces alrededor de los datos y pase esas).
Además, no ate sus clases de cliente (su
MyGreatClass
) al tipo deCodingBlock
; Esto significa que, siMyGreatClass
toma una cadena y cinco enteros, será mejor que pase esa cadena y los enteros, que pasará aCodingBlock
.fuente
Respuesta corta:
Usted no necesita todos los ajustes para cada uno de los módulos / clases en el código. Si lo hace, entonces hay algo mal con su diseño orientado a objetos. Especialmente en el caso de pruebas unitarias, establecer todas las variables que no necesita y pasar ese objeto no ayudaría con la lectura o el mantenimiento.
fuente
ConfigBlock
clase. El punto aquí es no proporcionar todo, en este caso, el contexto del estado del sistema, solo los valores requeridos en particular para hacerlo.