Ответ
Иммутабельный класс в Dart — это класс, состояние которого нельзя изменить после создания. Вот как его правильно реализовать:
Базовый иммутабельный класс:
class ImmutableUser {
final String name;
final int age;
final List<String> roles;
const ImmutableUser({
required this.name,
required this.age,
List<String>? roles,
}) : roles = List.unmodifiable(roles ?? []);
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is ImmutableUser &&
runtimeType == other.runtimeType &&
name == other.name &&
age == other.age &&
const ListEquality().equals(roles, other.roles);
}
@override
int get hashCode => name.hashCode ^ age.hashCode ^ roles.hashCode;
@override
String toString() => 'ImmutableUser(name: $name, age: $age, roles: $roles)';
}
Метод copyWith для создания модифицированных копий:
class ImmutableUser {
// ... поля и конструктор из примера выше
ImmutableUser copyWith({
String? name,
int? age,
List<String>? roles,
}) {
return ImmutableUser(
name: name ?? this.name,
age: age ?? this.age,
roles: roles ?? this.roles,
);
}
}
// Использование:
final user1 = ImmutableUser(name: 'Алексей', age: 30, roles: ['admin']);
final user2 = user1.copyWith(age: 31);
print(user1); // ImmutableUser(name: Алексей, age: 30, roles: [admin])
print(user2); // ImmutableUser(name: Алексей, age: 31, roles: [admin])
Иммутабельная коллекция с вложенными объектами:
import 'package:collection/collection.dart';
class ImmutableProject {
final String id;
final String title;
final ImmutableUser owner;
final List<ImmutableUser> members;
const ImmutableProject({
required this.id,
required this.title,
required this.owner,
List<ImmutableUser>? members,
}) : members = List.unmodifiable(members ?? []);
ImmutableProject copyWith({
String? id,
String? title,
ImmutableUser? owner,
List<ImmutableUser>? members,
}) {
return ImmutableProject(
id: id ?? this.id,
title: title ?? this.title,
owner: owner ?? this.owner,
members: members ?? this.members,
);
}
// Для глубокого сравнения используем DeepCollectionEquality
@override
bool operator ==(Object other) {
return identical(this, other) ||
other is ImmutableProject &&
runtimeType == other.runtimeType &&
id == other.id &&
title == other.title &&
owner == other.owner &&
const DeepCollectionEquality().equals(members, other.members);
}
@override
int get hashCode => Object.hash(id, title, owner,
const DeepCollectionEquality().hash(members));
}
Freezed package для автоматической генерации:
// pubspec.yaml: freezed: ^2.0.0
// pubspec.yaml: freezed_annotation: ^2.0.0
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
@freezed
class User with _$User {
const factory User({
required String name,
required int age,
@Default([]) List<String> roles,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Freezed автоматически генерирует:
// - copyWith
// - toString
// - equals/hashCode
// - toJson/fromJson
// - конструктор const
Преимущества иммутабельных классов:
- Потокобезопасность — можно безопасно использовать в асинхронном коде
- Предсказуемость — состояние не меняется неожиданно
- Кэширование — const-конструкторы позволяют кэшировать экземпляры
- Отладка — проще отслеживать изменения через copyWith
- Совместимость с ValueNotifier/ChangeNotifier — иммутабельные объекты идеально подходят для реактивного программирования
Важные нюансы:
- Используйте
List.unmodifiable()илиUnmodifiableListViewдля защиты коллекций - Для глубокой иммутабельности все вложенные объекты тоже должны быть иммутабельными
- Const-конструкторы работают только если все поля final и сами являются compile-time константами