Dart 객체지향 — 클래스, mixin, extension

Dart는 순수 객체지향 언어입니다. 모든 값이 객체이고, 함수도 객체입니다. Flutter 위젯 자체가 클래스이기 때문에 OOP 개념은 반드시 익혀야 합니다.


클래스 기본

DART
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이어야 함

상속

DART
// 부모 클래스
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을 사용합니다.


추상 클래스와 인터페이스

DART
// 추상 클래스: 직접 인스턴스화 불가
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인터페이스 구현 (여러 개)모든 메서드 구현 필수
withmixin 적용 (여러 개)선택적 오버라이드

Mixin

mixin은 여러 클래스에 기능을 주입하는 방법입니다. 다중 상속의 문제(다이아몬드 문제) 없이 코드를 재사용할 수 있습니다.

DART
// 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에 제약 걸기

DART
// 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 같은 기본 타입에 유틸리티 메서드를 추가할 때 유용합니다.

DART
// 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를 가능하게 합니다.

DART
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으로 코드 재사용성을 높입니다
  • extends vs implements vs with의 차이를 명확히 알아두세요
  • Extension methods로 기존 타입을 깔끔하게 확장할 수 있습니다
  • Sealed class + 패턴 매칭은 상태 관리에서 매우 유용합니다
  • Factory 생성자의 활용 이유는 면접 단골 질문입니다
댓글 로딩 중...