Singleton
En enxeñaría de software, o patrón de deseño Singleton (Instancia Única) emprégase para garantir que unha clase soamente teña unha instancia, ademais de proporcionar un punto de acceso global a esa instancia. Trátase polo tanto dun patrón de creación.
É de utilidade cando se necesita exactamente un obxecto para coordinar accións nun sistema. Este concepto pode xeneralizarse para restrinxir a instanciación a un certo número de obxectos.
Índice |
Motivación [editar]
En certos contextos, é importante que algunhas clases teñan exactamente unha instancia que sexa facilmente accesible (por exemplo, ter un único xestor de ventás nunha aplicación).
O uso dunha variable global fai accesible a un obxecto, pero non evita que se poidan crear múltiples instancias de obxectos. Unha solución mellor é facer que sexa a propia clase a responsable da súa única instancia. A clase pode garantir que non se poida crear ningunha outra instancia (restrinxindo o acceso ao construtor) e pode así mesmo proporcionar un modo de acceso á instancia.
Aplicabilidade [editar]
O uso do patrón é axeitado cando:
- Deba existir unha única instancia dunha clase, que debe ser accesible aos clientes dende un punto de acceso ben coñecido.
- A única instancia pode ser estendida (subclase), e os clientes deberían poder utilizar a instancia estendida sen modificar o seu código.
Estrutura [editar]
Participantes [editar]
- Instancia Única (Singleton): define un método de clase que permite aos clientes acceder á instancia única. Normalmente é responsable de crear a súa única instancia.
Colaboracións [editar]
Os clientes acceden á instancia do Singleton a través da operación definida con esa finalidade.
Consecuencias [editar]
As principais vantaxes do patrón Singleton son:
- Proporciona un acceso controlado a súa única instancia grazas á encapsulación da mesma, podendo ter un control estrito sobre o acceso a ela por parte dos clientes.
- Reduce o espazo de nomes debido a evitar o uso de variables globais para almacenar instancias.
- Posibilita o refinamento de operacións e representación, é dicir, pódense crear subclases da clase Singleton, sendo sinxelo configurar os sistemas cunha instancia desta clase estendida.
- Permite un número variable de instancias, sendo necesario cambiar unicamente a operación que proporciona acceso á instancia do Singleton.
- Outorga unha maior flexibilidade con respecto ás operacións de clase, pois o uso destas dificulta o cambio dos deseños para permitir máis dunha instancia.
Implementación [editar]
Á hora de utilizar o patrón Singleton, débense ter en conta algúns aspectos de implementación, entre os que destacan:
- Garantir a unicidade da instancia da clase.
- Estender a clase Singleton.
Garantir a unicidade da instancia da clase [editar]
O patrón Singleton fai que a única instancia sexa unha instancia normal da clase, pero coa particularidade de que dita clase soamente permite crear unha instancia. Adóitase conseguir isto mediante a ocultación da operación que crea a instancia tras unha operación de clase. Esta operación ten acceso á variable que contén a instancia e asegura que a variable estea inicializada con dita instancia previamente a devolver o seu valor. Este enfoque asegura que un Singleton se crea e inicializa antes do seu primeiro uso. Por exemplo, en Java pódese definir a operación de clase mediante un método estático obterInstancia() da clase Singleton, que tamén define unha variable estática _instancia que contén a súa única instancia.
public class Singleton { private static Singleton _instancia = null; private Singleton() { } public static synchronized Singleton obterInstancia() { if (_instancia == null) { _instancia = new Singleton(); } return _instancia; } }
Os clientes acceden ao Singleton exclusivamente a través do método obterInstancia(). A variable _instancia inicialízase a null e o método estático obterInstancia() devolve o seu valor, inicializándoa coa única instancia en caso de que sexa null. obterInstancia() utiliza inicialización "preguiceira" (lazy initialization): o valor que devolve non se crea e almacena ata que se accede a el por primeira vez.
Por outra banda, o construtor declárase como privado: un cliente que trate de crear unha instancia de Singleton directamente obterá un erro en tempo de compilación. Isto garante que soamente se pode crear unha instancia. Ademais, e debido a que _instancia é de tipo Singleton, o método obterInstancia() pode asignar a esta variable unha instancia dunha subclase de Singleton.
Nótese o uso de synchronized para permitir a súa utilización en aplicacións multi-fío. No caso de que dous fíos executen o método de creación simultaneamente cando a instancia do Singleton aínda non existe, ambos deben comprobar a instancia do Singleton e soamente debe creala un deles. Por iso é típico o uso de exclusión mutua na clase que indica que o obxecto está sendo instanciado.
Unha alternativa sería o uso de inicialización "impaciente" (eager initialization), na que sempre se crea a instancia. Este tipo de inicialización emprégase cando o programa sempre necesita unha instancia ou cando o custo de crear a instancia non é demasiado alto en términos de tempo/recursos.
public class Singleton { private static Singleton _instancia = new Singleton(); private Singleton() { } public static Singleton obterInstancia() { return _instancia; } }
Este método ten varias vantaxes:
- Non se necesita utilizar
synchronizedno métodoobterInstancia(). - O programador non ten que preocuparse da creación de instancias, pois Java garante que a inicialización xa estará feita antes de que o código sexa accedido por calquera clase.
Sen embargo, tamén ten algúns inconvenientes. Destaca principalmente:
- Se o programa non necesita a instancia do Singleton, pode ser unha mellor opción o uso da inicialización "preguiceira".
Estender a clase Singleton [editar]
O principal problema non é definir a subclase, senón crear a súa única instancia de maneira que os clientes a poidan utilizar. En esencia, a variable que fai referencia á única instancia debe ser inicializada cunha instancia da subclase. A técnica máis sinxela é determinar que Singleton queremos usar na operación obterInstancia() de Singleton.
Outra maneira de elixir a subclase de Singleton é trasladar a implementación de obterInstancia() dende a clase pai á subclase. Este enfoque dificulta a elección da clase de Singleton en tempo de execución, mentres que utilizar instrucións condicionais para determinar a subclase é máis flexible, pero fixa o conxunto de posibles clases Singleton. Ningún dos dous enfoques é o suficientemente flexible para tódolos casos.
Un enfoque máis flexible utiliza un rexistro de obxectos Singleton. Así, en lugar de que sexa obterInstancia() quen defina o conxunto de posibles clases Singleton, estas clases poden rexistrar a súa única instancia polo seu nome nun rexistro que sexa coñecido por todos.
O rexistro establece unha correspondencia entre nomes e obxectos Singleton. Cando obterInstancia() necesita un Singleton, consulta o seu rexistro, pedindo un Singleton polo seu nome. O rexistro busca o Singleton correspondente (no caso de que exista) e o devolve. Así, obterInstancia() non ten que coñecer tódalas posibles clases de Singleton. Todo o que necesita é unha interface común para tódalas clases de Singleton que inclúa operacións para o rexistro.
Exemplos de implementación [editar]
Java [editar]
A continuación amósase un exemplo no que se utiliza o patrón Singleton. Trátase dunha táboa onde claves e valores son cadeas de caracteres. O patrón Singleton garante que tódolos clientes utilizarán soamente a instancia única, é dicir, a mesma táboa hash (utilizada internamente para representar a táboa asociativa).
class Main { static void buscar() { // Obsérvese que en lugar de crear un obxecto de tipo Dicionario (o cal é // imposible debido a que o construtor é privado) o que se fai é chamar // ao método de clase instancia() para obter a instancia única. Dicionario d = Dicionario.instancia(); System.out.println("O valor asociado a can é " + d.buscar("can")); System.out.println("O valor asociado a oso é " + d.buscar("oso")); } static void engadir() { Dicionario d = Dicionario.instancia(); d.engadir("gato", "cat"); d.engadir("can", "dog"); d.engadir("rato", "mouse"); d.engadir("oso", "bear"); d.engadir("león", "lyon"); } static public void main(String argv[]) { engadir(); buscar(); } } class Dicionario { // A clase Singleton garda un atributo de clase, normalmente privado, que // mantén a referencia coa única instancia da clase. static private Dicionario _instancia; // Estado privado asociado coa instancia única. private Hashtable _taboa; // O construtor da clase, que crea a táboa de dispersión, é privado // para garantir que non se poidan crear instancias dende fóra da clase. private Dicionario() { _taboa = new Hashtable(); } // O método de clase instancia() encárgase de crear a instancia // asociada ao Singleton a primeira vez que se solicita. En accesos // posteriores, devólvese a instancia orixinal. static Dicionario instancia() { if (_instancia == null) { _instancia = new Dicionario(); } return _instancia; } // Estes dous métodos son aplicables sobre obxectos da clase Dicionario, // é dicir, a súa única instancia. Engaden e buscan elementos na táboa hash. // Dado que a táboa hash almacena obxectos de tipo Object (igual que o // vector) debemos facer unha conversión ao extraer elementos da táboa. public void engadir(String clave, String valor) { _taboa.put(clave, valor); } public String buscar(String clave) { return (String) _taboa.get(clave); } }
Patróns relacionados [editar]
- Algúns dos patróns que se poden implementar facendo uso do Singleton son o Abstract Factory (Fábrica Abstracta), o Builder (Construtor virtual) e o Prototype (Prototipo).
- Tamén é habitual que os obxectos Facade (Fachada) sexan Singletons debido a que soamente se require un obxecto Facade.
Véxase tamén [editar]
Bibliografía [editar]
- Design Patterns. Elements of Reusable Object-Oriented Software - Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides - Addison Wesley (GoF- Gang of Four)