Обобщенные классы
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 65 из 110.
Вы видели, как встроенные коллекции Dart, такие как List<String>, используют параметры типов. Теперь пришло время создать свои собственные обобщенные классы, которые работают с любым указанным вами типом.
Обобщенный класс использует параметр типа (обычно T) в качестве заполнителя для фактического типа, который будет предоставлен при создании экземпляра:
class Box<T> {
T content;
Box(this.content);
T getContent() => content;
}
void main() {
var stringBox = Box<String>('Hello');
var intBox = Box<int>(42);
print(stringBox.getContent()); // Hello
print(intBox.getContent()); // 42
}T выступает в качестве переменной типа — когда вы создаете Box<String>, каждый T в классе становится String. Это обеспечивает типобезопасность без необходимости написания отдельных классов для каждого типа.
Вы можете использовать несколько параметров типа, когда вашему классу необходимо работать с более чем одним типом:
class Pair<K, V> {
K first;
V second;
Pair(this.first, this.second);
}
void main() {
var pair = Pair<String, int>('age', 25);
print('${pair.first}: ${pair.second}'); // age: 25
}Обобщенные классы — это основа для создания повторно используемого и типобезопасного кода. Вместо дублирования логики для различных типов или потери информации о типах при использовании dynamic, обобщения позволяют писать гибкий код, который компилятор все еще может проверить.
Задание
ЛегкоДавайте создадим систему упаковки подарков, которая использует универсальные классы (generics) для создания гибких контейнеров для различных типов предметов. Вы создадите универсальный класс Wrapper, который может упаковать любой тип содержимого, и класс GiftBox с двумя параметрами типа, чтобы объединить подарок с сообщением на открытке.
Вы организуете свой код в два файла:
wrapper.dart: Определите здесь свои универсальные классы:- Универсальный класс
Wrapper<T>со свойствомT contentи конструктором. Добавьте методunwrap(), который возвращает содержимое, и методdescribe(), который выводитWrapped: [content] - Универсальный класс
GiftBox<G, C>с двумя свойствами:G giftиC card. Добавьте конструктор и методopen(), который выводитGift: [gift]на одной строке иCard: [card]на следующей
- Универсальный класс
main.dart: Импортируйте файл wrapper и продемонстрируйте работу универсальных классов с различными типами:- Создайте
Wrapper<String>, содержащий'Chocolate', и вызовитеdescribe() - Создайте
Wrapper<int>, содержащий42, и вызовитеdescribe() - Создайте
Wrapper<double>, содержащий3.14, и вызовитеdescribe() - Выведите пустую строку
- Создайте
GiftBox<String, String>с подарком'Teddy Bear'и открыткой'Happy Birthday!', затем вызовитеopen() - Выведите пустую строку
- Создайте
GiftBox<int, String>с подарком100(представляющим сумму подарочной карты) и открыткой'Congratulations!', затем вызовитеopen()
- Создайте
Обратите внимание, как один и тот же класс Wrapper беспрепятственно работает со строками, целыми числами и числами с плавающей точкой. Класс GiftBox демонстрирует использование двух параметров типа для объединения различных типов — подарок может быть любого типа, в то время как сообщение на открытке остается строкой.
Ожидаемый вывод:
Wrapped: Chocolate
Wrapped: 42
Wrapped: 3.14
Gift: Teddy Bear
Card: Happy Birthday!
Gift: 100
Card: Congratulations!Шпаргалка
Обобщенные классы используют параметры типа (например, T) в качестве заполнителей для реальных типов, указываемых при создании экземпляров:
class Box<T> {
T content;
Box(this.content);
T getContent() => content;
}
void main() {
var stringBox = Box<String>('Hello');
var intBox = Box<int>(42);
print(stringBox.getContent()); // Hello
print(intBox.getContent()); // 42
}При создании Box<String> каждый T в классе заменяется на String, обеспечивая типобезопасность без необходимости создания отдельных классов для каждого типа.
Используйте несколько параметров типа при работе с более чем одним типом:
class Pair<K, V> {
K first;
V second;
Pair(this.first, this.second);
}
void main() {
var pair = Pair<String, int>('age', 25);
print('${pair.first}: ${pair.second}'); // age: 25
}Попробуйте сами
import 'wrapper.dart';
void main() {
// TODO: Создайте Wrapper<String>, содержащий 'Chocolate', и вызовите describe()
// TODO: Создайте Wrapper<int>, содержащий 42, и вызовите describe()
// TODO: Создайте Wrapper<double>, содержащий 3.14, и вызовите describe()
// TODO: Выведите пустую строку
// TODO: Создайте GiftBox<String, String> с подарком 'Teddy Bear'
// и открыткой 'Happy Birthday!', затем вызовите open()
// TODO: Выведите пустую строку
// TODO: Создайте GiftBox<int, String> с подарком 100
// и открыткой 'Congratulations!', затем вызовите open()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий