Menu
Coddy logo textTech

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.

challenge icon

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 clase GameSettings que 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), y playerName (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)
  • 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 playerName a "Hero", difficulty a "Hard", y soundEnabled a false
    • Obtén otra referencia a la configuración y llámala settings2
    • Llama a displaySettings() en settings2 para demostrar que refleja los cambios realizados a través de settings1
    • Imprime si settings1 y settings2 son idénticos usando: Same instance: [true/false]

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: true

Hoja 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 externa
  • factory 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));  // true

Los 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]
}
quiz iconPonte a prueba

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