생성자

Dart에서 생성자는 객체를 초기화하는 역할을 한다. Dart는 두 가지 주요 생성자 유형을 제공한다: 기본 생성자명명된 생성자이다. 생성자는 객체가 메모리에 생성될 때 호출되며, 객체의 초기 상태를 설정하는 데 사용된다.

기본 생성자

기본 생성자는 클래스 이름과 동일한 이름을 사용하며, 특별한 경우가 아니라면 매개변수를 받지 않는다. 예를 들어, 아래와 같은 간단한 클래스에서 생성자를 정의할 수 있다:

class Point {
  int x, y;

  // 기본 생성자
  Point(this.x, this.y);
}

위 코드에서 Point 클래스는 두 개의 정수형 멤버 변수 xy를 갖는다. 생성자는 this.xthis.y를 통해 멤버 변수를 초기화한다. 이와 같이, 생성자는 객체를 생성할 때 자동으로 호출되어 필드를 초기화한다.

수학적으로, 생성자를 사용해 객체를 초기화하는 과정은 벡터의 초기값을 설정하는 과정과 비슷한다. 예를 들어, 2차원 공간에서 좌표 \mathbf{p} = [x, y]^T를 초기화하는 것은 생성자에서 좌표값을 설정하는 과정과 동일한다.

명명된 생성자

명명된 생성자는 클래스 내에서 여러 생성자를 정의할 수 있게 해준다. 이는 서로 다른 초기화 방법을 제공하거나 객체를 여러 가지 방식으로 생성할 때 유용하다. Dart에서 명명된 생성자는 다음과 같이 정의된다:

class Point {
  int x, y;

  // 기본 생성자
  Point(this.x, this.y);

  // 명명된 생성자
  Point.origin() {
    x = 0;
    y = 0;
  }
}

여기서 Point.origin()은 새로운 명명된 생성자이다. 이 생성자는 항상 좌표를 [0, 0]으로 설정하는데, 이는 수학적으로 \mathbf{p}_0 = [0, 0]^T와 같은 초기값을 갖는 점을 생성하는 것과 같다.

명명된 생성자는 객체를 다양한 방법으로 생성할 때 매우 유용하다. 특정 상황에 맞는 초기화 방식이 필요할 때 사용할 수 있으며, 여러 방식으로 객체를 생성할 수 있도록 한다.

초기화 리스트

Dart에서 초기화 리스트는 생성자 본문이 실행되기 전에 멤버 변수들을 초기화할 수 있는 방법을 제공한다. 이는 생성자에서 특정한 필드가 반드시 초기화되어야 할 경우 매우 유용하다. 초기화 리스트는 생성자 선언 뒤에 콜론을 붙이고, 초기화할 필드에 대해 값을 지정한다.

class Rectangle {
  int width, height;

  // 초기화 리스트
  Rectangle(int w, int h) : width = w, height = h {
    print('Rectangle created with width $width and height$height');
  }
}

위 코드에서, Rectangle 클래스의 생성자는 초기화 리스트를 사용하여 widthheight 필드를 초기화한다. 이 방식은 특히 상수형 필드가 있거나, 부모 클래스의 생성자를 호출해야 할 때 유용하다.

초기화 리스트의 수학적 개념을 생각해 보면, 이는 벡터의 성분을 초기화하는 것과 유사한다. 예를 들어, 2차원 벡터 \mathbf{v} = [w, h]^T를 초기화하는 과정에서, 초기화 리스트는 벡터의 성분을 초기 상태로 설정하는 데 쓰이다.

\mathbf{v} = \begin{bmatrix} w \\ h \end{bmatrix}

상수 필드 초기화

Dart에서 final 키워드로 선언된 필드는 반드시 생성자에서 초기화되어야 한다. 초기화 리스트는 이 final 필드를 생성자 본문이 실행되기 전에 초기화할 수 있는 유일한 방법이다.

class Circle {
  final double radius;

  // 초기화 리스트를 통한 상수 필드 초기화
  Circle(this.radius);
}

이와 같이, final 필드인 radius는 생성자가 호출될 때 한 번만 초기화되며, 이후에는 값이 변경되지 않는다. 이 과정을 수학적으로 표현하면, 반지름 r을 갖는 원의 방정식이 특정 값으로 초기화되는 것과 같다.

초기화 순서

Dart에서 멤버 변수와 상위 클래스는 초기화 순서에 따라 생성된다. 이 순서는 다음과 같다:

  1. 상위 클래스의 생성자가 먼저 호출된다.
  2. 초기화 리스트가 실행된다.
  3. 필드가 초기화된다.
  4. 생성자 본문이 실행된다.

이 초기화 순서는 상위 클래스의 필드와 하위 클래스의 필드가 적절하게 초기화될 수 있도록 보장한다. 예를 들어, 만약 상위 클래스의 생성자가 먼저 실행되지 않으면, 상위 클래스의 필드가 초기화되지 않은 상태에서 하위 클래스의 동작이 불안정해질 수 있다.

상속과 생성자

Dart에서 상속 관계에 있는 클래스는 상위 클래스의 생성자를 호출해야 한다. 상위 클래스의 생성자는 하위 클래스 생성자에서 직접 호출되지 않으면, 자동으로 상위 클래스의 기본 생성자가 호출된다. 하지만 상위 클래스에 기본 생성자가 없고, 매개변수를 받는 생성자만 존재할 경우, 하위 클래스는 명시적으로 상위 클래스의 생성자를 호출해야 한다.

class Shape {
  int x, y;

  // 매개변수를 받는 생성자
  Shape(this.x, this.y);
}

class Circle extends Shape {
  double radius;

  // 하위 클래스에서 상위 클래스 생성자 호출
  Circle(int x, int y, this.radius) : super(x, y);
}

위 예제에서, Circle 클래스는 Shape 클래스로부터 상속받고 있으며, Circle의 생성자에서 Shape 클래스의 생성자를 호출할 때 super(x, y)를 사용한다. 이 방법을 통해 상위 클래스 필드 xy가 초기화된다.

상속 구조에서의 생성자 호출을 수학적으로 표현하면, 상위 클래스의 좌표 \mathbf{s} = [x, y]^T를 하위 클래스에서 확장하여 새로운 반지름 값 r을 추가하는 과정으로 생각할 수 있다:

\mathbf{circle} = \begin{bmatrix} x \\ y \\ r \end{bmatrix}

명시적 생성자 호출

상위 클래스에 여러 생성자가 있을 때, 특정 생성자를 명시적으로 호출해야 하는 경우도 있다. Dart는 super 키워드를 사용해 상위 클래스의 생성자를 명시적으로 호출할 수 있도록 지원한다. 만약 상위 클래스에 여러 생성자가 있다면, super를 통해 특정 생성자를 지정하여 호출할 수 있다.

class Rectangle {
  int width, height;

  Rectangle(this.width, this.height);
  Rectangle.square(int size) : width = size, height = size;
}

class ColoredRectangle extends Rectangle {
  String color;

  // 특정 생성자 호출
  ColoredRectangle(int width, int height, this.color) : super(width, height);
  ColoredRectangle.square(int size, this.color) : super.square(size);
}

여기서 ColoredRectangle 클래스는 Rectangle의 생성자 중 하나를 선택적으로 호출할 수 있다. 이 방법은 상속받은 클래스가 다양한 생성자를 가질 때 매우 유용하며, 초기화 과정에서 더 많은 유연성을 제공한다.

소멸자

Dart에는 소멸자가 따로 존재하지 않는다. Dart의 가비지 컬렉터가 메모리 관리를 담당하기 때문에, 객체가 더 이상 참조되지 않으면 자동으로 메모리에서 해제된다. 따라서 C++ 같은 언어에서 사용되는 소멸자를 명시적으로 구현할 필요는 없다.

대신, 필요한 경우 객체가 삭제되기 전에 리소스를 정리하거나, 종료 작업을 수행해야 할 때는 dispose() 메서드를 수동으로 호출하여 소멸자 역할을 대신할 수 있다. 예를 들어, 다음과 같이 dispose() 메서드를 사용할 수 있다:

class ResourceHandler {
  // 리소스 할당
  void allocate() {
    print("리소스를 할당한다.");
  }

  // 리소스 정리
  void dispose() {
    print("리소스를 해제한다.");
  }
}

void main() {
  var handler = ResourceHandler();
  handler.allocate();

  // 작업이 끝난 후 리소스 정리
  handler.dispose();
}

위 예제에서, ResourceHandler 클래스는 리소스를 할당하고 정리하는 메서드를 제공한다. 객체가 더 이상 필요하지 않게 되었을 때, dispose()를 호출하여 리소스를 해제할 수 있다. Dart의 메모리 관리는 가비지 컬렉터에 의해 자동으로 이루어지므로, 직접적인 소멸자가 필요하지 않지만, 명시적인 리소스 해제가 필요한 경우에는 이와 같은 방법을 사용할 수 있다.