코드 팩토리의 플러터 프로그래밍 - 다트 언어 마스터하기, 2장 다트(Dart) 객체지향 프로그래밍
"이 글은 골든래빗 《코드 팩토리의 플러터 프로그래밍》의 02장 써머리입니다."
객체지향 프로그래밍의 필요성
- 적절한 추상수준을 유지하며 소스코드의 유지보수를 용이하게 하는 것
main()
함수에 모든 행위를 기술하지 않는다.
- 기본적으로는 변수와 메서드를 클래스에 종속되도록 코딩하여 유지보수를 용이하게 한다.
- 클래스를 만들어 데이터가 가질 값과 기능을 설계한다.
클래스
- 클래스에 속한 함수는 메서드라 부름
- 함수는 메서드를 포함하는 더 큰 개념
- 클래스 내부 멤버 변수나 메서드에 접근할 때는
this
키워드 이용
- [[001.다트-클래스]]
- 다트(Dart) 언어의 클래스(Class) 정리
class Car {
// 멤버
String name = "sonata";
// 메서드
void printName() {
// this 는 함수 내부에 같은 이름의 변수가 없다면 생략 가능하다.
print('This car is ${this.name}');
}
}
void main() {
Car sonata = Car(); // 인스턴스 생성
sonata.printName();
print(sonata.name);
}
생성자
- 클래스의 인스턴스를 생성하는 메서드
- 클래스와 같은 이름을 가진 메서드
- [[004.다트-생성자(constructors)]]
- 다트 (Dart) 언어의 생성자 (Constructors) 정리
class Car {
// 값을 변경할지 안할지 모르는 변수는 일단 final 키워드를 붙여준다.
final String name;
// 생성자
// 클래스와 같은 이름이어야 한다.
// 함수의 매개변수를 선언하는 것처럼 매개변수를 지정해준다.
Car(String name) : this.name = name;
void printName() {
print('This car is ${this.name}');
}
}
void main() {
Car sonata = Car('sonata');
Car k5 = Car('k5');
sonata.printName();
k5.printName();
}
네임드 생성자
- 네임드 파라미터와 비슷한 개념
- 클래스를 생성하는 방법을 여러개 사용하고 싶을 때 사용
Class.fromMap()
과 같은 네임드 생성자 기법을 이용하면, 멤버 변수가 많아 순서가 헷갈릴 때나 모든 멤버를 설정할 필요 없을 때 큰 이득을 볼 수 있음- 생성자에서 멤버 변수를 여러개 설정할 땐
,
키워드 이용
[[004.다트-생성자(constructors)#명명된 생성자 (Named constructors)]]
class Car {
final String name;
final int price;
Car(String name, int price)
: this.name = name,
this.price = price;
Car.fromMap(Map<String, dynamic> map)
: this.name = map['name'],
this.price = map['price'];
void printName() {
print('This car is ${this.name} and the price is ${this.price}');
}
}
void main() {
Car sonata = Car('sonata', 3000);
sonata.printName();
Car k5 = Car.fromMap({
'name': 'k5',
'price': 2500
});
k5.printName();
}
프라이빗 (Private) 변수
_
로 시작하는 변수명을 지으면 프라이빗 변수 설정이 가능함- Dart 언어의 프라이빗 변수는 다른 언어와 다르게 같은 파일 내에서만 접근이 가능함
- 자바에서의 프라이빗 변수는 클래스 내부에서만 사용하고 외부 클래스에서 접근할 수 없음
class Car {
String _name;
Car(this._name);
}
void main() {
Car sonata = Car('sonata');
// 다른 파일에서는 접근 불가능.
print(sonata._name);
}
게터(Getter) / 세터(Setter)
- 객체지향 프로그래밍에서 보통 클래스 멤버 변수 값을 가져오거나 설정하는 부분이 외부로 노출될지 결정하기 위해 사용한다.
- 요즘엔 객체지향 프로그래밍에서 유지보수를 위해 불변성을 많이 이용하기 때문에 세터를 많이 사용하지 않는다.
final
을 이용해 불변 멤버를 최대한 활용하고 진짜 필요할 때만 일반 멤버를 생성하는 것이 좋다.- 자바에서도 비슷한 문제로 고통을 겪는 경우가 많다. [[04.lombok-주의점]] 에서
@Setter
사용 주의 부분이 그렇다.
get
과set
키워드가 있는 것도 특이하다.- 보통은
void setName() { ... }
같은 식으로 메서드를 구성하는데 키워드가 있으니 좀 더 명확하고 언어적으로 접근해 특수한 기능을 넣기도 좋을 것 같다.
- 보통은
class Car {
String _name = 'car';
String get name {
return this._name;
}
// final 이 아니어야 setter 가 의미가 있음
set name(String name) {
this._name = name;
}
}
void main() {
Car sonata = Car();
sonata.name = 'sonata'; // setter 로 설정, 'car' -> 'sonata'
print(sonata.name); // getter 로 접근
}
상속 (Externds, Inheritance)
- 하위 클래스에게 멤버 변수와 메서드를 물려줌
- 단 하나의 클래스만 상속 가능함
super()
생성자를 통해 부모 클래스의 멤버에 값이 설정되는 것이 강제됨- 그래야만 부모 클래스의 모든 메서드나 멤버 변수를 정상적으로 이용할 수 있기 때문임
class Car {
final String _name;
String get name {
return this._name;
}
Car(this._name);
void printCar() {
print('Car');
}
}
class Sonata extends Car {
Sonata(
String name
) : super (
name
);
void printSonata() {
print('$name Sonata');
}
}
void main() {
Sonata sonata = Sonata('LF');
sonata.printSonata();
sonata.printCar();
}
오버라이드
@override
, 부모 클래스에 정의된 메서드를 자식 클래스의 방식으로 재정의할 때 쓰는 키워드- 안써도 동작에는 무관하나 유지보수를 위해 써주는 것이 좋음
class Car {
final String _name;
String get name {
return this._name;
}
Car(this._name);
void printCar() {
print('Car');
}
}
class Sonata extends Car {
Sonata(
String name
) : super (
name
);
void printSonata() {
print('$name Sonata');
}
@override
void printCar() {
print('I am $name Sonata');
}
}
void main() {
Sonata sonata = Sonata('LF');
sonata.printSonata();
sonata.printCar(); // 오버라이드된 메서드 호출
}
인터페이스
- 공통 기능을 정의
- 상속과 다르개 몇개든 한 클래스에 몇개의 인터페이스든 구현(
implements
) 가능하다. - 정의한 클래스를
implements
키워드를 통해 구현하면 된다.
class Car {
final String _name;
String get name {
return this._name;
}
Car(this._name);
void printCar() {
print('Car');
}
}
class Printer {
void printItsNameTwice() {
}
}
class Random {
void printAnything() {
}
}
class Sonata extends Car implements Printer, Random {
Sonata(
String name
) : super (
name
);
void printSonata() {
print('$name Sonata');
}
@override
void printCar() {
print('I am $name Sonata');
}
void printItsNameTwice() {
print('$name$name');
}
void printAnything() {
print('Anything about sonata');
}
}
void main() {
Sonata sonata = Sonata('LF');
sonata.printItsNameTwice();
sonata.printAnything();
}
믹스인 (Mixins)
- 보통 다른 객체지향 언어에서도 클래스에 원하는 기능만 덧붙일 수 있는 개념으로 사용된다.
- [[01.mixin-and-include]]
- SCSS 의 믹스인
- [[what-is-mixin]]
- 자바의 믹스인
mixin
키워드를 따로 제공한다.- 뒤에
on 클래스명
으로 어떤 클래스에 대한 믹스인인지 명시하면 된다.
- 뒤에
mixin RearCamera on Car {
void turnOnRearCamera() {
print('$name Turn on Rear camera');
}
void turnOffRearCamera() {
print('$name Turn off Rear camera');
}
}
mixin AutoPilot on Car {
void turnOnAutoPilot() {
print('$name Turn on Auto pilot');
}
void turnOffAutoPilot() {
print('$name Turn off Auto pilot');
}
}
class Car {
final String _name;
String get name {
return this._name;
}
Car(this._name);
void printCar() {
print('Car');
}
}
class Sonata extends Car with RearCamera, AutoPilot{
Sonata(
String name
) : super (
name
);
void printSonata() {
print('$name Sonata');
}
}
void main() {
Sonata sonata = Sonata('LF');
sonata.turnOnRearCamera();
sonata.turnOnAutoPilot();
}
추상 (Abstract)
- 상속이나 인터페이스 전용 클래스를 생성하는 개념
- 사용할 멤버 변수와 메서드 시그니쳐까지만 이용된다.
- 어떤 클래스들의 추상적인 공통 설계도 목적으로 이용할 수 있다.
- 인스턴스화 할 수 없다
- 구현하는 곳에서 멤버 변수까지 그대로 다시 재작성해주어야 함
abstract class Car {
final String name;
final int capacity; // engine displacement
Car(this.name, this.capacity);
void printName(); // 구현 필요 없음
void printCapacity(); // 구현 필요 없음
}
class Sonata implements Car {
final String name;
final int capacity;
Sonata(
this.name,
this.capacity
);
void printName() {
print('this sonata is $name');
}
void printCapacity() {
print('this sonata capacity is ${capacity}cc');
}
}
void main() {
Sonata sonata = Sonata('LF', 1999);
sonata.printName();
sonata.printCapacity();
}
제네릭 (Generic)
- 클래스나 함수가 사용하는 타입을 정의하는 타이밍을 인스턴스화하거나 실행할 때로 미룬다.
List
,Map
,Set
등의 예시가 있다.
class Cache<T> {
// 인스턴스화 될 때까지 알 수 없음
final T data;
Cache({
required this.data
});
}
void main() {
final cache = Cache<List<int>>(
data: [1, 2, 3]
);
print(cache.data.reduce((value, element) => value + element));
}
제네릭에서 자주 이용되는 1글자 알파벳
T
: 변수의 타입 (Type)E
: 리스트 내부의 요소 (Element)K
: 키 (Key)V
: 값 (Value)
스태틱 (Static)
- 클래스의 인스턴스에 귀속되지 않고 클래스 자체에 귀속됨
- 인스턴스끼리 공유해야 하는 값이 있을 때 용이
class Counter {
static int i = 0;
// int i = 0; 같은 이름의 멤버를 할당하면 에러난다.
Counter() {
i++;
print(i);
}
}
void main() {
Counter count1 = Counter();
Counter count2 = Counter();
Counter count3 = Counter();
}
캐스케이드 연산자
- 인스턴스의 멤버 변수나 메서드를 연속해서 사용하는 기능
- 조금 더 간결한 코드 작성 가능
class Counter {
int i = 0;
void increment() {
i++;
print('now counter is $i');
}
}
void main() {
Counter counter = Counter()
..increment()
..increment()
..increment()
..increment();
}
반응형
'코드팩토리의 플러터 프로그래밍' 카테고리의 다른 글
코드 팩토리의 플러터 프로그래밍 - 플러터 기본 다지기, 4장 플러터 입문하기 (1) | 2023.10.29 |
---|---|
코드 팩토리의 플러터 프로그래밍 - 다트 언어 마스터하기, 3장 다트(Dart) 비동기 프로그래밍 (0) | 2023.10.21 |
코드 팩토리의 플러터 프로그래밍 - 다트 언어 마스터하기, 1장 다트 (Dart) 입문하기 (0) | 2023.10.08 |