# Principios SOLID en Dart y Flutter
Los principios SOLID son cinco principios de diseño orientado a objetos que hacen que el software sea más
comprensible, flexible y mantenible.
## Resumen Visual
```mermaid
graph TD
SOLID[SOLID Principles]
SRP[Single Responsibility]
OCP[Open/Closed]
LSP[Liskov Substitution]
ISP[Interface Segregation]
DIP[Dependency Inversion]
SOLID --> SRP
SOLID --> OCP
SOLID --> LSP
SOLID --> ISP
SOLID --> DIP
SRP --> SRP_Desc[One reason to change]
OCP --> OCP_Desc[Open for extension, closed for mod]
LSP --> LSP_Desc[Subtypes must be substitutable]
ISP --> ISP_Desc[Client specific interfaces]
DIP --> DIP_Desc[Depend on abstractions]
style SOLID fill:#f9f,stroke:#333,stroke-width:2px
style SRP fill:#bbf,stroke:#333,stroke-width:2px
style OCP fill:#bbf,stroke:#333,stroke-width:2px
style LSP fill:#bbf,stroke:#333,stroke-width:2px
style ISP fill:#bbf,stroke:#333,stroke-width:2px
style DIP fill:#bbf,stroke:#333,stroke-width:2px
```
## S - Single Responsibility Principle (SRP)
### Principio de Responsabilidad Única
> "Una clase debe tener una, y solo una, razón para cambiar"
### ❌ Violación del SRP
```dart
class UserManager {
// ❌ Múltiples responsabilidades en una clase
// Responsabilidad 1: Validación
bool validateEmail(String email) {
return email.contains('@');
}
// Responsabilidad 2: Persistencia
Future saveToDatabase(User user) async {
final db = await database;
await db.insert('users', user.toMap());
}
// Responsabilidad 3: Networking
Future fetchFromApi(String id) async {
final response = await http.get(Uri.parse('/users/$id'));
return User.fromJson(json.decode(response.body));
}
// Responsabilidad 4: Email
Future sendWelcomeEmail(User user) async {
await emailService.send(to: user.email, subject: 'Welcome!');
}
}
```
### ✅ Aplicando SRP
```dart
// Responsabilidad 1: Validación
class UserValidator {
bool validateEmail(String email) {
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
return emailRegex.hasMatch(email);
}
bool validatePassword(String password) {
return password.length >= 8;
}
}
// Responsabilidad 2: Persistencia
class UserLocalDataSource {
final Database database;
UserLocalDataSource(this.database);
Future saveUser(UserModel user) async {
await database.insert('users', user.toMap());
}
Future getUser(String id) async {
final maps = await database.query('users', where: 'id = ?', whereArgs: [id]);
if (maps.isEmpty) return null;
return UserModel.fromMap(maps.first);
}
}
// Responsabilidad 3: Networking
class UserRemoteDataSource {
final http.Client client;
UserRemoteDataSource(this.client);
Future fetchUser(String id) async {
final response = await client.get(Uri.parse('/users/$id'));
if (response.statusCode == 200) {
return UserModel.fromJson(json.decode(response.body));
}
throw ServerException();
}
}
// Responsabilidad 4: Notificaciones
class EmailService {
Future sendWelcomeEmail(String email, String name) async {
// Lógica de envío de email
}
}
```
## O - Open/Closed Principle (OCP)
### Principio Abierto/Cerrado
> "Las entidades de software deben estar abiertas para extensión, pero cerradas para modificación"
### ❌ Violación del OCP
```dart
class PaymentProcessor {
void processPayment(String type, double amount) {
if (type == 'credit_card') {
// Procesar tarjeta de crédito
print('Processing credit card payment: \$amount');
} else if (type == 'paypal') {
// Procesar PayPal
print('Processing PayPal payment: \$amount');
} else if (type == 'bitcoin') {
// ❌ Cada vez que agregamos un método de pago, modificamos esta clase
print('Processing Bitcoin payment: \$amount');
}
}
}
```
### ✅ Aplicando OCP
```dart
// Abstracción
abstract class PaymentMethod {
Future process(double amount);
String get name;
}
// Implementaciones concretas (extensiones)
class CreditCardPayment implements PaymentMethod {
final String cardNumber;
final String cvv;
CreditCardPayment({required this.cardNumber, required this.cvv});
@override
String get name => 'Credit Card';
@override
Future process(double amount) async {
// Lógica específica de tarjeta de crédito
print('Processing $name payment: \$$amount');
return PaymentResult.success();
}
}
class PayPalPayment implements PaymentMethod {
final String email;
PayPalPayment({required this.email});
@override
String get name => 'PayPal';
@override
Future process(double amount) async {
// Lógica específica de PayPal
print('Processing $name payment: \$$amount');
return PaymentResult.success();
}
}
// Nuevo método de pago (extensión sin modificar código existente)
class BitcoinPayment implements PaymentMethod {
final String walletAddress;
BitcoinPayment({required this.walletAddress});
@override
String get name => 'Bitcoin';
@override
Future process(double amount) async {
print('Processing $name payment: \$$amount');
return PaymentResult.success();
}
}
// Procesador que trabaja con la abstracción
class PaymentProcessor {
Future processPayment(PaymentMethod method, double amount) {
return method.process(amount);
}
}
// Uso
final processor = PaymentProcessor();
await processor.processPayment(CreditCardPayment(cardNumber: '...', cvv: '...'), 100.0);
await processor.processPayment(PayPalPayment(email: '[email protected]'), 50.0);
await processor.processPayment(BitcoinPayment(walletAddress: '...'), 200.0);
```
## L - Liskov Substitution Principle (LSP)
### Principio de Sustitución de Liskov
> "Los objetos de una superclase deben ser reemplazables por objetos de sus subclases
sin romper la aplicación"
### ❌ Violación del LSP
```dart
class Rectangle {
double width;
double height;
Rectangle(this.width, this.height);
double get area => width * height;
}
class Square extends Rectangle {
Square(double size) : super(size, size);
// ❌ Viola LSP: cambiar el comportamiento esperado
@override
set width(double value) {
super.width = value;
super.height = value; // Cambio inesperado
}
@override
set height(double value) {
super.width = value; // Cambio inesperado
super.height = value;
}
}
// Problema
void printArea(Rectangle rectangle) {
rectangle.width = 5;
rectangle.height = 4;
print('Expected area: 20, Got: ${rectangle.area}');
// Si es Square, obtendremos 16 en lugar de 20!
}
```
### ✅ Aplicando LSP
```dart
// Abstracción común
abstract class Shape {
double get area;
}
class Rectangle implements Shape {
final double width;
final double height;
Rectangle(this.width, this.height);
@override
double get area => width * height;
Rectangle copyWith({double? width, double? height}) {
return Rectangle(
width ?? this.width,
height ?? this.height,
);
}
}
class Square implements Shape {
final double size;
Square(this.size);
@override
double get area => size * size;
Square copyWith({double? size}) {
return Square(size ?? this.size);
}
}
// Ahora podemos usar cualquier Shape sin problemas
void printArea(Shape shape) {
print('Area: ${shape.area}');
}
printArea(Rectangle(5, 4)); // OK: 20
printArea(Square(4)); // OK: 16
```
## I - Interface Segregation Principle (ISP)
### Principio de Segregación de Interfaces
> "Los clientes no deberían verse forzados a depender de interfaces que no utilizan"
### ❌ Violación del ISP
```dart
// Interfaz demasiado grande
abstract class Worker {
void work();
void eat();
void sleep();
void attendMeeting();
void writeCode();
void designUI();
}
// Robot worker no necesita eat() ni sleep()
class RobotWorker implements Worker {
@override
void work() => print('Robot working');
@override
void eat() => throw UnimplementedError('Robots don\'t eat'); // ❌
@override
void sleep() => throw UnimplementedError('Robots don\'t sleep'); // ❌
@override
void attendMeeting() => print('Robot attending meeting');
@override
void writeCode() => print('Robot writing code');
@override
void designUI() => throw UnimplementedError(); // ❌
}
```
### ✅ Aplicando ISP
```dart
// Interfaces segregadas
abstract class Workable {
void work();
}
abstract class Eatable {
void eat();
}
abstract class Sleepable {
void sleep();
}
abstract class MeetingAttendable {
void attendMeeting();
}
abstract class Codeable {
void writeCode();
}
abstract class Designable {
void designUI();
}
// Cada trabajador implementa solo lo que necesita
class HumanDeveloper implements Workable, Eatable, Sleepable, MeetingAttendable,
Codeable {
@override
void work() => print('Human working');
@override
void eat() => print('Human eating');
@override
void sleep() => print('Human sleeping');
@override
void attendMeeting() => print('Human attending meeting');
@override
void writeCode() => print('Human writing code');
}
class RobotWorker implements Workable, MeetingAttendable, Codeable {
@override
void work() => print('Robot working');
@override
void attendMeeting() => print('Robot attending meeting');
@override
void writeCode() => print('Robot writing code');
// ✅ No implementa eat(), sleep(), ni designUI() porque no las necesita
}
class Designer implements Workable, Eatable, Sleepable, Designable {
@override
void work() => print('Designer working');
@override
void eat() => print('Designer eating');
@override
void sleep() => print('Designer sleeping');
@override
void designUI() => print('Designer designing UI');
}
```
## D - Dependency Inversion Principle (DIP)
### Principio de Inversión de Dependencias
> "Depende de abstracciones, no de concreciones"
### ❌ Violación del DIP
```dart
// Acoplamiento fuerte a implementaciones concretas
class UserService {
final MySQLDatabase database; // ❌ Dependencia concreta
UserService(this.database);
Future getUser(String id) async {
return await database.query('SELECT * FROM users WHERE id = ?', [id]);
}
}
// Si queremos cambiar a PostgreSQL, tenemos que modificar UserService
```
### ✅ Aplicando DIP
```dart
// Abstracción
abstract class Database {
Future