User Tools

Site Tools


wiki:pdd_2

Singleton

Contexto: Supongamos una clase Logger, utilizada para registrar las operaciones realizadas en un sistema. Un uso de esta clase se muestra a continuación:

void f() {
  Logger log = new Logger();
  log.println("Ejecutando f");
  ...
}
void g() {
  Logger log = new Logger();
  log.println("Ejecutando g");
  ...
}
void h() {
  Logger log = new Logger();
  log.println("Ejecutando h");
  ...
}

Problema: En el código anterior, cada método que necesita registrar eventos crea su propia instancia de Logger. Sin embargo, nos gustaría que todos los usos de Logger apuntaran a la misma instancia de la clase. En otras palabras, no queremos una proliferación de objetos Logger. En cambio, nos gustaría que existiera, como máximo, una única instancia de esta clase y que se utilizara en todas las partes del sistema que necesitan registrar algún evento. Esto es importante, por ejemplo, si el registro de eventos se realiza en archivos. Si es posible la creación de varios objetos Logger, cada nuevo objeto instanciado sobrescribirá el archivo anterior creado por otros objetos del tipo Logger.

Solución: La solución para este problema consiste en transformar la clase Logger en un Singleton. Este patrón de diseño define cómo implementar clases que tendrán, como su propio nombre indica, como máximo una instancia. A continuación, mostramos la versión de Logger que funciona como un Singleton:

class Logger {
 
  private Logger() {} // prohíbe a los clientes llamar a un new Logger()
 
  private static Logger instance; // instancia única
 
  public static Logger getInstance() {
    if (instance == null) // 1a vez que se llama a getInstance
      instance = new Logger();
    return instance;
  }
 
  public void println(String msg) {
    // registra msg en la consola, pero podría ser en archivo
    System.out.println(msg);
  }
}

Primero, esta clase tiene un constructor por defecto privado. De este modo, ocurrirá un error de compilación cuando cualquier código fuera de la clase intente llamar a new Logger(). Además, un atributo estático almacena la única instancia de la clase. Cuando necesitemos esta instancia, debemos llamar al método público y estático getInstance(). A continuación, se muestra un ejemplo:

void f() {
  Logger log = Logger.getInstance();
  log.println("Ejecutando f");
  ...
}
 
void g() {
  Logger log = Logger.getInstance();
  log.println("Ejecutando g");
  ...
}
 
void h() {
  Logger log = Logger.getInstance();
  log.println("Ejecutando h");
  ...
}

En este nuevo código, estamos seguros de que las tres llamadas a getInstance devuelven la misma instancia de Logger. Todas las mensajes se registrarán usando esta instancia.

Entre los patrones de diseño propuestos en el libro de la Gang of Four, el Singleton es el más polémico y criticado. La razón es que puede ser utilizado para camuflar la creación de variables y estructuras de datos globales. En nuestro caso, la instancia única de Logger es, en la práctica, una variable global que puede ser leída y modificada en cualquier parte del programa. Para ello, basta con llamar a Logger.getInstance(). Las variables globales representan una forma de acoplamiento no aceptable (o fuerte) entre clases, es decir, una forma de acoplamiento que no está mediada a través de interfaces estables. Sin embargo, en el caso de Logger, el uso de Singleton no genera preocupaciones, ya que es exactamente el recurso recomendado por el patrón: tenemos un recurso que es único — un archivo de registro, en este caso — y queremos reflejar esta característica en el diseño, garantizando que sea manipulado a través de una clase que, por construcción, tendrá como máximo una instancia.

En resumen: el Singleton debe ser usado para modelar recursos que, conceptualmente, deben tener como máximo una instancia durante la ejecución de un programa. Por otro lado, un uso abusivo del patrón ocurre cuando se adopta como un artificio para la creación de variables globales.

Por último, hay una crítica adicional al uso de Singletons: complican la prueba automática de métodos. La razón es que el resultado de la ejecución de un método puede depender ahora de un estado global almacenado en un Singleton. Por ejemplo, supongamos un método m que retorna el valor de x + y, donde x es un parámetro de entrada y y es una variable global, que forma parte de un Singleton. Así, para probar este método necesitamos proporcionar el valor de x, lo cual es bastante fácil, ya que es un parámetro del método. Pero también necesitamos garantizar que y tendrá un valor conocido, lo cual puede ser más difícil, ya que es un atributo de otra clase.

wiki/pdd_2.txt · Last modified: 2024/09/02 01:16 by admin