관리 메뉴

나만을 위한 블로그

[Dart] 생성자(constructor) 알아보기 본문

Flutter

[Dart] 생성자(constructor) 알아보기

참깨빵위에참깨빵 2024. 7. 21. 23:03
728x90
반응형

이 포스팅에선 Dart에서 생성자를 어떻게 사용하는지 확인한다. 포스팅에서 사용할 Dart 버전은 3.4.4임을 참고한다.

아래는 플러터 공식문서 중 생성자의 공식문서다.

 

https://dart.dev/language/constructors

 

Constructors

Everything about using constructors in Dart.

dart.dev

생성자는 클래스의 인스턴스를 만드는 특수 함수다. 기본 생성자를 제외하고 이런 함수는 클래스와 같은 이름을 쓴다

< 생성자 종류 >

- Generative constructors : 새 인스턴스를 만들고 인스턴스를 초기화
- Default constructors : 생성자가 지정되지 않은 경우 새 인스턴스를 만들 때 사용. 인수를 받지 않고 이름이 지정되지 않음
- Named constructors : 생성자의 목적을 명확히 하거나 같은 클래스에 대해 여러 생성자를 만들 수 있게 한다
- Constant constructors : 인스턴스를 컴파일 타임 상수로 생성
- Factory constructors : 하위 타입의 새 인스턴스를 만들거나 캐시에서 기존 인스턴스를 리턴함
- Redirecting constructors : 같은 클래스의 다른 생성자로 호출을 전달

 

아래는 각 생성자 별 설명이다.

 

Generative constructors

 

클래스를 인스턴스화할 때 사용한다.

 

class Point {
  // 변수, 값의 Initializer 목록
  double x = 2.0;
  double y = 2.0;

  // 공식 파라미터를 초기화하는 Generative constructor
  Point(this.x, this.y);
}

 

x, y의 2가지 값을 받아서 Point 클래스의 인스턴스를 생성한다. 둘 다 2.0으로 초기화되어 있어서 메인 함수에서 사용할 땐 이 값을 사용하게 된다.

하지만 이 상태로 사용하려 할 경우 컴파일 에러가 발생하고 실행도 되지 않는다.

 

 

이 에러는 생성자 매개변수에 아무 소수나 넣어주면 해결된다. 값 출력도 정상적으로 이뤄진다.

 

void main() {
  Point p = Point(2.0, 2.0);
  print(p.x);
  print(p.y);
}

class Point {
  double x = 2.0;
  double y = 2.0;

  Point(this.x, this.y);
}

// >> 2.0
// >> 2.0

 

Point 클래스 안에서 x, y를 각각 2.0으로 초기화한 다음 메인 함수에서 사용하려고 한 건데 왜 안되는 건가?

값을 받는 생성자만 존재하기 때문이다. 위 코드는 아래와 같이 수정하면 작동한다.

 

void main() {
  Point p = Point();
  print(p.x);
  print(p.y);
}

class Point {
  double x = 2.0;
  double y = 2.0;

  Point();
}

 

또는 아래처럼 작성할 수도 있다.

 

void main() {
  Point p = Point();
  print(p.x);
  print(p.y);
}

class Point {
  double x = 2.0;
  double y = 2.0;

  Point({this.x = 2.0, this.y = 2.0});
}

 

2번째 코드는 값을 받아도 되고 안 받아도 된다. x, y에 다른 숫자를 넣어서 초기화하고 싶은 경우 사용할 수 있다.

 

Default constructors

 

기본 생성자라는 뜻인데 만약 클래스에 생성자를 만들어두지 않으면 dart가 이 기본 생성자를 사용한다.

참고로 클래스에 없던 생성자가 코드 에디터에 갑자기 생기는 건 아니다. 클래스 인스턴스를 만들 때 dart 컴파일러가 내부적으로 호출하는 것이라 실제론 볼 수 없다.

이 기본 생성자가 있기 때문에 만약 생성자를 정의하지 않았더라도 Point()를 써서 Point 클래스의 인스턴스를 만들어 사용할 수 있는 것이다.

기본 생성자는 생성자 매개변수, 이름이 없는 Generative constructor다.

 

Named constructors

 

명명된 생성자라고 번역되는데 이걸 써서 클래스에 여러 생성자를 구현하거나 명확성을 높일 수 있다.

 

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // 생성자 본문이 실행되기 전에 x, y 변수 초기화
  Point(this.x, this.y);

  // Named constructor
  Point.namedConstructor()
      : x = xOrigin,
        y = yOrigin;
}

 

Point를 상속하는 서브클래스는 Named constructor를 상속하지 않는다.

슈퍼클래스에 정의된 Named constructor가 있는 서브클래스를 만들려면 서브클래스에서 해당 생성자를 구현해야 한다.

예를 들면 아래와 같다.

 

void main() {
  // 기본 생성자 사용
  ColorPoint cp = ColorPoint(4.0, 5.0, "빨강");
  print(cp);

  // Named constructor 사용
  ColorPoint cpNamed = ColorPoint.namedConstructor("파랑");
  print(cpNamed);
}

const double xOrigin = 0;
const double yOrigin = 0;

class Point {
  final double x;
  final double y;

  // 생성자 본문이 실행되기 전에 x, y 변수 초기화
  Point(this.x, this.y);

  // Named constructor
  Point.namedConstructor()
      : x = xOrigin,
        y = yOrigin;
}

class ColorPoint extends Point {
  final String color;

  // 기본 생성자
  ColorPoint(double x, double y, this.color) : super(x, y);

  // Named constructor를 서브 클래스에서 구현
  ColorPoint.namedConstructor(this.color) : super.namedConstructor();

  @override
  String toString() => "ColorPoint(x : $x, y : $y, color : $color)";
}

 

Constant constructors

 

클래스가 불변 객체를 생성한다면 이런 객체를 컴파일 타임 상수로 만들라고 공식문서에 써 있다.

객체를 컴파일 타임 상수로 만들려면 모든 인스턴스 변수를 final 변수로 설정한 생성자를 정의해야 한다.

 

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

 

그러나 상수 생성자라 해서 항상 상수를 만드는 건 아니다. 상수가 아닌 컨텍스트에서 호출될 수도 있다.

이 클래스를 사용하려면 아래와 같이 한다.

 

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

void main() {
  var p = const ImmutablePoint(2, 2);
  print(p.x);
  print(p.y);
}

// >> 2.0
// >> 2.0

 

2개의 컴파일 타임 상수를 만들어 비교하면 두 상수가 일치한다.

 

class ImmutablePoint {
  static const ImmutablePoint origin = ImmutablePoint(0, 0);

  final double x, y;

  const ImmutablePoint(this.x, this.y);
}

void main() {
  var a = const ImmutablePoint(1, 1);
  var b = const ImmutablePoint(1, 1);
  bool isAbEquals = identical(a, b);
  print(isAbEquals); // true

  assert(identical(a, b)); // 프로그램 정상 종료됨. 만약 둘이 다르다면 에러가 발생한다
}

 

Redirecting constructors

 

생성자는 같은 클래스의 다른 생성자로 리디렉션될 수 있다. 이 때 리디렉션 생성자를 사용한다.

이 생성자엔 빈 본문이 있고 콜론 뒤에 이름 대신 this를 사용한다.

 

class Point {
  double x, y;

  // Point 클래스의 주 생성자
  Point(this.x, this.y);

  // 주 생성자에 위임
  Point.alongXAxis(double x) : this(x, 0);
}

 

재사용성, 가독성, 코드 안전성을 향상시킬 수 있는 생성자라고 하는데 아직 많이 사용해보지 않아서 잘 모르겠다.

 

Factory constructors

 

생성자를 구현하는 중 아래 2가지 중 하나의 경우에 해당하면 factory 키워드를 사용하라고 권장한다.

 

  • 생성자가 항상 그 클래스의 새 인스턴스를 만드는 건 아니다. 팩토리 생성자는 null을 리턴할 순 없지만 아래를 리턴할 수 있다
    • 캐시에서 기존 인스턴스를 가져옴
    • 하위 타입의 새 인스턴스
  • 인스턴스 생성 전에 사소하지 않은 작업(매개변수 확인, 이니셜라이저 리스트에서 처리할 수 없는 다른 처리 작업 등)을 수행해야 하는 경우

 

아래는 예시다.

 

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to
  // the _ in front of its name.
  static final Map<String, Logger> _cache = <String, Logger>{};

  factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
  }

  factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) print(msg);
  }
}

 

Logger는 캐시에서 객체를 리턴하는 팩토리 생성자고, Logger.fromJson은 JSON 객체에서 final 변수를 초기화하는 팩토리 생성자다.

그리고 팩토리 생성자는 this에 접근할 수 없다. 아래 블로그에서 그 이유를 설명하는데 참고용으로 가져왔다.

 

https://medium.com/@chetan.akarte/in-dart-what-is-factory-constructors-658436155d06

 

In dart what is Factory constructors?

In Dart, a factory constructor is a special type of constructor that can be used to return an instance of a class. Unlike regular…

medium.com

this 접근 불가 : 팩토리 생성자는 인스턴스 변수나 메서드에 직접 접근할 수 없다. 인스턴스별 데이터에 의존하지 않는 복잡한 인스턴스화 로직에 주로 사용한다

 

 

참고한 사이트)

 

https://dart.dev/language/classes#using-constructors

 

Classes

Summary of classes, class instances, and their members.

dart.dev

 

https://medium.com/nerd-for-tech/named-constructor-vs-factory-constructor-in-dart-ba28250b2747

 

Named Constructor vs Factory Constructor in Dart

In this article, we will understand what are the differences between a named and a factory constructor and when to prefer one over the…

medium.com

 

반응형
Comments