PATRÓN SINGLETON

 

VISTA AÉREA DE UNA PARTE DEL PARQUE NACIONAL DE LAS CAÑADAS DEL TEIDE, CON EL CONTORNO DE LA ISLA DE LA PALMA DIBUJÁNDOSE A LA DERECHA, SOBRE UN MAR DE VERANO, CENTRO Y CORAZÓN DE TENERIFE.



      El patrón Singleton es precisamente éso: un patrón concreto, un pattern, en inglés, entre otros más patrones, eso sí, el más sencillo, entre los más socorridos y cómodo de emplear, y muy común entre otros lenguajes de programación como Java, C#, PHP, etc.

Se trata de un patrón de diseño CDP, de tipo creacional, (Creational Design Pattern) cuyo fundamento lo asemeja a una constante (recordemos que Python no cuenta con una instrucción específica para crear y mantener constantes, y que lo más parecido que podemos conseguir es a través de las tuplas, aprovechando en este sentido su carácter inmutable, que no se puede modificar, y los criterios de visibilidad (v. VISIBILIDAD), con el doble guión bajo antecediendo al nombre de una variable donde almacenamos la información susceptible de ser constante y con la que generamos un atributo 'privado'), en tanto en cuanto es capaz de generar un objeto/instancia (por tanto, debemos crear una clase ad hoc), que va a ser ÚNICO, y al que sólo podremos acceder a través de un modo determinado desde cualquier otra parte del código o, incluso, desde otro código distinto.

Podemos pensar en una comunidad de vecinos, donde sólo puede existir un único presidente de la comunidad y su equipo de gobierno, quienes controlan y procesan todas las actuaciones propias de la gestión de los bienes comunes a los propietarios de la comunidad; escaleras, hall de acceso, fachada, patios de luces, azoteas y solariums, etc., y permiten el acceso a éstos a cada vecino particular. Obviamente, sólo puede existir una única presidencia aunque, como veremos en el ejemplo, es posible "elegir" un nuevo presidente que "heredará" todo lo gestionado por el equipo de gobierno anterior.






      UNA ÚNICA CLASE ➡ UNA ÚNICA INSTANCIA ➡ UN ÚNICO PUNTO DE ACCESO.





El patrón Singleton pretende modelizar una clase de la que se instancie un único objeto al que sólo se pueda acceder a través de un único punto de acceso (access point) al que podremos acudir cuando queramos en nuestro código de una forma análoga a las variables globales que ya conocemos.

Suele recurrirse al patrón Singleton cuando desarrollamos un código (o una parte de un código) donde necesitamos tener un control sobre quién y cómo acceder a un determinado recurso, como puede ser un logging, quizás el uso más común, desde distintas partes de nuestro programa; determinados archivos o ficheros, como por ejemplo, una imagen del logo de una empresa que implementaremos en diferentes archivos PDF que se generan, según su función, en distintos módulos de nuestro proyecto; acceder a registros de una base de datos dese distintas partes de un mismo programa, etc.

Por si fuera poco, la modelización de una clase única para la instanciación de un objeto/instancia también único conlleva una protección especial contra la temida sobreescritura, lo que redunda en la eficacia e integridad de nuestro código, de tal manera que podamos acceder a esos recursos que mencionábamos antes con total seguridad desde cualquier punto de un determinado módulo, e incluso, desde otros módulos distintos, sin temor a que caigamos en el desastre de la sobreescritura.

Traemos aquí un trozo de texto del sitio web PHAROS donde se nos ofrece un ejemplo magnífico de lo que acabamos de decir: "[...]las conexiones a la base de datos se realizan una vez en nuestros programas, y el mismo objeto se utiliza para realizar operaciones en nuestra base de datos en toda la aplicación. Si diferentes partes de nuestra aplicación pudieran crear sus propias conexiones de base de datos, pueden surgir problemas de integridad con el tiempo, ya que cada parte intenta acceder a la base de datos por su cuenta."

Ahora que ya sabemos qué es un patrón Singleton y para qué sirve, ¿cómo se codifica ésto?

     
      PUES BÁSICAMENTE LO QUE VAMOS A HACER ES MODELIZAR UNA CLASE Y, DENTRO DE ELLA, DECLARAMOS UNA variable de clase, QUE SERÁ ESE OBJETO ÚNICO QUE CODICIAMOS. JUSTO A CONTINUACIÓN LLAMAMOS AL MÉTODO ESPECIAL CONSTRUCTOR __new__(cls) QUE SERÁ EL ENCARGADO DE CONSTRUIR UNA Y OTRA VEZ EL MISMO OBJETO ÚNICO CADA VEZ QUE LLAMEMOS A LA CLASE.




Vamos con un ejemplo.



En 1. modelizamos la clase Singleton que, como su nombre indica, creará nuestro patrón Singleton (obviamente, a la clase le podíamos haber dado cualquier nombre, el que quisiéramos: el resultado sería exactamente el mismo), pero con la salvedad, fundamental para conseguir el efecto de "unicidad" que buscamos con este patrón, de que en su zona de paréntesis llamamos ex profeso a la superclase madre de todas las clases habidas y por haber en Python: object. Sí, sí, recordemos, la misma de la que derivan los métodos especiales (v. MÉTODOS ESPECIALES) que podemos llamar sobre nuestras clases, como __init__(), __str__() y __new__() que utilizamos en la confección de nuestro patrón Singleton, como podemos ver en nuestro ejemplo. De hecho, el quid del patrón Singleton está en object.

En 2. inicializamos la clase con el método especial __init__(self). Esto es fundamental en la versión de Python que estamos utilizando: en nuestro caso, la 3.8.0. En otras versiones anteriores, podemos prescindir de la inicialización. Como devolución del método nos basta usar pass, pues sólo nos interesa utilizar el método método especial __init__(self) de manera meramente instrumental.

En 3. encapsulamos (visibilidad) la variable de clase __instance con un doble guión bajo para que no sea accesible (acceso privado, no se puede modificar), de manera que su valor inicial, None,  permanezca constante, mientras que la segunda variable de clase, clave, se inicializa igualmente con el valor None, aunque sí resulta accesible mediante la sintaxis adecuada.

En 4. definimos un segundo método para el que llamamos al método especial __str__(self) que, de manera interna, esto es, no necesita ser llamado de manera explícita, devuelve como cadena o string el valor de la variable de claseclave.

En 5. tenemos el meollo de la cuestión a nivel de código: instanciamos el método constructor de las clases, el constructor "real", __new__(cls), y no el inicializador __init__(self), que es aquél por el que la clase se construye (comienza su existencia). Obviamente, el método especial __new__(cls) llevará como argumento cls y no self porque la clase aún no existe.

El cuerpo de la función/método especial __new__(cls) sólo lleva un condicional if, que aplica sobre la variable de clase __instance, por lo que la llama a partir de la clase mediante la consabida sintaxis del punto: Singleton.__instance. La condición es que __instance is None. Como no tenemos acceso a esta variable de clase, su estado no varía, por lo que el resultado de la condición siempre es True, en el primer análisis. Y si esto es así, a la variable se le asigna el constructor (__new__(cls)) sobre, ojo, 👀, la superclase matriz de Python object, de quien hereda nuestra clase modelizada Singleton, siendo esta "situación" o "estado" de la clase lo que retorna nuestro constructor, lo que destruye cada vez su valor original asignado a la instancia recién creada pero no la instancia en sí, que asume cada nuevo valor de cualquier nueva instancia (k1, k2, etc.) dado que "recreamos" cada vez nuestra variable de clase clave con cada "limpieza" del objeto object.

TROCITO DE COSTA NORTE DE TENERIFE, AZUL, SURF Y MARESÍA.




      
      COMO CURIOSIDAD DIREMOS QUE ESTE MISMO PATRÓN SINGLETON ES EL QUE UTILIZA EL PROPIO LENGUAJE PARA IMPLEMENTAR EL OBJETO None Y LOS BOOLEANOS True Y False.



PROS Y CONTRAS



      PROS:



  • El patrón Singleton se justifica por su capacidad para generar con absoluta seguridad un único objeto/instancia de una clase disminuyendo la posibilidad de que nuestro programa adolezca de un comportamiento anómalo a la hora de acceder a dicho objeto/instancia, comúnmente por el solapamiento indeseado de los espacios de nombres (namespaces).
  • La sencillez de su implementación y el hecho de que todo parta de una clase dedicada hija (en el ejemplo, class Singleton) que hereda o extiende de la superclase por antonomasia de Python, object, nos proporciona un alto grado de flexibilidad y manejo.


      CONTRAS:



  • El patrón Singleton no se comporta de manera adecuada cuando desarrollamos un programa en un entorno multihilo (threading), esto es, en resumidas cuentas, cuando nuestro programa es capaz de ejecutar dos o más procesos a la vez: si, siguiendo el ejemplo anterior, creamos un programa multihilo donde existan tres procesos que llaman a la vez a la clase Singleton, se generarían tres claves distintas. Aún más, si no se ejecutan las llamadas exactamente al mismo tiempo (en milisegundos) la clave generada en la última llamada (por ejemplo, k2, en la devolución que arrojó nuestro ejemplo) se superpondría a las dos anteriores anulándolas. Vamos, un desastre...
  • En Programación existe un principio que se denomina Principio de Responsabilidad Única, SRP, por sus siglas en inglés, que establece que cada módulo, clase o método/función en un programa debe ser responsable de una (y sólo una) parte de la funcionalidad del programa, y debe por tanto "encapsular", aislar, confinar, esa parte del programa que queda bajo su responsabilidad. Pues bien, es probable que el patrón Singleton termine por manejar varias "responsabilidades" a lo largo de la ejecución del programa. Este concepto, como vemos, tiene concomitancias con el expuesto más arriba.
  • Dado que el patrón Singleton permanece en estado activo durante todo el ciclo de vida de un programa o aplicación, tendremos que tenerlo en cuenta cuando vayamos a hacer pruebas unitarias, ya que podría requerir cambios en función de los diferentes casos de prueba al que sometamos al programa.
  • Un último problema lo plantea las variables globales (en nuestro caso, la variable de clase, __instance, en el ejemplo,  que, al fin y al cabo, es una suerte de variable global sui generis) que, por una parte, justo por lo que comentábamos en el segundo punto, puede haber modificado su valor sin que  el programador se haya percatado de ello, generando varias referencias a un mismo objeto: k, k1 y k2, en nuestro ejemplo. Además se produce una acoplamiento, attachment, dada la dependencia de las clases a la variable global



Patrón Singleton perezoso (lazy)



      En programación, una inicialización perezosa (lazy) es una técnica que utilizan algunos programadores para RETRASAR la instanciación de un objeto dentro de una clase hasta la primera ocasión en que se necesita crearlo/instanciarlo. Para conseguirlo,  se suele recurrir a la adición de una forma supletoria de llamar a la clase evitando hacerlo de manera directa. Esto se puede hacer perfectamente con el patrón Singleton en la mayoría de los casos construyendo un método de clase, @classmethod, (v. MÉTODOS DE CLASE, ESTÁTICOS Y @PROPERTY) que envuelva una función que contenga la ejecución del patrón en su propio cuerpo o ámbito.

Vamos a verlo en el siguiente ejemplo:


 En 1. modelizamos la clase Singleton aunque, a diferencia del ejemplo anterior, no heredamos o extendemos de la superclase object de Python: usaremos un "atajo" como veremos más adelante a través de la autorreferencia cls del constructor __new__(cls). ¡Y eso que en ninguna parte de nuestro código lo vamos a utilizar expresamente! Si alguien a estas alturas dudaba aún de la plasticidad de Python...

En 2., a diferencia de nuestro ejemplo anterior, pedimos la introducción de una clave que se almacenará en la variable de clase clave mediante una función input() normal y corriente. Aquí tenemos, junto a __instancia, a la que asignamos el valor inicial None, nuestras dos variables globales sui generis.

En 3.,... ¡Ah! Nuestra forma añadida para retrasar la instanciación de un objeto a partir de la clase Singleton, nuestro "código perezoso", nuestra técnica lazy: un método de clase@classmethod, que envuelve  a una función definida por nosotros mismos, crearInstancia, en 4., que, muy importante y he aquí el quid de la cuestión, llevará como argumento obligatorio la autorreferencia cls del método especial constructor __new__(cls), con lo cual, de manera elíptica está INVOCANDO al mencionado método especial constructor. Ahí está, presente, aunque no lo veamos incorporándolo a nuestro código de manera explícita como sí lo hacemos en el punto 5. de nuestro primer ejemplo.

En 5., y ya dentro del ámbito de la función crearInstancia, instauramos un condicional: si no existe un valor para la variable de clase __instancia una vez acaba de crearse (cls), entonces, en 6., llamamos al constructor (recordemos, usando el consabido "atajo" de su autorreferencia cls) sobre la variable de clase __instancia a través de la sintaxis del punto y lo igualamos a la llamada a la clase Singleton que se ejecutará.

En 7. instanciamos un método usando para ello por nombre el propio método especial __str__(self) para que devuelva como una string el valor de nuestra clave, en 8., con cada instancia que creemos con cada nueva instancia que creemos. 

En 9., tenemos dos instancias, instancia1 e instancia2,  que se generan a partir de sendas llamadas a la clase Singleton. Como vemos en los resultados, ambas instancias tienen la misma clave y son iguales.



Hemos visto un par de ejemplos de este patrón donde, repetimos, modelizamos una clase que nos faculta para instanciar un único objeto, además de un punto de acceso, access point, al mismo de manera que podamos acceder a él desde cualquier punto de nuestro código. ¿Y para qué sirve el invento? Pues básicamente para dotar de solidez y eficacia a aquéllos procesos de programación donde varios objetos-clientes (aquéllos objetos que necesitan obtener datos de algo, por ejemplo, de una base de datos para poder desarrollar la función para la que lo hemos diseñado) necesitan acceder a un objeto determinado (pongamos por caso, siguiendo con el ejemplo, a un registro concreto, específico de la base de datos, una contraseña, sin ir más lejos) de manera que se nos garantice que en ese momento sólo existe una instancia capaz de hacer esto: el objeto que instanciamos del patrón Singleton.

Imaginemos que gozamos de un precioso y extenso jardín en nuestra casa. Un jardín repleto de plantas, veredas, alguna que otra fuente, parterres, macetones y jardineras que necesitan el cuidado y la atención de un jardinero profesional, de confianza. Pues para eso creamos la clase Jardinero que será un patrón Singleton, y en cuyo ámbito explicitaremos las características ➡ atributos/propiedades que queremos que tenga nuestro jardinero: años de experiencia, titulación, recomendaciones, etc... y capacidades métodos: habilidades de corte, construcción de semilleros, poda, rastrillaje, etc. Con esta información (características y capacidades) instanciamos al jardinero Pedro Pérez, que será el único objeto que a través de la puerta del jardín (el punto de acceso, el access point) podrá llegar al espacio donde desarrollar su labor y que es el ámbito para el que lo hemos instanciado(la base de datos que citábamos más arriba como ejemplo) y no a otro de nuestra casa (un jardinero trabaja en el jardín, un cocinero en la cocina, un chófer en la cochera, etc.). De este modo, cada vez que necesitemos hacer un trabajo en el jardín llamamos a nuestro jardinero de confianza, Pedro Pérez, nuestra única y flamante instancia de la clase Jardinero que es, a la vez, nuestro patrón Singleton, y sólo a él.

Como hemos podido apreciar en los ejemplos, para conseguir esto, desde el punto de vista de la codificación, lo ideal sería crear un método privado, de la misma manera que podemos crear, como acabamos de decir, una variable de clase a la que privatizamos con los guiones bajos, (__instance, por ejemplo), y luego crear un método estático (ya sabemos que un método estático no puede acceder ni a la clase ni a los objetos instanciados a partir de ella, lo que nos garantizaría esa unicidad que buscamos, es decir, ese Pedro Pérez que queremos y sólo Pedro Pérez y nada más), pero Python nos dice que "de eso nada, monada", por lo que para poder implementar un patrón Singleton debemos configurar procesos alternativos, crear atajos como quien dice, para poder construirlo.

Y para obtener un patrón Singleton debemos modelizar una clase Singleton que asuma la función del constructor __new__(cls), el alma de la cosa, que asumirá la función de instanciar los objetos de la clase de una forma personalizada.

VALLE INTERIOR DE ANAGA, CON LA CRESTERÍA DE CARMONA AL FONDO A LA DERECHA, Y LAS NUBES DE OTOÑO AMENAZANDO LLUVIA, MACIZO DE ANAGA, NORESTE DE TENERIFE.




      HOCIQUEANDO, Y NUNCA MEJOR DICHO TRATÁNDOSE DE MÍ, HE DESCUBIERTO QUE LA PALABRA CLAVE ES "PERSONALIZAR" EN ESTO DEL PATRÓN DE MARRAS. ASÍ ME HE ENTERADO DE QUE EXISTEN FORMAS DIFERENTES DE IMPLEMENTAR EL PATRÓN. MOSTRAREMOS EJEMPLOS DE ALGUNAS DE ELLAS Y QUE CADA CUAL DECIDA CUÁL LE CONVENCE MÁS.


     


        

      1) EL BORG DE ALEX MARTELLI:



      Sostiene Alex Martelli, desarrollador experimentado y pythonista de pro, que los sufridos programadores, a veces, necesitan instanciar más de un objeto a partir de la misma clase, donde cada objeto comparta el mismo estado, esto es, que comparta el mismo conjunto de valores asignados a sus atributos/propiedades, sin que ello signifique que compartan una misma identidad: mismos valores pero distinta identidad.

Fijémonos que en nuestro primer ejemplo de patrón Singleton modelizábamos una clase para recrear el patrón cuyas instancias, k, k1 y k2 compartían la MISMA identidad: k is k1 is k2 era igual a True. Se trata ahora de que las instancias compartan los mismos valores, como sería el caso de kk1 y k2, pero no la misma identidad.

Para lograr esto, Alex Martelli desarrolló su propia variable de patrón Singleton a la que denominó Borg. Vamos a verlo:


El Borg de Alex Martelli parte de la creación de dos clases: una primera, la clase Borg propiamente dicha, que será la clase padre o superclase de la clase Singleton, que se modeliza después y que será hija de la anterior, heredando ambas, cómo no, no lo olvidemos, de la superclase de clases object, presente siempre en cualquier clase, predefinida o modelizada por nosotros en Python, y de donde se toman los métodos especiales.

En el ámbito de la clase Borg declaramos una única variable de clase con carácter de privacidad, en esta ocasión, con un único guión bajo, underscore, lo que le otorga un nivel de acceso laxo, de tal modo que sólo es una advertencia para el programador de que aquí "no se debe tocar nada". Tenemos así la variable de clase _shared_estate, "estado compartido", traducida del inglés, a la que asignamos en virtud del operador = de asignación un diccionario vacío. 

A continuación inicializamos la clase con nuestro archiconocido método especial __init__(self), con la consabida autorreferencia self que apunta a la propia clase Borg (hemos iniciado a la misma clase Borg) volviéndola "operativa" para instanciar nuevos atributos/propiedades y métodos que podremos aplicar luego sobre cualquier objeto que instanciemos a partir de ella.

En el ámbito de este método especial __init__(self) de inicialización de la clase Borg instanciamos un único atributo/propiedad: __dict__, que viene a ser un atributo especial (ojo, atributo especial, no método especial, porque dentro de una función de inicialización como __init__(self) deberían instanciarse sólo atributos/propiedades y no llamarse a otros métodos, especiales o no, que deben instanciarse en ámbitos diferentes al del propio método inicializador. Además, visualmente podemos comprobar que __dict__ no lleva paréntesis), y que determina la instanciación de un diccionario o cualquier otro objeto de mapeo (mapping) que se utiliza para almacenar los atributos de escritura de un objeto. ¿De cuál? Pues el de la propia variable de clase_shared_estate, que es lo que asignamos a nuestro objeto self.__dict__. Observemos que la variable de clase usa también de la autorreferencia self, con la sintaxis de punto preceptiva. Lógico, ya que estamos diciendo que nuestra variable de clase _shared_estate pertenece a la clase Borg y no a otra. Por esta razón, se le asigna a la variable de clase _shared_estate un objeto diccionario, {}, para que pueda ser asignado a un atributo especial __dict__ que, como acabamos de decir, admite solamente un diccionario u objeto de mapeo: __dict__ = {}.

Hecho esto, concluimos nuestra clase padre Borg y modelizamos la clase Singleton, que es la encargada de desarrollar el patrón Singleton. Esta clase será hija de la clase Borg por lo que pasamos el nombre de la clase padre en la zona de herencias, entre los paréntesis, de la clase Singleton, que heredará o extenderá de la clase Borg, y en segunda instancia, entre bambalinas, de manera elipsada, como cualquier otra clase en Python, de la superclase de clases object.

Y con esto tenemos una de las características singulares del Borg de Martelli: la clase que implementa el patrón Singleton no hereda sólo de object, sino que lo hace también de una clase padre previamente modelizada, la clase Borg, y en consecuencia, de todo cuanto hayamos instanciado en ella.

A continuación inicializamos la clase Singleton de nuevo con el método especial ad hoc __init__(self) que, en esta ocasión, y en su zona de parámetros, justo a continuación de la autorreferencia self preceptiva, lleva un segundo argumento obligatorio: arg.

En la línea de código siguiente, llama a la clase padre Borg (para esto necesitábamos heredarla en la zona de herencias de la clase Singleton) y en concreto, mediante la sintaxis del punto, el inicializador de la clase Borg

En la tercera línea de código, última del inicializador def __init__(self, arg) de la clase Singleton, instanciamos una variable, val a la que asignamos el valor de arg, aquél que pasamos como argumento obligatorio en el inicializador de la clase Singleton.

Finalmente, instanciamos el método especial __str__(self) para que nos devuelva una cadena con el valor asignado a la variable val.

Ahora que ya conocemos bien la estructura del Borg de Alex Martelli... ¿Cómo funciona la cosa? La genialidad, la magia, está en el... ¡diccionario!. Recordemos que la idea de Martelli era construir un patrón Singleton que devolviera objetos que compartieran los mismos valores (estados) pero con su propia identidad cada uno. ¿Y qué tipo de dato en Python nos ofrece esta posibilidad por su propia naturaleza? Los diccionarios, donde los valores (values, y de val de values viene el nombre de la variable val instanciada en el inicializador de la clase Singleton, en su segunda línea de código) pueden ser los mismos pero las claves (keys) no. Así, los valores pueden repetirse todas las veces que queramos pero como están configurados como elementos de un diccionario (_shared_state) en pares clave/valor, cada clave (key) que genere el objeto especial __dict__ será única y exclusiva para cada valor de arg que, como vemos, se asigna en última instancia a val, el valor (value), pudiendo repetirse hasta el infinito pero no así su clave, que SIEMPRE será diferente, proporcionando al objeto esa IDENTIDAD (id) distinta que se demandaba. 

En la resolución del ejemplo, instanciamos una variable n a partir de la clase Singleton, donde pasamos el argumento obligatorio arg que nos exige su inicializador: hemos elegido "Jose". Tengamos en cuenta que, a partir de la asignación a la variable val del valor de arg, este "Jose" será nuestro valor actual, éso que SÍ puede repetirse cuantas veces queramos. Lo comprobamos a través de n.val, que internamente llama al método especial  __str__(self) y nos devuelve la cadena "Jose".

Instanciamos un nuevo objeto de la clase Singleton, n1, y repetimos la misma operación con la cadena "Ana" como valor de arg. y lo validamos igualmente a través de la llamada  n1.val, que nos devuelve, efectivamente, "Ana".

A continuación pedimos que nos imprima los valores actuales de los objetos n y n1, donde ambos asumen el último valor asignado: "Ana".  Esto es así porque cada vez que instanciamos un objeto de la clase Singleton, el argumento arg se empareja cada vez con la variable val, que actúa como clave (key) asumiendo el nuevo valor como valor (value) y modificándolo en el antiguo para mantener la coherencia de la estructura. Lo vemos abajo:




Como podemos ver en 1. y 2., si llamamos al diccionario que generamos por defecto a través del atributo especial __dict__, la clave (key) siempre es la misma, "val" mientras que su valor (value) difiere cada vez; primero "América" y luego "Oceanía", igualándose al último valor, "Oceanía", al final del proceso, donde conseguimos que los valores sean el mismo y se guarda en memoria con un identificador (id) específico. Si observamos bien, podemos pensar que existe una contradicción: ¿No habíamos quedado en que los diccionarios no podían contener dos o más elementos con la misma clave (key)? Sí. Pero fijémonos: con cada instanciación, primero n, luego n1, n2, n3, y así hasta el infinito, se llama a la clase Singleton, que a su vez llama a la clase padre Borg de la que hereda, y en cada ocasión se "reinicia" el diccionario _shared_state, pero no se suma al anterior (dict.update()). Como cada vez que se "reinicia" el diccionario se genera un objeto dict ("val":"..."), este objeto se guardará en memoria con un identificador propio que conservará, (el objeto/instancia n"val":"América", tiene su propio id; el objeto/instancia n1 ➡ "val":"Oceanía", tiene a su vez el suyo propio, etc.), con lo que cada uno conserva su propia identidad aunque todos ellos tendrán al final el mismo valor.





      2) FUNCIÓN INTEGTRADA hasattr():




      Entre las muchas funciones integradas de las que nos provee el lenguaje disponemos de la función booleana hasattr(object, name), que comprueba si un objeto o instancia (object) contiene  tal o cual atributo/propiedad (name): obviamente, si el objeto contiene el atributo/propiedad pasado como cadena, devuelve True; en caso contrario, False. Veamos como podemos aprovechar esta cualidad para aplicar el patrón Singleton:




      3) CONTROL DE EXCEPCIONES: try/except:




      La verdad que el manejo y control de excepciones en Python da para mucho, la verdad: usando esta herramienta podemos acceder "por pasiva" al atributo instance de la clase Singleton que desarrolla el patrón mediante la captura de una excepción específica para el caso, AttributeError, que sería lanzada en el caso de que nuestra clase Singleton no contase con el atributo de clase instance. Veremos que esta opción tiene evidentes concomitancias con el caso anterior.




       4) USANDO UN DECORADOR:




      Los decoradores (decorators, v. DECORATORS) pueden utilizarse para implementar nuestro patrón Singleton. Una de las características que definen a los decoradores es su reusabilidad, tan querida en Python, con lo cual podemos construir una clase Singleton que podamos llamar en cualquier punto de nuestro código como punto de acceso, access point, cada vez que tengamos que construir un objeto único.



     5) EL OBJETO None:



      
      Una forma muy sencilla y socorrida de implementar un patrón Singleton.


      6) USANDO @classmethod COMO CONSTRUCTOR ALTERNATIVO:





      Se trata en esta ocasión de implementar un método de clase, @classmethod, (v. MÉTODOS DE CLASE, ESTÁTICOS Y @PROPERTY) cuya función sería la de crear objetos partiendo de una o más condiciones proporcionadas por el programador. Dentro del lenguaje podemos tomar como ejemplo al método de clase fromkeys(iterable, value=None) de los diccionarios (siempre los diccionarios, ¿nos damos cuenta?). Este método de los diccionarios permite construir un diccionario nuevo (de aquí lo de "alternativo") a partir de un iterable: cada elemento de ese iterable será, por defecto, la clave, key, del nuevo diccionario, mientras que su valor asignado para establecer el consabido par clave/valor, value, será aquél que le pasemos que, por defecto, será None. Recordemos que en los diccionarios las claves, keys, por el que podemos obtener el valor asociado que es lo que nos interesa en lugar de los índices, que siempre son distintos porque apuntan a posiciones en una secuencia que se expresa de izquierda a derecha, en el caso del resto de iterables como las listas o las tuplas, deben ser diferentes, mientras que los valores, values, pueden ser los mismos.

Veamos el funcionamiento básico de fromkeys(iterable, value=None):

 
Veámoslo integrado en un patrón Singleton:

Finalizamos con un pequeño esquema para ayudar a entenderlo un poco mejor.



OCASO EN LA COSTA DE PUNTA DEL HIDALGO, CON EL ROQUE DE LOS DOS HERMANOS EN EL ÁNGULO SUPERIOR DERECHO, NORTE DEL MACIZO DE ANAGA, NORESTE DE TENERIFE.





No hay comentarios:

Publicar un comentario