Dart : https://dart.dev/guides/language/language-tour
- 다트는 클래스와 Mixin 기반 상속이있는 객체 지향 언어이다
- 모든 객체는 클래스의 인스턴스이며 모든 클래스는 Object 의 자식이다
- 믹스인 기반 상속은 모든 클래스 (Object 제외) 에 하나의 수퍼 클래스가 있지만,
클래스 본문은 여러 클래스 계층에서 재사용 될 수 있다 - 확장 메서드는 클래스를 변경하거나 하위 클래스를 만들지 않고 클래스에 기능을 추가하는 방법이다
1. Class members
- 객체는 함수와 데이터 (각각 메서드 및 인스턴스 변수) 로 구성된 멤버가 있다
- 메소드를 호출하면 객체에서 invoke 한다: 메소드는 해당 객체의 함수와 데이터에 액세스 할 수 있다
var p = Point(2, 2);
// nullable 인스턴스 변수 y 에 non-null 일 경우 값 3을 셋팅한다
p?.y = 3;
// y 의 값을 가져와서 비교한다 -> true
assert(p?.y == 3);
// p 에서 distanceTo() 를 호출한다 (invoke)
double distance = p.distanceTo(Point(4, 4));
1-1. Instance varaiables
- 초기화되지 않은 인스턴스 변수의 값은 null 이다
- 생성자 또는 메서드가 아닌 선언된 인스턴스 변수를 초기화하는 경우,
이 값은 인스턴스가 생성될 때 설정되며, 이는 생성자와 initializer 목록이 실행되기 전이다
class Point {
double x; // 초기화되지 않은 인스턴스 변수 x의 값은 null 이다
double y; // 초기화되지 않은 인스턴스 변수 y의 값은 null 이다
double z = 0;
}
1-2. Static variables
- static 키워드를 사용하여 클래스 전체 변수 및 메서드를 구현할 수 있다
- static 변수는 사용되기 전까지 초기화되지 않는다
class Queue {
static const initialCapacity = 16;
// ···
}
void main() {
assert(Queue.initialCapacity == 16);
}
1-3. Instance methods
- 객체의 인스턴스 메서드는 인스턴스 변수에 액세스 할 수 있다
import 'dart:math';
class Point {
double x, y;
Point(this.x, this.y);
// 인스턴스 메소드
double distanceTo(Point other) {
var dx = x - other.x;
var dy = y - other.y;
return sqrt(dx * dx + dy * dy);
}
}
1-4. Static methodes
- static 메서드 (클래스 메서드) 는 인스턴스에서 작동하지 않으므로 this 액세스 권한이 없다
- static 메서드는 컴파일 타임 상수로 사용할 수 있다
- ex) static 메서드를 상수 생성자에 매개 변수로 전달할 수 있다
- 일반적이거나 여러곳에서 사용되는 유틸리티 같은 함수는 static 메서드 대신 top-level 함수를 사용하는게 좋다
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);
}
1-5. Getters and setters
- 객체 속성에 대한 읽기 및 쓰기 접근을 제공하는 메서드이다
- 모든 인스턴스 변수는 암시적으로 getter 메서드를 생성한다
- non-final 인스턴스 변수는 암시적으로 setter 메서드가 생성된다
- get 및 set 키워드를 사용하여 getter 및 setter 를 구현하여 추가 속성을 만들 수 있다
- getter 와 setter를 사용하면 클라이언트 코드를 변경하지 않고도 나중에 인스턴스 변수를 메소드로 wrapping 할 수 있다
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// right 을 계산하여 값을 반환하는 getter 함수
double get right => left + width;
// right 을 계산하여 값을 할당하는 setter 함수
set right(double value) => left = value - width;
// bottom 을 계산하여 값을 반환하는 getter 함수
double get bottom => top + height;
// bottom 을 계산하여 값을 할당하는 setter 함수
set bottom(double value) => top = value - height;
}
void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.right = 12;
assert(rect.left == -8);
}
2. Getting an object's type
- 런타임에 객체의 타입을 가져 오려면 Type 객체를 반환하는 Object 의 runtimeType 속성을 사용할 수 있다
print('The type of a is ${a.runtimeType}');
3. Constructors
- 생성자를 사용하여 객체를 만들 수 있다
- 생성자 이름은 클래스와 이름이 같거나 ClassName.identifier 이다
- 가장 일반적인 형태의 생성자인 generative constructor 는 클래스의 새로운 인스턴스를 생성한다
// 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});
3-1. Default constructors
- 생성자를 선언하지 않으면 기본 생성자가 제공된다
- 기본 생성자에는 인수가 없으며 super 클래스에서 인수가 없는 생성자를 호출한다
- 생성자는 상속되지 않는다
- 하위 클래스는 super 클래스에서 생성자를 상속하지 않는다
- 생성자를 선언하지 않은 하위 클래스에는 기본(인수, 이름 없음) 생성자만 있다
3-2. Named constructors
- 이름이 지정된 생성자를 사용하여 여러 생성자를 구현하거나 명확성을 제공할 수 있다
- 생성자는 상속되지 않으므로 super 클래스의 이름이 지정된 생성자가 하위 클래스에서 상속되지 않는다
- super 클래스의 이름이 지정된 생성자로 서브 클래스를 생성하려면 해당 생성자를 서브 클래스에서 구현해야한다
class Point {
double x, y;
Point(this.x, this.y);
// 이름이 지정된 생성자
Point.origin() {
x = 0;
y = 0;
}
}
3-3. Constant constructors
- 클래스가 변경되지 않는 객체를 생성하는 경우 객체를 컴파일 타임 상수로 만들 수 있다
- 생성자 이름 앞에 const 키워드를 넣고 모든 인스턴스 변수가 final 인지 확인해야한다
class ImmutablePoint {
static final ImmutablePoint origin =
const ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
var p = const ImmutablePoint(2, 2);
// 두 개의 동일한 컴파일 타임 상수를 생성하면 표준 인스턴스 하나가 생성된다
var a = const ImmutablePoint(1, 1);
var b = const ImmutablePoint(1, 1);
assert(identical(a, b)); // 둘은 같은 인스턴스!
- 상수 컨텍스트 내에서 생성자 또는 리터럴 앞에 const를 생략 할 수 있다
// const 키워드가 너무 많다
const pointAndLine = const {
'point': const [const ImmutablePoint(0, 0)],
'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
// 상수 context 를 설정하는 한개의 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)); // 같은 인스턴스가 아님
3-4. Factory constructors
- 클래스의 새 인스턴스를 만들지는 않는 생성자를 구현할 때 factory 키워드를 사용한다
- ex1) 팩토리 생성자는 캐시에서 인스턴스를 반환하거나 하위 타입의 인스턴스를 반환 할 수 있다
- ex2) 이니셜 라이저 목록에서 처리 할 수없는 로직을 사용하여 final 변수를 초기화한다
- 아래 예제에서 Logger 팩토리 생성자는 캐시에서 객체를 반환한다
- Logger.fromJson 팩토리 생성자는 JSON 객체에서 final 변수를 초기화한다
- 팩토리 생성자는 this 권한이 없다
class Logger {
final String name;
bool mute = false;
// _cache 는 library-private 하다.
static final Map<String, Logger> _cache = <String, Logger>{};
// factory 생성자인 아래 Logger 를 통해 log() 를 호출할 수 있다
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);
3-5. Constructor argument
// this 를 이용하여 클래스의 x, y 인스턴스에 값을 할당할 수 있다
class Point {
double x, y;
Point(double x, double y) {
// There's a better way to do this, stay tuned.
this.x = x;
this.y = y;
}
}
// 아래와 같이 사용할 수도 있다
class Point {
double x, y;
// 생성자 내부가 실행되기 전에 인스턴스 x, y 에 값이 할당된다
Point(this.x, this.y);
}
3-6. Invoking a non-default superclass constructor
- 기본적으로 하위 클래스의 생성자는 super 클래스의 이름이 지정되지 않고 인수 없는 생성자를 호출한다
- super 클래스의 생성자는 생성자 본문의 시작 부분에서 호출됩니다
- initializer 목록도 사용중인 경우 수퍼 클래스가 호출되기 전에 실행된다
- 기본 실행 순서
- initializer list
- super 클래스의 인수없는 생성자
- main 클래스의 인수없는 생성자
- super 클래스에 이름이 지정되지 않은 인수없는 생성자가 없으면, 수퍼 클래스의 생성자 중 하나를 수동으로 호출해야한다
- 생성자 본문 (있는 경우) 바로 앞에 콜론 (:) 뒤에 수퍼 클래스 생성자를 지정한다
class Person {
String firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person 클래스는 기본 생성자가 없다
// 따라서 super.fromJson(data) 를 호출해야만 한다
Employee.fromJson(Map data) : super.fromJson(data) {
print('in Employee');
}
}
main() {
var emp = new Employee.fromJson({});
}
// result
in Person
in Employee
- 슈퍼 클래스 생성자에 대한 인수는 해당 생성자를 호출하기 전에 할당되기 때문에 인수는 함수 호출과 같이 표현식이 될 수 있다
- 슈퍼 클래스 생성자에 대한 인수는 'this' 로 액세스 할 수 없다
class Employee extends Person {
Employee() : super.fromJson(defaultData);
// ···
}
3-7. Initializer list
- 슈퍼 클래스 생성자를 호출하는 것 외에도 생성자 본문이 실행되기 전에 인스턴스 변수를 초기화 할 수 있다
- 이니셜 라이저는 쉼표로 구분한다
Point.fromJson(Map<String, double> json)
: x = json['x'],
y = json['y'] {
print('In Point.fromJson(): ($x, $y)');
}
// initializer list 에 assert 를 사용하여 검증할 수 있다
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
- 이니셜라이저는 final 필드를 셋팅할 때 유용하다
class Point {
final num x;
final num y;
final num z;
Point(x, y)
: x = x,
y = y,
z = x + y;
}
main() {
var p = new Point(2, 3);
print(p.z);
}
// result
5
3-8. Redirecting constructors
- 생성자의 목적으로 동일한 클래스의 다른 생성자로 리디렉션하는 경우도 있다
- 리디렉션 할 생성자의 본문이 비어 있으면 콜론 (:) 뒤에 생성자 호출을 작성한다
class Point {
double x, y;
// 메인 생성자
Point(this.x, this.y);
// 메인 생성자로 랜딩
Point.alongXAxis(double x) : this(x, 0);
}
'기타개발 > Flutter' 카테고리의 다른 글
Dart #7 : Extension method, Enum, Mixin (0) | 2020.08.28 |
---|---|
Dart #6 : Abstract, Implicit interface, Extends (0) | 2020.08.28 |
Dart #4 : if-else, for/while/do-while, switch, exceptions (0) | 2020.08.27 |
Dart #3 : Operators (0) | 2020.08.27 |
Dart #2 : Functions (1) | 2020.08.27 |
댓글