DAY 05 | Dart 언어의 class의 constructor를 살펴보자

Pt J·2026년 7월 8일
post-thumbnail

DAY 05 | Dart 언어의 class의 constructor를 살펴보자

수업하고는 무관하지만 무언가 발견해 버렸다. 지난 봄에 이지스퍼블리싱의 《Do it! 바이브코딩 + 안티그래비티》 라는 책의 서평단에 선정된 김에 출판사에서 하는 스터디에도 참여했는데, 참여 완주 보상으로 받을 수 있는 이지스퍼블리싱 도서 목록을 살펴보다가 작년 일이 떠올랐다.

작년에 서울시 기술교육원 동부캠퍼스 디지털콘텐츠디자인과에 다닐 때 교재로 이지스퍼블리싱의 《Do it! HTML+CSS+자바스크립트 웹 표준의 정석》이라는 책을 받았는데, 그때도 이 책으로 진도를 나가고 각자 복습도 해야 하는 김에 (사실 나로서는 대체로 아는 내용이라 복습할 것도 별로 없었지만) 출판사 스터디도 참여하고 완주 보상을 받은 바가 있다.

도서 목록에서 그래도 좀 받아서 공부해 볼 만한 게 뭐가 있을까 하고 살펴보고 고른 게 그 당시 이제 막 개정판이 나왔던 《Do it! 플러터 앱 개발 & 출시하기 - 개정판》이었다. 받아놓고 그걸로 공부를 하지는 않았지만. 아무튼 나한테 Flutter 3.x 버전을 다루는 책이 있었다...?

취향과 대중픽의 타협점이 Flutter라는 건 어쩌면 생각보다 오래 전부터 자명한 사실이었을지도 모르겠다. 잊고 지냈는데 나도 참 한결같은 녀석이다. (일상적인 것부터 나 자신에 대한 것까지 너무 잘 잊어버리는 게 내가 가진 큰 이슈 중 하나라 앱 개발 아이디어 중에도 이를 보완할 수 있는 것을 잠깐 생각해 보기도 했는데 결국 그 맥락의 앱은 투두리스트/스케줄러 계열의 앱에서 벗어나지는 못할 것 같아 그만두었다는 건 여담.)

하여간 책 내용 중 [플러터 프로젝트에 AI 챗봇 추가하기] 같은 내용도 있어서 경우에 따라 개인 프로젝트에서 Gemini를 활용하게 된다면 그 내용을 참고하게 될 수도 있겠다.

function과 class에 대하여

function은 statement를 묶는 게 핵심이고 class는 variable을 묶는 게 핵심이다. class로 묶은 variable은 property라고 하며 property와 관련된 기능을 method라고 한다.

정의한 class는 instance로 생성하여 메모리에 올려야 의미 있다. class는 개발자 정의 type으로 사용할 수 있다. 이를 통해 동일한 메모리 크기를 가진, 내용물만 다른 데이터를 생성할 수 있다.

다음과 같은 class에 대하여

class User {
  String name;
  String id;
  String password;
  int dateOfBirth;
	
  User({
    required this.name,
    required this.id,
    required this.password,
    required this.dateOfBirth,
  });
}

다음과 같이 instance를 생성할 수 있다.

User daniil = User(name: 'Daniil A. Np.', id='edenjint3927', password='qwer1424', dateOfBirth='19990108');
User hayu = User(name: 'Yujin Ha', id='tlvr21', password='qwer1234', dateOfBirth='19980707');

instance는 기본적으로 복사가 아닌 레퍼런스로 작동한다. User neo-hayu = hayu; 를 하면 hayu 와 같은 값을 가진 neo-hayu 가 생성되는 게 아니라 hayu 에 접근할 수 있는 다른 이름이 생기는 것뿐이다. 복사해서 새로 만들려면 .copyWith() 를 사용해야 한다.

찾아보니 .copyWith()?? 를 사용하여 값이 제공되지 않으면 기존 객체의 값을 유지하도록 설계하는 게 일반적이라는 모양이다.

class User {
  String name;
  String id;
  String password;
  int dateOfBirth;
	
  User({
    required this.name,
    required this.id,
    required this.password,
    required this.dateOfBirth,
  });

  User copyWith({String? name, String? id, String? password, int? dateOfBirth}) {
    return User(
      name: name ?? this.name,
      id: id ?? this.id,
      password: password ?? this.password,
      dateOfBirth: dateOfBirth ?? this.dateOfBirth,
    );
  }
}

instance 내부에 접근할 땐 . operator

[. operator] '닷 오퍼레이터'라고 읽는다. '도트 연산자'라고도 부르는 모양이지만 그냥 [dot operator]라고 하겠다. 이것은 instance의 정보에 접근할 때 사용한다. property에 접근할 때에도 사용하고 method에 접근할 때도 사용한다.

다음과 같이 사용할 수 있다.

print(daniil.id);
User neo-hayu = hayu.copyWith(id: 'tlvr210', password: 'asdf1234');

물론 daniil.id 와 같이 instance의 property에 직접 접근하는 건 encapsulation 관점에서 좋지 않다. property는 _ 를 붙여 private으로 두고 method를 통해서만 읽고 쓸 수 있게 하는 게 좋지만 여기서는 그냥 넘어간다.

instance를 생성할 땐 constructor

property를 초기화하고 instance를 생성하는 역할을 하는 녀석. 따로 정의하지 않으면 내부적으로 숨겨진 constructor를 사용하게 되는데, 그것을 default constructor라고 부른다. nullable이 아니면서 default 값도 지정되지 않은 property가 하나라도 있으면 default constructor를 사용할 수 없고 반드시 constructor를 작성해야 한다.

generative constructor

개발자가 정의하는 가장 기본적인 형태의 constructor는 generative constructor다. 앞서 사용한 constructor도 generative constructor에 해당한다. instance를 생성하는 시점에 값을 주입(injection)하는 것이다.

User({
  required this.name,
  required this.id,
  required this.password,
  required this.dateOfBirth,
});

여기서 this 는 생성되는 instance를 의미한다. class가 아닌 instance! constructor를 통해 instance를 생성할 때 name 에 대입한 값이 this.name = name; 처럼 들어간다.

initializer list를 사용하여 다음과 같이 작성한 것과 동일하게 작동한다.

User({
  required String name,
  required String id,
  required String pw,
  required int date,
}) : name = name,
    id = id,
    password = pw,
    dateOfBirth = date;

주입된 값을 그대로 사용하지 않고 변형해서 사용할 필요가 있을 경우, 연산을 포함한 constructor body를 작성할 수도 있다. this 와 initializer list, constructor body를 모두 사용할 경우 그 순서대로 수행된다.

constructor body 영역은 객체가 생성된 직후에 실행되므로 default 값도 없고 nullable도 아닌 property는 반드시 this 혹은 initializer list에서 지정해 주어야 한다. 따라서 Dart의 constructor body는 constructor의 일부가 아니라 객체가 생성되면 수행해야 할 life cycle상의 루틴을 작성하는 용도로 사용된다.

instance에 값을 대입할 때, 처음부터 그 값을 가지고 있어야 하는 것은 나중에 값을 설정하는 것보다 constructor로 생성할 때부터 그 값을 주입하여 생성하는 게 좋다. 가독성 측면에서도 나중에 설정된 값이 아니라 처음부터 그 값이었음을 명시할 수 있고, 생성과 대입 사이에 값을 먼저 사용하는 실수도 예방할 수 있다.

class Point {
  double? x;
  double? y;
   
  Point({this.x, this.y});
}

에 대하여

Point point = Point(); // default constructor 사용
point.x = 3;
point.y = 4;

라고 하기 보다는

Point point = Point(x: 3, y: 4);

하라는 것이다.

named constructor

특정 property를 어떤 값으로 고정한 채 생성하고자 한다면 named constructor를 사용하면 편리하다.

x축 혹은 y축 위에 있는 점을 생성하는 constructor를 따로 만든다면

class Point {
  double? x;
  double? y;
   
  Point({this.x, this.y});

  // 이렇게 initializer list를 사용할 수도 있고
  Point.onXAxis({this.x}):
    y = 0;

  // 이렇게 constructor body를 사용할 수도 있고
  Point.onYAxis({this.y}) {
    x = 0;
  }
}

const constructor

immutable 객체를 생성하고 싶다면 const constructor 를 사용하며, 모든 property에 final 을 사용한다. property를 const 로 하면 컴파일 시점에 정해지고 final 로 하면 런타임에 시점에 정해진다. const constructor는 constructor 정의 앞에 const 를 붙인다.

여기선 그냥 그런 게 있구나 하고 넘어가자.

redirecting constructor

특정 값만 설정하고 다른 constructor에게 위임하는 redirecting constructor 라는 녀석도 있다. named constructor로 작성했던 녀석들을 다음과 같이 작성할 수 있다.

class Point {
  double? x;
  double? y;
   
  Point({this.x, this.y});

  // redirecting constructor는
  // constructor body를 사용하지 않고
  // initializer list를 사용한다
  Point.onXAxis({this.x}): this(x, 0);

}

factory constructor

factory constructor 를 사용하면 다른 instance를 return할 수 있다. singleton 패턴에서 주로 사용한다. 가령 name property가 특정 값을 가진 instance가 존재하지 않을 경우에만 새로 생성하고, 이미 존재할 경우에는 존재하는 instance를 반환한다거나. factory constructor는 constructor 정의 앞에 factory 를 붙인다.

constructor에 대해 더 알고 싶다면 공식문서를 확인해 보자.

어디까지 노출할 것인가

instance 밖에서 instance 내부의 것을 접근할 때 dot operator를 사용한다고 했다. instance 내부의 모든 것에 접근할 수 있는 건 아니고 public 으로 설정된 것만 접근할 수 있다.

Dart에서는 기본적으로 모든 property와 method가 public으로 설정되며, property나 method의 이름 앞에 _ 를 붙이면 private으로 설정되어 외부에서 접근할 수 없게 된다. 이 때, '외부'의 기준은 class가 아니라 파일임을 유의할 것.

constructor도 _ 를 붙여 private constructor로 만들 수 있다. private constructor는 외부에서 instance를 직접 생성하지 못하게 할 때 사용한다. factory constructor를 통해서만 instance를 생성할 수 있게 하고 싶으면 기본 생성자는 private으로 만든다.

다른 언어에서는 constructor에서도 private property에 접근하지 못하는 경우가 많지만, Dart에서는 constructor에 한해 외부에서 private property에 직접 접근할 수 있다.

Dart의 instance에서 property에 접근할 땐 this 를 생략할 수 있다. 생략하는 것을 권장한다. method의 parameter로 해당 property랑 같은 이름을 가진 녀석이 들어오는 경우에만 명시해 준다.


실습 시간

실습은 생략이다. 실습 예제를 가볍게 훑어보며 뇌코딩 & 문법 체크만 했다. 지금은 그것보다는 어떤 앱을 만들면 좋을지 생각을 해 보아야 한다. Gemini와 이것저것 논의해 보았는데 이렇다 할 만한 건 아직 못 찾았다.

IMU 센서 기반 뭐시깽이는 얘가 과연 얼마나 잘 인식해서 데이터를 뽑아낼 수 있을지 잘 모르겠고.... 해보면 재밌을 것 같긴 한데 flutter의 특장점 영역은 아니긴 하다. 좀 더 native의 영역. 못할 건 없긴 한데... 일단 A안으로 킵해두고 좀 더 탐색해 보기로 했다.

영수증 어쩌고 하는 거랑 약속 장소 어쩌고 하는 거는 내가 그런 거에 대한 니즈가 없어서 영 동기부여가 안 되는 느낌. 누군가는 쓸 수도 있겠지만 나는 필요성 자체를 못 느끼는? 누군가 만들어 놓은 게 있어도 안 쓸 것 같다.

방치형 소셜 아카이브도 뭔가 애매하다. 인간관계 스케줄링이니 라이프 대시보드니 게릴라 모임이니 하는 것보단 나은 것 같긴 한데.

나의 니즈를 충족시키는 쪽으로 가면 결국 투두리스트/스케줄러 계열인데, 이와 관련된 건 바이브코딩 스터디에서 어느 정도 충족된 영역이다. 그때 구현한 웹은 실제로 사용 중이기도 하고. (>>> 이 프로젝트의 기반이 된 AI와의 대화)

일단은 이력서와 자기소개서를 먼저 건드리는 게 나으려나. 너무 길어지면 다른 작업으로 일단 넘어가는 방식의 스케줄링으로 병렬 처리를 하는 게 그나마 뭐라도 진행이 될 것 같다.

profile
Peter J Online Space - since July 2020 | 아무데서나 채용해줬으면 좋겠다 (지금은 학생 때 하던 거 아무거나 공부하고 있고요, 취업시켜 주시면 그 분야로 공부할게요)

0개의 댓글