c++ logging dependency-injection singleton

c++: clase de registrador sin globals o singletons o pasándolo a todos los métodos



logging dependency-injection (3)

¿Qué tal una clase con algunos métodos estáticos?

class Logger { public: static void record(string message) { static ofstream fout("log"); fout << message << endl;; } ... }; ... void someOtherFunctionSomewhere() { Logger::record("some string"); ... }

No Singleton, ninguna variable global, pero cualquier código que pueda ver a Logger.h puede llamar a la función miembro, y si todas las funciones públicas de miembro se void , es trivialmente fácil de apagar para la prueba.

¿Alguien sabe si es posible tener una clase como un registrador sin:

  • usando un singleton o un global (a la std :: cout)

  • pasando una instancia / puntero / referencia a cada método que lo necesite

Tomo el ejemplo de una clase de registrador, pero tengo algunas clases en mi aplicación que se beneficiarían de esto (por ejemplo, el administrador de deshacer).

Hay varios problemas con cada solución:

  • el uso de un singleton es problemático para la prueba (junto con los muchos motivos por los que generalmente no es una buena idea usar uno). Es lo mismo con un global. Además, nada garantiza que solo habrá UNA instancia en la aplicación, y ni siquiera es un requisito (¿por qué no tener 2 registradores, por ejemplo?)

  • pasarlo a cada constructor de objetos (inyección de dependencia), conduce a una gran cantidad de código repetitivo, y puede ser propenso a errores debido a que tiene que copiar / pegar el mismo código muchas veces. ¿Se puede considerar seriamente tener un puntero a un registrador en cada constructor de una sola clase ???????

¿Entonces me preguntaba si hay una tercera alternativa, en C ++, de la que nunca he oído hablar? Para mí, parece que requeriría algo de magia negra debajo del capó, pero me han sorprendido gratamente algunas técnicas que aprendí en el desbordamiento de pila que no pude encontrar en Google, así que sé que hay algunos gurús reales aquí;)

Sorprendentemente, encontré muchas discusiones sobre cómo diseñar singletons, o por qué no se deberían usar singletons, pero no pude encontrar una publicación que aborde mi problema ...


Me imagino que podrías hacer algo similar a lo que se hace en Java con el paquete Log4j (y probablemente se haga con la versión Log4c):

Tener un método estático que puede devolver múltiples instancias de registrador:

Logger someLogger = Logger.getLogger("logger.name");

El método getLogger() no está devolviendo un objeto singleton. Está devolviendo el registrador nombrado (creándolo si es necesario). Logger es solo una interfaz (en C ++ podría ser una clase totalmente abstracta): la persona que llama no necesita saber los tipos reales de los objetos del Logger que se están creando.

Podría seguir imitando Log4j y tener una sobrecarga de getLogger() que también toma un objeto de fábrica:

Logger someLogger = Logger.getLogger("logger.name", factory);

Esa llamada usaría la factory para construir la instancia del registrador, lo que le dará más control sobre qué objetos subyacentes del registrador se estaban creando, lo que probablemente ayudaría a su burla.

Así que no hay necesidad de pasar nada a los constructores, métodos, etc. de su propio código. Simplemente, tome el Logger nombre deseado cuando lo necesite y acceda a él. Dependiendo de la seguridad de los subprocesos del código de registro que escriba, podría hacer que lo que getLogger() devuelva sea un miembro estático de su clase, por lo que solo tendría que llamar a getLogger() una vez por clase.


No creo que haya una buena alternativa en C ++, sin embargo, hay una en Emacs Lisp en la que vale la pena pensar: vinculación dinámica de variables . El concepto básico es el siguiente: cuando se refiere a una variable, accederá no necesariamente a la variable global por el nombre, sino a la última definida en la ruta de ejecución con el mismo nombre. En el código pseudo-C ++ esto se vería así:

// Pseudo-C++ with dynamic binding Logger logger = Logger("GlobalLogger"); void foo() { logger.log("message"); } int main() { foo(); // first { Logger logger = Logger("MyLogger"); foo(); // second } foo(); // third }

En este ejemplo de pseudo-C ++, cuando llama a foo() la primera vez, se usa GlobalLogger, cuando se llama una segunda vez, se llama a MyLogger en su lugar, ya que MyLogger anulará la variable global durante el tiempo que esté alcance, incluso dentro de foo() . La tercera llamada volvería a GlobalLogger ya que MyLogger quedó fuera de alcance. Esto permite anular el registrador global con uno personalizado para piezas de código seleccionadas sin tener que pasar un objeto de registrador a través de todo el código y sin tener que establecer una variable global.

Real C ++ no tiene enlace dinámico, pero debería ser posible replicar aspectos de él:

std::stack<Logger> logger = { Logger("GlobalLogger") }; void foo() { logger.top().log("message"); } int main() { foo(); { logger.push(Logger("MyLogger")); foo(); logger.pop(); } foo(); }

Para una mayor limpieza, la pila debe estar oculta dentro de una clase DynamicBinding , las operaciones manuales de .push () /. Pop () podrían estar ocultas detrás de una guarda de alcance y habrá problemas con el multihilo que deberán ser atendidos. Pero el concepto básico podría funcionar y dar más flexibilidad que una variable simple o global simple.