Что такое имутабельный класс?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Имутабельный (Immutable) класс в Dart и Flutter
Определение
Имутабельный класс — это класс, у которого поля (свойства) не могут быть изменены после создания объекта. Один раз созданный объект остаётся неизменным на протяжении всей жизни.
Это ключевая концепция в функциональном программировании и современном Dart, особенно в Flutter.
Почему это важно в Flutter
Flutter использует концепцию immutability для:
- Оптимизации производительности — Flutter может сравнивать объекты по значению
- Предсказуемости — нет неожиданных побочных эффектов
- Безопасности потоков — immutable объекты безопасны в многопоточной среде
- Реактивности — легче отслеживать изменения состояния
Практические примеры
1. Базовый immutable класс
// ❌ НЕПРАВИЛЬНО: изменяемый класс
class User {
String name; // Может быть изменено!
int age; // Может быть изменено!
User(this.name, this.age);
}
final user = User('John', 25);
user.name = 'Jane'; // Можно менять — плохо!
// ✅ ПРАВИЛЬНО: имутабельный класс
class User {
final String name; // Не может быть изменено
final int age; // Не может быть изменено
const User(this.name, this.age); // const конструктор
}
final user = User('John', 25);
// user.name = 'Jane'; // ❌ Ошибка компиляции!
2. Аннотация @immutable (лучше практика)
import 'package:flutter/foundation.dart';
@immutable
class User {
final String id;
final String name;
final int age;
final List<String> hobbies; // ⚠️ Список сам по себе изменяемый!
const User({
required this.id,
required this.name,
required this.age,
required this.hobbies,
});
// Метод для создания копии с изменениями
User copyWith({
String? id,
String? name,
int? age,
List<String>? hobbies,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
age: age ?? this.age,
hobbies: hobbies ?? this.hobbies,
);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is User &&
runtimeType == other.runtimeType &&
id == other.id &&
name == other.name &&
age == other.age &&
hobbies == other.hobbies;
@override
int get hashCode => id.hashCode ^ name.hashCode ^ age.hashCode ^ hobbies.hashCode;
}
3. Использование copyWith для изменений
// Вместо прямого изменения:
user.name = 'Jane'; // ❌
// Создаём новый объект с изменениями:
final updatedUser = user.copyWith(name: 'Jane'); // ✅
// Старый объект остаётся неизменным
print(user.name); // 'John'
print(updatedUser.name); // 'Jane'
Immutable коллекции
4. Правильная работа с коллекциями
@immutable
class Team {
final String name;
final List<User> members; // Проблема: List изменяемый!
const Team({
required this.name,
required this.members,
});
}
// ❌ Проблема: можно изменить список через тот же объект
final team = Team(name: 'Developers', members: [
User('id1', 'John', 25, []),
User('id2', 'Jane', 28, []),
]);
team.members.add(User('id3', 'Bob', 30, [])); // Список изменился!
// ✅ РЕШЕНИЕ: использовать const или UnmodifiableListView
@immutable
class Team {
final String name;
final List<User> members;
Team({
required this.name,
required List<User> members,
}) : members = List.unmodifiable(members); // Защита от изменений
Team copyWith({
String? name,
List<User>? members,
}) {
return Team(
name: name ?? this.name,
members: members ?? [...this.members], // Создаём новый список
);
}
}
// Теперь нельзя изменить список
team.members.add(User(...)); // ❌ Ошибка!
Freezed пакет — автоматизация
5. Использование freezed для автоматического создания immutable классов
// pubspec.yaml
dependencies:
freezed_annotation: ^2.4.0
dev_dependencies:
build_runner: ^2.4.0
freezed: ^2.4.0
// ===== user.dart =====
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart'; // Генерируется автоматически
part 'user.g.dart'; // JSON сериализация
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required int age,
@Default([]) List<String> hobbies,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// ✅ Freezed автоматически генерирует:
// - copyWith() метод
// - == и hashCode
// - toString()
// - JSON сериализацию
Использование в Flutter State Management
6. Immutable состояние с BLoC паттерном
// ❌ НЕПРАВИЛЬНО: изменяемое состояние
abstract class LoginState {}
class LoginInitial extends LoginState {}
class LoginLoading extends LoginState {}
class LoginSuccess extends LoginState {
String? token;
void setToken(String t) => token = t; // Опасно!
}
// ✅ ПРАВИЛЬНО: immutable состояния
abstract class LoginState {
const LoginState();
}
class LoginInitial extends LoginState {
const LoginInitial();
}
class LoginLoading extends LoginState {
const LoginLoading();
}
@immutable
class LoginSuccess extends LoginState {
final String token;
final DateTime timestamp;
const LoginSuccess({
required this.token,
required this.timestamp,
});
}
@immutable
class LoginError extends LoginState {
final String message;
final StackTrace? stackTrace;
const LoginError({
required this.message,
this.stackTrace,
});
}
// Использование в BLoC
class LoginBloc extends Bloc<LoginEvent, LoginState> {
LoginBloc() : super(const LoginInitial()) {
on<LoginSubmittedEvent>(_onLoginSubmitted);
}
Future<void> _onLoginSubmitted(
LoginSubmittedEvent event,
Emitter<LoginState> emit,
) async {
emit(const LoginLoading()); // Новое состояние
try {
final token = await _authRepository.login(
email: event.email,
password: event.password,
);
emit(LoginSuccess(
token: token,
timestamp: DateTime.now(),
));
} catch (e) {
emit(LoginError(message: e.toString()));
}
}
}
Лучшие практики
Чеклист immutable класса:
@immutable
class GoodClass {
// ✓ Все поля final
final String field1;
final int field2;
final List<String> list; // Защищена от изменений
// ✓ const конструктор
const GoodClass({
required this.field1,
required this.field2,
required this.list,
});
// ✓ copyWith для создания модифицированных копий
GoodClass copyWith({
String? field1,
int? field2,
List<String>? list,
}) {
return GoodClass(
field1: field1 ?? this.field1,
field2: field2 ?? this.field2,
list: list ?? this.list,
);
}
// ✓ Переопределены == и hashCode
@override
bool operator ==(Object other) => /* ... */;
@override
int get hashCode => /* ... */;
}
Сравнение производительности
Immutable vs Mutable
// Mutable: каждое изменение требует слежения
class MutableUser {
String name = 'John';
// При изменении name нужно уведомить все слушатели
}
// Immutable: можно просто сравнить ссылку на объект
class ImmutableUser {
final String name;
const ImmutableUser(this.name);
// Flutter знает, что если ссылка другая, то данные изменились
}
// В Flutter это означает более эффективный rebuild
Заключение
Имутабельные классы — это:
- Основа правильного дизайна в Dart и Flutter
- Оптимизация производительности за счёт эффективного сравнения
- Безопасность — нет неожиданных побочных эффектов
- Читаемость — явно видно, что объект не меняется
- Важно для State Management — BLoC, Riverpod, Provider требуют immutable состояния
Можно использовать либо ручное создание с @immutable аннотацией, либо freezed пакет для автоматизации.