티스토리 뷰
다트는 클래스와 믹스인 기반 상속을 갖는 객체지향 언어이다. 모든 객체는 클래스의 인스턴스이며, Null을 제외한 모든 클래스는 Obejct에서 상속된다. 믹스인 기반 상속은 모든 클래스(최상위 클래스인 Object를 제외한)가 정확히 하나의 슈퍼 클래스를 갖지만, 클래스 본문은 여러 클래스 계층 구조에서 재사용될 수 있다는 것을 의미한다. 익스텐션 메서드는 클래스를 변경하지 않고 클래스에 기능을 추가하는 방법이다. 클래스 수정자는 라이브러리가 클래스를 서브타입화(subtype)할 수 있는 방법을 제어하는 데 사용된다.
클래스 멤버 사용하기
객체는 함수와 데이터로 구성된 멤버를 가진다. 이를 각각 메서드와 인스턴스 변수라고 한다. 메서드를 호출할 때, 해당 메서드는 객체에서 호출되며, 메서드는 해당 객체의 메서드와 인스턴스 변수에 접근할 수 있다.
인스턴스 변수나 메서드를 참조할 때는 점(.)을 사용한다.
// Point클래스의 인스턴스 p를 만든다.
var p = Point(2, 2);
// y의 값을 가져옵니다.
assert(p.y == 2);
// p에서 distanceTo()를 호출합니다.
double distance = p.distanceTo(Point(4, 4));
왼쪽의 피연산자가 null인 경우, 예외를 피하기 위해 . 대신 ?.를 사용해야 한다.
// p가 null이 아니면 변수에 y의 값을 할당한다.
var a = p?.y;
생성자 사용하기
생성자를 사용하여 객체를 만들 수 있다. 생성자 이름은 ClassName 또는 ClassName.identifier가 된다. 예를 들어, 다음 코드는 Point()와 Point.fromJson() 생성자를 사용하여 Point 객체를 생성한다.
var p1 = Point(2, 2);
var p2 = Point.fromJson({'x': 1, 'y': 2});
생성자 이름 앞에 선택적으로 new 키워드를 사용할 수 있다. 결과는 위와 동일하다.
var p1 = new Point(2, 2);
var p2 = new Point.fromJson({'x': 1, 'y': 2});
일부 클래스는 상수 생성자를 제공한다. 컴파일 타임 상수를 생성하려면 상수 생성자 앞에 const 키워드를 붙인다.
var p = const ImmutablePoint(2, 2);
두 개의 동일한 컴파일 타임 상수를 생성하면 하나의 인스턴스가 생성되어 공유된다.
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 동일한 인스턴스입니다!
상수 문맥에서는 생성자나 리터럴 앞의 const를 생략할 수 있다. 예를 들어, 다음 코드는 const map을 생성한다.
// 많은 const 키워드가 사용되었습니다.
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
첫 번째 const를 제외한 const 키워드는 모두 생략할 수 있다.
// 첫 번째 const로 상수 문맥이 설정됩니다.
const pointAndLine = {
'point': [ImmutablePoint(0, 0)],
'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};
상수 생성자가 상수 문맥이 아닌 곳에서 const 없이 호출되면 상수가 아닌 객체가 생성된다.
var a = const ImmutablePoint(1, 1); // 상수를 생성합니다.
var b = ImmutablePoint(1, 1); // 상수를 생성하지 않습니다.
assert(!identical(a, b)); // 동일한 인스턴스가 아닙니다!
객체의 타입 가져오기
런타임에서 객체의 타입을 얻으려면 Object 속성인 runtimeType을 사용할 수 있다. runtimeType은 Type 객체를 반환한다.
print('The type of a is ${a.runtimeType}');
runtimeType로 객체의 타입을 확인하는 대신 is 연산자를 사용하자. 실무 환경에서는 is 연산자가 더 안정적이다.
인스턴스 변수
인스턴스 변수를 선언하는 방법은 다음과 같다.
class Point {
double? x; // x 인스턴스 변수를 선언하고 초기값은 null입니다.
double? y; // y를 선언하고 초기값은 null입니다.
double z = 0; // z를 선언하고 초기값은 0입니다.
}
초기화되지 않은 널-가능 타입의 인스턴스 변수는 값이 null이다. 널-불가능 타입은 인스턴스 변수 선언 시에 초기화를 해야 한다.
모든 인스턴스 변수는 암시적 getter 메서드를 생성한다. final이 아닌 인스턴스 변수와 초기값이 없는 late final 인스턴스 변수는 암시적 setter 메서드도 생성된다.
class Point {
double? x; // x 인스턴스 변수를 선언하고 초기값은 null입니다.
double? y; // y를 선언하고 초기값은 null입니다.
}
void main() {
var point = Point();
point.x = 4; // x의 setter 메서드를 사용합니다.
assert(point.x == 4); // x의 getter 메서드를 사용합니다.
assert(point.y == null); // 값은 기본적으로 null입니다.
}
late가 아닌 인스턴스 변수를 선언 위치에서 초기화하면, 인스턴스가 생성될 때, 생성자와 초기화 목록이 실행되기 전에 값이 설정된다. 그 결과 late가 아닌 인스턴스 변수의 초기화 표현식 (=의 오른쪽)은 this에 접근할 수 없다.
double initialX = 1.5;
class Point {
// OK, `this`에 의존하지 않는 선언에 접근 가능:
double? x = initialX;
// 오류, 비-`late` 초기화에서 `this`에 접근할 수 없음:
double? y = this.x;
// OK, `late` 초기화에서 `this`에 접근 가능:
late double? z = this.x;
// OK, `this.x`와 `this.y`는 표현식이 아닌 매개변수 선언:
Point(this.x, this.y);
}
인스턴스 변수는 final로 선언될 수 있으며, 이 경우 반드시 한 번만 설정되어야 한다. final이면서 late가 아닌 인스턴스 변수는 선언 시에 생성자 매개변수를 사용하거나 생성자 초기화 목록을 사용하여 초기화하자.
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
생성자 본문이 시작된 후에 final 인스턴스 변수의 값을 설정해야 하는 경우, 다음 중 하나를 사용할 수 있다.
- 팩토리 생성자를 사용한다.
- late final을 사용한다. 초기화하지 않은 late final은 암시적 setter가 추가되니 주의하자.
암시적 인터페이스
모든 클래스는 해당 클래스와 구현하는 모든 인터페이스의 인스턴스 멤버를 포함하는 인터페이스를 암시적으로 정의한다. 클래스 A가 B의 구현을 상속받지 않고 B의 API를 지원하려면, 클래스 A는 B 인턴페이스를 구현하면 된다.
// 암시적 인터페이스에 greet()가 포함됨
class Person {
// 인터페이스에 있지만 private이므로 이 라이브러리에서만 보임
final String _name;
// 생성자는 암시적 인터페이스에 포함되지 않음
Person(this._name);
// 인터페이스에 포함됨
String greet(String who) => 'Hello, $who. I am $_name.';
}
// Person 인터페이스를 구현
class Impostor implements Person {
String get _name => '';
String greet(String who) => 'Hi $who. Do you know who I am?';
}
String greetBob(Person person) => person.greet('Bob');
void main() {
print(greetBob(Person('Kathy')));
print(greetBob(Impostor()));
}
다음은 클래스가 여러 인터페이스를 구현하도록 지정하는 예제이다.
class Point implements Comparable, Location {...}
클래스 변수와 메서드
클래스 정적 변수와 정적 메서드를 구현하려면 static 키워드를 사용한다.
클래스 정적 변수
클래스 정적 변수는 클래스의 공통된 변수나 상수를 공유하기에 유용하다.
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
클래스 정적 변수는 사용될 때까지 초기화되지 않는다.
클래스 정적 메서드
클래스 정적 메서드는 인스턴스에서 작동하지 않으며, 클래스에서 작동한다. 따라서 정적 메서드는 this에 접근할 수 없다. 하지만 클래스 정적 변수에는 접근할 수 있다. 다음 예제에서 보듯이, 클래스 정적 메서드는 클래스에서 직접 호출한다.
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
static double distanceBetween(Point a, Point b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
}
void main() {
var a = Point(2, 2);
var b = Point(4, 4);
var distance = Point.distanceBetween(a, b);
assert(2.8 < distance && distance < 2.9);
print(distance);
}
공통적으로 사용되거나 널리 사용되는 유틸리티 및 기능은 클래스 정적 메서드보다 최상위 함수를 사용하는 것을 고려해 보자.
클래스 정적 메서드는 컴파일 타임 상수로 사용할 수 있다. 예를 들어, 정적 메서드를 상수 생성자의 매개변수로 전달할 수 있다.
'다트 공식 문서 번역' 카테고리의 다른 글
다트] 메서드 (0) | 2024.08.01 |
---|---|
다트] 생성자 (0) | 2024.08.01 |
다트] 에러 처리하기 (0) | 2024.07.29 |
다트] 분기 (0) | 2024.07.28 |
다트] 반복 (0) | 2024.07.28 |