# 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.