JavaRush /Blog Java /Random-ES /Patrones de diseño: Singleton

Patrones de diseño: Singleton

Publicado en el grupo Random-ES
¡Hola! Hoy veremos más de cerca diferentes patrones de diseño y comenzaremos con el patrón Singleton, que también se llama "singleton". Patrones de diseño: Singleton - 1Recordemos: ¿qué sabemos sobre los patrones de diseño en general? Los patrones de diseño son mejores prácticas que se pueden seguir para resolver una serie de problemas conocidos. Los patrones de diseño generalmente no están vinculados a ningún lenguaje de programación. Tómalos como un conjunto de recomendaciones, siguiendo las cuales podrás evitar errores y no reinventar tu rueda.

¿Qué es un singleton?

Un singleton es uno de los patrones de diseño más simples que se pueden aplicar a una clase. A veces la gente dice "esta clase es un singleton", lo que significa que esta clase implementa el patrón de diseño singleton. A veces es necesario escribir una clase para la que sólo se pueda crear un objeto. Por ejemplo, una clase responsable de iniciar sesión o conectarse a una base de datos. El patrón de diseño Singleton describe cómo podemos realizar dicha tarea. Un singleton es un patrón de diseño que hace dos cosas:
  1. Proporciona una garantía de que una clase tendrá solo una instancia de la clase.

  2. Proporciona un punto de acceso global a una instancia de esta clase.

Por lo tanto, hay dos características que son características de casi todas las implementaciones del patrón singleton:
  1. Constructor privado. Restringe la capacidad de crear objetos de clase fuera de la propia clase.

  2. Un método estático público que devuelve una instancia de la clase. Este método se llama getInstance. Este es el punto de acceso global a la instancia de clase.

Opciones de implementación

El patrón de diseño singleton se utiliza de diferentes maneras. Cada opción es buena y mala a su manera. Aquí, como siempre: no existe el ideal, pero hay que esforzarse por alcanzarlo. Pero antes que nada, definamos qué es bueno y qué es malo, y qué métricas influyen en la evaluación de la implementación de un patrón de diseño. Empecemos por lo positivo. Estos son los criterios que dan jugosidad y atractivo a la implementación:
  • Inicialización diferida: cuando se carga una clase mientras la aplicación se ejecuta exactamente cuando es necesaria.

  • Simplicidad y transparencia del código: la métrica, por supuesto, es subjetiva, pero importante.

  • Seguridad de subprocesos: funciona correctamente en un entorno multiproceso.

  • Alto rendimiento en un entorno de subprocesos múltiples: los subprocesos se bloquean entre sí mínimamente o no se bloquean en absoluto cuando comparten un recurso.

Ahora los contras. Enumeremos los criterios que muestran mal la implementación:
  • Inicialización no diferida: cuando una clase se carga cuando se inicia la aplicación, independientemente de si es necesaria o no (una paradoja, en el mundo de las tecnologías de la información es mejor ser vago)

  • Complejidad y mala legibilidad del código. La métrica también es subjetiva. Supondremos que si la sangre sale de los ojos, la implementación es regular.

  • Falta de seguridad del hilo. En otras palabras, "peligro del hilo". Funcionamiento incorrecto en un entorno multiproceso.

  • Rendimiento deficiente en un entorno de subprocesos múltiples: los subprocesos se bloquean entre sí todo el tiempo o con frecuencia cuando comparten un recurso.

Código

Ahora estamos listos para considerar varias opciones de implementación, enumerando los pros y los contras:

Solución simple

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
La implementación más simple. Ventajas:
  • Simplicidad y transparencia del código.

  • Seguridad del hilo

  • Alto rendimiento en un entorno multiproceso

Desventajas:
  • Inicialización no diferida.
En un intento de corregir el último defecto, obtenemos la implementación número dos:

Inicialización diferida

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {}

  public static Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Ventajas:
  • Inicialización diferida.

Desventajas:
  • No es seguro para subprocesos

La implementación es interesante. Podemos inicializar de forma perezosa, pero hemos perdido la seguridad de los subprocesos. No hay problema: en la implementación número tres sincronizamos todo.

Accesorio sincronizado

public class Singleton {
  private static Singleton INSTANCE;

  private Singleton() {
  }

  public static synchronized Singleton getInstance() {
    if (INSTANCE == null) {
      INSTANCE = new Singleton();
    }
    return INSTANCE;
  }
}
Ventajas:
  • Inicialización diferida.

  • Seguridad del hilo

Desventajas:
  • Mal rendimiento en un entorno de subprocesos múltiples

¡Excelente! En la implementación número tres, ¡recuperamos la seguridad de subprocesos! Es cierto que es lento... Ahora el método getInstanceestá sincronizado y solo puedes ingresarlo uno a la vez. De hecho, no necesitamos sincronizar todo el método, sino solo la parte en la que inicializamos un nuevo objeto de clase. Pero no podemos simplemente envolver synchronizedla parte responsable de crear un nuevo objeto en un bloque: esto no proporcionará seguridad para los subprocesos. Es un poco más complicado. El método de sincronización correcto se proporciona a continuación:

Bloqueo doblemente comprobado.

public class Singleton {
    private static Singleton INSTANCE;

  private Singleton() {
  }

    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized (Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();
                }
            }
        }
        return INSTANCE;
    }
}
Ventajas:
  • Inicialización diferida.

  • Seguridad del hilo

  • Alto rendimiento en un entorno multiproceso

Desventajas:
  • No es compatible con versiones de Java inferiores a 1.5 (la palabra clave volátil se corrigió en la versión 1.5)

Observo que para que esta opción de implementación funcione correctamente, se requiere una de dos condiciones. La variable INSTANCEdebe ser final, o volatile. La última implementación que discutiremos hoy es Class Holder Singleton.

Titular de la clase Singleton

public class Singleton {

   private Singleton() {
   }

   private static class SingletonHolder {
       public static final Singleton HOLDER_INSTANCE = new Singleton();
   }

   public static Singleton getInstance() {
       return SingletonHolder.HOLDER_INSTANCE;
   }
}
Ventajas:
  • Inicialización diferida.

  • Seguridad del hilo.

  • Alto rendimiento en un entorno multiproceso.

Desventajas:
  • Para un correcto funcionamiento es necesario garantizar que el objeto de clase Singletonse inicialice sin errores. De lo contrario, la primera llamada al método getInstanceterminará en un error ExceptionInInitializerErrory todas las siguientes fallarán NoClassDefFoundError.

La implementación es casi perfecta. Y perezoso, seguro para subprocesos y rápido. Pero hay un matiz descrito en el menos. Tabla comparativa de varias implementaciones del patrón Singleton:
Implementación Inicialización diferida Seguridad del hilo Velocidad de subprocesos múltiples ¿Cuándo usar?
Solución simple - + Rápido Nunca. O cuando la inicialización diferida no es importante. Pero nunca mejor dicho.
Inicialización diferida + - No aplica Siempre cuando no se necesita subprocesos múltiples
Accesorio sincronizado + + Despacio Nunca. O cuando no importa la velocidad de trabajo con subprocesos múltiples. pero nunca mejor dicho
Bloqueo doblemente comprobado. + + Rápido En casos excepcionales, es necesario manejar excepciones al crear un singleton. (cuando el Titular de Clase Singleton no es aplicable)
Titular de la clase Singleton + + Rápido Siempre cuando se necesita subprocesos múltiples y existe la garantía de que se creará un objeto de clase singleton sin problemas.

Pros y contras del patrón Singleton

En general, el singleton hace exactamente lo que se espera de él:
  1. Proporciona una garantía de que una clase tendrá solo una instancia de la clase.

  2. Proporciona un punto de acceso global a una instancia de esta clase.

Sin embargo, este patrón tiene desventajas:
  1. Singleton viola el SRP (principio de responsabilidad única): la clase Singleton, además de sus responsabilidades inmediatas, también controla el número de sus copias.

  2. La dependencia de una clase o método regular de un singleton no es visible en el contrato público de la clase.

  3. Las variables globales son malas. El singleton eventualmente se convierte en una variable global importante.

  4. La presencia de un singleton reduce la capacidad de prueba de la aplicación en general y de las clases que utilizan el singleton en particular.

OK, todo ha terminado. Observamos el patrón de diseño singleton. Ahora, en una conversación de por vida con tus amigos programadores, podrás decir no sólo lo bueno de ello, sino también algunas palabras sobre lo malo que tiene. Buena suerte en el dominio de nuevos conocimientos.

Lectura adicional:

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION