Реализация против наследования
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 47 из 110.
Теперь, когда вы понимаете и extends, и implements, давайте проясним, когда использовать каждое из них.
Используйте extends, когда вы хотите наследовать поведение. Ваш подкласс получает весь код родителя бесплатно и может дополнять его:
class Animal {
void breathe() {
print('Breathing...');
}
}
class Dog extends Animal {
void bark() {
print('Woof!');
}
}
void main() {
var dog = Dog();
dog.breathe(); // Наследуется — работает автоматически
dog.bark();
}Используйте implements, когда вы хотите гарантировать структуру без наследования кода. Вы должны написать каждый метод самостоятельно:
class Robot implements Animal {
@override
void breathe() {
print('Ventilating circuits...'); // Полностью кастомная реализация
}
}Ключевое отличие: extends говорит: «Я являюсь типом этого», в то время как implements говорит: «Я могу делать то же, что и это». Dog — это Animal, и она дышит так же. Robot не является животным, но он может выполнять тот же контракт, используя свою собственную логику.
Помните: вы можете расширить только один класс, но можете реализовать множество интерфейсов. Выбирайте extends для истинных отношений наследования, и implements, когда вам нужна гибкость или вы хотите обеспечить соблюдение контракта без совместного использования деталей реализации.
Задание
ЛегкоДавайте создадим систему рабочих, которая продемонстрирует, когда использовать extends, а когда — implements. Вы создадите базовый класс рабочего, а затем покажете оба подхода: один класс, который действительно «является» рабочим и наследует поведение, и другой, который просто «может делать» то же, что и рабочий, но с полностью собственной логикой.
Вы организуете свой код в двух файлах:
worker.dart: Определите здесь иерархию рабочих:- Класс
Workerсо свойствомString nameи конструктором. Включите методwork(), который выводит'$name is working...', и методtakeBreak(), который выводит'$name takes a coffee break' - Класс
OfficeWorker, который расширяет (extends)Worker. Добавьте свойствоString department. Переопределитеwork(), чтобы он выводил'$name is typing reports in $department', но сохраните унаследованное поведениеtakeBreak() - Класс
RobotWorker, который реализует (implements)Worker. Добавьте свойствоString model. Поскольку роботы на самом деле не являются людьми-рабочими, но могут выполнять их функции, предоставьте полностью собственные реализации:work()должен выводить'Robot $model is assembling parts', аtakeBreak()должен выводить'Robot $model is recharging'
- Класс
main.dart: Импортируйте файл с рабочими и продемонстрируйте оба подхода:- Создайте
OfficeWorkerс именем'Alice'в отделе'Marketing' - Создайте
RobotWorkerс моделью'RX-7' - Для офисного работника вызовите
work(), а затемtakeBreak() - Выведите пустую строку
- Для робота-рабочего вызовите
work(), а затемtakeBreak()
- Создайте
Обратите внимание, как OfficeWorker автоматически наследует takeBreak(), потому что он расширяет Worker — он действительно «является» рабочим. В то же время RobotWorker должен предоставить собственную реализацию для всего, так как он только реализует интерфейс — он «может делать» то, что делает рабочий, но по-своему, по-роботизированному.
Ожидаемый вывод:
Alice is typing reports in Marketing
Alice takes a coffee break
Robot RX-7 is assembling parts
Robot RX-7 is rechargingШпаргалка
Используйте extends для наследования поведения от родительского класса. Подкласс автоматически получает все методы родителя и может их переопределять:
class Animal {
void breathe() {
print('Breathing...');
}
}
class Dog extends Animal {
void bark() {
print('Woof!');
}
}
void main() {
var dog = Dog();
dog.breathe(); // Наследуется автоматически
dog.bark();
}Используйте implements, чтобы гарантировать структуру без наследования кода. Вы должны предоставить собственную реализацию для каждого метода:
class Robot implements Animal {
@override
void breathe() {
print('Ventilating circuits...');
}
}Ключевые различия:
extends: «Я являюсь типом этого» — истинное наследование с общим кодомimplements: «Я могу делать то же, что и это» — обеспечивает соблюдение контракта с пользовательской логикой- Вы можете расширять только один класс, но реализовывать несколько интерфейсов
Выбирайте extends для истинных отношений «родитель-потомок», где поведение должно наследоваться. Выбирайте implements, когда вам нужна гибкость или вы хотите обеспечить соблюдение контракта без совместного использования реализации.
Попробуйте сами
import 'worker.dart';
void main() {
// TODO: Создайте OfficeWorker по имени 'Alice' в отделе 'Marketing'
// TODO: Создайте RobotWorker с моделью 'RX-7'
// TODO: Для офисного работника вызовите work(), затем takeBreak()
// TODO: Выведите пустую строку
// TODO: Для робота-работника вызовите work(), затем takeBreak()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий