상속과 오버로딩
상속은 클래스를 재활용하는 객체지향 프로그래밍의 핵심 기능입니다. 기존 클래스를 상속받으면 새 클래스에서 기존 클래스의 멤버를 이용할 수 있습니다. 이때 기존 클래스를 '부모 클래스', 상속받은 새 클래스를 '자식 클래스'라고 합니다. 다트도 상속을 제공하며 다른 객체지향 프로그래밍 언어와 큰 차이는 없습니다. 다트에서 클래스를 선언할 때 어떤 클래스를 상속받으려면 extends 예약어를 사용합니다.
다음 코드를 보면 SuperClass라는 이름으로 선언한 클래스가 있습니다. 그리고 SubClass는 SuperClass를 상속받아 선언했습니다. 이렇게 하면 SubClass의 객체로 SuperClass에 선언된 멤버를 사용할 수 있습니다.
// 함수에서 널 불허 지역 변수 초기화
class SuperClass {
int myData = 10;
void myFun() {
print('Super..myFun()...');
}
}
class SubClass extends SuperClass {
}
main(List<String> args) {
var obj = SubClass();
obj.myFun();
print('obj.data : ${obj.myData}');
}
▶ 실행 결과
Super..myFun()...
obj.data : 10
이처럼 클래스를 상속받으면 부모 클래스에 선언된 멤버를 자식 클래스에서 그대로 사용할 수 있지만, 원한다면 재정의할 수도 있습니다. 이를 오버라이딩이라고 합니다.
다음 코드는 부모 클래스의 멤버를 자식 클래스에서 재정의한 예입니다. 이렇게 하면 부모 클래스에 선언된 멤버가 자식 클래스에 상속되지 않습니다. 즉, 자식 클래스의 객체는 자신의 멤버에 접근합니다.
// 오버라이딩
class SuperClass {
int myData = 10;
void myFun() {
print('Super..myFun()...');
}
}
class SubClass extends SuperClass {
int myData = 20;
void myFun() {
print('Sub...myFun()...');
}
}
main(List<String> args) {
var obj = SubClass();
obj.myFun();
print('obj.data : ${obj.myData}');
}
▶ 실행 결과
Sub..myFun()...
obj.data : 20
만약 자식 클래스에서 부모 클래스에 선언된 멤버를 재정의할 때 부모 클래스에 선언된 똑같은 이름의 멤버를 이용하고 싶다면 다음처럼 super라는 예약어로 접근합니다.
// 부모 클래스의 멤버에 접근하기
class SuperClass {
int myData = 10;
void myFun() {
print('Super..myData()...');
}
}
class SubClass extends SuperClass {
int myData = 20;
void myFun() {
super.myFun();
print('Sub...myFun()..myData : $myData, super.myData : ${super.myData}');
}
}
main(List<String> args) {
var obj = SubClass();
obj.myFun();
}
▶실행 결과
Super..myFun()...
Sub... myFun()..myData : 20, super.myData : 10
부모 생성자 호출하기
클래스의 상속 기능을 이용할 때 꼭 기억해야 할 내용이 있습니다. 바로 부모 클래스의 생성자 호출입니다. 즉, 자식 클래스의 객체를 생성할 때 자신의 생성자가 호출되는데, 이때 부모 클래스의 생성자도 반드시 호출되게 해줘야 합니다.
다음 코드에서는 SubClass의 객체 obj를 생성할 때 자식 클래스의 생성자를 호출합니다.
// 자식 클래스의 생성자 호출(부모 생성자는 자동 호출됨)
class SuperClass {
SuperClass() {}
}
class SubClass extends SuperClass {
SubClass() {}
}
main() {
var obj = SubClass();
}
자식 클래스의 생성자에서 부모 클래스의 생성자를 호출하려면 super() 문을 사용합니다. super()문은 생성자의 본문이 아닌 선언부의 콜론(:) 오른쪽에 작성합니다. 그런데 앞선 코드에서 오류가 발생하지 않은 이유는 컴파일러가 자동으로 부모 클래스의 기본 생성자를 호출해 주기 때문입니다.
// 부모 생성자 호출
class SuperClass {
SuperClass() {}
}
class SubClass extends SuperClass {
SubClass() : super() {}
}
그런데 만일 부모 생성자가 매개변수나 명명된 생성자를 가진다면 super()문을 생략하면 안 되고 반드시 그에 맞는 생성자를 호출해 줘야 합니다. 아래 코드에서는 자식 클래스의 생성자에 super() 문을 작성하긴 했지만, 컴파일러가 부모 클래스의 어떤 생성자를 호출할지 몰라서 오류가 발생합니다.
// 부모 생성자 호출의 잘못된 예
class SuperClass {
SuperClass(int arg) {}
SuperClass.first() {}
}
class SubClass extends SuperClass {
SubClass() : super() {} // 오류
}
즉, 컴파일러가 자동으로 추가하는 super() 코드는 매개변수가 없고 부모 클래스 이름으로 선언된 기본 생성자만 호출해 줍니다. 따라서 다음 코드처럼 부모 클래스의 생성자 사양에 맞춰 super() 문을 작성해야 합니다.
// 부모 생성자 호출의 올바른 예
class SuperClass {
SuperClass(int arg) {}
SuperClass.first() {}
}
class SubClass extends SuperClass {
SubClass() : super(10) {} // 성공
SubClass.name() : super.first() {} // 성공
}
부모 클래스 초기화
객체를 생성할 때 전달받은 값으로 클래스의 멤버 변수를 초기화할 때는 생성자의 매개변수에 this를 사용합니다.
// 생성자의 매개변수로 멤버 변수 초기화
class User {
String name;
int age;
User(this.name, this.age);
}
만약 생성자의 매개변수로 부모 클래스에 선언된 멤버를 초기화해야 할 때는 다음처럼 작성할 수 있습니다. 부모 클래스의 생성자를 호출하는 super() 구문에 매개변숫값을 전달하면 됩니다.
// 부모 클래스의 멤버 변수 초기화 1
class SuperClass {
String name;
int age;
SuperClass(this.name, this.age) {}
}
class SubClass extends SuperClass {
SubClass(String name, int age) : super(name, age) {} // 부모 클래스 멤버 초기화
}
main() {
var obj = SubClass('kkang', 10);
print('${obj.name}, ${obj.age}');
}
▶ 실행 결과
kkang, 10
이 코드는 다음처럼 간소화할 수 있습니다. 즉, 생성자의 매개변수에 super로 부모 클래스의 멤버를 작성하면 해당 값으로 부모 클래스의 생성자가 호출돼 멤버 변수가 초기화됩니다.
// 부모 클래스의 멤버 변수 초기화 2
class SuperClass {
String name;
int age;
SuperClass(this.name, this.age) {}
}
class SubClass extends SuperClass {
SubClass(super.name, Super.age);
}
main() {
var obj = SubClass('kkang', 10);
print('${obj.name}, ${obj.age}');
}
▶실행 결과
kkang, 10
'Flutter' 카테고리의 다른 글
멤버를 공유하는 믹스인 (1) | 2024.11.15 |
---|---|
추상 클래스와 인터페이스 (0) | 2024.11.15 |
상수 생성자 (0) | 2024.11.14 |
팩토리 생성자 (0) | 2024.11.14 |
명명된 생성자 (0) | 2024.11.14 |