Módulo 01: Fundamentos y Buenas Prácticas

Design Patterns

Principios Diseno

# Design Patterns en Flutter Los patrones de diseño son soluciones probadas a problemas comunes en el desarrollo de software. En Flutter, ciertos patrones son especialmente útiles. ## Comparación Visual ```mermaid graph TD subgraph "Singleton Pattern" S1[Instance] C1[Client A] --> S1 C2[Client B] --> S1 C3[Client C] --> S1 end subgraph "Factory Pattern" F[Factory] F -->|Create| P1[Product A] F -->|Create| P2[Product B] C4[Client] --> F end style S1 fill:#f9f,stroke:#333,stroke-width:2px style F fill:#bbf,stroke:#333,stroke-width:2px ``` ## Patrones Creacionales ### 1. Singleton Garantiza que una clase tenga una única instancia y proporciona un punto de acceso global a ella. ```dart // ✅ Singleton con inicialización lazy class DatabaseService { static DatabaseService? _instance; late Database _database; // Constructor privado DatabaseService._(); static DatabaseService get instance { _instance ??= DatabaseService._(); return _instance!; } Future initialize() async { _database = await openDatabase('app.db'); } Database get database => _database; } // Uso final db = DatabaseService.instance; await db.initialize(); ``` ```dart // ✅ Singleton con factory constructor class Logger { static final Logger _instance = Logger._internal(); factory Logger() => _instance; Logger._internal(); void log(String message) { print('[${DateTime.now()}] $message'); } } // Uso (siempre devuelve la misma instancia) final logger1 = Logger(); final logger2 = Logger(); print(logger1 == logger2); // true ``` ### 2. Factory Pattern Crea objetos sin especificar la clase exacta del objeto que se creará. ```dart // Abstracción abstract class Button { void render(); void onClick(); } // Implementaciones concretas class IOSButton implements Button { @override void render() => print('Rendering iOS button'); @override void onClick() => print('iOS button clicked'); } class AndroidButton implements Button { @override void render() => print('Rendering Android button'); @override void onClick() => print('Android button clicked'); } // Factory class ButtonFactory { static Button createButton(TargetPlatform platform) { switch (platform) { case TargetPlatform.iOS: return IOSButton(); case TargetPlatform.android: return AndroidButton(); default: return AndroidButton(); } } } // Uso final button = ButtonFactory.createButton(Theme.of(context).platform); button.render(); ``` ### 3. Builder Pattern Construye objetos complejos paso a paso. ```dart class User { final String id; final String name; final String email; final String? phone; final String? address; final bool isVerified; User._({ required this.id, required this.name, required this.email, this.phone, this.address, this.isVerified = false, }); } class UserBuilder { String? _id; String? _name; String? _email; String? _phone; String? _address; bool _isVerified = false; UserBuilder setId(String id) { _id = id; return this; } UserBuilder setName(String name) { _name = name; return this; } UserBuilder setEmail(String email) { _email = email; return this; } UserBuilder setPhone(String phone) { _phone = phone; return this; } UserBuilder setAddress(String address) { _address = address; return this; } UserBuilder setVerified(bool verified) { _isVerified = verified; return this; } User build() { if (_id == null || _name == null || _email == null) { throw Exception('Required fields not set'); } return User._( id: _id!, name: _name!, email: _email!, phone: _phone, address: _address, isVerified: _isVerified, ); } } // Uso final user = UserBuilder() .setId('123') .setName('John Doe') .setEmail('[email protected]') .setPhone('+1234567890') .setVerified(true) .build(); ``` ## Patrones Estructurales ### 4. Adapter Pattern Permite que interfaces incompatibles trabajen juntas. ```dart // API antigua class LegacyUser { String fullName; String emailAddress; LegacyUser(this.fullName, this.emailAddress); } // Nueva interfaz esperada abstract class User { String get name; String get email; } // Adaptador class UserAdapter implements User { final LegacyUser _legacyUser; UserAdapter(this._legacyUser); @override String get name => _legacyUser.fullName; @override String get email => _legacyUser.emailAddress; } // Uso final legacyUser = LegacyUser('John Doe', '[email protected]'); final user = UserAdapter(legacyUser); print(user.name); // John Doe ``` ### 5. Decorator Pattern Añade funcionalidad a objetos dinámicamente. ```dart // Componente base abstract class Coffee { String get description; double get cost; } class SimpleCoffee implements Coffee { @override String get description => 'Simple coffee'; @override double get cost => 2.0; } // Decoradores abstract class CoffeeDecorator implements Coffee { final Coffee coffee; CoffeeDecorator(this.coffee); } class MilkDecorator extends CoffeeDecorator { MilkDecorator(Coffee coffee) : super(coffee); @override String get description => '${coffee.description}, milk'; @override double get cost => coffee.cost + 0.5; } class SugarDecorator extends CoffeeDecorator { SugarDecorator(Coffee coffee) : super(coffee); @override String get description => '${coffee.description}, sugar'; @override double get cost => coffee.cost + 0.2; } // Uso Coffee coffee = SimpleCoffee(); coffee = MilkDecorator(coffee); coffee = SugarDecorator(coffee); print('${coffee.description} costs \$${coffee.cost}'); // Simple coffee, milk, sugar costs $2.7 ``` ### 6. Facade Pattern Proporciona una interfaz simplificada a un sistema complejo. ```dart // Subsistemas complejos class NetworkService { Future fetchData(String url) async { await Future.delayed(Duration(seconds: 1)); return 'data from $url'; } } class CacheService { Map _cache = {}; String? get(String key) => _cache[key]; void set(String key, String value) => _cache[key] = value; } class LoggingService { void log(String message) => print('[LOG] $message'); } // Facade - Interfaz simple class DataFacade { final _network = NetworkService(); final _cache = CacheService(); final _logger = LoggingService(); Future getData(String url) async { _logger.log('Fetching data from $url'); // Intentar obtener del cache final cached = _cache.get(url); if (cached != null) { _logger.log('Data found in cache'); return cached; } // Si no está en cache, obtener de red _logger.log('Fetching from network'); final data = await _network.fetchData(url); _cache.set(url, data); return data; } } // Uso simple final facade = DataFacade(); final data = await facade.getData('https://api.example.com/users'); ``` ## Patrones de Comportamiento ### 7. Observer Pattern (BLoC) Define dependencia uno-a-muchos entre objetos. Flutter usa esto extensivamente. ```dart import 'dart:async'; class Counter { int _count = 0; final _controller = StreamController.broadcast(); Stream get stream => _controller.stream; int get value => _count; void increment() { _count++; _controller.add(_count); } void decrement() { _count--; _controller.add(_count); } void dispose() { _controller.close(); } } // Uso en Flutter class CounterWidget extends StatelessWidget { final Counter counter; const CounterWidget({required this.counter}); @override Widget build(BuildContext context) { return StreamBuilder( stream: counter.stream, initialData: counter.value, builder: (context, snapshot) { return Text('Count: ${snapshot.data}'); }, ); } } ``` ### 8. Strategy Pattern Define una familia de algoritmos intercambiables. ```dart // Estrategias de ordenamiento abstract class SortStrategy { List sort(List list); } class BubbleSortStrategy implements SortStrategy { @override List sort(List list) { final copy = List.from(list); // Implementación de bubble sort for (var i = 0; i < copy.length; i++) { for (var j=0; j < copy.length - 1 - i; j++) { if (copy[j]> copy[j + 1]) { final temp = copy[j]; copy[j] = copy[j + 1]; copy[j + 1] = temp; } } } return copy; } } class QuickSortStrategy implements SortStrategy { @override List sort(List list) { if (list.length <= 1) return list; final pivot=list[list.length ~/ 2]; final less=list.where((x)=> x < pivot).toList(); final equal=list.where((x)=> x == pivot).toList(); final greater = list.where((x) => x > pivot).toList(); return [...sort(less), ...equal, ...sort(greater)]; } } // Contexto class Sorter { SortStrategy strategy; Sorter(this.strategy); List sort(List list) => strategy.sort(list); } // Uso final numbers = [5, 2, 8, 1, 9]; final bubbleSorter = Sorter(BubbleSortStrategy()); print(bubbleSorter.sort(numbers)); // [1, 2, 5, 8, 9] final quickSorter = Sorter(QuickSortStrategy()); print(quickSorter.sort(numbers)); // [1, 2, 5, 8, 9] ``` ### 9. Command Pattern Encapsula una solicitud como un objeto. ```dart // Comando abstracto abstract class Command { void execute(); void undo(); } // Receptor class Light { void turnOn() => print('Light is ON'); void turnOff() => print('Light is OFF'); } // Comandos concretos class TurnOnCommand implements Command { final Light light; TurnOnCommand(this.light); @override void execute() => light.turnOn(); @override void undo() => light.turnOff(); } class TurnOffCommand implements Command { final Light light; TurnOffCommand(this.light); @override void execute() => light.turnOff(); @override void undo() => light.turnOn(); } // Invocador class RemoteControl { final List _history = []; void executeCommand(Command command) { command.execute(); _history.add(command); } void undo() { if (_history.isNotEmpty) { final command = _history.removeLast(); command.undo(); } } } // Uso final light = Light(); final remote = RemoteControl(); remote.executeCommand(TurnOnCommand(light)); // Light is ON remote.executeCommand(TurnOffCommand(light)); // Light is OFF remote.undo(); // Light is ON ``` ## Patrones Específicos de Flutter ### 10. InheritedWidget Pattern Propaga datos hacia abajo en el árbol de widgets. ```dart class UserData { final String name; final String email; UserData({required this.name, required this.email}); } class UserProvider extends InheritedWidget { final UserData user; const UserProvider({ required this.user, required Widget child, }) : super(child: child); static UserProvider? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @override bool updateShouldNotify(UserProvider oldWidget) { return user != oldWidget.user; } } // Uso class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return UserProvider( user: UserData(name: 'John', email: '[email protected]'), child: MaterialApp( home: HomePage(), ), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { final user = UserProvider.of(context)?.user; return Text('Welcome, ${user?.name}'); } } ``` ### 11. BLoC Pattern (Business Logic Component) Separa lógica de negocio de la UI. ```dart import 'package:flutter_bloc/flutter_bloc.dart'; // Events abstract class CounterEvent {} class Increment extends CounterEvent {} class Decrement extends CounterEvent {} // States class CounterState { final int count; CounterState(this.count); } // BLoC class CounterBloc extends Bloc { CounterBloc() : super(CounterState(0)) { on((event, emit) => emit(CounterState(state.count + 1))); on((event, emit) => emit(CounterState(state.count - 1))); } } // UI class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (_) => CounterBloc(), child: CounterView(), ); } } class CounterView extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: BlocBuilder( builder: (context, state) { return Text('Count: ${state.count}'); }, ), ), floatingActionButton: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( onPressed: () => context.read().add(Increment()), child: Icon(Icons.add), ), SizedBox(height: 8), FloatingActionButton( onPressed: () => context.read ().add(Decrement()), child: Icon(Icons.remove), ), ], ), ); } } ``` ## Conclusión Los patrones de diseño son herramientas, no reglas. Úsalos cuando: - ✅ **Resuelven un problema real** - ✅ **Mejoran la claridad del código** - ✅ **Facilitan el mantenimiento** No los uses cuando: - ❌ **Sobre-complican soluciones simples** - ❌ **Son solo por "aplicar un patrón"** - ❌ **No aportan valor al proyecto** > "El mejor patrón es el que resuelve tu problema de la forma más simple posible."

Este apartado profundiza en los conceptos clave, proporcionando ejemplos prácticos y mejores prácticas para su aplicación en proyectos reales.

Al comprender estos detalles, podrás diseñar soluciones más robustas, mantenibles y escalables en tus aplicaciones Flutter y Dart.