Dart: Constructors, Inheritance, Null Safety, Mixins
Null Safety
null과 관련된 runtime error 예시
void main() { String message; //오류! Null Safety에 의해 변수에 값을 할당해야 한다고 알려줍니다 print(message.length); } void main() { String? message; print(message.length); //오류! 변수가 null이기 때문에 .length는 실행할 수 없다고 알려줍니다 }
Null-aware Operators
//====== < ?? 예제 > ====== void main() { String? input; String message; if (input != null) { message = input; } else { message = 'Error'; } print(message); } void main() { String? input; String message = input ?? 'Error'; print(message); } //====== < ??= 예제 > ====== void main() { String? input; input = input ?? 'Error'; } void main() { String? input; input ??= 'Error'; } //====== < ?. 예제 > ====== void main() { String? input; if (input != null) { print(input.length); print(input.toLowerCase()); } } //?. 는 객체가 null인 경우 error가 아닌 null을 반환합니다 void main() { String? input; print(input?.length); print(input?.toLowerCase()); } //====== < ! 예제 > ====== //아래 코드는 bool에 bool?을 할당할 수 없어 오류입니다 bool? isTextFile(String? filename) { if (filename != null) { return filename.endsWith('.txt') ? true : false; } return null; } void main() { bool result = isTextFile('readme.txt'); //<== print(result); } //하지만 isTextFile('readme.txt')이 null을 반환하지 않는다고 확신한다면 //화살표로 가리킨 코드를 아래 코드로 바꾸었을 때 오류가 뜨지 않습니다. bool result = isTextFile('readme.txt')!; //====== < ?[ ] 예제 > ====== //list가 null이라도 list 요소에 접근을 허용합니다. //이때 list가 null이라면 아래 코드의 결과 값은 null이 됩니다 void main() { List? scores = [1, 2, 3, 4, 5]; scores = null; print(scores?[3]); }
Constructors
생성자(Constructor)는 객체를 생성할 때 자동으로 호출되지만 생성과 동시에 Class 객체의 속성을 초기화하고 싶을 때 생성자를 Custom 할 수 있습니다.
아래는 그 예시이며 방법 2는 방법 1을 간결하게 표현한 것입니다.
class Point { int x = 0; int y = 0; //방법 1 Point(int x, int y) { this.x = x; this.y = y; } //방법 2 Point(this.x, this.y); } void main() { var p1 = Point(10, 20); }
Named constructor
Named constructor는 이름을 가지는 생성자입니다. Dart에서는 생성자 Overloading을 지원하지 않고 대신에 Named constructor를 사용합니다.
아래는 그 예시입니다. 방법 1을 간단히 방법 2와 3으로 나타낼 수 있습니다. 방법 2와 3 모두 initializer list 에서 변수를 할당합니다. 특히 방법 2를 통해 Class 내부에 있는 생성자는 이미 정의된 또 다른 생성자를 호출해서 사용할 수 있음을 알 수 있습니다.
class Point { int x = 0; int y = 0; Point(this.x, this.y); //방법 1 Point.origin() { this.x = 0; this.y = 0; } //방법 2 Point.origin() : this(0, 0); //방법 3 Point.origin() : x = 0, y = 0; } void main() { var p1 = Point.origin(); }
Inheritance
상속은 다른 class의 methods와 properties를 재사용하기 위해서 존재합니다.
Dart는 한 번의 상속만 지원하기 때문에 어떤 class가 2개 이상의 다른 class를 상속 받을 수 없습니다. 쉽게 표현해서 자식에게 부모는 오로지 '하나'인 것과 같습니다.
상속 방법은 'extends' 키워드를 사용합니다. 상속을 받은 (1) 자식 class는 부모 class의 methods를 이용할 수 있습니다. 그리고 (2) 자식 class 만의 새로운 methods와 properties를 정의할 수 있습니다.
Super
'super'는 부모 class를 참조할 때 사용합니다. 그래서 'super'로 부모 class의 properties를 자식 class에서 사용할 수 있습니다.
Dart에서는 자식 class가 부모 class의 생성자를 상속 받지 않습니다. 보통 자식 class에서 생성자를 정의할 때 부모 class의 properties와 자식 class에서 정의한 properties를 초기화하므로 (3) 부모 class의 properties 초기화는 'super'를 통해 할 수 있습니다.
Method overriding
@override 키워드를 사용해 부모 class의 method를 자식 class에서 (4) 같은 이름으로 재정의할 수 있습니다.
아래는 예시 코드이며 주석으로 번호를 표시했습니다.
class Person { int age = 0; String name = "default"; Person(this.age, this.name); void printInfo() { print("$name is $age years old"); } void printAge() { print("$age"); } } class Student extends Person { int className = 0; //(2) properties Student(int age, String name, this.className) : super(age, name); //(3) void printClassName() { //(2) methods print("$className"); } @override void printInfo() { //(4) print("$name is $age years old and class is $className !"); } } void main() { var person1 = Student(17, "jin", 5); person1.printAge(); //(1) person1.printClassName(); person1.printInfo(); }
Mixins
앞서 Dart는 단 한 번의 상속만 지원한다고 했습니다. 그런데 class 대신 mixin 키워드를 사용하면 mixin으로 정의된 methods는 한 class 에서 여러 개를 공유 받아 사용할 수 있어 마치 다중 상속과 비슷한 역할을 합니다. 또한 여러 class 계층에서 코드를 재사용할 때 mixin 을 씁니다.
mixin을 다른 class 에서 공유 받을 때는 with 키워드를 이용합니다. mixin의 특징은 다음과 같습니다.
1. 생성자(constructor)를 가지지 않습니다. 따라서 mixin을 통해 객체를 생성할 수 없습니다.
var share = Shareable(); //Error
2. mixin 에서 상속 받을 수 없습니다.
class myShareable extends Shareable {} //Error
아래는 mixin 사용 예시 입니다.
class Player { void play() { print("경기하는 중..."); } } class BasketBallPlayer extends Player{ @override void play() { super.play(); print("농구경기 하는 중..."); } } mixin CanRun { void run() { print("뛰는 중..."); } } mixin CanJump { void jump() { print("점프하는 중..."); } } class Pitcher extends BaseBallPlayer with CanRun,CanJump { @override void play() { run(); jump(); } } class Human with CanRun,CanJump { void activity() { run(); jump(); } }
위 예시처럼 Human 과 Player 계층은 서로 연관이 없지만 mixin을 통해 효율적으로 공통된 methods를 사용할 수 있습니다.