Mixin и интерфейс
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 55 из 110.
Вы увидели, как миксины соотносятся с наследованием. Теперь давайте сравним миксины с интерфейсами, так как и те, и другие позволяют работать с несколькими типами. Ключевое отличие заключается в том, что они предоставляют: интерфейсы определяют контракт, в то время как миксины предоставляют реализацию.
Когда вы реализуете интерфейс, вы обязуетесь предоставить определенные методы, но весь код пишете сами. Когда вы используете миксин (mixin), вы получаете готовый код бесплатно.
// Интерфейсный подход — вы должны реализовать всё
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // Вы пишете это
}
}
// Подход с использованием примесей (mixin) — реализация включена
mixin Printable {
void printInfo() {
print('Printing...'); // Уже написано за вас
}
}
class Report with Printable {} // Получает printInfo() автоматическиИтак, когда же следует использовать каждый из них? Используйте interface, когда различным classes требуются одинаковые сигнатуры методов, но разные реализации. Используйте mixin, когда classes должны иметь абсолютно одинаковое поведение.
| Особенность | Интерфейс | Миксин |
|---|---|---|
| Предоставляет | Только контракт | Контракт + код |
| Реализация | Вы пишете её сами | Включена |
| Ключевое слово | implements | with |
| Лучше всего подходит для | Различное поведение, одинаковый API | Общее поведение |
Вы даже можете комбинировать оба подхода — использовать интерфейсы для определения того, что может делать класс, и миксины для предоставления общих реализаций там, где это уместно. В Dart при сочетании обоих вариантов всегда ставьте with перед implements:
class MyClass with SomeMixin implements SomeInterface {
// with указывается перед implements
}Задание
ЛегкоДавайте создадим систему уведомлений, которая продемонстрирует, когда использовать интерфейсы, а когда — миксины. Вы создадите интерфейс для уведомлений, требующих различных реализаций, и миксин для общего поведения логирования, которое должно быть одинаковым для всех типов уведомлений.
Вы организуете свой код в три файла:
notifiable.dart: Определите абстрактный классNotifiable, который будет выступать в роли интерфейса. Он должен объявлять два абстрактных метода:send(String message)иgetChannel(), который возвращаетString. Различные типы уведомлений будут реализовывать эти методы по-разному в зависимости от их конкретного канала.logging.dart: Создайте миксинLogging, который предоставляет общее поведение для всех уведомлений. Включите методlogSent(String channel, String message), который выводит[LOG] Sent via [channel]: [message], и методlogTimestamp(), который выводит[LOG] Timestamp recorded. Это поведение идентично для всех типов уведомлений, что делает его идеальным для миксина.main.dart: Импортируйте оба файла и создайте два класса уведомлений, которые сочетают интерфейс с миксином:- Класс
EmailNotification, который использует миксинLoggingи реализуетNotifiable. Его методgetChannel()должен возвращать'Email', а методsend()должен выводитьSending email: [message], затем вызыватьlogSent()с каналом и сообщением, а после —logTimestamp() - Класс
SMSNotification, который использует миксинLoggingи реализуетNotifiable. Его методgetChannel()должен возвращать'SMS', а методsend()должен выводитьSending SMS: [message], затем вызыватьlogSent()с каналом и сообщением, а после —logTimestamp()
EmailNotificationи вызовитеsend('Hello World'), выведите пустую строку, создайте экземплярSMSNotificationи вызовитеsend('Quick update')- Класс
Обратите внимание, как интерфейс заставляет каждый класс писать собственные реализации send() и getChannel() (различное поведение), в то время как миксин предоставляет методы логирования, которые работают одинаково для обоих (общее поведение).
Ожидаемый результат:
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 recordedШпаргалка
Интерфейсы определяют контракт (сигнатуры методов), в то время как миксины предоставляют реализацию (фактический код).
Когда вы реализуете интерфейс, вы должны написать весь код самостоятельно:
abstract class Printable {
void printInfo();
}
class Report implements Printable {
@override
void printInfo() {
print('Printing report...'); // Вы пишете это
}
}Когда вы используете миксин, вы получаете реализацию бесплатно:
mixin Printable {
void printInfo() {
print('Printing...'); // Уже написано за вас
}
}
class Report with Printable {} // Получает printInfo() автоматическиКогда использовать каждый из них:
- Используйте интерфейс, когда разным классам нужны одинаковые сигнатуры методов, но разные реализации
- Используйте миксин, когда классы должны иметь одинаковое поведение
| Особенность | Интерфейс | Миксин |
|---|---|---|
| Предоставляет | Только контракт | Контракт + код |
| Реализация | Пишете вы | Включена |
| Ключевое слово | implements | with |
| Лучше всего для | Разного поведения при одинаковом API | Общего поведения |
Вы можете комбинировать оба подхода. При комбинировании всегда ставьте with перед implements:
class MyClass with SomeMixin implements SomeInterface {
// with идет перед implements
}Попробуйте сами
import 'notifiable.dart';
import 'logging.dart';
// TODO: Создайте класс EmailNotification, который:
// - использует примесь Logging (с помощью with)
// - реализует Notifiable (с помощью implements)
// - getChannel() возвращает 'Email'
// - send() выводит 'Sending email: [message]', затем вызывает logSent(), а затем logTimestamp()
// TODO: Создайте класс SMSNotification, который:
// - использует примесь Logging (с помощью with)
// - реализует Notifiable (с помощью implements)
// - getChannel() возвращает 'SMS'
// - send() выводит 'Sending SMS: [message]', затем вызывает logSent(), а затем logTimestamp()
void main() {
// TODO: Создайте экземпляр EmailNotification и вызовите send('Hello World')
// TODO: Выведите пустую строку
// TODO: Создайте экземпляр SMSNotification и вызовите send('Quick update')
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий