Please enable JavaScript to view the comments powered by Disqus.

El Patrón de Diseño Singleton

#designpattern #python

1. Descripción General

2. Diagrama UML del Patrón

classDiagram
class Singleton {
    -Singleton instance
    -Singleton()
    +getInstance()$ Singleton
}

3. Estructura del Patrón

4. Implementación en Python

La forma de implementarlo sería:

class Singleton:

    _instance = None 

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

    @classmethod
    def getInstance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

s1 = Singleton.getInstance()

El método __new__ es el núcleo del patrón en Python. Este método es responsable de verificar si ya existe una instancia de la clase. Si no existe, crea una nueva instancia usando super(Singleton, cls).new(cls) y la asigna a instance. Si ya existe, simplemente devuelve la instancia existente. De esta forma, se garantiza que nunca habrá más de una instancia de la clase Singleton.

El diseño que hemos visto es el más genérico y válido para la inmensa mayoría de los lenguajes orientados a objetos. Aprovechando algunas de las características propias de Python podemos simplificarlo ligeramente para que quede una implementación más compacta y sencilla:

class Singleton(object):

    def __new__(cls):
	    if not hasattr(cls, 'instance'):
		    cls.instance = super(Singleton, cls).__new__(cls)
	    return cls.instance

    def some_business_method(self):
        # Método de ejemplo que podría realizar alguna operación
        print("Método de negocio ejecutado.")

s1 = Singleton()

5. Ventajas y Desventajas

6. Aplicaciones Prácticas

El patrón Singleton se utiliza comúnmente en situaciones donde se necesita un punto de acceso global a un recurso único y centralizado. Algunos ejemplos prácticos incluyen:

7. Ejemplos del Mundo Real

El siguiente código muestra la implementación de la clase Config, que utilizo en todos mis proyectos. Esta clase se encarga de leer el archivo de configuración de la aplicación y proporciona a los demás componentes un acceso centralizado para leer y escribir diferentes parámetros de configuración. La implementación del patrón Singleton en esta clase ofrece dos ventajas clave:

from configparser import ConfigParser, NoSectionError, NoOptionError

from src.model.exception.ModelException import ModelException

DEFAULT_FILE = './src/config/config.ini'

class Config():

    def __new__(cls, file = DEFAULT_FILE):
        if not hasattr(cls, '_singleton'):
            cls._singleton = super(Config, cls).__new__(cls)
        return cls._singleton
    
    def __init__(self, file = './src/config/config.ini'):
        if not hasattr(self, '_config'):
            self._config =  ConfigParser()
            try:
                self._config.read(file)
            except Exception as e:
                raise ModelException(f'Error reading config file: {file}')
    
    def _get(self, section : str, parameter : str) -> str | int | float | None:
        try:
            return self._config.get(section, parameter)
        except NoSectionError as e:
            raise ModelException(f'Error setting section {section}')
        except NoOptionError as e:
            raise ModelException(f'Error setting parameter {parameter} in section {section}')
        except Exception as e:
            raise ModelException('Error setting a parameter in config file')

    def _set(self, section: str, parameter : str, value : float) -> None:
        try:
            self._config.set(section, parameter, value)
        except NoSectionError as e:
            raise ModelException(f'Error getting section {section}')
        except NoOptionError as e:
            raise ModelException(f'Error getting parameter {parameter} from section {section}')
        except Exception as e:
            raise ModelException('Error accesing a parameter from config file')


    def get_str(self, section : str, parameter : str) -> str | None:
        try:
            return self._get(section, parameter)
        except Exception as e:
            raise e
        
    def set_str(self, section: str, parameter : str, value : str) -> None:
        try:
            return self._set(section, parameter, value)
        except Exception as e:
            raise e
        
    def get_int(self, section : str, parameter : str) -> int | None:
        try:
            return self._get(section, parameter)
        except Exception as e:
            raise e
            
    def set_int(self, section: str, parameter : str, value : int) -> None:
        try:
            return self._set(section, parameter, value)
        except Exception as e:
            raise e
        
    def get_float(self, section : str, parameter : str) -> float | None:
        try:
            return self._get(section, parameter)
        except Exception as e:
            raise e
        
    def set_flotat(self, section: str, parameter : str, value : float) -> None:
        try:
            return self._set(section, parameter, value)
        except Exception as e:
            raise e
        
    def write(self, file = DEFAULT_FILE) -> None:
        try:
            with open(file, 'w') as fp:
                self._config.write(fp)
        except Exception as e:
            raise ModelException(f'Error writing config file: {file}')

Podéis acceder al proyecto completo en GitHub: Newswave. Este proyecto automatiza la recopilación de noticias desde fuentes RSS y envía un resumen diario por correo electrónico. Es un ejemplo práctico de cómo aplicar el patrón Singleton para gestionar la configuración y el acceso a recursos compartidos en una aplicación real.

8. Variaciones y Alternativas

El patrón Singleton tiene algunas variantes y adaptaciones que pueden ser útiles dependiendo del contexto y de los requisitos de la aplicación. Las dos variantes más comunes son Lazy Instantiation y el Monostate Singleton.

8.1. Lazy Instantiation

La instanciación tardía es una técnica utilizada para retrasar la creación de la instancia única de la clase Singleton hasta que sea realmente necesaria. Esta técnica es especialmente útil en los siguientes casos:

8.2. Monostate Singleton

El Monostate Singleton, también conocido como Borg Pattern, es una variación del patrón Singleton en la que todas las instancias de la clase comparten el mismo estado en lugar de tener una única instancia. En este enfoque, se permite la creación de múltiples instancias de la clase, pero todas comparten los mismos atributos de clase, logrando un comportamiento similar al Singleton tradicional.

9. Referencias y Recursos Adicionales

  1. Learning Python Design Patterns (Second Edition)
    • Autor: Chetan Giridhar
    • Editorial: Packt Publishing
    • Año: 2017
    • Enlace: Learning Python Design Patterns – Second Edition
    • Descripción: Este libro ofrece una guía completa sobre los patrones de diseño en Python, proporcionando ejemplos prácticos y técnicas para implementar estos patrones de manera efectiva. Es una excelente referencia para desarrolladores que buscan comprender y aplicar patrones de diseño en proyectos Python.
  2. Design Patterns: Elements of Reusable Object-Oriented Software
    • Autores: Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
    • Editorial: Addison-Wesley
    • Año: 1994
    • Enlace: Design Patterns: Elements of Reusable Object-Oriented Software
    • Descripción: Conocido como el libro de los “Gang of Four” (GoF), esta obra es un clásico en el campo de los patrones de diseño de software. Proporciona una base sólida en los patrones de diseño orientados a objetos y es fundamental para entender los principios detrás de los patrones y su aplicación en el diseño de software.