인스턴스의 변수나 메소드를 참조하려면 dot(.)를 사용한다.
1
2
3
4
5
6
7
var p = Point(2, 2); // Point는 dart에서 기본 제공되는 2차원 (직교좌표 )위치 표현방법이다.
// Get the value of y.
assert(p.y == 2);
// Invoke distanceTo() on p.
double distance = p.distanceTo(Point(4, 4));
1
2
// If p is non-null, set a variable equal to its y value.
var a = p?.y;
객체 지향 프로그래밍이 가지는 특징은 다음과 같다.
Object | Class |
---|---|
객체는 class의 인스턴스이다. | class 는 객체를 생성하기 위한 청사진(템플리트) 이다. |
객체는 펜, 마우스, 의자 등과 같은 현실 세계의 개체(entity)이다. | class 는 유사한 객체들의 집합이다. |
객체는 물리적 개체이다. | class는 논리적 개체(entity)이다. |
객체는 필요할 때마다 반복해서 생성된다. | class는 오직 한번 만 선언한다. |
객체가 생성되면 메모리에 상주한다. | class는 선언해도 메모리를 점유하지않는다. |
1
2
3
4
class Cat {
String name;
String color;
}
위에서 정의된 String name
과 String color
는 속성 또는 class member 라고 부른다.
1
2
3
Cat nora = new Cat(); // dart 언어에서 `new` 키워드는 생략할 수 있다.
nora.name = 'Nora';
nora.color = 'Orange';
Cat nora
이렇게 변수명 앞에 Cat
이라고 쓰는 것은 일종의 변수type 선언이다. 하지만 var nora
라고 선언할 수도 있다.
인스턴스 변수를 선언하는 방법을 알아보자
1
2
3
4
5
{
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
double z = 0; // Declare z, initially 0.
}
초기화하지 않은 모든 변수는 null값을 가진다. 모든 인스턴스 변수는 절대적인 gertter 메소드를 생성한다. non-final 인스턴스변수와 초기화하지 않은 late final 인스턴스 변수는 setter 메소드를 생성한다. 상세한 것은 getters - setters 에서 다룬다. 만일 non-late 인스턴스 변수를 선언하면서 초기화하면 그 값은 인스턴스가 생성될 때 (생성자 앞에서 초기화 리스트가 실행될 때) 결정된다.
1
2
3
4
5
6
7
8
9
10
11
class Point {
double? x; // Declare instance variable x, initially null.
double? y; // Declare y, initially null.
}
void main() {
var point = Point();
point.x = 4; // Use the setter method for x.
assert(point.x == 4); // Use the getter method for x.
assert(point.y == null); // Values default to null.
}
인스턴 변수는 final 이 될 수 있고 이 경우 반드시 한번 만 지정해야 한다. final , non-late 인스턴스 변수를 선언할 때 생성자 파라메터를 이용하거나 생성자의 초기화 리스트를 이용하여 초기화한다.
1
2
3
4
5
6
7
class ProfileMark {
final String name;
final DateTime start = DateTime.now();
ProfileMark(this.name);
ProfileMark.unnamed() : name = '';
}
만일 생성자 몸체(본문)를 시작한 이후에 final 인스턴스 변수를 지정하고 싶다면 다음 중 한가지방법을 산용할 수 있다.
Class이름과 동일한 이름을 가지는 함수(메소드)를 생성하는 방법으로 선언(생성)된다. 생성자를 이용하여 객체를 생성한다. 생성자는 미리 결정된 속성값을 이용해서 객체들을 생성한다. 생성자에는 표준 생성자(standard constructor,default constructor), 이름있는 생성자(Named constructor),팩토리 생성자(factory constructor) 등의 종류가 있다.
이외에도 초기화 리스트(initializer list), 리다이렉팅 생성자(redirecting constructor), 상수 생성자(constant constructor) 등의 개념을 알아두어야 한다.
class를 선언할 때 생성자를 선언하지 않으면 기본생성자가 자동으로 제공된다. 매개변수(arguments)를 가지지 않으며 부모클라스의 비매개변수 생성자를 호출한다.
하위 클라스는 부모 클라스로부터 생성자를 상속받지 않는다. 생성자를 선언하지 않은 하위 클라스는 단지 기본 생성자(no argument, no name)를 가질 뿐이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person{
Person(){
print('This is Person Constructor!');
}
}
class Student extents Person{
// 자식클래스는 부모클래스의 생성자를 상속받지 않는다.
// 다만 생성자가 생략된 경우 기본생성자가 부모클라스의 기본생성자를 호줄한다.
//
}
Main(){
var student = new Student(); // ( 인스턴스화 ) new 를 생략할 수 있다.
}
따라서 다음과 같은 결과를 호출한다.
1
This is Person Constructor!
클라스 내에서 많은 생성자를 형성하거나 생성자를 명확히 하기위해 Named constructor(이름있는 생성자)를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class 클래스명{
클래스명. 생성자명(){
}
}
const double xOrigin = 0;
const double yOrigin = 0;
class Point {
final double x;
final double y;
Point(this.x, this.y);
// Named constructor
Point.origin()
: x = xOrigin,
y = yOrigin;
}
하위 클라스는 부모 클라스로부터 생성자를 상속받지 않는다는 점을 상기 할때 부모 클라스의 이름있는 생성자라도 자식 클라스에서 사용하기 위해서는 자식 클라스에서 생성자로 선언해 주어야 한다.
기본적으로 자식 클라스에서 생성자는 부모 클라스의 이름없는 생성자를 호출한다. 부모 클라스의 생성자는 생성자 본체가 시작할때 호출된다. 초기화 리스트는 부모 클라스가 호출되기 전에 실행된다.
종합해서 실행 순서를 살펴보면 아래와 같다.
만일 부모 클라스에 이름없는 생성자가 없다면 반드시 부모클라스의 생성자 중에 하나를 수동적으로 호출해주어야만 한다. 생성자 몸체 바로 앞에서 콜론(:)을 붙이고 뒤에 부모 클라스생성자를 지정한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person {
String? firstName;
Person.fromJson(Map data) {
print('in Person');
}
}
class Employee extends Person {
// Person does not have a default constructor;
// you must call super.fromJson().
Employee.fromJson(super.data) : super.fromJson() {
print('in Employee');
}
}
void main() {
var employee = Employee.fromJson({});
print(employee);
// Prints:
// in Person
// in Employee
// Instance of 'Employee'
}
수퍼 클라스 생성자에 대한 인수는 생성자 호출 전에 값이 정해지고 평가되기 때문에 인자는 마치 함수에서의 호출인 것 같은 표현이 된다.
1
2
3
4
class Employee extends Person {
Employee() : super.fromJson(fetchDefaultData());
// ···
}
부모 생성자를 호출하기 위해 각 매개변수를 수동으로 전달하지 않으려면 super
초기화 매개변수를 사용하여 지정된 또는 기본 수퍼클래스 생성자로 매개변수를 전달할 수 있다. 다만 이 방법은 리디렉션 생성자와 함께 사용할 수 없다. super
초기화 매개변수는 정식 매개변수 초기화와 유사한 구문 및 의미 체계를 갖는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector2d {
final double x;
final double y;
Vector2d(this.x, this.y);
}
class Vector3d extends Vector2d {
final double z;
// Forward the x and y parameters to the default super constructor like:
// Vector3d(final double x, final double y, this.z) : super(x, y);
Vector3d(super.x, super.y, this.z);
}
super
생성자 호출에 이미 위치한 매개변수가 있는 경우 super
초기화 매개변수가 위치할 수 없지만 항상 다음과 같이 named 를 활용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector2d {
// ...
Vector2d.named({required this.x, required this.y});
}
class Vector3d extends Vector2d {
// ...
// Forward the y parameter to the named super constructor like:
// Vector3d.yzPlane({required double y, required this.z})
// : super.named(x: 0, y: y);
Vector3d.yzPlane({required super.y, required this.z}) : super.named(x: 0);
}
슈퍼클래스 생성자를 호출하는 것 말고도 생성자 본문이 실행되기 전에 인스턴스 변수를 초기화할 수 있다. 생성자 옆에서 콜론(:)을 붙여 선언한다. 쉼표(콤마 ,)로 복수의 이니셜라이저를 구분한다.
1
2
3
4
5
6
7
// Initializer list sets instance variables before
// the constructor body runs.
Point.fromJson(Map<String, double> json)
: x = json['x']!,
y = json['y']! {
print('In Point.fromJson(): ($x, $y)');
}
개발과정에서 초기화 리스트 assert 을 사용하면 입력의 유효성을 검사할 수 있다.
1
2
3
Point.withAssert(this.x, this.y) : assert(x >= 0) {
print('In Point.withAssert(): ($x, $y)');
}
초기화 리스트는 최종 필드를 설정할 때 편리하다. 다음 예제는 초기화 리스트에서 세 개의 최종 필드를 초기화한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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(3, 4);
print(p.distanceFromOrigin);
}
실행 결과
1
5
(최기화 리스트를 사용하면 인스턴스가 생성될 때 생성자의 구현부가 실행되기 전에 인스턴스 변수를 초기화 할 수 있다. )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
main() {
final rectangle = Rectangle(2, 5);
}
class Rectangle {
final int width;
final int height;
final int area;
Rectangle(this.width, this.height)
: area = width * height {
print(area);
}
}
사각형의 면적은 넓이와 높이를 알아야 계산되지만 인스턴스가 생성될 때까지는 그 값을 알수 없으므로 생성자 밖에서는 계산할 수 없다. 이런 경우에 유용하게 사용된다.
1
10
때로는 생성자의 유일한 목적이 동일한 클래스의 다른 생성자로 리디렉션하는 것이 될 때가 있다. 리디렉션하는 생성자의 본문은 비어 있으며 생성자 호출(클래스 이름 대신 this 사용)은 콜론 ( : ) 뒤에 나타난다.
1
2
3
4
5
6
7
8
9
class Point {
double x, y;
// The main constructor for this class.
Point(this.x, this.y);
// Delegates to the main constructor.
Point.alongXAxis(double x) : this(x, 0);
}
클래스가 절대 변경되지 않는 객체를 생성하는 경우 이러한 객체를 컴파일 타임 상수로 만들 수 있다. 이렇게 하려면 생성자를 const
로 정의하고 모든 인스턴스 변수가 최종(final) 변수인지 확인한다.
1
2
3
4
5
6
7
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final double x, y;
const ImmutablePoint(this.x, this.y);
}
상수 생성자가 항상 상수를 생성하는 것은 아니다. 자세한 내용은 생성자의 사용 섹션을 참조하자.
항상 해당 클래스의 새 인스턴스를 생성하지 않는 생성자를 구현하고 싶을 때 factory 키워드를 사용한다. 예를 들어 팩토리 생성자는 캐시(이미 존재하는 인스턴스)에서 인스턴스를 반환하거나 하위 유형의 인스턴스를 반환할 수 있다. 또 다른 사용 사례는 초기화 리스트에서는 처리할 수 없는 logic
을 사용하여 최종 변수를 초기화하는 경우이다.
다음 예제에서 Logger
팩터리 생성자는 캐시에서 개체를 반환하고 Logger.fromJson
팩터리 생성자는 JSON
개체에서 최종 변수를 초기화한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}
}
this
에 접근할 수 없다.다른 생성자에서 처럼 팩토리 생성자를 호출한다.
1
2
3
4
5
6
var logger = Logger('UI');
logger.log('Button clicked');
var logMap = {'name': 'UI'};
var loggerJson = Logger.fromJson(logMap);
메서드는 개체에 작용(기능)을 부여하는 함수이다.
객체의 인스턴스 메서드는 인스턴스 변수와 this
에 액세스할 수 있다. 다음 예제의 distanceTo()
메서드는 인스턴스 메서드의 사례이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'dart:math';
class Point {
final double x;
final double 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);
}
}
연산자는 특별한 이름을 가진 인스턴스 메서드이다. Dart를 사용하면 다음 이름으로 연산자를 정의할 수 있다. | < | + | |
| »> | |—-|—-|—|—-| | > | / | ^ | [] | |<= |~/| &| []= | | >=| *|« |~ | | - |%| » |== |
!=
와 같은 일부 연산자는 이름 목록에 없다. 그것 들은 단지 문법적 조미료(syntactic sugar)이기 때문이다. 예를 들어, e1 != e2
표현식은 !(e1 == e2)
에 대한 문법적 조미료이다.연산자의 선언은 내장 (built-in) 식별자 연산자를 사용하여 식별된다. 다음 예에서는 벡터 더하기(+), 빼기(-) 및 동일성(==)을 정의한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Vector {
final int x, y;
Vector(this.x, this.y);
Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);
@override
bool operator ==(Object other) =>
other is Vector && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
}
void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);
assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}
Getter 및 Setter는 개체의 속성에 대한 읽기 및 쓰기 액세스를 제공하는 특수 메서드이다. 각 인스턴스 변수에는 암시적 getter와 적절한 경우 setter가 있음을 기억하자. get
및 set
키워드를 사용하여 getter 및 setter를 구현하여 추가 속성을 만들 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
// Define two calculated properties: right and bottom.
double get right => left + width;
set right(double value) => left = value - width;
double get bottom => top + height;
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);
}
getter 및 setter를 사용하면 클라이언트 코드를 변경하지 않고도 인스턴스 변수로 시작하여 나중에 메서드로 포장(wrapping)할 수 있다.
주의 : ++
과 같은 연산자는 getter가 명시적으로 정의되었는지 여부에 관계없이 예상대로 작동한다. 예상치 못한 부작용을 피하기 위해 연산자는 getter를 정확히 한 번 호출하여 해당 값을 임시 변수에 저장한다.
인스턴스, getter 및 setter 메서드는 인터페이스를 정의하여 추상화할 수 있지만 그 구현은 다른 클래스(하위클래스?)에 맡겨진다. 추상 메서드는 추상 클래스에만 존재할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
abstract class Doer {
// Define instance variables and methods...
void doSomething(); // Define an abstract method.
}
class EffectiveDoer extends Doer {
void doSomething() {
// Provide an implementation, so the method is not abstract here...
}
}
abstract
한정자를 사용하여 인스턴스화할 수 없는 추상 클래스를 정의한다. 추상 클래스는 흔히 어떤 구현과 함께 인터페이스를 정의하는 데 유용하다. 추상 클래스를 인스턴스화할 수 있는 것처럼 보이게 하려면 팩토리 생성자를 정의한다.
추상 클래스에는 흔히 추상 메서드가 사용된다. 다음 예제는 추상 메서드가 있는 추상 클래스를 선언하는 경우이다.
1
2
3
4
5
6
7
// This class is declared abstract and thus
// can't be instantiated.
abstract class AbstractContainer {
// Define constructors, fields, methods...
void updateChildren(); // Abstract method.
}
(2부에 계속…)
[플러터(flutter) 순한맛 강좌 12 | 플러터 다트(dart) 핵심정리: 클래스와 위젯의 정체 1](https://youtu.be/8k4vaoga2co) |
새 버전의 콘텐츠를 사용할 수 있습니다.
Comments powered by Disqus.