객체지향 프로그래밍의 필요성
- 모든 코드를 main()함수에다가 정의하면 코드 조짐 : 유지보수 빡세고 가독성 최악
- 변수와 메서드를 특정 클래스에 종속되게 함으로서 객체지향 프로그래밍을 할 수 있음
- 클래스라는 설계도를 사용해서 데이터의 속성 , 기능을 정의하여 객체지향적으로 코드 작성 가ㅡㄴㅇ
클래스
- 일종의 설계도
// class 키워드를 입력 후 클래스명을 지정해 클래스를 선언합니다.
class Idol {
// ❶ 클래스에 종속되는 변수를 지정할 수 있습니다.
String name = '블랙핑크';
// ❷ 클래스에 종속되는 함수를 지정할 수 있습니다.
// 클래스에 종속되는 함수를 메서드라고 부릅니다.
void sayName() {
// ❸ 클래스 내부의 속성을 지칭하고 싶을 때는 this 키워드를 사용하면 됩니다.
// 결과적으로 this.name은 Idol 클래스의 name 변수를 지칭합니다.
print('저는 ${this.name}입니다.');
// ➍ 스코프 안에 같은 속성 이름이 하나만 존재한다면 this를 생략할 수 있습니다.
print('저는 $name입니다.');
}
}
void main() {
// 변수 타입을 Idol로 지정하고
// Idol의 인스턴스를 생성할 수 있습니다.
// 인스턴스를 생성할 때는 함수를 실행하는 것처럼
// 인스턴스화하고 싶은 클래스에 괄호를 열고 닫아줍니다.
Idol blackPink = Idol(); // ➊ Idol 인스턴스 생성
// 메서드를 실행합니다.
blackPink.sayName();
}
1. 생성자
- 클래스의 인스턴스를 만드는 메서드
- 파라미터를 받아서 클래스의 멤버 변수를 초기화 할 수 있음
- 보통 멤버 변수는 final로 정의하여 변수의 변경을 방지함
- 클래스 하나로 인스턴스를 여러개 만들어 코드 중복없이 활용이 가능해짐 - 유지보수가 편해짐
class Idol {
// ❶ 생성자에서 입력받는 변수들은 일반적으로 final 키워드 사용
final String name;
// ❷ 생성자 선언
// 클래스와 같은 이름이어야 합니다.
// 함수의 매개변수를 선언하는 것처럼 매개변수를 지정해줍니다.
Idol(String name) : this.name = name;
void sayName() {
print('저는 ${this.name}입니다.');
}
}
void main() {
// name에 '블랙핑크' 저장
Idol blackPink = Idol('블랙핑크');
blackPink.sayName();
// name에 'BTS' 저장
Idol bts = Idol('BTS');
bts.sayName();
}
2. 네임드 생성자
- 클래스를 생성하는 방법을 여러가지 정의하고 싶을 때 사용
- 딱히 기본생성자랑 큰 틀에 차이는 없는 듯
class Idol {
final String name;
final int membersCount;
// ❶ 생성자
Idol(String name, int membersCount)
// 1개 이상의 변수를 저장하고 싶을 때는 , 기호로 연결해주면 됩니다.
: this.name = name,
this.membersCount = membersCount;
// ❷ 네임드 생성자
// {클래스명.네임드 생성자명} 형식
// 나머지 과정은 기본 생성자와 같습니다.
Idol.fromMap(Map<String, dynamic> map)
: this.name = map['name'],
this.membersCount = map['membersCount'];
void sayName() {
print('저는 ${this.name}입니다. ${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
void main() {
// 기본 생성자 사용
Idol blackPink = Idol('블랙핑크', 4);
blackPink.sayName();
// fromMap이라는 네임드 생성자 사용
Idol bts = Idol.fromMap({
'name': 'BTS',
'membersCount': 7,
});
bts.sayName();
}
3. 프라이빗 변수
- 같은 파일에서만 접근 가능한 변수
- '_' 로 시작하는 변수
class Idol {
// ❶ '_'로 변수명을 시작하면
// 프라이빗 변수를 선언할 수 있습니다.
String _name;
Idol(this._name);
}
void main() {
Idol blackPink = Idol('블랙핑크');
// 같은 파일에서는 _name 변수에 접근할 수 있지만
// 다른 파일에서는 _name 변수에 접근할 수 없습니다.
print(blackPink._name);
}
4. 게터 / 세터
- 값을 가져오고 값을 세팅하는 함수
- 보통 객체지향 프로그래밍을 할 때 변수에 불변성을 지정하기에 세터는 잘 안 씀
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 = '에이핑크'; // ❶ 세터
print(blackPink.name); // ❷ 게터
}
상속
- extends를 사용해서 상속 가능
- 상속받은 클래스의 변수나 메서드를 사용할 수 있음
- 상속받으면 되니까 중복 코드가 없어짐
class Idol {
final String name;
final int membersCount;
Idol(this.name, this.membersCount);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
// ❶ extends 키워드를 사용해서 상속받습니다.
// class 자식 클래스 extends 부모 클래스 순서입니다.
class BoyGroup extends Idol {
// ❷ 상속받은 생성자
BoyGroup(
String name,
int membersCount,
) : super( // super는 부모 클래스를 지칭합니다.
name,
membersCount,
);
// ❸ 상속받지 않은 기능
void sayMale() {
print('저는 남자 아이돌입니다.');
}
}
void main() {
BoyGroup bts = BoyGroup('BTS', 7); // 생성자로 객체 생성
bts.sayName(); // ❶ 부모한테 물려받은 메서드
bts.sayMembersCount(); // ❷ 부모한테 물려받은 메서드
bts.sayMale(); // ❸ 자식이 새로 추가한 메서드
}
오버라이드
- 부모 클래스 또는 인터페이스에 정의된 메서드를 재정의할 때 사용
- ovveride는 생략 가능
class Idol {
final String name;
final int membersCount;
Idol(this.name, this.membersCount);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
class GirlGroup extends Idol {
// 2.3 상속에서처럼 super 키워드를 사용해도 되고 다음처럼 생성자의 매개변수로 직접 super 키워드를 사용해도 됩니다.
GirlGroup(
super.name,
super.membersCount,
);
// ❶ override 키워드를 사용해 오버라이드 합니다.
@override
void sayName() {
print('저는 여자 아이돌 ${this.name}입니다.');
}
}
void main() {
GirlGroup redVelvet = GirlGroup('블랙핑크', 4);
redVelvet.sayName(); // ❶ 자식 클래스의 오버라이드된 메서드 사용
// sayMembersCount는 오버라이드하지 않았기 때문에
// 그대로 Idol 클래스의 메서드가 실행됩니다.
redVelvet.sayMembersCount(); // ❷ 부모 클래스의 메서드 사용
}
인터페이스
- 공통으로 필요한 기능을 정의
- 따로 인터페이스라고 선언하는 문구는 없음
- 인터페이스는 implements를 사용해서 다수 적용이 가능
- 인터페이스로 받은 메서드를 반드시 다 재정의 해줘야함 ! - 원래 목적자체가 재정의할 필요가 있는 기능을 정의하는 용도임
class Idol {
final String name;
final int membersCount;
Idol(this.name, this.membersCount);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
// ❶ implements 키워드를 사용하면 원하는 클래스를 인터페이스로 사용할 수 있습니다.
class GirlGroup implements Idol {
final String name;
final int membersCount;
GirlGroup(
this.name,
this.membersCount,
);
void sayName() {
print('저는 여자 아이돌 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
void main() {
GirlGroup redVelvet = GirlGroup('블랙핑크', 4);
redVelvet.sayName();
redVelvet.sayMembersCount();
}
믹스 인
- 특정 클래스에 원하는 기능들만 골라 넣을 수 있는 기능
- 특정 클래스의 속성을 정의
- 여러개의 믹스인 정의 가능 : with
- 클래스에 다른 클래스 기능을 추가할 수 있음 - 다중상속이 안 되니까 믹스인으로 코드 모듈화 및 재사용 가능
class Idol {
final String name;
final int membersCount;
Idol(this.name, this.membersCount);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
mixin IdolSingMixin on Idol{
void sing(){
print('${this.name}이 노래를 부릅니다.');
}
}
// 믹스인을 적용할 때는 with 키워드 사용
class BoyGroup extends Idol with IdolSingMixin{
BoyGroup(
super.name,
super.membersCount,
);
void sayMale() {
print('저는 남자 아이돌입니다.');
}
}
void main(){
BoyGroup bts = BoyGroup('BTS', 7);
// 믹스인에 정의된 dance() 함수와 sing() 함수 사용 가능
bts.sing();
}
추상
- 상속이나 인터페이스로 사용하는데 필요한 속성만 정의하고 인스턴스는 못 만드는 놈
- 추상 메서드 선언 가능
- 오히려 쓰는 거는 추상처럼 기능 정의할 거만 만들어두고 상속해서 기능을 재정의하는 쪽일 듯
// ❶ abstract 키워드를 사용해 추상 클래스 지정
abstract class Idol {
final String name;
final int membersCount;
Idol(this.name, this.membersCount); // ❷ 생성자 선언
void sayName(); // ❸ 추상 메서드 선언
void sayMembersCount(); // ➍ 추상 메서드 선언
}
// implements 키워드를 사용해 추상 클래스를 구현하는 클래스
class GirlGroup implements Idol {
final String name;
final int membersCount;
GirlGroup(
this.name,
this.membersCount,
);
void sayName() {
print('저는 여자 아이돌 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
void main() {
GirlGroup redVelvet = GirlGroup('블랙핑크', 4);
redVelvet.sayName();
redVelvet.sayMembersCount();
}
제너릭
- 클래스나 함수를 선언할 때가 아니라 인스턴스화하거나 실행할 때 동작
- 특정 변수 타입을 하나의 타입으로 제한하고 싶지 않을 때 씀
- 제너릭을 통해서 파라미터로 받는 타입별로 메서드를 만드는 것이 아니라 하나의 메서드로 통일이 가능해짐
// 인스턴스화할 때 입력받을 타입을 T로 지정합니다.
class Cache<T> {
// data의 타입을 추후 입력될 T 타입으로 지정합니다.
final T data;
Cache({
required this.data,
});
}
void main() {
// T의 타입을 List<int>로 입력합니다.
final cache = Cache<List<int>>(
data: [1,2,3],
);
// 제네릭에 입력된 값을통해 data 변수의 타입이 자동으로 유추됩니다.
// reduce() 함수가 기억나지 않는다면 1.3.1절 List타입의 reduce() 함수를 복습해보세요.
print(cache.data.reduce((value, element) => value + element));
}
스태틱
- 변수와 메서드 등 보통 클래스의 인스턴스에 귀속되어 있음
- static으로 정의하면 클래스에 귀속됨
- 인스턴스마다 해당 값을 공유하게 됨
class Counter{
// ❶ static 키워드를 사용해서 static 변수 선언
static int i= 0;
// ❷ static 키워드를 사용해서 static 변수 선언
Counter(){
i++;
print(i++);
}
}
void main() {
Counter count1 = Counter();
Counter count2 = Counter();
Counter count3 = Counter();
}
1
3
5
케스케이드 연산자
- 해당 인스턴스의 속성이나 멤버 변수를 연속해서 사용하는 기능
- .. 라는 기호사용
- 코드가 간결해짐
void main(){
Idol blackpink = Idol('blackpink',4)
..sayName()
..sayMembersCount();
}
class Idol {
final String name;
final int membersCount;
Idol(this.name, this.membersCount);
void sayName() {
print('저는 ${this.name}입니다.');
}
void sayMembersCount() {
print('${this.name} 멤버는 ${this.membersCount}명입니다.');
}
}
저는 blackpink입니다.
blackpink 멤버는 4명입니다.
참고
“이 글은 골든래빗 《Must Have 코드팩토리의 플러터 프로그래밍 2판》의 스터디 내용 입니다.”
'프로그래밍 > 플러터' 카테고리의 다른 글
Getters and Setters (0) | 2024.02.18 |
---|---|
다트 비동기 프로그래밍 (1) | 2024.02.10 |
다트 기본 문법 [변수 , 컬랙션, 람다] (1) | 2024.02.10 |
플러터 안드로이드 스튜디오 세팅 (0) | 2024.01.27 |
플러터 macOS 환경설정 (0) | 2024.01.27 |
하고 싶은 걸 하고 되고 싶은 사람이 되자!
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!