Freezed 도입 이전에 직렬화와 불변 객체 생성에 대한 이해 개념이 필요하다.
본 글은 코딩 공부를 시작하는 조카를 위해 최대한 쉽게 설명해보겠다.
불변 객체는 한 번 만들어지면 그 값을 바꿀 수 없는 객체다.
예를 들어, 생성한 게임 캐릭터가 있다고 생각해보면 이 캐릭터의 이름이나 능력치를 한 번 정하면 바꿀 수 없는 것과 같다.
안정성: 불변 객체는 값을 바꿀 수 없어서 실수로 값을 바꾸는 일이 없다.
게임 중에 갑자기 캐릭터의 능력치가 바뀌면 곤란하기 때문이다.
예측 가능성: 언제나 같은 값을 유지하니까 코드가 어떻게 동작할지 예측하기 쉽다.
안전한 공유: 여러 사람이 동시에 객체를 사용할 때도 안전하다.
예를 들어, 친구와 함께 게임을 하는데 친구가 캐릭터의 능력치를 바꾸면 혼란스러울 것이다.
불변 객체는 이런 일을 방지해준다.
간단한 디버깅: 값이 변하지 않아서 디버깅(오류를 찾는 작업)이 더 쉬워진다.
직렬화는 객체를 JSON 같은 형식으로 바꾸는 작업이다.
이렇게 하면 데이터를 네트워크로 주고받거나 파일에 저장할 수 있다.
데이터 전송: 서버와 통신할 때 데이터를 JSON 형식으로 주고 받는다.
예를 들어, 게임 캐릭터 정보를 서버에 보내려면 JSON 형식으로 바꿔서 보내야 한다.
영구 저장: 데이터를 파일에 저장할 때도 JSON 형식으로 저장하면 나중에 쉽게 읽을 수 있다.
디버깅과 로깅: JSON 형식의 데이터를 쉽게 읽고 분석할 수 있어서 디버깅과 로깅에 유용하다.
freezed는 불변 객체를 쉽게 만들고, JSON 직렬화도 간편하게 해주는 라이브러리다.
다음과 같은 장점이 있다:
자동으로 불변 객체 생성: freezed는 불변 객체를 자동으로 만들어줘서 쉽게 사용할 수 있다.
직렬화 및 역직렬화 자동화: JSON 형식으로 데이터를 쉽게 변환하고 다시 객체로 만들 수 있다.
보일러플레이트 코드 제거: 반복적인 코드를 자동으로 생성해줘서 코드가 간결해진다.
built_value는 Dart에서 불변 객체와 빌더 패턴을 제공하는 라이브러리다.
그러나 freezed와 비교했을 때 몇 가지 차이점이 있다.
코드 간결성:
freezed는 코드 제너레이션을 통해 더 간단한 문법을 제공한다.
built_value는 빌더 패턴을 사용하여 좀 더 복잡한 설정이 필요하다고 느꼈다.
// `freezed` 예제
class User with _$User {
factory User({
required String name,
required int age,
}) = _User;
}
// `built_value` 예제
abstract class User implements Built<User, UserBuilder> {
String get name;
int get age;
User._();
factory User([void Function(UserBuilder) updates]) = _$User;
}
패턴 매칭:
freezed는 패턴 매칭을 통해 다양한 상태를 쉽게 처리할 수 있다.
built_value는 패턴 매칭을 제공하지 않는다.
// `freezed` 패턴 매칭 예제
class Result with _$Result {
const factory Result.success(String data) = Success;
const factory Result.error(String message) = Error;
}
void main() {
final result = Result.success('Data loaded');
result.when(
success: (data) => print('Success: $data'),
error: (message) => print('Error: $message'),
);
}
json_serializable은 JSON 직렬화 및 역직렬화에 특화된 라이브러리다.
freezed와 함께 사용될 수 있지만, freezed만으로도 JSON 관련 기능을 처리할 수 있디.
기능의 포괄성:
freezed는 JSON 직렬화 외에도 불변 객체, 패턴 매칭 등 다양한 기능을 제공한다.
json_serializable은 JSON 직렬화에 특화되어 있으며, 데이터 클래스를 직접 생성하지는 않는다.
// `freezed` 예제
class User with _$User {
factory User({
required String name,
required int age,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
equatable은 객체의 동등성 비교를 쉽게 할 수 있도록 도와주는 라이브러리다.
freezed는 이를 내장하고 있어 별도로 사용하지 않아도 된다.
동등성 비교:
freezed는 equatable을 내장하여 객체의 동등성 비교를 자동으로 처리한다.
equatable은 동등성 비교만을 위한 라이브러리로, 다른 기능은 제공하지 않는다.
// `freezed` 예제
class User with _$User {
factory User({
required String name,
required int age,
}) = _User;
}
// `equatable` 예제
class User extends Equatable {
final String name;
final int age;
User({required this.name, required this.age});
List<Object> get props => [name, age];
}
freezed는 다양한 기능을 하나의 라이브러리로 제공하여, 별도의 라이브러리를 사용하지 않고도 불변 객체 생성, JSON 직렬화, 패턴 매칭, 동등성 비교 등을 쉽게 할 수 있게 해준다.
built_value, json_serializable, equatable 등 다른 라이브러리와 비교했을 때, 더 많은 기능을 제공하며 코드의 간결성과
생산성을 높여준다.
Step 1: pubspec.yaml 파일에 의존성 추가
dependencies:
flutter:
sdk: flutter
freezed_annotation: ^2.0.0
dev_dependencies:
build_runner: ^2.0.0
freezed: ^2.0.0
json_serializable: ^6.0.0 # JSON serialization을 사용하는 경우
Step 2: 의존성 설치
flutter pub get
freezed를 사용하여 간단한 예제를 만들어 보겠다.
예제에서는 사용자(User) 모델을 생성하고, 이를 JSON으로 직렬화하는 방법을 포함한다.
Step 1: 모델 클래스 생성
user.dart 파일을 생성한다.
필요한 패키지를 임포트한다.
import 'package:freezed_annotation/freezed_annotation.dart';
part 'user.freezed.dart';
part 'user.g.dart';
class User with _$User {
const factory User({
required String name,
required int age,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
Step 2: 코드 생성
flutter pub run build_runner build
이 명령어는 user.freezed.dart와 user.g.dart 파일을 생성한다.
이 파일들은 freezed가 필요한 모든 boilerplate 코드를 생성한다.
Step 3: 사용 예제
void main() {
// User 인스턴스 생성
final user = User(name: 'John Doe', age: 30);
// User 인스턴스를 JSON으로 변환
final userJson = user.toJson();
print(userJson); // { "name": "John Doe", "age": 30 }
// JSON을 User 인스턴스로 변환
final newUser = User.fromJson(userJson);
print(newUser); // User(name: John Doe, age: 30)
}