Mixin vs interfaccia
Fa parte della sezione Programmazione orientata agli oggetti del percorso Dart di Coddy. Lezione 55 di 110.
Hai visto come i mixin si confrontano con l'ereditarietà. Ora confrontiamo i mixin con le interfacce, poiché entrambi ti permettono di lavorare con più tipi. La differenza principale è ciò che forniscono: le interfacce definiscono un contratto, mentre i mixin forniscono un'implementazione.
Quando implementi un'interfaccia, prometti di fornire determinati metodi, ma scrivi tutto il codice da solo. Quando usi un mixin, ottieni il codice effettivo gratuitamente.
// Approccio con interfaccia - devi implementare tutto
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // Questo lo scrivi tu
}
}
// Approccio con mixin - implementazione inclusa
mixin Printable {
void printInfo() {
print('Printing...'); // Già scritto per te
}
}
class Report with Printable {} // Ottiene printInfo() automaticamenteQuindi, quando dovresti usare ciascuno dei due? Usa un'interfaccia quando classi diverse hanno bisogno delle stesse firme dei metodi ma di implementazioni diverse. Usa un mixin quando le classi devono condividere esattamente lo stesso comportamento.
| Funzionalità | Interfaccia | Mixin |
|---|---|---|
| Fornisce | Solo contratto | Contratto + codice |
| Implementazione | La scrivi tu | Inclusa |
| Parola chiave | implements | with |
| Ideale per | Comportamenti diversi, stessa API | Comportamento condiviso |
Puoi persino combinare entrambi gli approcci: usare le interfacce per definire cosa può fare una classe e i mixin per fornire implementazioni comuni, quando opportuno. In Dart, quando li combini, metti sempre with prima di implements:
class MyClass with SomeMixin implements SomeInterface {
// with viene prima di implements
}Sfida
FacileCostruiamo un sistema di notifiche che mostri quando usare le interfacce anziché i mixin. Creerai un'interfaccia per le notifiche che richiedono implementazioni diverse e un mixin per il comportamento di registrazione condiviso, che dovrebbe essere identico per tutti i tipi di notifica.
Organizzerai il codice in tre file:
notifiable.dart: Definisci una classe astrattaNotifiableche funge da interfaccia. Dovrebbe dichiarare due metodi astratti:send(String message)egetChannel(), che restituisce unString. I diversi tipi di notifica implementeranno questi metodi in modo diverso in base al proprio canale specifico.logging.dart: Crea un mixinLoggingche fornisce il comportamento condiviso che tutte le notifiche dovrebbero avere. Includi un metodologSent(String channel, String message)che stampa[LOG] Sent via [channel]: [message]e un metodologTimestamp()che stampa[LOG] Timestamp recorded. Questo comportamento è identico per tutti i tipi di notifica, quindi è perfetto per un mixin.main.dart: Importa entrambi i file e crea due classi di notifica che combinano l'interfaccia con il mixin:- Una classe
EmailNotificationche usa il mixinLogginge implementaNotifiable. Il suogetChannel()dovrebbe restituire'Email', mentre il suo metodosend()dovrebbe stampareSending email: [message], poi chiamarelogSent()con il canale e il messaggio, quindi chiamarelogTimestamp() - Una classe
SMSNotificationche usa il mixinLogginge implementaNotifiable. Il suogetChannel()dovrebbe restituire'SMS', mentre il suo metodosend()dovrebbe stampareSending SMS: [message], poi chiamarelogSent()con il canale e il messaggio, quindi chiamarelogTimestamp()
EmailNotificatione chiamasend('Hello World'), stampa una riga vuota, crea unSMSNotificatione chiamasend('Quick update')- Una classe
Nota come l'interfaccia imponga a ciascuna classe di scrivere le proprie implementazioni di send() e getChannel() (comportamento diverso), mentre il mixin fornisce i metodi di registrazione che funzionano in modo identico per entrambe (comportamento condiviso).
Output previsto:
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 recordedProvalo tu
import 'notifiable.dart';
import 'logging.dart';
// TODO: Crea una classe EmailNotification che:
// - usa il mixin Logging (with)
// - implementa Notifiable (implements)
// - getChannel() restituisce 'Email'
// - send() stampa 'Sending email: [message]', poi chiama logSent() e infine logTimestamp()
// TODO: Crea una classe SMSNotification che:
// - usa il mixin Logging (with)
// - implementa Notifiable (implements)
// - getChannel() restituisce 'SMS'
// - send() stampa 'Sending SMS: [message]', poi chiama logSent() e infine logTimestamp()
void main() {
// TODO: Crea un'istanza di EmailNotification e chiama send('Hello World')
// TODO: Stampa una riga vuota
// TODO: Crea un'istanza di SMSNotification e chiama send('Quick update')
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniLibrerie e importazioniIntroduzione alla programmazione orientata agli oggettiClassi e oggettiLa parola chiave thisMetodiVariabili di istanzaFondamenti dei costruttoriRiepilogo - Calcolatrice semplice4Sicurezza dei valori null
Introduzione alla sicurezza dei valori nullNullable e non nullableGli operatori ? e !La keyword late e la sicurezza dei valori nullOperatori null-awareSicurezza dei valori null nelle classiRiepilogo - Sistema di profili utente7Classi astratte e interfacce
Classi astratteMetodi astrattiInterfacce in DartInterfacce impliciteImplementare vs estendereInterfacce multipleRiepilogo - Calcolatore di forme10Collezioni e generici
Panoramica di List, Set e MapCollezioni sicure rispetto ai tipiClassi genericheMetodi genericiVincoli sui genericiIterable e IteratorRiepilogo - Archiviazione generica13Concetti avanzati di OOP
Composizione vs ereditarietàMetodi di estensioneClassi chiamabiliClassi sealed (Dart 3)Record (Dart 3)Pattern e matching (3.0)Enum con metodi16Progetto: Gestione della biblioteca
Panoramica del progettoClassi Libro e Utente2I costruttori in Dart
Costruttore predefinitoCostruttori con nomeListe di inizializzazioneCostruttori costantiCostruttori factoryCostruttori di reindirizzamentoRiepilogo - Crea forme5Incapsulamento
Membri pubblici e privatiConvenzione del prefisso _Visibilità a livello di libreriaApprofondimento su getter e setterNascondimento delle informazioniRiepilogo - Registri degli studenti8Mixin
Introduzione ai MixinCreare MixinUsare più MixinParola chiave on nei MixinMixin vs ereditarietàMixin vs interfacciaRiepilogo - Sistema animale11Metodi speciali
Sovrascrittura di toString()Sovrascrittura di hashCode e ==Interfaccia ComparableMetodo call()Sovrascrittura di noSuchMethodRiepilogo - Collezione personalizzata14Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneSingletonFactoryObserverStrategy3Proprietà delle classi
Membri di istanza e staticiCampi final e constVariabili lateMetodi e campi staticiGetter e setterRiepilogo - Gestore di conti bancari6Ereditarietà
Ereditarietà di baseLa parola chiave superOverride dei metodiL’annotazione @overrideLa parola chiave final per le classiCostruttori ed ereditarietàRiepilogo - Gerarchia dei dipendenti9Polimorfismo
Fondamenti di polimorfismoPolimorfismo tramite interfacceControllo del tipo a runtimeGli operatori is e asParola chiave covariantRiepilogo - Elaboratore di pagamenti12OOP asincrona
Future e async/awaitNozioni di base sugli streamController degli streamCostruttori asincroniAsync nei metodi di classeRiepilogo - Recupero dei dati15Pattern di progettazione Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositePattern RepositoryEsercitati da solo: Compilatore Dart online