Menu
Coddy logo textTech

Неявные интерфейсы

Часть раздела Объектно-ориентированное программирование путешествия по 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, может работать с любым классом, реализующим его интерфейс, независимо от того, как эта реализация устроена внутри.

challenge icon

Задание

Легко

Давайте создадим систему уведомлений, которая демонстрирует, как конкретные классы могут служить неявными интерфейсами. Вы создадите базовый класс уведомлений, а затем реализуете его совершенно другим способом, показывая, что 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-уведомление
  
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование