💡 이 글은 "깡샘의 플러터&다트 프로그래밍" 도서에서
'클래스와 생성자' & '상속과 추상 클래스' 내용을 바탕으로 정리한 것입니다.
Dart의 클래스는 다른 객체지향 언어의 클래스와 비슷하지만 몇 가지 독특한 내용을 정리해본다.
멤버 초기화 생성자 단순화
class User {
late String name;
late int age;
User(this.name, this.age); // this 키워드로 멤버 바로 초기화
}
초기화 목록(Initializer list)
// ':' 사용한 초기화 목록
User(String name, int age) : this.name = name, this.age = age { }
// 리스트 데이터로 초기화
class MyClass {
late int data1;
late int data2;
MyClass(List<int> args)
: this.data1 = args[0],
this.data2 = args[1] { }
}
// 멤버 함수 반환값으로 초기화
class MyClass {
late int data1;
late int data2;
MyClass(int arg1, int arg2)
: this.data1 = calFun(arg1),
this.data2 = calFun(arg2) { }
static int calFun(int arg) {
return arg * 10;
}
}
명명된 생성자(Named Constructor)
생성자 오버로딩대신 이름이 있는 생성자를 여러 개 선언하는 기법
// 명명된 생성자
class MyClass {
late imt data1;
late imt data2;
MyClass(this.data1, this.data2);
MyClass.first(int arg) : this(arg, 0); // 기본 생성자(MyClass) 호출
MyClass.second() : this.first(0); // 명명된 생성자(MyClass.first) 호출
}
팩토리 생성자(Factory Constructor)
클래스 외부에서는 생성자처럼 이용하지만 실제로 객체를 생성하진 않고, 상황에 맞는 객체를 반환하는 역할
캐시 알고리즘이나 상속 관계에 따른 다형성 구현 시 유용.
// 캐시 알고리즘 구현 예
class Image {
late String url;
static Map<String, Image> _cache = 〈String, Image>{};
Image._instance(this.url); // named constructor
factory Image(String url) {
if (_cache[url] == null) { // 전달받은 식별자가 캐시에 없으면
var obj = Image.instance(url); // 해당 식별자로 객체를 새로 생성하고
_cache[url] = obj; // 캐시에 추가
}
return _cache[url]!; // 캐시에서 식별자에 해당하는 객체 반환
}
}
main() {
var imagel = Image('a.jpg');
var image2 = Image('a.jpg');
print('image1 == imaged : ${image1 == image2}'); // image1 == image2 : true
}
다른 객체지향 언어와 큰 차이는 없다.
상속, 오버라이딩, 부모 클래스 멤버 접근
// 함수 객체 활용 예
class SuperClass {
int myData = 10;
void myFun() {
print ('Super..myFun()...');
}
}
class SubClass extends Superclass { // 'extends' 키워드로 상속
int myData = 20;
void myFun() {
super.myFun(); // 'super' 키워드로 부모 클래스 멤버 접근
print('Sub..myFun()..myData : $myData, super.myData : ${super.myData}'); // 'super' 키워드로 부모 클래스 멤버 접근
}
}
main(List<String> args) {
var obj = SubClass();
obj.myFun();
}
// 실행 결과
Super..myFun()...
Sub..myFun()..myData : 20, super.myData : 10
부모 생성자 호출 및 멤버 초기화
// 부모 클래스 생성자 호출 및 멤버 변수 초기화
class Superclass {
String name;
int age;
SuperClass(this.name, this.age) {}
}
class SubClass1 extends Superclass {
SubClass(String name, int age) : super(name, age) {} // 부모 클래스 멤버 초기화
}
class SubClass2 extends Superclass {
SubClass(super.name, super.age);
}
main() {
var obj1 = SubClass1('kkang', 10);
print('${obj1.name}, ${obj1.age}');
var obj2 = SubClass2('kkim', 20);
print('${obj2.name}, ${obj2.age}');
}
// 실행 결과
kkang, 10
kkim, 20
추상 클래스
// 추상 클래스 선언 및 재정의
abstract class User { // 'abstract' 키워드로 추상 클래스 선언
void some();
}
class Customer extends User {
void some() {}
}
인터페이스
// 일반 클래스
class User {
String name;
User(this.name);
String greet(String who) => 'Hello, $who. I'm $name';
}
// 'implements' 키워드로 인터페이스 구현 클래스 선언
class MyClass implements User { // User는 암시적 인터페이스 역할
String name = 'kim';
String greet(String who) => return 'hello';
}
main() {
User user = MyClass(); // 구현 클래스 객체는 인터페이스 타입으로 선언 가능
}
믹스인(Mixin)
믹스인은 변수와 함수는 선언할 수 있지만 클래스가 아니므로 생성자가 없다.
하지만 내부에 선언된 멤버들을 다른 클래스에 상속한 것처럼 이용할 수 있다.
// 믹스인을 다중 상속처럼 활용한 예
mixin MyMixin { // 'mixin' 키워드로 믹스인 선언
int mixinData = 10;
void mixInFun() {
print ('MyMixin...mixInFun()...');
}
}
class MySuper {
int superData = 20;
void superFun() {
print('MySuper... superFun()');
}
}
// 다중 상속처럼 선언
class MyClass extends MySuper with MyMixin { // 'with' 키워드로 믹스인 멤버 사용 선언
void sayHello() {
print('class data : $superData, mixin data : $mixinData');
mixInFun();
superFun();
}
}
main() {
var obj = MyClass();
obj.sayHello();
}
// 실행 결과
class data : 20, mixin data : 10
MyMixin... mixInFun()...
MySuper...superFun()