Mixin и наследование
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 54 из 110.
Теперь, когда вы узнали, как работают миксины, давайте проясним, когда их следует использовать вместо наследования. Ключевое отличие заключается в отношениях, которые они выражают.
Наследование представляет собой отношение "is-a". Dog является Animal. Подкласс — это специализированная версия родительского класса, и вы можете расширять только один класс.
Миксины, с другой стороны, представляют собой отношение «может делать». Dog может плавать, но плавание — это не то, что определяет собаку.
// Наследование: Dog ЯВЛЯЕТСЯ Animal
class Animal {
String name;
Animal(this.name);
}
class Dog extends Animal {
Dog(String name) : super(name);
}
// Mixin: Dog МОЖЕТ плавать (но не определяется плаванием)
mixin Swimming {
void swim() => print('Swimming');
}
class Dog extends Animal with Swimming {
Dog(String name) : super(name);
}Используйте наследование, когда классы имеют общую основу и вам нужны конструкторы в родительском классе. Используйте примеси (mixins), когда вы хотите добавить функциональные возможности классам, не имеющим общего предка.
| Особенность | Наследование | Примесь (Mixin) |
|---|---|---|
| Тип связи | является (is-a) | может (can-do) |
| Ограничение | Только один родитель | Несколько примесей |
| Конструкторы | Разрешены | Не разрешены |
| Вариант использования | Специализация | Общее поведение |
И что самое приятное? Вы можете комбинировать и то, и другое — наследовать один класс и подмешивать несколько возможностей, наделяя ваши классы именно теми функциями, которые им необходимы.
Задание
ЛегкоДавайте создадим систему сотрудников, которая продемонстрирует, когда использовать наследование, а когда — примеси (mixins). Вы создадите базовый класс для отношений «является» (is-a) и примеси для способностей «может делать» (can-do), а затем объедините их, чтобы показать, как оба подхода работают вместе.
Вы организуете свой код в три файла:
worker.dart: Определите базовый класс, который устанавливает основную идентичность всех сотрудников:- Класс
Workerсо свойствамиString nameиString department. Включите конструктор, принимающий оба значения. Добавьте методintroduce(), который выводит'$name works in $department'
- Класс
skills.dart: Определите примеси, которые представляют способности, которыми могут обладать сотрудники (отношения «может делать»):- Примесь
CanCodeс методомwriteCode(), который выводит'Writing code...' - Примесь
CanDesignс методомcreateDesign(), который выводит'Creating design...' - Примесь
CanManageс методомleadTeam(), который выводит'Leading the team...'
- Примесь
main.dart: Импортируйте оба файла и создайте специализированные типы сотрудников, сочетающие наследование с примесями:- Класс
Developer, который расширяетWorkerи использует примесьCanCode - Класс
TechLead, который расширяетWorkerи использует примесиCanCodeиCanManage - Создайте
Developerс именем'Alice'в отделе'Engineering' - Создайте
TechLeadс именем'Bob'в отделе'Engineering' - Для Alice: вызовите
introduce(), затемwriteCode() - Выведите пустую строку
- Для Bob: вызовите
introduce(), затемwriteCode(), затемleadTeam()
- Класс
Обратите внимание, что и Developer, и TechLead являются сотрудниками (наследование), но они могут делать разные вещи (примеси). TechLead объединяет несколько способностей без необходимости множественного наследования.
Ожидаемый результат:
Alice works in Engineering
Writing code...
Bob works in Engineering
Writing code...
Leading the team...Шпаргалка
Наследование представляет собой отношение «is-a» (подкласс является специализированной версией родителя), в то время как примеси (mixins) представляют собой отношение «can-do» (добавление возможностей без определения идентичности).
// Наследование: Dog ЯВЛЯЕТСЯ Animal
class Animal {
String name;
Animal(this.name);
}
class Dog extends Animal {
Dog(String name) : super(name);
}
// Примесь: Dog МОЖЕТ плавать
mixin Swimming {
void swim() => print('Swimming');
}
class Dog extends Animal with Swimming {
Dog(String name) : super(name);
}| Особенность | Наследование | Примесь (Mixin) |
|---|---|---|
| Отношение | is-a | can-do |
| Ограничение | Только один родитель | Множество примесей |
| Конструкторы | Разрешены | Не разрешены |
| Сценарий использования | Специализация | Общее поведение |
Вы можете комбинировать оба подхода — расширять один класс и подмешивать несколько возможностей:
class TechLead extends Worker with CanCode, CanManage {
TechLead(String name, String department) : super(name, department);
}Попробуйте сами
import 'worker.dart';
import 'skills.dart';
// TODO: Создайте класс Developer, который расширяет Worker и использует примесь CanCode
// TODO: Создайте класс TechLead, который расширяет Worker и использует примеси CanCode и CanManage
void main() {
// TODO: Создайте Developer по имени 'Alice' в отделе 'Engineering'
// TODO: Вызовите introduce(), затем writeCode() для Alice
// TODO: Выведите пустую строку
// TODO: Создайте TechLead по имени 'Bob' в отделе 'Engineering'
// TODO: Вызовите introduce(), writeCode(), затем leadTeam() для Bob
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий