Menu
Coddy logo textTech

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 должны иметь абсолютно одинаковое поведение.

ОсобенностьИнтерфейсМиксин
ПредоставляетТолько контрактКонтракт + код
РеализацияВы пишете её самиВключена
Ключевое словоimplementswith
Лучше всего подходит дляРазличное поведение, одинаковый APIОбщее поведение

Вы даже можете комбинировать оба подхода — использовать интерфейсы для определения того, что может делать класс, и миксины для предоставления общих реализаций там, где это уместно. В Dart при сочетании обоих вариантов всегда ставьте with перед implements:

class MyClass with SomeMixin implements SomeInterface {
  // with указывается перед implements
}
challenge icon

Задание

Легко

Давайте создадим систему уведомлений, которая продемонстрирует, когда использовать интерфейсы, а когда — миксины. Вы создадите интерфейс для уведомлений, требующих различных реализаций, и миксин для общего поведения логирования, которое должно быть одинаковым для всех типов уведомлений.

Вы организуете свой код в три файла:

  • 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() автоматически

Когда использовать каждый из них:

  • Используйте интерфейс, когда разным классам нужны одинаковые сигнатуры методов, но разные реализации
  • Используйте миксин, когда классы должны иметь одинаковое поведение
ОсобенностьИнтерфейсМиксин
ПредоставляетТолько контрактКонтракт + код
РеализацияПишете выВключена
Ключевое словоimplementswith
Лучше всего дляРазного поведения при одинаковом 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')
}
quiz iconПроверьте себя

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

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