2010-10-29 10 views
5

tengo una clase que puedo escribir así:¿Es mejor tomar un argumento de objeto o usar un objeto miembro?

class FileNameLoader 
{ 
    public: 
     virtual bool LoadFileNames(PluginLoader&) = 0; 
     virtual ~FileNameLoader(){} 
}; 

O esto:

class FileNameLoader 
{ 
    public: 
     virtual bool LoadFileNames(PluginLoader&, Logger&) = 0; 
     virtual ~FileNameLoader(){} 
}; 

El primero supone que hay un miembro de Logger& en la implementación de FileNameLoader. El segundo no. Sin embargo, tengo algunas clases que tienen muchos métodos que usan internamente Logger. Entonces, el segundo método me haría escribir más código en ese caso. Logger es un singleton por el momento. Mi suposición es que seguirá siendo así. ¿Qué es lo más "bello" de los dos y por qué? ¿Cuál es la práctica habitual?

EDITAR: ¿Qué pasa si esta clase no se llama Logger? :). Tengo un Builder también. ¿Qué tal entonces?

+0

¿Por qué no quería cambiar su registrador?Úselo con método estático, sin firma de función incorrecta y sin objeto adicional en todas sus clases. –

+0

Quizás para mayor claridad, edite en el primer ejemplo cómo entró el registrador. ¿Se pasó durante la construcción FileNameLoader? Si es así, consideraría mejor el # 1. – sdg

Respuesta

7

No veo qué ventaja adicional tienen dos acerca de uno (¡incluso teniendo en cuenta las pruebas unitarias!), De hecho con dos, debe asegurarse de que donde quiera que llame un método particular, un registrador esté disponible para pasar y eso podría complicar las cosas ...

Una vez que construyes un objeto con el registrador, ¿realmente ves la necesidad de cambiarlo? Si no, ¿por qué molestarse con el acercamiento dos?

+1

Acepto ... en algún momento en el futuro se dará cuenta de que desea registrar algo que está en una función anidada en el interior de la jerarquía de llamadas y deberá modificar todas las llamadas hasta ese punto para agregar el registro adicional. –

5

Prefiero el segundo método, ya que permite pruebas de caja negra más robustas. También hace más clara la interfaz de la función (el hecho de que utiliza un objeto Logger).

+1

No estoy del todo de acuerdo, esto tiene un problema de mantenimiento. En un momento dado, deberá agregar el registro en su jerarquía de llamadas y tendrá que actualizar todas las firmas de sus métodos hasta ese momento. No hay una ventaja real de pasar el registrador como argumento en comparación con el almacenamiento de una referencia en un miembro para fines de prueba; si la alternativa fuera un singleton, estaría de acuerdo. Tampoco creo que el registro sea parte de la interfaz, sino más bien un detalle de implementación: usted 'LoadFileNames', el registro no es su tarea, sino más bien un efecto secundario (probablemente para el diagnóstico). –

+1

Estoy de acuerdo con David hasta cierto punto. Tengo algunas funciones que no usan 'Logger' por sí mismas, pero llaman a otras funciones que sí lo hacen. Esto significa que tienen que aceptar un 'Logger &' aunque no los usen. Solo más desorden. – nakiya

0

El enfoque para el registro que me gusta mejor es tener un miembro del tipo Logger en mi clase (no es una referencia ni un puntero, sino un objeto real).
Dependiendo de la infraestructura de registro, eso hace posible decidir, por clase, a dónde debe ir la salida o qué prefijo usar.

Esto tiene la ventaja sobre su segundo enfoque de que no puede (accidentalmente) crear una situación en la que los miembros de la misma clase no puedan identificarse fácilmente como tales en los archivos de registro.

+0

Lo que describes está perfectamente bien si has implementado la clase 'Logger' por ti mismo desde cero. Pero mi experiencia es que este no siempre es el caso. Por ejemplo, no manejo las E/S reales en mi clase 'Logger'. Eso es manejado por un registrador de nivel inferior que es suministrado por el marco que uso. Solo uso la clase 'Logger' para formatear el registro según mi deseo. – nakiya

+0

@nakiya: Lo que describí se basa en mi experiencia con el marco de registro [log4cxx] [1]. Si su marco subyacente no le permite especificar un destino de registro, entonces mi enfoque podría seguir utilizándose para insertar un prefijo específico de clase en los mensajes de registro. [1] http://logging.apache.org/log4cxx/index.html –

1

En general, creo que menos argumentos equivalen a una mejor función. Normalmente, cuantos más argumentos tiene una función, más "común" es la función que tiende a convertirse en función, lo que a su vez puede conducir a funciones grandes y complicadas que intentan hacer todo.

Bajo la suposición de que la interfaz de Logger es para rastreo, en este caso dudo que el usuario de la clase FileNameLoader realmente quiera preocuparse por proporcionar la instancia de registro particular que se debe usar.

Probablemente también pueda aplicar la Ley de Demeter como argumento en contra de proporcionar la instancia de registro en una llamada de función.

Por supuesto, habrá momentos específicos en los que esto no sea apropiado. Los ejemplos generales pueden ser:

  • Para el rendimiento (solo se debe realizar después de la identificación de problemas de rendimiento específicos).
  • Para ayudar a probar a través de objetos simulados (en este caso, creo que un constructor es una ubicación más apropiada, para el registro restante un singleton es probablemente una mejor opción ...)
+0

Creo que si bien es una buena idea reducir el acoplamiento, la Ley de Demeter tiene que aceptar que hay algunas interfaces que estarán altamente acopladas a través de un aplicación (es decir, muchas otras clases los usarán). La clase 'string' es una, y creo que es razonable que algún tipo de interfaz Logger sea otra. –

1

Me quedaría con el primer método y usaría el registrador como singleton. Diferentes receptores e identificación de donde se registraron los datos es un problema diferente por completo. Identificar el receptor puede ser tan simple o tan complejo como desee. Por ejemplo (asumiendo Singleton <> es una clase base para los hijos únicos en su código):

class Logger : public Singleton<Logger> 
{ 
public: 
    void Log(const std::string& _sink, const std::string& _data); 
}; 

su clase:

class FileNameLoader 
{ 
public: 
    virtual bool LoadFileNames(PluginLoader& _pluginLoader) 
    { 
     Logger.getSingleton().Log("FileNameLoader", "loading xyz"); 
    }; 

    virtual ~FileNameLoader(){} 
}; 

Puede tener un gestor de registros inherentemente complejo con diferentes sumideros, diferente de registro -niveles diferentes salidas. Su método Log() en el administrador de registros debe admitir el registro simple como se describió anteriormente, y luego puede permitir ejemplos más complejos. Para fines de depuración, por ejemplo, puede definir diferentes salidas para diferentes sumideros, así como tener un registro combinado.

+0

¿Qué agrega Singleton aquí, que no se lograría con 'class Logger {static void Log (...); } ', luego llama a' Logger :: Log' desde 'LoadFileNames'? –

+0

Vi al registrador como algo más que simplemente una función de registro(), quizás debería haberlo expuesto. Es posible que administre varios registros (diferentes destinos, por ejemplo, Archivo, Consola, UDP). También podría permitir que los oyentes se "enganchen" a él. Tal vez sobre compliqué el escenario :) –

3

Lo primero es asegurarse de que el usuario proporcione la dependencia del registrador en ambos casos. Presumiblemente en el primer caso, el constructor de FileNameLoader toma un parámetro Logger&?

En ningún caso, bajo ningún concepto haré que el Registrador sea Singleton. Nunca, nunca, ni manera, ni cómo. Es una dependencia inyectada, o bien tiene una función gratuita Log, o si es absolutamente necesario, utilice una referencia global a un objeto std::ostream como su registrador predeterminado universal. Una clase de Singleton Logger es una forma de crear obstáculos para las pruebas, para absolutamente ningún beneficio práctico. Entonces, ¿qué pasa si algún programa crea dos objetos Logger? ¿Por qué es eso incluso malo, y menos vale la pena crear problemas para ti mismo para prevenir? Una de las primeras cosas que me encuentro haciendo, en cualquier sistema de registro sofisticado, es crear un PrefixLogger que implementa la interfaz Logger pero imprime una cadena especificada al inicio de todos los mensajes, para mostrar algún contexto. Singleton es incompatible con este tipo de flexibilidad dinámica.

Lo segundo, entonces, es preguntar si los usuarios van a querer tener un único FileNameLoader y llamar a LoadFileNames varias veces, con un registrador la primera vez y otro registrador la segunda vez.

Si es así, definitivamente desea un parámetro Logger para la llamada de función, porque un acceso para cambiar el Logger actual es (a) no una gran API, y (b) imposible con un miembro de referencia de todos modos: tiene que cambiar a un puntero. Sin embargo, es posible que el parámetro logger sea un puntero con un valor predeterminado de 0, con 0 que significa "usar la variable miembro". Eso permitiría usos donde el código de configuración inicial de los usuarios conoce y se preocupa por el registro, pero luego ese código transfiere el objeto FileNameLoader a otro código que llamará a LoadFileNames, pero no sabe o no le preocupa el registro.

Si no es así, entonces la dependencia del Registrador es una constante para cada instancia de la clase, y usar una variable miembro está bien. Siempre me preocupan un poco las variables de los miembros de referencia, pero por razones que no están relacionadas con esta elección.

[Editar en relación con el generador: Creo que puede buscar y reemplazar en mi respuesta y todavía se mantiene. La diferencia crucial es si "el Constructor utilizado por este objeto FileNameLoader" es invariante para un objeto dado, o si "el Constructor utilizado en la llamada" es algo que las personas que llaman a LoadFileNames necesitan configurar por llamada.

Podría ser un poco menos firme que el Creador no debería ser un Singleton. Ligeramente. Podría.]

+0

:). Descubrí cómo prefigurar el registro ayer. :). Como un aparte, ¿por qué preocuparse por las variables de los miembros de referencia? Porque pensé que esa era una forma de asegurarme de no entrar en el infierno. – nakiya

+0

@nakiya: mis dos pequeñas preocupaciones sobre los miembros de referencia son la asignabilidad y la administración de recursos. No estoy en contra de las clases no asignables como tales, y no estoy en contra de dejar la administración de recursos de las dependencias al usuario (como tal), pero ambas tienen que ser decisiones tomadas conscientemente, y los miembros de referencia limitan esa decisión. Por eso es solo una pequeña preocupación, solo lo mencioné porque me encontré escribiendo "una variable miembro está bien", y pensé que posiblemente debería calificar que una variable de miembro * reference * no siempre está bien :-) –

+0

+1 : no hay necesidad de singleton – Patrick

Cuestiones relacionadas