Mixin vs Interfaz
Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 55 de 110.
Has visto cómo se comparan los mixins con la herencia. Ahora comparemos los mixins con las interfaces, ya que ambos permiten trabajar con múltiples tipos. La diferencia clave es lo que proporcionan: las interfaces definen un contrato, mientras que los mixins proporcionan una implementación.
Cuando implementas una interfaz, prometes proporcionar ciertos métodos, pero escribes todo el código tú mismo. Cuando usas un mixin, obtienes el código real de forma gratuita.
// Enfoque de interfaz: debes implementar todo
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // Tú escribes esto
}
}
// Enfoque de mixin: implementación incluida
mixin Printable {
void printInfo() {
print('Printing...'); // Ya escrito para ti
}
}
class Report with Printable {} // Obtiene printInfo() automáticamente¿Entonces, cuándo deberías usar cada uno? Usa una interfaz cuando diferentes clases necesiten las mismas firmas de método pero diferentes implementaciones. Usa un mixin cuando las clases deban compartir exactamente el mismo comportamiento.
| Característica | Interfaz | Mixin |
|---|---|---|
| Proporciona | Solo contrato | Contrato + código |
| Implementación | Tú la escribes | Incluida |
| Palabra clave | implements | with |
| Ideal para | Diferentes comportamientos, misma API | Comportamiento compartido |
Incluso puedes combinar ambos enfoques: usa interfaces para definir lo que una clase puede hacer y mixins para proporcionar implementaciones comunes cuando sea apropiado. En Dart, al combinar ambos, coloca siempre with antes de implements:
class MyClass with SomeMixin implements SomeInterface {
// with va antes de implements
}Desafío
FácilVamos a construir un sistema de notificaciones que demuestre cuándo usar interfaces frente a mixins. Crearás una interfaz para notificaciones que requieren diferentes implementaciones y un mixin para el comportamiento de registro (logging) compartido que debe ser idéntico en todos los tipos de notificación.
Organizarás tu código en tres archivos:
notifiable.dart: Define una clase abstractaNotifiableque actúe como una interfaz. Debe declarar dos métodos abstractos:send(String message)ygetChannel()que devuelva unString. Los diferentes tipos de notificación implementarán estos métodos de manera distinta según su canal específico.logging.dart: Crea un mixinLoggingque proporcione el comportamiento compartido que todas las notificaciones deben tener. Incluye un métodologSent(String channel, String message)que imprima[LOG] Sent via [channel]: [message]y un métodologTimestamp()que imprima[LOG] Timestamp recorded. Este comportamiento es idéntico para todos los tipos de notificación, lo que lo hace perfecto para un mixin.main.dart: Importa ambos archivos y crea dos clases de notificación que combinen la interfaz con el mixin:- Una clase
EmailNotificationque use el mixinLogginge implementeNotifiable. SugetChannel()debe devolver'Email', y su métodosend()debe imprimirSending email: [message], luego llamar alogSent()con el canal y el mensaje, y finalmente llamar alogTimestamp(). - Una clase
SMSNotificationque use el mixinLogginge implementeNotifiable. SugetChannel()debe devolver'SMS', y su métodosend()debe imprimirSending SMS: [message], luego llamar alogSent()con el canal y el mensaje, y finalmente llamar alogTimestamp().
EmailNotificationy llama asend('Hello World'), imprime una línea vacía, crea unSMSNotificationy llama asend('Quick update').- Una clase
Observa cómo la interfaz obliga a cada clase a escribir sus propias implementaciones de send() y getChannel() (comportamiento diferente), mientras que el mixin proporciona los métodos de registro que funcionan de manera idéntica para ambos (comportamiento compartido).
Salida esperada:
Sending email: Hello World
[LOG] Sent via Email: Hello World
[LOG] Timestamp recorded
Sending SMS: Quick update
[LOG] Sent via SMS: Quick update
[LOG] Timestamp recordedHoja de referencia
Las interfaces definen un contrato (firmas de métodos), mientras que los mixins proporcionan la implementación (código real).
Cuando implementas una interfaz, debes escribir todo el código tú mismo:
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // Tú escribes esto
}
}Cuando usas un mixin, obtienes la implementación de forma gratuita:
mixin Printable {
void printInfo() {
print('Printing...'); // Ya escrito para ti
}
}
class Report with Printable {} // Obtiene printInfo() automáticamenteCuándo usar cada uno:
- Usa una interfaz cuando diferentes clases necesiten las mismas firmas de métodos pero diferentes implementaciones
- Usa un mixin cuando las clases deban compartir exactamente el mismo comportamiento
| Característica | Interfaz | Mixin |
|---|---|---|
| Proporciona | Solo contrato | Contrato + código |
| Implementación | Tú la escribes | Incluida |
| Palabra clave | implements | with |
| Ideal para | Diferentes comportamientos, misma API | Comportamiento compartido |
Puedes combinar ambos enfoques. Al combinar, siempre coloca with antes de implements:
class MyClass with SomeMixin implements SomeInterface {
// with va antes de implements
}Pruébalo tú mismo
import 'notifiable.dart';
import 'logging.dart';
// TODO: Crear la clase EmailNotification que:
// - use el mixin Logging (with)
// - implemente Notifiable (implements)
// - getChannel() devuelva 'Email'
// - send() imprima 'Sending email: [message]', luego llame a logSent(), y después a logTimestamp()
// TODO: Crear la clase SMSNotification que:
// - use el mixin Logging (with)
// - implemente Notifiable (implements)
// - getChannel() devuelva 'SMS'
// - send() imprima 'Sending SMS: [message]', luego llame a logSent(), y después a logTimestamp()
void main() {
// TODO: Crear una EmailNotification y llamar a send('Hello World')
// TODO: Imprimir una línea vacía
// TODO: Crear una SMSNotification y llamar a send('Quick update')
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository