Неявные интерфейсы
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 46 из 110.
Поскольку каждый класс Dart автоматически определяет интерфейс, вы можете использовать любой класс в качестве интерфейса — даже конкретные классы с полной реализацией. Это то, что мы называем неявным интерфейсом.
Рассмотрим класс со свойствами и методами:
class Logger {
String prefix = '[LOG]';
void log(String message) {
print('$prefix $message');
}
void clear() {
print('Clearing logs...');
}
}Когда другой класс реализует Logger, он должен предоставить собственную реализацию для всего — включая свойство prefix:
class FileLogger implements Logger {
@override
String prefix = '[FILE]';
@override
void log(String message) {
print('$prefix Writing to file: $message');
}
@override
void clear() {
print('Deleting log file...');
}
}В этом заключается ключевой момент: когда вы используете implements, вы заявляете: «мой класс будет иметь ту же форму» — те же методы и свойства. Вы не наследуете никакой код, конструкторы или значения по умолчанию. Вы только обещаете соответствовать структуре интерфейса.
Это делает неявные интерфейсы мощным инструментом для создания взаимозаменяемых компонентов. Код, который ожидает Logger, может работать с любым классом, реализующим его интерфейс, независимо от того, как эта реализация устроена внутри.
Задание
ЛегкоДавайте создадим систему уведомлений, которая демонстрирует, как конкретные классы могут служить неявными интерфейсами. Вы создадите базовый класс уведомлений, а затем реализуете его совершенно другим способом, показывая, что implements требует от вас самостоятельного предоставления всех членов класса.
Вы организуете свой код в два файла:
notification.dart: Определите здесь свои классы уведомлений:- Класс
Notificationсо свойствомString channel, установленным в'default', методомsend(String message), который выводит[channel]: [message], и методомgetStatus(), который возвращает строку'Notification ready' - Класс
EmailNotification, который реализует (implements)Notification. Поскольку вы используетеimplements, вы должны предоставить собственное свойствоchannel(установите его в'email'), собственный методsend(), который выводитSending email: [message], и собственный методgetStatus(), который возвращает'Email service active' - Класс
PushNotification, который реализует (implements)Notification. Установите егоchannelв'push', сделайте так, чтобыsend()выводилPush alert: [message], аgetStatus()возвращал'Push service connected'
- Класс
main.dart: Импортируйте файл уведомлений и продемонстрируйте, как разные классы могут реализовывать один и тот же неявный интерфейс с совершенно разным поведением:- Создайте экземпляры
EmailNotificationиPushNotification - Для каждого уведомления выведите его статус с помощью
getStatus(), затем вызовитеsend()с сообщением'Hello World' - Выведите пустую строку между двумя уведомлениями
- Создайте экземпляры
Помните: когда вы используете implements, вы не наследуете никакой код от Notification. Вы только обещаете, что ваш класс будет иметь ту же структуру — те же свойства и методы. Каждый реализующий класс предоставляет свою собственную полную реализацию.
Ожидаемый результат:
Email service active
Sending email: Hello World
Push service connected
Push alert: Hello WorldШпаргалка
В Dart каждый класс автоматически определяет неявный интерфейс. Это означает, что вы можете использовать любой класс в качестве интерфейса, даже конкретные классы с полной реализацией.
Когда класс использует implements, он должен предоставить собственную реализацию для всех свойств и методов интерфейса. Вы не наследуете никакой код, конструкторы или значения по умолчанию — вы только обещаете соответствовать структуре интерфейса.
Пример конкретного класса:
class Logger {
String prefix = '[LOG]';
void log(String message) {
print('$prefix $message');
}
void clear() {
print('Clearing logs...');
}
}Реализация неявного интерфейса:
class FileLogger implements Logger {
@override
String prefix = '[FILE]';
@override
void log(String message) {
print('$prefix Writing to file: $message');
}
@override
void clear() {
print('Deleting log file...');
}
}Это делает неявные интерфейсы мощным инструментом для создания взаимозаменяемых компонентов. Код, который ожидает Logger, может работать с любым классом, реализующим его интерфейс, независимо от внутренней реализации.
Попробуйте сами
import 'notification.dart';
void main() {
// TODO: Создайте экземпляр EmailNotification
// TODO: Выведите статус уведомления по электронной почте, используя getStatus()
// TODO: Отправьте сообщение 'Hello World', используя уведомление по электронной почте
// TODO: Выведите пустую строку
// TODO: Создайте экземпляр PushNotification
// TODO: Выведите статус push-уведомления, используя getStatus()
// TODO: Отправьте сообщение 'Hello World', используя push-уведомление
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий