Patrón Singleton
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 90 de 110.
El patrón Singleton garantiza que una clase tenga una única instancia en toda su aplicación y proporciona un punto de acceso global a ella. Esto es útil cuando se necesita exactamente un objeto para coordinar acciones en todo el sistema, como un gestor de configuración, una conexión a la base de datos o un servicio de registro.
En Dart, puedes implementar un Singleton usando un constructor factory combinado con una instancia estática privada:
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
String appName = 'MyApp';
String version = '1.0.0';
// Constructor nombrado privado
AppConfig._internal();
// El constructor de fábrica devuelve la misma instancia
factory AppConfig() {
return _instance;
}
}
void main() {
var config1 = AppConfig();
var config2 = AppConfig();
config1.appName = 'UpdatedApp';
print(config2.appName); // UpdatedApp
print(identical(config1, config2)); // true
}El constructor privado _internal() evita la instanciación externa. El campo estático _instance contiene la instancia única, creada una sola vez cuando se carga la clase. Cada llamada a AppConfig() devuelve esta misma instancia, por lo que los cambios realizados a través de una referencia son visibles en todas partes.
Usa los Singletons con moderación: introducen un estado global, lo que puede dificultar las pruebas. Son más adecuados para recursos verdaderamente compartidos que necesitan un estado consistente en toda la aplicación.
Desafío
Fácil¡Vamos a construir un gestor de configuración de juego utilizando el patrón Singleton! Crearás un sistema que garantice que solo exista una instancia de la configuración del juego en toda la aplicación, para que todas las partes de tu juego compartan la misma configuración.
Organizarás tu código en dos archivos:
game_settings.dart: Crea una claseGameSettingsque implemente el patrón Singleton. Tu gestor de configuración debe tener:- Una instancia privada estática y final que contenga el único objeto
GameSettings - Un constructor nombrado privado para evitar la instanciación externa
- Un constructor de fábrica (factory) que siempre devuelva la misma instancia
- Tres campos de configuración:
difficulty(String, por defecto: "Normal"),soundEnabled(bool, por defecto: true), yplayerName(String, por defecto: "Player1") - Un método
displaySettings()que imprima la configuración actual en este formato:Settings: [playerName] | Difficulty: [difficulty] | Sound: [on/off](imprime "on" si soundEnabled es true, "off" si es false)
- Una instancia privada estática y final que contenga el único objeto
main.dart: Importa la configuración de tu juego y demuestra que el Singleton funciona correctamente:- Obtén la instancia de configuración y llámala
settings1 - Llama a
displaySettings()para mostrar los valores por defecto - Modifica la configuración: cambia
playerNamea "Hero",difficultya "Hard", ysoundEnableda false - Obtén otra referencia a la configuración y llámala
settings2 - Llama a
displaySettings()ensettings2para demostrar que refleja los cambios realizados a través desettings1 - Imprime si
settings1ysettings2son idénticos usando:Same instance: [true/false]
- Obtén la instancia de configuración y llámala
Esto demuestra el beneficio principal de los Singletons: los cambios realizados en cualquier parte de tu aplicación son visibles inmediatamente en todos los demás lugares, ¡porque solo hay una instancia!
Salida esperada:
Settings: Player1 | Difficulty: Normal | Sound: on
Settings: Hero | Difficulty: Hard | Sound: off
Same instance: trueHoja de referencia
El patrón Singleton asegura que una clase tenga una única instancia en toda su aplicación y proporciona un punto de acceso global a ella.
En Dart, implemente un Singleton utilizando un constructor de fábrica combinado con una instancia estática privada:
class AppConfig {
static final AppConfig _instance = AppConfig._internal();
String appName = 'MyApp';
String version = '1.0.0';
// Constructor nombrado privado
AppConfig._internal();
// El constructor de fábrica devuelve la misma instancia
factory AppConfig() {
return _instance;
}
}Componentes clave:
static final _instance: Mantiene la instancia única, creada una vez cuando se carga la clase_internal(): El constructor privado evita la instanciación externafactory AppConfig(): Devuelve la misma instancia cada vez
Ejemplo de uso:
var config1 = AppConfig();
var config2 = AppConfig();
config1.appName = 'UpdatedApp';
print(config2.appName); // UpdatedApp
print(identical(config1, config2)); // trueLos cambios realizados a través de una referencia son visibles en todas partes porque todas las referencias apuntan a la misma instancia.
Pruébalo tú mismo
import 'game_settings.dart';
void main() {
// TODO: Obtener la instancia de configuración y llamarla settings1
// TODO: Llamar a displaySettings() para mostrar los valores por defecto
// TODO: Modificar la configuración:
// - Cambiar playerName a "Hero"
// - Cambiar difficulty a "Hard"
// - Cambiar soundEnabled a false
// TODO: Obtener otra referencia a la configuración y llamarla settings2
// TODO: Llamar a displaySettings() en settings2 para demostrar que refleja los cambios
// TODO: Imprimir si settings1 y settings2 son idénticos
// Formato: Same instance: [true/false]
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository