코딩을 하다 보면, 객체를 만들어야 하는데 생성자 매개변수가 딱 맞지 않을 때가 있습니다. 이럴 때를 위해 Dart에는 factory 키워드와 함께 생성자를 호출할 수 있어요.
케이크를 만들기 위해 밀가루(flour)와 계란(egg), 2가지 재료가 필요하다고 가정해 볼 텐데요, 비유를 통해 이해해보도록 해요.
일반 생성자: '레시피 원칙주의자'
일반 생성자는 레시피에 적힌 재료가 정확히 와야만 요리를 시작하는 원칙주의자입니다.
"'밀가루 500g'과 '계란 3개'를 각각 따로따로 저에게 주세요. 그렇지 않으면 케이크를 만들 수 없습니다."
factory 생성자: '적당히 타협하는 해결사'
그런데 만약 우리에게 주어진 재료가 "밀가루 500g과 계란 3개가 담긴 재료 상자" 라는 하나의 '묶음(box)'이라면 어떨까요?
원칙주의자는 이 상자를 보고는 요리를 거부할 겁니다. box가 아니라 flour와 egg가 필요하기 때문이지요.
바로 이때, factory 를 활용할 수 있어요. 이 해결사는 손님이 어떤 형태로 재료를 가져와도, 그것을 잘 손질하고 가공해서 원칙주의자(기본 생성자)에게 전달하는 역할을 합니다.
아래는 '재료 상자(Map)'를 받아 케이크를 만드는 예시입니다.
// 만들고 싶은 최종 제품
class Cake {
final String flour;
final int eggs;
// '원칙주의자'는 정확한 재료만 받는다.
Cake(this.flour, this.eggs);
// '해결사' 등장!
// 재료 상자(Map)를 받아서, 안을 열어보고 재료를 손질한다.
factory Cake.fromBox(Map<String, String> box) {
// 1단계: 재료 손질 (가공)
// 상자 안에서 'flour_info'를 꺼내고, 'g' 단위를 뗀다.
final preparedFlour = box['flour_info']!.replaceAll('g', '');
// 상자 안에서 'egg_count'를 꺼내고, 숫자(int)로 변환한다.
final preparedEggs = int.parse(box['egg_count']!);
// 2단계: 레시피대로 전달
// 이제 잘 손질된 재료로 원칙주의자를 불러 케이크를 만든다.
return Cake(preparedFlour, preparedEggs);
}
}
void main() {
// 재료 상자를 만든다.
final myBox = {
'flour_info': '500g',
'egg_count': '3',
};
// 재료 상자를 해결사에게 전달!
final aCake = Cake.fromBox(myBox);
print(aCake.flour); // 출력: 500
print(aCake.eggs); // 출력: 3
}
factory 생성자
factory는 한마디로, "그냥은 안돼? 그럼 되게 만들어 줄게!" 라고 말하는 해결사입니다.
일반 생성자가 원하는 재료의 형태와 우리가 가진 재료의 형태가 다를 때, 그 중간에서 재료를 똑똑하게 가공하고 변환하여 최종적으로 원하는 객체를 만들어주는 매우 유용한 특별 생성자인 것입니다.
factory 생성자 특징!
인스턴스를 캐시에서 반환하거나 서브타입의 인스턴스를 반환할 수 있어요.
final 변수를 초기화 리스트에서 다루지 않는 로직을 사용하여 초기화하는 방법으로 사용할 수 있어요.
this에 접근할 수 없어요.
return이 필수예요. factory는 기본 생성자로 구현하기 때문이에요.
