불변성(Immutability)이란 객체가 생성된 후 그 상태를 변경할 수 없는 특성을 말합니다.
즉, 불변 객체는 한 번 만들어지면 그 값을 바꿀 수 없는 객체입니다.
class Person {
final String name;
final int age;
Person(this.name, this.age);
}
위 예시에서 Person 클래스는 final 필드를 가지고 있으므로, 생성된 이후에는 값을 바꿀 수 없습니다.
하지만 객체 자체는 여전히 변할 수 있습니다. 예를 들어, 리스트를 포함하고 있으면 내부는 바뀔 수 있습니다.
class Team {
final List<String> members;
Team(this.members);
}
void main() {
final team = Team(['Alice', 'Bob']);
team.members.add('Charlie'); // 가능함
}
→ final은 변수 자체의 레퍼런스만 고정할 뿐, 내부는 바뀔 수 있습니다.
| 항목 | 설명 |
|---|---|
| 예측 가능한 코드 | 객체 상태가 바뀌지 않기 때문에 버그가 줄어듭니다. |
| 테스트 쉬움 | 상태가 고정되므로 단위 테스트하기에 매우 좋습니다. |
| 추론 쉬움 | 값이 바뀌지 않으니 코드 흐름을 추론하기 쉽습니다. |
| 멀티스레딩 안정성 | 상태가 고정되어 있으므로 스레드 간 충돌이 없습니다. |
| Flutter UI 빌드 최적화 | 변경되지 않으면 UI를 리빌드할 필요가 없어 효율적입니다. |
final 키워드 사용각 필드를 final로 선언하면 해당 필드는 한 번만 값을 가질 수 있습니다.
class User {
final String id;
final String name;
User(this.id, this.name);
}
하지만 이 방식으로는 모든 불변 객체를 만들기엔 한계가 있습니다.
==, hashCode, copyWith, toJson 등을 수동으로 작성해야 함Dart에서 객체를 비교할 때 기본적으로 주소를 비교합니다. 값이 같더라도 주소를 비교하기 때문에 ==으로 비교하게되면 false로 나옵니다.
final a = User(id: '1', name: 'Alice');
final b = User(id: '1', name: 'Alice');
print(a == b); // false (주소가 다름)
Set이나 Map의 key 비교는 hashCode를 보고 그 다음에 ==으로 비교합니다. 만약 ==은 true인데 hashCode가 다르면 같은 값인데도 다른 객체로 인식되기 때문에 항상 같이 오버라이드 해야합니다.
class User {
final String id;
final String name;
User(this.id, this.name);
bool operator ==(Object other) {
return other is User && other.id == id && other.name == name;
}
// ❌ hashCode는 오버라이드하지 않음!
}
void main() {
final a = User('1', 'Alice');
final b = User('1', 'Alice');
print(a == b); // true → 값은 같다고 판단
final map = {a: 'hello'};
print(map[b]); // ❌ null → hashCode가 달라서 찾지 못함
}
두 객체가
==로 같다고 판단되면, 그들의hashCode도 같아야 합니다.
class User {
final String id;
final String name;
User(this.id, this.name);
bool operator ==(Object other) {
return other is User && other.id == id && other.name == name;
}
int get hashCode => id.hashCode ^ name.hashCode;
}
void main() {
final a = User('1', 'Alice');
final b = User('1', 'Alice');
print(a == b); // ✅ true
print(a.hashCode == b.hashCode); // ✅ true
final map = {a: 'hello'};
print(map[b]); // ✅ hello → 같은 key로 인식
}
이렇게 하면 값이 같으면 같은 객체로 인식됩니다. 하지만 매번 이런 코드를 쓰는 건 귀찮고 실수 위험도 있습니다.
→ 이를 해결하기 위해 나온 게 Freezed 패키지입니다.
class User with _$User {
const factory User({
required String id,
required String name,
}) = _User;
}
void main() {
final a = User(id: '1', name: 'Alice');
final b = User(id: '1', name: 'Alice');
print(a == b); // ✅ true
print(a.hashCode == b.hashCode); // ✅ true
final map = {a: 'hello'};
print(map[b]); // ✅ hello
}
→ 내부적으로 operator와 hashcode가 자동 생성됩니다.
→ 값 비교 기반 객체를 안전하고 간결하게 구현 가능합니다.
Dart에서 불변 객체와 데이터 클래스를 쉽게 만들 수 있도록 도와주는 코드 생성기입니다.
| 기능 | 설명 |
|---|---|
| 자동 생성 | ==, hashCode, copyWith, toString 등 자동 생성 |
| copyWith 지원 | 일부 필드만 변경한 새 객체 생성 가능 |
| sealed class 지원 | 복잡한 상태 표현에 유리 (예: union type) |
| 진정한 불변성 | 모든 필드는 final, 리스트는 UnmodifiableListView 등으로 감쌈 |
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
class User with _$User {
const factory User({
required String id,
required String name,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
flutter pub run build_runner build
이 명령어는 user.freezed.dart 와 user.g.dart 파일을 생성합니다.
final user1 = User(id: '1', name: 'Alice');
final user2 = user1.copyWith(name: 'Bob'); // 변경은 이렇게만 가능
print(user1 == user2);
Dart에서 객체와 JSON 간 변환을 자동으로 해주는 코드 생성 도구입니다.
freezed와 함께 사용하면 강력한 시너지를 발휘합니다.
fromJson, toJson 만들면 귀찮고 실수 가능성 큼
class Product with _$Product {
const factory Product({
required int id,
required String name,
}) = _Product;
factory Product.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
}
flutter pub run build_runner build
이 명령어는 product.g.dart 파일을 생성합니다. 해당 파일이 생성되고 자동으로 JSON 변환이 가능해집니다.
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^3.0.0
dev_dependencies:
build_runner: ^2.4.15
freezed: ^3.0.6
json_serializable: ^6.9.5
flutter pub get
copyWith를 통해 일부 상태만 변경 가능 → 재사용 용이Widget.build()가 자주 호출되는 구조에서 객체가 바뀌지 않으면 성능 유지됨| 장점 | 단점 |
|---|---|
| 상태 관리, 테스트에 매우 유리 | 초기 작성 시 다소 복잡할 수 있음 |
| 불변 객체로 인해 버그 감소 | 코드 생성 툴 의존 (build_runner) |
| Freezed로 자동화 가능 | 빌드 속도가 느려질 수 있음 (대규모일 때) |
객체 불변성이란? → 생성 후 상태가 변하지 않는 객체
왜 중요? → 예측 가능성, 안정성, 테스트 용이성
어떻게 구현?
finalfreezed + json_serializable로 자동화