티스토리 뷰
생성자는 클래스의 인스턴스를 생성하는 특별한 함수이다. 다트는 여러 종류의 생성자를 구현할 수 있다. 디폴트 생성자를 제외하고는
- 일반 생성자 : 새로운 인스턴스를 생성하고 인스턴스 변수를 초기화한다. 일반 생성자는 하나만 정의할 수 있다.
- 디폴트 생성자 : 생성자가 정의되어 있지 않으면 자동으로 만들어지는 생성자이다. 인자를 받지 않으며 이름이 없다.
- 명명된 생성자 : 생성자의 목적을 명확히 할 수 있다. 명명된 생성자는 여러 개를 정의할 수 있다.
- 상수 생성자 : 컴파일 시 상수로 인스턴스를 생성할 수 있게 한다.
- 팩토리 생성자 : 하위 타입의 새 인스턴스를 생성하거나 캐시에서 기존 인스턴스를 반환할 수 있다.
- 재지정 생성자 : 동일한 클래스의 다른 생성자로 호출을 전달한다.
생성자의 종류
일반 생성자
일반 생성자를 사용하여 클래스를 인스턴스화할 수 있다. 일반 생성자의 이름은 클래스 이름과 같아야 한다. 반환 타입은 없으며 선택적으로 매개변수를 추가할 수 있다.
class Point {
// 인스턴스 변수 선언
double x = 2.0;
double y = 2.0;
//
Point(this.x, this.y);
}
디폴트 생성자
생성자를 선언하지 않으면 다트는 디폴트 생성자를 생성한다. 디폴트 생성자는 인수가 없는 일반 생성자가 된다.
명명된 생성자
클래스는 일반 생성자를 하나만 정의할 수 있다. 하지만 명명된 생성자를 사용하면 여러 생성자를 추가로 정의할 수 있다. 명명된 생성자는 추가로 이름이 포함되기 때문에 명확성을 제공한다.
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
// 일반 생성자
Point(this.x, this.y);
// 명명된 생성자
Point.origin()
: x = xOrigin,
y = yOrigin;
}
하위 클래스는 상위 클래스의 명명된 생성자를 상속하지 않는다. 하위 클래스에서 상위 클래스에 정의된 명명된 생성자가 필요하다면, 하위 클래스에 직접 구현해야 한다.
상수 생성자
상수 생성자는 객체를 컴파일 타임 상수로 만들 수 있게 한다. 상수 생성자를 정의하기 위해서는 모든 인스턴스 변수를 final로 설정해야 하고 클래스 정적 변수는 static const가 되어야 한다. 상수 생성자는 본문이 없다.
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
상수 생성자는 항상 상수를 만드는 것이 아니다. 객체 생성 시 상수 콘텍스트인 경우에만 상수가 된다.
// point1은 상수가 된다.
const point1 = const Point();
// point2는 상수 컨텍스트이므로 상수가 된다.
const point2 = const Point();
// Point3는 변수이지만 상수 Point를 담고 있다.
var point3 = const Point();
// Point4는 상수가 아닌 일반 객체가 된다.
var Point4 = Point();
재지정 생성자
생성자가 동일한 클래스의 다른 생성자를 호출할 수 있다. 재지정 생성자는 본문이 없다. 본문 대신 콜론(:) 뒤에 this를 사용해서 지정할 생성자를 호출한다.
class Point {
double x, y;
Point(this.x, this.y);
// 재지정 생성자
Point.alongXAxis(double x) : this(x, 0);
Point.origin() : this.alongXAxis(0);
}
팩토리 생성자
다음 경우 중 하나에 속한다면 팩토리 생성자를 구현하자.
- 생성자가 항상 클래스의 새 인스턴스를 생성하지 않는 경우
- 하위 타입의 새 인스턴스를 생성하는 경우
- 인스턴스를 구성하기 전에 비정상적인 작업을 수행해야 하는 경우, 인수 확인이나 초기화 목록에서 처리할 수 없는 기타 처리가 포함된다.
팩토리 생성자는 this에 접근할 수 없다.
다음 예제네는 두 개의 팩토리 생성자를 포함한다. Logger 팩토리 생성자는 캐시에서 객체를 반환한다. Logger.fromJson 팩토리 생성자는 JSON 객체에서 최종 변수를 초기화한다.
class Logger {
final String name;
bool mute = false;
// _cache는 라이브러리 전용으로, 이름 앞에 _가 붙어 있습니다.
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);
}
}
팩토리 생성자는 다른 생성자처럼 동일하게 사용하면 된다.
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
재지정 팩토리 생성자
재지정 팩토리 생성자는 호출 시 다른 클래스의 생성자가 호출되도록 지정한다.
class Circle implements Shape {
final double radius;
const Circle(this.radius);
}
class Shape {
factory Shape.cicle(double radius) = Circle;
}
재지정 팩토리는 일반 팩토리와 달리 다음과 같은 이점이 있다.
- 추상 클래스는 다른 클래스의 상수 생성자를 사용하는 상수 생성자를 제공할 수 있다.
- 재지정 팩토리 생성자는 타입 매개변수와 기본 값을 반복할 필요가 없다.
다음은 추상 클래스에서 다른 클래스의 상수 생성자를 사용하는 예제 코드이다.
abstract class Shape {
const Shape();
const factory Shape.circle(double radius) {
return const Circle(radius); // 에러 : raduis가 상수가 아님
}
const factory Shape.circle(double radius) = Circle; // OK
}
class Circle extends Shape {
final double radius;
const Circle(this.radius) : super();
}
생성자 Tear-Offs
다트는 생성자를 직접 호출하지 않고, 생성자를 매개변수로 전달할 수 있다. 이를 tear-off라고 하며, 생성자를 동일한 매개변수로 호출하는 클로저 역할을 한다.
생성자 tear-off는 메서드가 수용하는 동일한 매개변수와 반환 타입을 갖는 경우 전달할 수 있다.
tear-off는 익명함수와는 다르다. 익명함수는 생성자에 대한 래퍼 역할을 하지만, tear-off는 생성자 그 자체이다.
좋은 예 - 생성자 tear-off를 사용하는 경우
var strings = charCodes.map(String.fromCharCode);
var buffers = charCodes.map(StringBuffer.new);
나쁜 예 - 람다로 생성자를 래퍼하는 경우
var strings = charCodes.map((code) => String.fromCharCode(code));
var buffers = charCodes.map((code) => StringBuffer(code));
인스턴스 변수 초기화
다트에서는 세 가지 방법으로 변수를 초기화할 수 있다.
변수 선언 시 인스턴스 변수 초기화
변수를 선언할 때 인스턴스 변수를 초기화할 수 있다.
class PointA {
double x = 1.0;
double y = 2.0;
@override
String toString() {
return 'PointA($x,$y)';
}
}
초기화 형식 매개변수 사용
생성자 인수를 인스턴스 변수에 할당하는 패턴을 단순화하기 위해 다트는 초기화 형식 매개변수를 제공한다. 생성자 선언에서 this.<인스턴스 변수 이름>을 사용하고 본문을 생략한다.
초기화 형식 매개변수는 널-불가능 변수나 final 인스턴스 변수를 초기화하는 데에도 사용할 수 있다.
class Point {
int x;
final int y;
Point(int x, int y) {
this.x = x; // 에러
this.y = y; // 에러
}
}
초기화 형식 매개변수를 사용하면 에러가 발생하지 않게 된다.
class PointB {
int x;
final int y;
// 초기화 형식 매개변수로 x와 y 인스턴스 변수를 설정합니다.
PointB(this.x, this.y);
// 초기화 형식 매개변수는 옵셔널 매개변수에서도 사용할 수 있습니다.
PointB.optional([this.x = 0, this.y = 0]);
}
private 필드는 명명된 매개변수에서 초기화 형식 매개변수를 사용할 수 없다. 대신 다음과 같이 초기화 목록을 사용할 수 있다.
class PointB {
// ...
PointB.namedPrivate({required double x, required double y})
: _x = x,
_y = y;
// ...
}
private 필드가 아니라면 명명된 매개변수와 함께 사용할 수 있다.
class PointC {
double x;
double y;
// 초기화 형식 매개변수로 기본값이 있는 생성자
PointC.named({this.x = 1.0, this.y = 1.0});
@override
String toString() {
return 'PointC.named($x,$y)';
}
}
// 명명된 변수를 사용하는 생성자.
final pointC = PointC.named(x: 2.0, y: 2.0);
생성자 매개변수는 널-가능 필드에도 사용할 수 있다.
class PointD {
double? x;
double? y;
// 초기화 형식 매개변수를 사용하는 일반 생성자
PointD(this.x, this.y);
@override
String toString() {
return 'PointD($x,$y)';
}
}
초기화 목록 사용
초기화 목록을 사용하면 생성자 본문이 실행되기 전에 인스턴스 변수를 초기화할 수 있다. 초기화 목록의 항목은 쉼표로 구분된다.
// 초기화 목록은 생성자 본문이 실행되기 전에 인스턴스 변수를 설정합니다.
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
초기화 목록의 오른쪽은 this에 접근할 수 없다.
Point.raiseError() : x = this.y; // 초기화 목록에서 this에 접근해서 에러 발생
개발 중에 입력 값을 검증하려면 초기화 목록에서 assert를 사용할 수도 있다.
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
초기화 목록은 final 필드를 설정하는 데 도움이 된다.
import 'dart:math';
class Point {
final double x;
final double y;
final double distanceFromOrigin;
Point(double x, double y)
: x = x,
y = y,
distanceFromOrigin = sqrt(x * x + y * y);
}
void main() {
var p = Point(2, 3);
print(p.distanceFromOrigin);
}
생성자 상속
서브 클래스(자식 클래스)는 슈퍼 클래스(부모 클래스)의 생성자를 상속하지 않는다. 클래스가 생성자를 선언하지 않으면 자동으로 만들어지는 디폴트 생성자를 사용할 수 있다.
서브 클래스의 생성자는 슈퍼 클래스의 생성자를 호출할 수 있다. 만약 슈퍼 클래스에 디폴트 생성자를 호출한다면, 호출을 생략할 수도 있다.
비-디폴트 슈퍼 클래스 생성자
다트 생성자는 다음 순서로 실행된다.
- 초기화 목록
- 슈퍼 클래스 생성자
- 해당 클래스의 생성자
다음은 슈퍼 클래스의 생성자를 호출하는 예제이다. 생성자 본문 전에 콜론(:) 뒤에 슈퍼 클래스의 생성자를 호출하면 된다.
class SuperClass {
int x;
SuperClass(this.x);
SuperClass.zero() : x = 0;
}
class SubClass extends SuperClass {
SubClass() : super(0);
SubClass.zero() : super.zero();
}
슈퍼 클래스의 생성자 인수는 this에 접근할 수 없다. 인수는 정적 메서드를 호출할 수 있지만 인스턴스 메서드는 호출할 수 없다.
슈퍼 매개변수
슈퍼 클래스의 생성자를 호출시 각 매개변수를 전달하는 대신, 슈퍼 초기화 매개변수를 사용하여 인수를 전달할 수 있다. 슈퍼 매개변수는 초기화 형식 매개변수와 유사한 문법과 의미를 가진다.
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// 기본 슈퍼 생성자에 x와 y 매개변수를 전달합니다.
// Vector3d(double x, double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
위치 인수를 가지는 슈퍼 생성자를 호출 시에는 슈퍼 초기화 매개변수는 위치 인수가 될 수 없다.
class Vector3d extends Vector2d {
final double z;
// 슈퍼 생성자(`super(0)`)를 위치 인수로 호출하는 경우,
// 슈퍼 파라미터(`super.x`)를 사용하는 것은 오류를 초래합니다.
Vector3d.xAxisError(super.x): z = 0, super(0); // 에러
}
슈퍼 생성자가 명명된 인수를 갖는 경우에는 슈처 초기화 매개변수를 사용할 수 있다.
class Vector2d {
// ...
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
final double z;
// y 매개변수를 명명된 슈퍼 생성자로 전달합니다.
Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}
'다트 공식 문서 번역' 카테고리의 다른 글
다트] 클래스를 확장하기 (0) | 2024.08.03 |
---|---|
다트] 메서드 (0) | 2024.08.01 |
다트] 클래스 (0) | 2024.07.29 |
다트] 에러 처리하기 (0) | 2024.07.29 |
다트] 분기 (0) | 2024.07.28 |