Módulo 02: POO Avanzada en Dart

Clases Abstractas

Sistema Clases

# Clases Abstractas en Dart Las clases abstractas son la base para crear jerarquías de tipos sólidas y contratos claros en tu código Dart. ## ¿Qué es una Clase Abstracta? Una clase abstracta es una clase que no puede ser instanciada directamente. Sirve como plantilla para otras clases. - Puede contener métodos implementados (con cuerpo). - Puede contener métodos abstractos (sin cuerpo, solo firma). - Se define usando la palabra clave `abstract`. ## Sintaxis Básica ```dart abstract class Animal { // Propiedad concreta String name; Animal(this.name); // Método abstracto (debe ser implementado por hijos) void makeSound(); // Método concreto (heredado por hijos) void sleep() { print('$name is sleeping'); } } // ❌ Error: No se puede instanciar // final animal = Animal('Generic'); class Dog extends Animal { Dog(String name) : super(name); @override void makeSound() { print('Woof!'); } } class Cat extends Animal { Cat(String name) : super(name); @override void makeSound() { print('Meow!'); } } void main() { final dog = Dog('Buddy'); dog.makeSound(); // Woof! dog.sleep(); // Buddy is sleeping } ``` ## Casos de Uso en Flutter ### 1. Definición de Repositorios (Clean Architecture) Las clases abstractas son perfectas para definir contratos de repositorios, permitiendo múltiples implementaciones (producción, test, mock). ```dart abstract class AuthRepository { Future login(String email, String password); Future logout(); Stream get authStateChanges; } // Implementación con Firebase class FirebaseAuthRepository implements AuthRepository { @override Future login(String email, String password) async { // Lógica de Firebase } // ... } // Implementación Mock para Tests class MockAuthRepository implements AuthRepository { @override Future login(String email, String password) async { return User(id: '1', email: email); } // ... } ``` ### 2. Base Widgets o ViewModels Puedes crear clases base para compartir lógica común entre widgets o viewmodels. ```dart abstract class BaseViewModel extends ChangeNotifier { bool _isLoading = false; String? _error; bool get isLoading => _isLoading; String? get error => _error; void setLoading(bool value) { _isLoading = value; notifyListeners(); } void setError(String? value) { _error = value; notifyListeners(); } // Método abstracto que cada ViewModel debe implementar Future init(); } class HomeViewModel extends BaseViewModel { @override Future init() async { setLoading(true); try { // Cargar datos } catch (e) { setError(e.toString()); } finally { setLoading(false); } } } ``` ## Diferencia con Interfaces En Dart, **todas las clases definen implícitamente una interfaz**. Sin embargo, las clases abstractas se usan cuando quieres proveer alguna implementación base o estado compartido. | Característica | Clase Abstracta (`extends`) | Interfaz (`implements`) | |----------------|-----------------------------|-------------------------| | Estado (campos) | ✅ Sí | ❌ No (debes re-declararlos) | | Constructores | ✅ Sí | ❌ No | | Métodos concretos | ✅ Se heredan | ❌ Debes re-implementarlos | | Herencia múltiple | ❌ No (solo una superclase) | ✅ Sí (múltiples interfaces) | ## Mejores Prácticas 1. **Usa clases abstractas para relaciones "es un" (is-a).** - `Dog` *es un* `Animal`. 2. **Prefiere composición sobre herencia profunda.** - Evita jerarquías de herencia de más de 2-3 niveles. 3. **Define contratos claros.** - Usa métodos abstractos para forzar comportamiento en subclases. ## Ejemplo Avanzado: Shape System ```dart abstract class Shape { final Point position; Shape(this.position); double get area; double get perimeter; void draw() { print('Drawing shape at $position'); } } class Circle extends Shape { final double radius; Circle(Point position, this.radius) : super(position); @override double get area => 3.14159 * radius * radius; @override double get perimeter => 2 * 3.14159 * radius; } class Rectangle extends Shape { final double width; final double height; Rectangle(Point position, this.width, this.height) : super(position); @override double get area => width * height; @override double get perimeter => 2 * (width + height); } ```

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.