Dart는 class와 mixin 기반의 상속을 지원하는 OOP 언어입니다.
따라서, 이번 포스팅에서 소개드릴 class 관련 내용이 핵심이라고도 볼 수 있습니다.
용어가 조금 혼용되는 느낌이 있는 것 말곤 타 언어대비 특별할 건 딱히 없어서 편하게 보실 수 있을거에요.
Dart에서 Null을 제외한 모든 object는 Object로부터 상속됩니다.
Constructors
생성자는 Class 이름과 동일하게 만들거나, named constructor로 만들 수 있습니다.
아무런 생성자도 정의하지 않는다면, parameter를 받지않는 default constructor가 자동으로 제공됩니다.
생성자는 상속되지 않습니다.
Instance의 생성 시, new keyword는 생략이 가능합니다.
간단한 예제와 함께 보세요.
class Point {
int x;
int y;
Point(this.x, this.y);
Point.fromJson({int x = 0, int y = 0})
: x = x,
y = y;
}
void main() {
var p1 = Point(2, 2);
var p2 = Point.fromJson(x: 1, y: 2);
}
Named parameter도 사용 가능함을 보여드리려고 named constructor에 적용해보았습니다.
Redirecting Constructors
Constructor를 redirecting 시킬 수 있습니다.
class Point {
int x;
int y;
Point(this.x, this.y);
Point.fromJson(int x) : this(x, 0);
}
Named constructor가 호출되면 다른 생성자가 호출되도록 redirecting 시킨 예제입니다.
Constant Constructors
Constant constructor를 만들기 위해서는 const로 선언을 하고, 다른 모든 instance variable들은 final로 선언해야 합니다.
Constant constructor로 instance를 만들면 compile-time 상수가 됩니다.
즉, 동일한 argument로 생성하면 동일한 instance가 생성됩니다.
class ImmutablePoint {
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
void main() {
var p1 = const ImmutablePoint(2, 2);
var p2 = const ImmutablePoint(2, 2);
print(identical(p1, p2)); // true
}
Factory Constructors
Factory constructor는 factory keyword로 만들며, new keyword를 사용하지 않고 instance를 만들 수 있습니다.
Class의 instance를 반환하거나, sub-type의 instance를 반환합니다.
이 때, 새 instance를 생성하는 대신 이전에 생성되었거나 cache에서 가져온 instance를 반환합니다.
class Point {
num? x;
num? y;
Point(this.x, this.y);
factory Point.fromJson(Map<String, num> json) {
return Point(json['x'], json['y']);
}
}
void main() {
var p1 = Point.fromJson({'x': 1, 'y': 2});
var p2 = Point.fromJson({'x': 3, 'y': 4});
}
Invoking Superclass Constructor
super keyword를 사용하여 부모 class의 생성자를 호출할 수 있습니다.
class Person {
String? name;
int? age;
Person.fromJson(Map data) {
print('Person Class');
}
}
class Student extends Person {
Student.fromJson(super.data) : super.fromJson() {
print('Student Class');
}
}
void main() {
var student = Student.fromJson({});
print(student);
}
: super.fromJson() 부분을 super-initializer라고 하는데, 자식의 생성자가 호출될 때 부모의 생성자가 먼저 호출됩니다.
간접적으로 부모의 생성자를 호출할 수도 있습니다.
class Vector2D {
final double x;
final double y;
Vector2D(this.x, this.y);
}
class Vector3D extends Vector2D {
final double z;
Vector3D(super.x, super.y, this.z);
}
void main() {
var vector = Vector3D(1, 2, 3);
}
위 예제에서는 3개의 입력을 받아, 2개는 super keyword를 붙여 부모의 생성자로 보냅니다.
Named parameter를 적용할 수도 있습니다.
class Vector2D {
...
Vector2D.named({required this.x, required this.y});
}
class Vector3D extends Vector2D {
...
Vector3D.named({required super.y, required this.z}) : super.named(x: 0);
}
Instance Variables
Instance variable은 type을 명시해야합니다.
class Point {
int x = 0;
int? y; // initially null
}
모든 instance variable은 암묵적으로 getter method를 생성합니다.
또한, non-final과 late final instance variable들은 암묵적으로 setter method를 생성합니다.
class Point {
int x = 0;
int? y; // initially null
}
void main() {
var point = Point();
point.x = 3; // setter
point.y = 2; // setter
print("X: ${point.x}, Y: ${point.y}"); // getter
}
Methods
Instance Methods
Instance method는 instance variable에 접근할 수 있습니다.
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);
}
}
void main() {
var p1 = Point(1, 2);
var p2 = Point(2, 4);
print(p1.distanceTo(p2));
}
Operator Overriding
연산자를 overriding해서 원하는대로 변경할 수 있습니다.
변경이 가능한 연산자는 다음과 같습니다.
<, >, <=, >=, -, +, /, ~/, *, %, |, ^, &, <<, >>, >>>, [], []=, ~, ==
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 v1 = Vector(2, 3);
final v2 = Vector(2, 2);
print("${(v1 + v2).x}, ${(v1 + v2).y}");
print(v1 - v2 == Vector(0, 1));
print(v1.hashCode);
}
getter and setter
각각 get, set keyword를 사용해서 만들 수 있습니다.
class Rectangle {
double left, top, width, height;
Rectangle(this.left, this.top, this.width, this.height);
double get right => left + width;
set right(double value) => left = value - width;
}
void main() {
var rect = Rectangle(1, 2, 2, 4);
print(rect.right); // getter
rect.right = 12; // setter
}
Abstract Class
추상 class는 abstract modifier로 정의하며, instance를 만들 수 없습니다.
Interface를 정의할 때, 유용하게 사용할 수 있습니다.
Method도 interface만 정의하고 구현은 상속받는 class에게 맡기는 abstract method를 주로 갖습니다.
abstract class Super {
void doSomething();
}
class Sub extends Super {
void doSomething() {
// Implementing
}
}
Inheritance
상속을 class를 확장한다고 표현하며, extends keyword를 사용합니다.
자식 class에서 부모 class의 method를 overriding할 수 있습니다.
이 때, override keyword로 명시를 해야하며 return type은 동일해야 합니다.
Argument type은 같거나 상위 type 이어야 합니다.
부모의 positional parameter는 모두 받아야 합니다.
또, generic method와 non-generic method 간에는 override가 불가합니다.
존재하지 않는 instance variable이나 method를 사용했을 때, 검출하거나 처리하려면 noSuchMethod()를 overide해야 합니다.
class Super{
void method1() { ... }
void method2(int param) { ... }
}
class Sub extends Super {
void method1() {
super.method();
}
@override
void method2(num param) { ... }
@override
void noSuchMethod(Invocation invocation) {
print('You tried to use a non-existent member: ${invocation.memberName}');
}
}
Class Variables and Methods
static keyword를 사용하여 class 범위의 variable 혹은 method를 구현할 수 있습니다.
Static variable과 static method에 대해 각각 간단한 예제로 설명을 대신하겠습니다.
Static Variables
class Rectabgle {
static const vertices = 4;
}
void main() {
assert(Rectabgle.vertices == 4);
}
Static Methods
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);
}
Implicit Interfaces
implements keyword로 interface를 구현할 수 있습니다.
하나의 class가 여러 interface를 구현하는 것도 가능합니다.
class Person implements Eat, Say { ... }
Mixins
Mixin은 class를 재사용하는 방법으로, 생성자를 갖지 않는 class 입니다.
mixin과 with keyword를 사용하여 정의합니다.
mixin MixinExample {
bool testVar = false;
void testMe() {
if (testVar ) {
print('Test True');
} else {
print('Test False');
}
}
}
class Example with MixinExample { ... }
on keyword를 사용하여 mixin이 가능한 super class를 제한할 수도 있습니다.
class Super { ... }
mixin MixinA on Super { ... }
class Sub extends Super with MixinA { ... }
Wrap Up
이번에는 Dart의 핵심인 class에 관해 살펴보았습니다.
적당히 묶어서 설명을 시도해봤는데, 다른 언어를 통해 OOP가 이미 익숙하신 분들께는 괜히 쓸데없이 설명이 길었나 싶기도 하고, 낯선 분들께는 부족할 것 같기도 하네요.
모쪼록 도움이 되셨길 바랍니다.
'Dart (Flutter)' 카테고리의 다른 글
[Dart] Generics (0) | 2023.02.08 |
---|---|
[Dart] Enumerated Type (0) | 2023.02.08 |
[Dart] Functions (0) | 2023.02.03 |
[Dart] Control Flow Statements (0) | 2023.02.03 |
[Dart] Operators (0) | 2023.02.02 |
댓글