2012-03-16 14 views
5

Estaba reading sobre las desventajas de los patrones de singleton. Un uso válido de singleton sugerido en muchos foros es la aplicación de registro. Me preguntaba por qué este es un uso válido del patrón. ¿No mantenemos la información de estado en la memoria en toda la aplicación?¿Por qué usar singleton para el registro de aplicaciones?

Por qué no sólo tiene que utilizar una función:

class Logger 
{ 
    public static void Log(string message) 
    { 
     //Append to file 

    } 
} 

Respuesta

2

Es mejor declarar la interfaz:

interface ILogger 
{ 
    public void Log(string message); 
} 

luego implementar tipo específico de registrador

class FileLogger : ILogger 
{ 
    public void Log(string message) 
    { 
     //Append to file 
    } 
} 

class EmptyLogger : ILogger 
{ 
    public void Log(string message) 
    { 
     //Do nothing 
    } 
} 

e inyectar cuando sea necesario. Inyectará EmptyLogger en las pruebas. Usar singleton hará que las pruebas sean más difíciles, porque también tendrá que guardar en el archivo en las pruebas. Si desea probar si la clase realiza entradas de registro correctas, puede usar simulacros y definir expectativas.

Acerca de inyección:

public class ClassThatUsesLogger 
{ 
    private ILogger Logger { get; set; } 
    public ClassThatUsesLogger(ILogger logger) { Logger = logger } 
} 

ClassThatUsesLogger toma FileLogger en el código de producción:

classThatUsesLogger = new ClassThatUsesLogger(new FileLogger()); 

En las pruebas se tarda EmptyLogger:

classThatUsesLogger = new ClassThatUsesLogger(new EmptyLogger()); 

Se inyecta diferentes registradores en diferentes escenarios. Hay mejores formas de manejar las inyecciones, pero tendrás que leer un poco.

EDITAR

recuerde que aún puede utilizar Singleton en su código, como otros sugirieron, pero debe ocultar su uso detrás de interfaz para aflojar la dependencia entre una clase y la aplicación específica de la tala.

+0

que no comprendían la parte de inyección. ¿Podrías por favor elaborar? – Nemo

+0

Se hace referencia a una clase que requiere el registrador, pero no crea el registrador, se le ha pasado una durante la construcción de una clase (o más comúnmente a través del constructor) que tiene el registrador * inyectado * en la clase. http://en.wikipedia.org/wiki/Dependency_injection –

1

No estoy seguro de lo que usted se refiere a cuando se pregunta acerca de la información de estado que queda en la memoria, pero una de las razones para favorecer Singleton sobre estática para el registro es que Singleton todavía le permite a ambos
(1) Programa de abstracciones (ILogger) y
(2) se adhieren al principio de inversión de dependencia mediante la práctica de la inyección de dependencia.

No se puede inyectar su método de registro estático como una dependencia (a menos que quiera pasar algo así como Action<string> en todas partes), pero se puede pasar un objeto único, y se puede pasar a las distintas aplicaciones como NullLogger al escribir pruebas unitarias .

3

Para responder "¿por qué no utilizar una función?": Este código funciona incorrectamente en el registro de varios hilos. Si dos hilos intentan escribir el mismo archivo, se lanzará una excepción. Y esta es la razón por la cual es bueno usar singleton para el registro. En esta solución, tenemos un contenedor singleton seguro para hilos, otros hilos insertan mensajes (registros) en el contenedor de forma segura. Y el contenedor (siempre una cola segura para subprocesos) escribe los mensajes/registros en un archivo/db/etc uno por uno.

0

Una implementación de registrador de singleton le permite controlar fácilmente la frecuencia con la que se vacia su registro en el disco o la base de datos. Si tiene varias instancias del registrador, todas podrían estar intentando escribir al mismo tiempo, lo que podría causar colisiones o problemas de rendimiento. El singleton permite que esto se administre para que solo se descargue en la tienda durante los momentos de silencio y todos sus mensajes se mantengan en orden.

+0

Y estoy de acuerdo con @Jay y LukLed en que debe definir su registrador como una interfaz para permitir la inversión del control. Hará tus pruebas mucho más fáciles. –

1

En la mayoría de los casos, no se recomienda el patrón de diseño de Singleton, porque es un tipo de estado global, oculta las dependencias (lo que hace que las API sean menos obvias) y también difícil de probar.

El registro no es una de esas circunstancias. Esto se debe a que el registro no afecta la ejecución de su código. Es decir, como se explica aquí: http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html:

su aplicación no se comporta diferente si o no un determinado registrador está activado. La información aquí fluye de una manera: desde su aplicación al registrador.

Sin embargo, probablemente aún no desee utilizar el patrón Singleton. No del todo al menos. Esto se debe a que no hay razón para forzar una sola instancia de un registrador. ¿Qué pasaría si quisiera tener dos archivos de registro o dos registradores que se comportaron de manera diferente y se usaron para diferentes propósitos?

Así que todo lo que realmente desea para el registrador es hacerlo fácilmente accesible desde cualquier lugar cuando lo necesite. Básicamente, el registro es una circunstancia especial en la que la mejor manera de hacerlo es tener acceso global.

  1. La manera más fácil es simplemente tener un campo estático de la aplicación que contiene la instancia del registrador:

    public final static LOGGER = new Logger(); 
    
  2. O si su registrador es creado por una fábrica:

    public final static LOGGER = new LoggerFactory().getLogger("myLogger"); 
    
  3. O si su registrador es creado por un contenedor DI:

    public final static LOGGER = Container.getInstance("myLogger"); 
    
  4. Puede hacer que su implementación de registrador sea configurable, ya sea a través de un archivo de configuración, que puede establecer en "modo = prueba" cuando está realizando pruebas, para que el registrador en esos casos pueda comportarse como corresponda, ya sea no registrando o iniciando sesión en la consola.

    public final static LOGGER = new Logger("logConfig.cfg"); 
    
  5. También podría hacer que el comportamiento del registrador sea configurable en el tiempo de ejecución. Por lo tanto, al ejecutar pruebas, simplemente puede configurarlo como tal: LOGGER.setMode ("test");

  6. O si no hace la final estática, puede simplemente reemplazar el LOGGER estático con un registrador de prueba o un registrador simulado en la configuración de su prueba.

  7. algo un poco más elegante que puede hacer que esté cerca de un patrón Singleton pero no del todo es:

    public class Logger 
    { 
        private static Logger default; 
        public static getDefault() 
        { 
         if(default == null) 
         { 
          throw new RuntimeException("No default logger was specified."); 
         } 
         return default; 
        } 
    
        public static void setDefault(Logger logger) 
        { 
         if(default != null) 
         { 
          throw new RuntimeException("Default logger already specified."); 
         } 
         default = logger; 
        } 
    
        public Logger() 
        { 
        } 
    } 
    
    public static void main(String [] args) 
    { 
        Logger.setDefault(new Logger()); 
    } 
    
    @Test 
    public void myTest() 
    { 
        Logger.setDefault(new MockedLogger()); 
    
        // ... test stuff 
    } 
    
Cuestiones relacionadas