다트 기초 문법은 이전글에서 보실 수 있어요.😆
2023.02.20 - [독서/IT・컴퓨터] - [코드팩토리의 플러터 프로그래밍] 1장. 다트(dart) 입문하기
[코드팩토리의 플러터 프로그래밍] 1장. 다트(dart) 입문하기
다트에 입문하기에 앞서 플러터 개발 환경 구축에 대해 궁금하신 분은 이전 글을 참조하시면 될 것 같습니다. 2023.02.15 - [독서/IT・컴퓨터] - [코드팩토리의 플러터 프로그래밍] 0장. 플러터(flutter)
zzingonglog.tistory.com
다트 언어는 높은 완성도로 객체 지향 프로그래밍을 지원한다고 합니다. 플러터 역시 객체 지향 프로그래밍으로 설계된 프레임워크이고요.
열심히 따라쳐보면서 객체지향 프로그래밍 관련하여 문법 내용을 위주로 정리해보았습니다.😀
1. 클래스 (Class)
1) 클래스 선언
객체를 만들기 위해 클래스를 선언해봅시다.
this 키워드를 사용하지 않고 name 변수에 접근하고 싶다면 해당 메서드 안에서 동일한 변수명이 없으면 ${name} 만으로 값 접근이 가능합니다.
[코드]
class Idol {
//클래스에 종속되는 변수 선언 및 초기화
String name = '블랙핑크';
//클래스에 종속되는 메서드
void sayName() {
//String name = "사람"; //이 주석을 풀게되면 10라인의 값이 사람으로 바뀌게 됩니다.
//클래스 내부의 속성을 지칭하고 싶을 때는 this 키워드를 사용합니다.
print('저는 ${this.name}입니다.');
print('저는 ${name}입니다.');
}
}
void main() {
//변수의 타입이 클래스 명이 되며, Idol 인스턴스(=객체)를 생성합니다.
Idol blackPink = Idol();
blackPink.sayName();
}
[결과]
저는 블랙핑크입니다.
저는 블랙핑크입니다.
2) 생성자 (Constructor)
생성자는 클래스의 인스턴스를 생성하는 메서드입니다.
생성자를 이용하여 인스턴스를 처음 만들 때 변수를 초기화할 수 있습니다.
(1) 기본 생성자
일반적으로 생성자로 초기화 되는 내부 변수는 final로 선언한다고 합니다. 인스턴스화 한 다음에 변수의 값을 변경하는 실수를 막기 위함입니다.
[코드]
class Idol {
//final 선언
final String name;
//생성자 선언하기
//생성자명은 클래스명과 같습니다.
//콜론 : 뒤에 매개변수로 입력되는 값을 클래스의 변수에 대입합니다.
Idol(String name) : this.name = name;
void sayName() {
print('저는 ${this.name}입니다.');
}
}
void main() {
Idol blackPink = Idol('블랙핑크');
blackPink.sayName();
Idol bts = Idol('BTS');
bts.sayName();
}
[결과]
저는 블랙핑크입니다.
저는 BTS입니다.
생성자의 매개변수를 변수에 저장하는 과정을 생략하는 방법도 있습니다.
[코드]
class Idol {
//Private 변수 선언
String name;
//this를 사용할 경우 해당하는 변수에 자동으로 매개변수가 저장됩니다.
Idol(this.name);
void sayName() {
print('저는 ${this.name}입니다.');
}
}
void main() {
Idol blackPink = Idol('블랙핑크');
blackPink.sayName();
}
저는 블랙핑크입니다.
(2) 네임드 생성자
네임드 생성자는 네임드 파라미터와 비슷한 개념입니다. 인스턴스를 생성할 때 여러 가지 방법으로 초기화 함을 명시하고 싶을 때 사용합니다.
정의하는 방법은 아래 코드에서 확인할 수 있습니다.
[코드]
class Idol {
final String name;
final int memCnt;
//1개 이상의 변수를 초기화
Idol(String name, int memCnt) :
this.name = name,
this.memCnt = memCnt;
//네임드 생성자
Idol.fromMap(Map<String, dynamic> map)
: this.name = map['name'],
this.memCnt = map['memCnt'];
void sayName() {
print('저는 ${this.name}입니다. ${this.name}의 멤버는 ${this.memCnt}명 입니다.');
}
}
void main() {
Idol blackPink = Idol('블랙핑크', 4);
blackPink.sayName();
//fromMap이라는 네임드 생성자를 이용하여 bts 인스턴스 생성하기
Idol bts = Idol.fromMap({
'name' : 'BTS',
'memCnt' : 7,
});
bts.sayName();
}
[결과]
저는 블랙핑크입니다. 블랙핑크의 멤버는 4명 입니다.
저는 BTS입니다. BTS의 멤버는 7명 입니다.
(3) 프라이빗 변수 (Private variable)
프라이빗 변수는 변수명을 _ 기호로 시작하여 선언합니다.
일반적으로 다른 언어에서는 클래스 내부에서만 사용하도록 변수 접근을 제한을 하는 개념인데 다트에서는 같은 파일에서만 접근 가능한 변수입니다.
[코드]
class Idol {
//Private 변수 선언
String _name;
Idol(this._name);
}
void main() {
Idol blackPink = Idol('블랙핑크');
print(blackPink._name);
}
[결과]
블랙핑크
(4) 게터 세터 (getter, setter)
게터는 값을 가져올 때, 세터는 값을 저장할 때 사용합니다.
최근 객체지향 프로그래밍에서는 변수의 값을 불변성으로 사용하기 때문에 게터는 종종 사용하나, 세터는 거의 사용하지 않는다고 합니다.
[코드]
class Idol {
String _name = '블랙핑크';
//get 키워드 사용
String get name {
return this._name;
}
//set 키워드 사용
//세터는 매개변수로 딱 하나의 변수를 받을 수 있습니다.
set name (String name){
this._name = name;
}
}
void main() {
Idol blackPink = Idol();
//게터와 세터는 변수처럼 사용할 수 있습니다.
blackPink.name = '에이핑크'; //set
print(blackPink.name); //get
}
[결과]
에이핑크
2. 상속
extends 키워드로 클래스 간의 상속 관계를 설정 할 수 있습니다.
상속은 단 하나의 클래스만 상속받을 수 있습니다.
부모 클래스를 상속받은 자식 클래스에서는 부모 클래스의 생성자를 실행해주어야 합니다.
자식 클래스에서는 부모에게서 물려받은 메서드와 속성을 사용할 수 있습니다.
[코드]
class Idol {
final String name;
final int memCnt;
Idol(this.name, this.memCnt);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.');
}
}
//extends 키워드로 Idol 클래스를 상속 받는다.
class BoyGroup extends Idol {
BoyGroup (
String name,
int memCnt,
) : super(name, memCnt,); //super는 부모클래스를 지칭. 부모의 생성자 실행
//상속받지 않은 메서드.
void sayMale() {
print('저는 남자아이돌입니다.');
}
}
void main() {
BoyGroup bts = BoyGroup('BTS', 7);
bts.sayName(); //상속받은 메서드
bts.sayMemCnt(); //상속받은 메서드
bts.sayMale(); //새로 추가한 메서드
}
[결과]
저는 BTS입니다.
BTS 멤버는 7명입니다.
저는 남자아이돌입니다.
3. 오버라이드 (Override)
부모 클래스나 인터페이스에 정의된 메서드를 재정의 할 때 사용합니다.
override 키워드를 이용하지만 생략할 수도 있으나, 코드 가독성을 위해 @override 키워드를 사용해주면 좋을 것 같습니다.
[코드]
class Idol {
final String name;
final int memCnt;
Idol(this.name, this.memCnt);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.');
}
}
class GirlGroup extends Idol {
//생성자의 매개변수로 직접 super 키워드를 사용.
GirlGroup(super.name,
super.memCnt,);
@override
void sayName() {
print('저는 여자 아이돌 ${this.name}입니다.');
}
}
void main() {
GirlGroup blackPink = GirlGroup('블랙핑크', 4);
blackPink.sayName(); //자식 클래스에서 오버라이드된 메서드
blackPink.sayMemCnt(); //오버라이드 되지 않은 메서드, 부모클래스의 메서드
}
[결과]
저는 여자 아이돌 블랙핑크입니다.
블랙핑크 멤버는 4명입니다.
4. 인터페이스 (Interface)
인터페이스는 공통으로 필요한 기능을 정의만 해두는 역할을 합니다.
인터페이스는 implements 키워드를 사용하며 여러 개의 클래스를 콤마 ,로 구분하여 한 클래스에 여러개의 인터페이스로 적용 할 수도 있습니다.
상속과 인터페이스의 차이점은 상속은 메서드를 반드시 재정의할 필요가 없지만, 인터페이스는 반드시 모든 기능을 다시 정의해주어야 하는 차이가 있습니다.
반드시 재정의할 필요가 있는 기능을 정의할하는 용도가 인터페이스이기 때문입니다.
[코드]
class Idol {
final String name;
final int memCnt;
Idol(this.name, this.memCnt);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.');
}
}
//Idol 클래스를 인터페이스로 사용합니다.
class GirlGroup implements Idol {
//변수를 재정의
final String name;
final int memCnt;
GirlGroup(this.name,
this.memCnt,);
//메서드 재정의
void sayName() {
print('저는 여자 아이돌 ${this.name}입니다.');
}
//메서드재정의
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.!!!!');
}
}
void main() {
GirlGroup blackPink = GirlGroup('블랙핑크', 4);
blackPink.sayName();
blackPink.sayMemCnt()
}
[결과]
저는 여자 아이돌 블랙핑크입니다.
블랙핑크 멤버는 4명입니다.!!!!
5. 믹스인 (Mixin)
믹스인은 특정 클래스에 원하는 기능들만 골라넣을 수 있습니다.
특정 클래스를 지정해서 속성들을 정의할 수 있으며 지정한 클래스를 상속하는 클래스에서도 사용할 수 있습니다.
인터페이스처럼 콤마로 열거하여 한 개의 클래스에 여러 개의 믹스인을 적용할 수도 있습니다.
mixin을 구현하기 위해서는 먼저 Object를 상속하는 클래스 (= 일반적인 클래스를 작성) 를 만든 후에 생성자를 따로 선언하지 않아야 합니다. 이대로 두면 그냥 클래스이겠지요. 그리고 일반적인 클래스로 사용하기를 원치 않는다면, class 를 mixin 키워드로 수정하시면 됩니다.
또한 믹스인의 사용을 타입으로 제한할 수 있습니다. 바로 on 키워드를 이용해서 인데요.
아래 코드를 보면 on 키워드를 사용해서 Idol 부모 클래스를 지정하여 타입을 제한하였습니다.
그러면 BoyGroup 클래스에서 믹스인을 사용하려면 반드시 Idol 클래스를 상속하고 믹스인을 with해야 합니다.
[코드]
class Idol {
final String name;
final int memCnt;
Idol(this.name, this.memCnt);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.');
}
}
//믹스인 정의하는 방법.
//on을 사용하여 믹스인 타입 사용 제한
mixin IdolSingMixin on Idol {
void sing() {
print('${this.name}이 노래를 부릅니다.');
}
}
//믹스인을 클래스에 적용할 때는 with 키워드를 사용합니다.
class BoyGroup extends Idol with IdolSingMixin {
BoyGroup(super.name,
super.memCnt,);
void sayMale() {
print('저는 남자 아이돌입니다.');
}
}
void main() {
BoyGroup bts = BoyGroup('BTS', 7);
bts.sing();
}
[결과]
BTS이 노래를 부릅니다.
6. 추상 (Abstract)
상속이나 인터페이스로 사용하는 데 필요한 속성만 정의하고 인스턴스화 할 수 없도록 하는 기능입니다.
어떤 클래스 A를 다른 클래스 등에서 인터페이스로 사용하고 있는데 A 클래스 자체는 인스턴스화할일이 없다고 합시다. 이런 경우에는 추상클래스로 선언하는 것이 좋습니다.
추상 클래스는 메서드에 바디가 없고 함수의 반환타입, 이름, 매개변수만 정의하고 자식 클래스에서 함수의 바디를 정의하도록 강제합니다.
추상클래스는 인터페이스와 동일하게 implements 키워드로 구현을 하며,추상클래스에 선언되어있는 속성과 생성자 메서드를 전부 정의하여야 합니다.
[코드]
abstract class Idol {
final String name;
final int memCnt;
Idol(this.name, this.memCnt);
void sayName();
void sayMemCnt();
}
//implements 키워드를 이용해 추상 클래스를 구현합니다.
class GirlGroup implements Idol {
final String name;
final int memCnt;
GirlGroup(this.name,
this.memCnt,);
void sayName() {
print('저는 여자 아이돌 ${this.name}입니다.');
}
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.');
}
}
void main() {
GirlGroup blackPink = GirlGroup('블랙핑크', 4);
blackPink.sayName();
blackPink.sayMemCnt();
}
[결과]
저는 여자 아이돌 블랙핑크입니다.
블랙핑크 멤버는 4명입니다.
7. 제네릭 (Generic)
제네릭은 특정 변수의 타입을 하나의 타입으로만 사용하도록 정의 하는 것이 아닌 여러 자료형을 입력받도록 합니다.
[코드]
//인스턴스화 할 때 입력받을 타입을 T로 지정합니다.
class Cache<T> {
final T data;
Cache({
required this.data,
});
}
void main() {
final cache = Cache<List<int>>(data : [1, 2, 3],);
//제네릭에 입력된 값을 통해 data 변수의 타입이 자동으로 유추됩니다.
print(cache.data.reduce((value, element) => value + element));
}
[결과]
6
8. 스태틱 (Static)
static은 정적이라는 의미로, 변수와 메서드등 모든 속성이 클래스 자체에 귀속됩니다.
아래 코드에서는 3개의 Counter 객체가 생성되었으나, i변수가 static으로 선언되었기 때문에 각각의 객체가 생성자를 호출하는 과정에서 i변수의 값을 공유하게 됩니다.
this.i라는 키워드를 사용하지 않고 그냥 i 를 사용했는데, 이유는 this는 현재 인스턴스를 가리키는 키워드이기 때문에 클래스에 종속되는 static 변수에 접근할 때 사용하지 않는 것입니다.
[코드]
class Counter {
static int i = 0;
Counter() {
i++;
print(i++);
}
}
void main() {
Counter count1 = Counter();
Counter count2 = Counter();
Counter count3 = Counter();
}
[결과]
6
9. 캐스케이드 연산자 (Cascade Operator)
캐스케이드 연산자는 인스턴스에서 해당 인스턴스의 속성이나 멤버 함수를 연속해서 사용하는 기능입니다.
연산자 기호는 .. 을 사용합니다.
[코드]
class Idol {
final String name;
final int memCnt;
Idol(this.name, this.memCnt);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMemCnt() {
print('${this.name} 멤버는 ${this.memCnt}명입니다.');
}
}
void main() {
//cascade 연산자 ..를 사용하여 변수의 메서드를 연속으로 실행할 수 있습니다.
Idol blackPink = Idol('블랙핑크', 4)
..sayName()
..sayMemCnt();
}
[결과]
저는 블랙핑크입니다.
블랙핑크 멤버는 4명입니다.
여기까지 객체지향 프로그래밍 관련 다트 문법을 맛보기로 알아보았습니다.
다양한 쓰임이나 문법에 대해 세부 내용이 궁금하시다면 공식문서를 보시는게 낫습니다.😅
다음글은 3장 비동기 프로그래밍에 대해 정리해보겠습니다.👏
'Programming > Dart & Flutter' 카테고리의 다른 글
플러터 앱에 Firebase 연동하기 (0) | 2024.02.22 |
---|---|
[코드팩토리의 플러터 프로그래밍] 3장. 다트 비동기 프로그래밍 (0) | 2023.02.23 |
[코드팩토리의 플러터 프로그래밍] 1장. 다트(dart) 입문하기 (2) | 2023.02.20 |
[코드팩토리의 플러터 프로그래밍] 0장. 플러터(flutter) 개발 환경 구축 (0) | 2023.02.15 |