Dart 객체지향 — 클래스, mixin, extension
Dart 객체지향 — 클래스, mixin, extension
Dart는 순수 객체지향 언어입니다. 모든 값이 객체이고, 함수도 객체입니다. Flutter 위젯 자체가 클래스이기 때문에 OOP 개념은 반드시 익혀야 합니다.
클래스 기본
class User {
// 필드
final String name;
final int age;
// 생성자
const User({required this.name, required this.age});
// Named 생성자
User.guest() : name = '게스트', age = 0;
// Factory 생성자 (캐싱, 서브타입 반환 등에 활용)
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
age: json['age'] as int,
);
}
// 메서드
String introduce() => '$name ($age세)';
// toString 오버라이드
@override
String toString() => 'User(name: $name, age: $age)';
}
생성자 종류 정리
면접에서 factory 생성자를 왜 쓰는지 물어보는 경우가 많습니다.
| 종류 | 용도 | 특징 |
|---|---|---|
| 기본 생성자 | 일반적인 인스턴스 생성 | this.field로 간결하게 |
| Named 생성자 | 다양한 생성 방법 제공 | User.guest() |
| Factory 생성자 | 캐싱, 서브타입 반환 | factory 키워드, this 접근 불가 |
| Const 생성자 | 컴파일타임 상수 객체 | 모든 필드가 final이어야 함 |
상속
// 부모 클래스
class Animal {
final String name;
const Animal(this.name);
void speak() => print('...');
}
// 자식 클래스
class Dog extends Animal {
final String breed;
const Dog(super.name, this.breed);
@override
void speak() => print('멍멍!');
}
// 사용
final dog = Dog('바둑이', '진돗개');
dog.speak(); // 멍멍!
Dart는 단일 상속 만 지원합니다. 다중 상속이 필요하면 mixin을 사용합니다.
추상 클래스와 인터페이스
// 추상 클래스: 직접 인스턴스화 불가
abstract class Shape {
double get area; // 추상 getter
void describe() => print('넓이: $area');
}
class Circle extends Shape {
final double radius;
const Circle(this.radius);
@override
double get area => 3.14159 * radius * radius;
}
// Dart에서는 모든 클래스가 암시적 인터페이스
// implements로 인터페이스 구현
class Printable {
void printInfo() => print('정보 출력');
}
class Report implements Printable {
@override
void printInfo() => print('리포트 출력'); // 반드시 구현
}
extends vs implements vs with
| 키워드 | 용도 | 구현 의무 |
|---|---|---|
extends | 상속 (1개만) | 선택적 오버라이드 |
implements | 인터페이스 구현 (여러 개) | 모든 메서드 구현 필수 |
with | mixin 적용 (여러 개) | 선택적 오버라이드 |
Mixin
mixin은 여러 클래스에 기능을 주입하는 방법입니다. 다중 상속의 문제(다이아몬드 문제) 없이 코드를 재사용할 수 있습니다.
// mixin 선언
mixin Flyable {
void fly() => print('날고 있습니다!');
}
mixin Swimmable {
void swim() => print('수영하고 있습니다!');
}
// mixin 적용
class Duck extends Animal with Flyable, Swimmable {
const Duck(super.name);
@override
void speak() => print('꽥꽥!');
}
final duck = Duck('도널드');
duck.fly(); // 날고 있습니다!
duck.swim(); // 수영하고 있습니다!
duck.speak(); // 꽥꽥!
mixin에 제약 걸기
// Animal을 상속한 클래스에만 적용 가능
mixin Trainable on Animal {
void train() => print('$name을(를) 훈련합니다');
}
class Dog2 extends Animal with Trainable {
const Dog2(super.name);
@override
void speak() => print('멍!');
}
Extension Methods
기존 클래스를 수정하지 않고 메서드를 추가할 수 있습니다. 특히 String, int 같은 기본 타입에 유틸리티 메서드를 추가할 때 유용합니다.
// String에 메서드 추가
extension StringExtension on String {
// 첫 글자를 대문자로
String get capitalize =>
isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
// 이메일 형식 검증
bool get isValidEmail =>
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this);
}
// 사용
print('flutter'.capitalize); // Flutter
print('test@email.com'.isValidEmail); // true
// int에 메서드 추가
extension IntExtension on int {
Duration get seconds => Duration(seconds: this);
Duration get minutes => Duration(minutes: this);
}
// 사용
await Future.delayed(3.seconds); // 3초 대기
Sealed Class (Dart 3+)
sealed class는 패턴 매칭에서 exhaustive check를 가능하게 합니다.
sealed class Result<T> {}
class Success<T> extends Result<T> {
final T data;
const Success(this.data);
}
class Failure<T> extends Result<T> {
final String message;
const Failure(this.message);
}
// switch에서 모든 케이스를 강제로 처리
String handleResult(Result<String> result) => switch (result) {
Success(:final data) => '성공: $data',
Failure(:final message) => '실패: $message',
// 케이스를 빼먹으면 컴파일 에러!
};
정리
- Dart는 단일 상속 + mixin으로 코드 재사용성을 높입니다
extendsvsimplementsvswith의 차이를 명확히 알아두세요- Extension methods로 기존 타입을 깔끔하게 확장할 수 있습니다
- Sealed class + 패턴 매칭은 상태 관리에서 매우 유용합니다
- Factory 생성자의 활용 이유는 면접 단골 질문입니다
댓글 로딩 중...