Los patrones de diseño son soluciones probadas y reutilizables para problemas comunes en el diseño de software. En Dart, como en otros lenguajes orientados a objetos, los patrones de diseño pueden ayudar a crear código más modular, flexible y mantenible. En esta sección, exploraremos algunos de los patrones de diseño más comunes y cómo implementarlos en Dart.
- Singleton
Descripción
El patrón Singleton asegura que una clase tenga una única instancia y proporciona un punto de acceso global a ella.
Implementación en Dart
class Singleton { // La instancia única de la clase static final Singleton _instance = Singleton._internal(); // Constructor privado Singleton._internal(); // Método para obtener la instancia única static Singleton get instance => _instance; // Ejemplo de método de la clase Singleton void someMethod() { print('Método del Singleton'); } } void main() { var singleton1 = Singleton.instance; var singleton2 = Singleton.instance; singleton1.someMethod(); // Verificar que ambas variables apuntan a la misma instancia print(singleton1 == singleton2); // true }
Explicación
- Constructor Privado:
Singleton._internal()
es un constructor privado que impide la creación de instancias desde fuera de la clase. - Instancia Única:
_instance
es una instancia estática y final de la claseSingleton
. - Método de Acceso:
instance
es un getter estático que devuelve la instancia única.
- Factory Method
Descripción
El patrón Factory Method define una interfaz para crear un objeto, pero permite a las subclases alterar el tipo de objetos que se crearán.
Implementación en Dart
abstract class Product { void operation(); } class ConcreteProductA implements Product { @override void operation() { print('Operación de ConcreteProductA'); } } class ConcreteProductB implements Product { @override void operation() { print('Operación de ConcreteProductB'); } } abstract class Creator { // Método Factory Product factoryMethod(); void someOperation() { var product = factoryMethod(); product.operation(); } } class ConcreteCreatorA extends Creator { @override Product factoryMethod() { return ConcreteProductA(); } } class ConcreteCreatorB extends Creator { @override Product factoryMethod() { return ConcreteProductB(); } } void main() { Creator creatorA = ConcreteCreatorA(); creatorA.someOperation(); // Operación de ConcreteProductA Creator creatorB = ConcreteCreatorB(); creatorB.someOperation(); // Operación de ConcreteProductB }
Explicación
- Interfaz Product: Define la interfaz de los productos que se crearán.
- ConcreteProductA y ConcreteProductB: Implementaciones concretas de
Product
. - Creator: Clase abstracta que declara el método factory
factoryMethod
. - ConcreteCreatorA y ConcreteCreatorB: Subclases que implementan el método factory para crear instancias de
ConcreteProductA
yConcreteProductB
.
- Observer
Descripción
El patrón Observer define una dependencia de uno a muchos entre objetos, de manera que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.
Implementación en Dart
abstract class Observer { void update(String state); } class ConcreteObserver implements Observer { final String name; ConcreteObserver(this.name); @override void update(String state) { print('$name recibió actualización: $state'); } } class Subject { List<Observer> _observers = []; String _state; void attach(Observer observer) { _observers.add(observer); } void detach(Observer observer) { _observers.remove(observer); } void notify() { for (var observer in _observers) { observer.update(_state); } } void setState(String state) { _state = state; notify(); } } void main() { var subject = Subject(); var observer1 = ConcreteObserver('Observer 1'); var observer2 = ConcreteObserver('Observer 2'); subject.attach(observer1); subject.attach(observer2); subject.setState('Nuevo Estado'); // Observer 1 y Observer 2 reciben la actualización }
Explicación
- Observer: Interfaz que define el método
update
que los observadores deben implementar. - ConcreteObserver: Implementación concreta de
Observer
. - Subject: Clase que mantiene una lista de observadores y notifica a todos cuando su estado cambia.
Ejercicios Prácticos
Ejercicio 1: Implementar el Patrón Singleton
Crea una clase Database
que siga el patrón Singleton. La clase debe tener un método query
que imprima un mensaje indicando que se está ejecutando una consulta.
Ejercicio 2: Implementar el Patrón Factory Method
Crea una jerarquía de clases para representar diferentes tipos de documentos (PDFDocument
, WordDocument
). Implementa el patrón Factory Method para crear instancias de estos documentos.
Ejercicio 3: Implementar el Patrón Observer
Crea un sistema de notificaciones donde diferentes tipos de observadores (por ejemplo, EmailObserver
, SMSObserver
) reciben actualizaciones cuando hay un nuevo mensaje en el sistema.
Soluciones
Solución Ejercicio 1
class Database { static final Database _instance = Database._internal(); Database._internal(); static Database get instance => _instance; void query(String sql) { print('Ejecutando consulta: $sql'); } } void main() { var db1 = Database.instance; var db2 = Database.instance; db1.query('SELECT * FROM users'); print(db1 == db2); // true }
Solución Ejercicio 2
abstract class Document { void open(); } class PDFDocument implements Document { @override void open() { print('Abriendo documento PDF'); } } class WordDocument implements Document { @override void open() { print('Abriendo documento Word'); } } abstract class DocumentCreator { Document createDocument(); void openDocument() { var doc = createDocument(); doc.open(); } } class PDFDocumentCreator extends DocumentCreator { @override Document createDocument() { return PDFDocument(); } } class WordDocumentCreator extends DocumentCreator { @override Document createDocument() { return WordDocument(); } } void main() { DocumentCreator pdfCreator = PDFDocumentCreator(); pdfCreator.openDocument(); // Abriendo documento PDF DocumentCreator wordCreator = WordDocumentCreator(); wordCreator.openDocument(); // Abriendo documento Word }
Solución Ejercicio 3
abstract class Observer { void update(String message); } class EmailObserver implements Observer { @override void update(String message) { print('Email recibido: $message'); } } class SMSObserver implements Observer { @override void update(String message) { print('SMS recibido: $message'); } } class NotificationSystem { List<Observer> _observers = []; void attach(Observer observer) { _observers.add(observer); } void detach(Observer observer) { _observers.remove(observer); } void notify(String message) { for (var observer in _observers) { observer.update(message); } } void newMessage(String message) { notify(message); } } void main() { var notificationSystem = NotificationSystem(); var emailObserver = EmailObserver(); var smsObserver = SMSObserver(); notificationSystem.attach(emailObserver); notificationSystem.attach(smsObserver); notificationSystem.newMessage('Tienes un nuevo mensaje'); // Email y SMS reciben la actualización }
Conclusión
En esta sección, hemos explorado algunos de los patrones de diseño más comunes y cómo implementarlos en Dart. Los patrones de diseño son herramientas poderosas que pueden ayudarte a escribir código más limpio, modular y mantenible. A medida que avances en tu carrera como desarrollador, te encontrarás con estos patrones una y otra vez, y comprender cómo y cuándo usarlos será una habilidad invaluable.
Curso de Programación en Dart
Módulo 1: Introducción a Dart
- Introducción a Dart
- Configuración del Entorno de Desarrollo
- Tu Primer Programa en Dart
- Sintaxis y Estructura Básica