Domieszka a interfejs
Część sekcji Programowanie obiektowe ścieżki Dart w Coddy. Lekcja 55 z 110.
Wiesz już, czym miksy różnią się od dziedziczenia. Teraz porównajmy miksy z interfejsami, ponieważ oba rozwiązania pozwalają pracować z wieloma typami. Kluczowa różnica polega na tym, co zapewniają: interfejsy definiują kontrakt, a miksy zapewniają implementację.
Gdy implementujesz interfejs, zobowiązujesz się udostępnić określone metody, ale cały kod piszesz samodzielnie. Gdy używasz mixina, otrzymujesz gotowy kod za darmo.
// Podejście z interfejsem – musisz zaimplementować wszystko
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // To musisz napisać samodzielnie
}
}
// Podejście z mixinem – implementacja jest dołączona
mixin Printable {
void printInfo() {
print('Printing...'); // To już zostało napisane za Ciebie
}
}
class Report with Printable {} // Automatycznie uzyskuje printInfo()Kiedy więc używać każdego z nich? Używaj interfejsu, gdy różne klasy potrzebują tych samych sygnatur metod, ale różnych implementacji. Używaj mixina, gdy klasy powinny współdzielić dokładnie to samo zachowanie.
| Funkcja | Interfejs | Miksyna |
|---|---|---|
| Zapewnia | Tylko kontrakt | Kontrakt + kod |
| Implementacja | Piszesz ją samodzielnie | Dołączona |
| Słowo kluczowe | implements | with |
| Najlepsze zastosowanie | Różne zachowania, to samo API | Wspólne zachowanie |
Możesz nawet połączyć oba podejścia — używać interfejsów do określania, co może robić klasa, a miksinów do dostarczania wspólnych implementacji tam, gdzie to odpowiednie. W Dart, łącząc oba podejścia, zawsze umieszczaj with przed implements:
class MyClass with SomeMixin implements SomeInterface {
// with występuje przed implements
}Wyzwanie
ŁatwyZbudujmy system powiadomień, który pokazuje, kiedy używać interfejsów, a kiedy mikserów. Utworzysz interfejs dla powiadomień wymagających różnych implementacji oraz mikser dla współdzielonego zachowania związanego z logowaniem, które powinno być identyczne dla wszystkich typów powiadomień.
Podzielisz kod na trzy pliki:
notifiable.dart: Zdefiniuj klasę abstrakcyjnąNotifiable, która pełni rolę interfejsu. Powinna deklarować dwie metody abstrakcyjne:send(String message)orazgetChannel(), która zwraca wartość typuString. Różne typy powiadomień zaimplementują te metody na różne sposoby, zależnie od konkretnego kanału.logging.dart: Utwórz mikserLogging, który zapewnia współdzielone zachowanie, jakie powinny mieć wszystkie powiadomienia. Dodaj metodęlogSent(String channel, String message), która wypisuje[LOG] Sent via [channel]: [message], oraz metodęlogTimestamp(), która wypisuje[LOG] Timestamp recorded. To zachowanie jest identyczne dla wszystkich typów powiadomień, dlatego idealnie nadaje się do miksera.main.dart: Zaimportuj oba pliki i utwórz dwie klasy powiadomień, które łączą interfejs z mikserem:- Klasa
EmailNotification, która używa mikseraLoggingi implementujeNotifiable. Jej metodagetChannel()powinna zwracać'Email', a metodasend()powinna wypisywaćSending email: [message], następnie wywoływaćlogSent()z kanałem i wiadomością, a potem wywoływaćlogTimestamp() - Klasa
SMSNotification, która używa mikseraLoggingi implementujeNotifiable. Jej metodagetChannel()powinna zwracać'SMS', a metodasend()powinna wypisywaćSending SMS: [message], następnie wywoływaćlogSent()z kanałem i wiadomością, a potem wywoływaćlogTimestamp()
EmailNotificationi wywołajsend('Hello World'), wypisz pusty wiersz, utwórz obiektSMSNotificationi wywołajsend('Quick update')- Klasa
Zwróć uwagę, że interfejs wymusza na każdej klasie napisanie własnych implementacji metod send() i getChannel() (różne zachowanie), podczas gdy mikser zapewnia metody logowania, które działają identycznie w obu klasach (współdzielone zachowanie).
Oczekiwany wynik:
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 recordedSpróbuj swoich sił
import 'notifiable.dart';
import 'logging.dart';
// TODO: Utwórz klasę EmailNotification, która:
// - używa miksinu Logging (with)
// - implementuje Notifiable (implements)
// - getChannel() zwraca 'Email'
// - send() wypisuje 'Sending email: [message]', następnie wywołuje logSent(), a potem logTimestamp()
// TODO: Utwórz klasę SMSNotification, która:
// - używa miksinu Logging (with)
// - implementuje Notifiable (implements)
// - getChannel() zwraca 'SMS'
// - send() wypisuje 'Sending SMS: [message]', następnie wywołuje logSent(), a potem logTimestamp()
void main() {
// TODO: Utwórz obiekt EmailNotification i wywołaj send('Hello World')
// TODO: Wypisz pustą linię
// TODO: Utwórz obiekt SMSNotification i wywołaj send('Quick update')
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP
Pliki zewnętrzneBiblioteki i importyWprowadzenie do OOPKlasy a obiektySłowo kluczowe thisMetodyZmienne instancjiPodstawy konstruktorówPowtórka – prosty kalkulator4Bezpieczeństwo wartości null
Wprowadzenie do bezpieczeństwa wartości nullTypy dopuszczające null i niedopuszczające nullOperatory ? i !Słowo kluczowe late a bezpieczeństwo wartości nullOperatory uwzględniające nullBezpieczeństwo wartości null w klasachPodsumowanie — system profili użytkowników7Klasy abstrakcyjne i interfejsy
Klasy abstrakcyjneMetody abstrakcyjneInterfejsy w DartInterfejsy niejawneImplementowanie a rozszerzanieWiele interfejsówPowtórka — kalkulator kształtów10Kolekcje i typy generyczne
Przegląd List, Set i MapKolekcje bezpieczne typowoKlasy generyczneMetody generyczneOgraniczenia typów generycznychIterable i IteratorPowtórka — generyczne przechowywanie danych13Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMetody rozszerzająceKlasy wywoływalneKlasy zapieczętowane (Dart 3)Rekordy (Dart 3)Wzorce i dopasowywanie (3.0)Typy wyliczeniowe z metodami16Projekt: Zarządzanie biblioteką
Omówienie projektuKlasy książek i użytkowników2Konstruktory w Dart
Konstruktor domyślnyKonstruktory nazwaneListy inicjalizująceKonstruktory stałeKonstruktory fabryczneKonstruktory przekierowującePodsumowanie — budowanie kształtów5Hermetyzacja
Elementy publiczne a prywatneKonwencja prefiksu _Prywatność na poziomie bibliotekiGettery i settery — szczegółowoUkrywanie informacjiPodsumowanie — dane uczniów8Domieszki
Wprowadzenie do domieszekTworzenie domieszekKorzystanie z wielu domieszekSłowo kluczowe on w domieszkachDomieszka a dziedziczenieDomieszka a interfejsPodsumowanie – system zwierząt11Metody specjalne
Nadpisanie toString()Nadpisanie hashCode i ==Interfejs ComparableMetoda call()Nadpisanie noSuchMethodPodsumowanie — własna kolekcja14Wzorce projektowe, część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec Strategy3Właściwości klas
Elementy instancji a elementy statycznePola final i constZmienne inicjalizowane z opóźnieniemMetody i pola statyczneGettery i setteryPowtórzenie – menedżer kont bankowych6Dziedziczenie
Podstawy dziedziczeniaSłowo kluczowe superPrzesłanianie metodAdnotacja @overrideSłowo kluczowe final dla klasKonstruktory i dziedziczeniePowtórzenie – hierarchia pracowników9Polimorfizm
Podstawy polimorfizmuPolimorfizm za pomocą interfejsówSprawdzanie typu w czasie działaniaOperatory is i asSłowo kluczowe covariantPodsumowanie — procesor płatnościPoćwicz samodzielnie: Kompilator Dart online