Patron singleton: una clase propia

Los llamados patrones de diseño ayudan a los desarrolladores en la programación orientada a objetos proporcionando plantillas probadas y comprobadas para resolver tareas de programación. Una vez seleccionada la plantilla más adecuada a partir de los aproximadamente setenta patrones de diseño, se refina haciendo adaptaciones individuales. Sin embargo, el enfoque general para el patrón sigue siendo el mismo. El patrón singleton es muy poderoso, pero tiene la reputación de ser una reliquia en la programación orientada a objetos. En este artículo, te presentamos sus fortalezas y debilidades, y te mostramos cómo se utiliza en la programación.

¿Qué es el patrón singleton?

El patrón singleton, o singleton pattern en inglés, pertenece a la categoría de patrones creativos dentro del grupo de los patrones de diseño. También se le conoce simplemente como “singleton”. El propósito de este patrón es evitar que sea creado más de un objeto por clase. Esto se logra creando el objeto deseado en una clase y recuperándolo como una instancia estática. El singleton es uno de los patrones más simples, pero más poderosos en el desarrollo de software.

Cita

La “Gang of Four” (GoF), banda de los cuatro en español, un equipo de programadores de los EE. UU. dice esto sobre el patrón singleton: “Asegúrate de que una clase tenga exactamente una copia y provea un punto de acceso global a ella.”

¿Cuáles son las características principales del patrón singleton?

Si se utiliza el patrón singleton para crear una instancia de una clase, entonces el patrón se asegura de que realmente sólo permanezca con esta instancia única. El singleton hace que esta clase de software sea accesible globalmente. En los diferentes lenguajes de programación, hay diferentes métodos para lograrlo. Para asegurarse de que permanezca con una sola instancia única, se debe impedir que los usuarios creen nuevas instancias. Esto se logra mediante el constructor, declarando el patrón como “privado”. Esto significa que sólo el código en el singleton puede instanciar el singleton en sí mismo. Por lo tanto, esto garantiza que sólo un mismo objeto puede llegar al usuario. Si esta instancia ya existe, no se crea ninguna nueva instancia. Un posible singleton pattern se ve así:

public class Singleton {
	private static Singleton instance; // protected from external access and static
	private Singleton() {} // private constructor with external access protection
	public static  getInstance() { // public method, call out by code
		if (instance == null) { // only if no instance exists, then create a new
			instance = new Singleton();
		}
		return instance;
	}
}

El patrón singleton: una representación en el diagrama UML

En el siguiente diagrama, diseñado utilizando UML, observamos todo el patrón de diseño de Singleton, que se compone de un solo objeto, ya que sólo es necesario crear una única instancia de una clase.

Desde el exterior, no es posible cambiar ninguna parte de la pieza única que se ha creado. Justo este es el objetivo cuando se utiliza el patrón singleton.

Ventajas y desventajas del singleton pattern

Una visión general de las ventajas

Al no estar poblado de innumerables variables (globales), un singleton puede escribirse de forma rápida y sencilla. El patrón encapsula su creación, lo que significa que también puede ejercer un control preciso sobre cuándo y cómo se accede a él. Un patrón singleton existente puede derivarse mediante subclases para cumplir nuevas funcionalidades. La funcionalidad que se utiliza se decide dinámicamente. Y, por último, pero no menos importante, un singleton se crea exactamente cuándo se necesita, una característica que se denomina lazy loading. El proceso de instanciar un singleton anteriormente, es decir antes de que se necesite, por otro lado, se llama carga ansiosa.

Una visión general de las desventajas

El uso desinhibido de los singletons conduce a un estado similar al de la programación procedimental (es decir, el no orientado a objetos), y puede llevar a ensuciar el código fuente. La disponibilidad global de patrones singleton plantea riesgos si se manejan datos sensibles. Esto porque si se hacen cambios en el singleton, no se podrá rastrear qué partes del programa están afectadas. Esto dificulta el mantenimiento de software, porque los fallos de funcionamiento son difíciles de rastrear. La disponibilidad global del patrón también dificulta la eliminación de los singleton, ya que los componentes del software siempre pueden referirse a este singleton. En aplicaciones con muchos usuarios (aplicaciones multiusuario), un patrón singleton puede reducir el rendimiento del programa, porque representa un atasco de datos, al ser singular.

El patrón singleton “en la vida real”

El singleton se utiliza principalmente cuando hay que completar tareas recurrentes en la rutina de un programa. Esto incluye los datos que tienen que ser escritos en un archivo, por ejemplo, durante el registro, o los trabajos de impresión que tienen que ser escritos en una sola memoria intermedia de la impresora una y otra vez. Dado que los controladores y los mecanismos de caché también tienen procesos recurrentes, el patrón singleton se utiliza comúnmente para éstos también.

Puesto que es muy difícil probar el patrón singleton, ilustraremos su funcionamiento con el ejemplo de una pequeña empresa en la que varios empleados utilizan una sola impresora. Un ejemplo cercano a la práctica se presenta en la Serie de Tutoriales de Patrones de Diseño de Daniel H. Jacobsen. El patrón singleton que mostramos a continuación se basa en esto.

Si un usuario envía una solicitud a la impresora, el patrón singleton hace la “pregunta”: “¿Ya hay un objeto de la impresora? Si no, entonces crea uno.” Esto se resuelve con un if/then-statement (devolver impresora == cero ?). Para evitar el acceso y los cambios, las variables individuales y la impresora se establecen como “privadas” en lugar de “públicas”.

public class impresora {
	private static impresora;
	private int NúmeroPáginas;
	private impresora() {
	}
	public static impresora getInstance() {
		return impresora == Null ? 
				impresora = new impresora() : 
				impresora;
	}
	public void print(String text){
		System.out.println(text +
				"\n" + "número de páginas impresas hoy" + ++ NumeroPáginas +
				"\n" + "---------");
	}
}

El siguiente paso es “encapsular” a los empleados de la sucursal. Las cadenas para los nombres, posición y función dentro de la compañía también se establecen como “privados”.

public class Employee {
	private final String name;
	private final String position;
	private final String role;
	public empleado(String name, String position, String role) {
		this.nombre = nombre;
		this.posición = posición;
		this.función = función;
	}
	public void printCurrent función (){
		impresora = impresora.getInstance();
		impresora.print("empleado: " + nombre + "\n" +
			"Posición: " + posición + "\n" +
			" Función: " + función + "\n");
	}
}

Finalmente, los dos singleton se integran en una rutina de salida.

public class Main {
	public static void main(String[] args) {
		Empleado andreas = new empleado ("Andreas",
				"Jefe",
				"Gestiona la sucursal");
		Empleado julia = new empleado ("Julia",
				"Consultor",
				"Asesora a los clientes sobre las quejas");
		Empleado tom = new empleado ("Tom",
				"Venta",
				"Vende los productos");
		Empleado stefanie = new empleado ("Stefanie",
				"Desarrollador",
				" Mantenimiento informático en la sucursal.");
		Empleado matthias = new empleado ("Matthias",
				"Contable",
				"contabilidad financiera de la sucursal.");
		andreas.printCurrentFunción ();
		julia.printCurrentFunción ();
		tom.printCurrentFunción ();
		stefanie.printCurrentFunción ();
		matthias.printCurrentFunción ();
	}
}