factory constructor를 통해서 constructor body 실행 전 final 클래스변수가 초기화 되지 않는 문제를 해결하는 것에 대한 글
https://dart.dev/language/constructors#factory-constructors
Constructors
Everything about using constructors in Dart.
dart.dev
factory constructor는 알 것 같으면서도 잘 모르겠는 그런 존재였다.
싱글턴 패턴을 적용하기 위해 자주 사용했지만, 공식문서에서 이 용도 이외에도 다음과 같은 다른 용도들을 언급하고 있었다:
- subtype의 인스턴스를 반환
- initializer list에서 초기화 하기엔 초기화 로직이 너무 복잡한 final 클래스 변수 초기화
아직까지 factory constructor를 위 두가지 목적으로 한번도 사용해본적이 없었다.
그런데 최근에 flutter_flavorizr 패키지의 소스코드를 읽어보던 중 factory constructor가 사용된 것을 보게되었고, 왠지 위 두가지 중 하나의 이유로 factory constructor를 사용했을 것 같다는 생각이 들었다.
그래서 왜 factory constructor를 사용했는지 궁금했고, 한번 내 나름대로 그 이유를 찾아보았다.
factory constructor의 사용처 - Pubspec 클래스
flutter_flavorizer 프로젝트의 lib/src/parser/models/pubspec.dart 파일은 다음과 같다:
import 'package:checked_yaml/checked_yaml.dart';
import 'package:flutter_flavorizr/src/parser/models/flavorizr.dart';
import 'package:json_annotation/json_annotation.dart';
part 'pubspec.g.dart';
@JsonSerializable(anyMap: true, createToJson: false)
class Pubspec {
@JsonKey(required: true)
final Flavorizr flavorizr;
const Pubspec({required this.flavorizr});
factory Pubspec.fromJson(Map json) => _$PubspecFromJson(json);
factory Pubspec.parse(String yaml) =>
checkedYamlDecode(yaml, (o) => Pubspec.fromJson(o ?? {}));
}
Pubspec 클래스에는 총 2개의 factory constructor가 존재한다.
factory constructor가 사용된 이유에 대한 추측
내가 이해하기론 여기서 factory constructor가 쓰인 이유는:
- constructor 내에서 Pubspec 클래스의 final 변수인 flavorizer를 초기화 하는 로직이 복잡해서 이를 다른 함수 등에 위임하고 싶은데,
- 그렇게 하면 constructor내에서 final 변수를 초기화하지 않아서 컴파일러가 내뱉는 오류를 피하기 위함이다.
실제로 Pubspec.parse 생성자에서 factory 키워드를 지우면 final 변수가 초기화되지 않았다는 에러가 발생한다.
그렇다면 factory constructor를 사용했을 때 왜 컴파일러는 초기화 되지 않은 final 변수에 대한 에러를 내뱉지 않는 것일까?
factory constructor는 일반 생성자와는 다르게 마치 함수 같아서 Pubspec 클래스의 인스턴스를 반환한다.
- Pubspec 인스턴스를 반환했다는 말은 그 인스턴스를 생성했다는 것이고
- 그 인스턴스를 생성했다는 말은 Pubspec 클래스의 다른 생성자를 호출했다는 것이고,
- 그 말은 그 생성자는 아무런 컴파일 에러없이 정의됐다는 뜻이고,
- 그말은 그 생성자 안에서 클래스의 final 변수들을 초기화 하고 있다는 뜻이다.
따라서 dart compiler는 factory constructor를 마주쳤을 때 위와 같은 순서를 거쳐 클래스 내의 모든 final 클래스 변수들이 초기화 될 것을 알 수 있다고 추측해볼 수 있다.
그래서 factory constructor 내에서는 final 변수 초기화 로직이 없어도 에러를 뱉지 않는다.
이로 인해 우리는 final 클래스 변수 초기화 로직이 복잡하다면 factory constructor를 통해 그 로직 실행을 다른 constructor/함수 등에 위임할 수 있고, final 변수가 초기화 되지 않았다는 컴파일러의 에러를 피할 수 있다.
물론 공식문서에서는 단순히 final 클래스 변수 초기화 로직이 initializer list에서 구현하기에 복잡한 경우 굳이 위임을 하지 않더라도 factory constructor를 쓰라고 한다.
flavorizr의 Pubspec클래스에서의 활용
마찬가지로 flavorizr 패키지의 Pubspec.parse factory constructor가 Pubspec 인스턴스를 string으로 부터 생성하는 과정이 다음과 같이 복잡하다:
“pubspec 파일문자열로 앍어들이기
→ json으로 파싱
→ 파싱된 json에서 flavorizr value값 가져오기
→ Pubspec 인스턴스 생성”
이는 결국 Pubspec 클래스의 final 변수인 flavorizr를 초기화하는 로직이 복잡하다고 생각할 수 있다.
그래서 flavorizr 변수 초기화를 하는 로직을 Pubspec.parse 생성자가 아닌 다른 로직으로 위임했고, 그 과정에서 Pubspec.parse 내에서는 더이상 final로 정의된 flavorizr 변수를 초기화하지 않게되었으며, 이때 생기는 컴파일러 에러를 피하기 위해 Pubspec.parse 생성자를 factory constructor로 정의한 것이다.
또한 이는 다트 공식문서에서 말한 factory constructor의 유즈케이스 중 하나인 “final로 선언된 flavorizr 변수를 초기화하는 로직이 복잡”한 상황에 해당 된다.
따라서 여러모로 적합한 factory constructor 사용이라 생각한다.
factory constructor의 또다른 용도 - subtype 반환하기
class Book {
// named generative
Book.comic(String title) : this(title, "Champ");
// named factory
factory Book.comic(String title) {
return ComicBook(title);
}
}
class ComicBook extends Book {
ComicBook(String title) : super(title, "Champ");
}
코드출처 https://another-light.tistory.com/77
이렇게도 가능하다.
'mobile > dart & flutter' 카테고리의 다른 글
다트에서의 추상화 전략 (0) | 2024.04.28 |
---|