← Назад к вопросам

Что такое имутабельный класс?

2.3 Middle🔥 191 комментариев
#Dart#State Management#ООП и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Имутабельный (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 пакет для автоматизации.