iBetter Books
수정

클래스와 OOP

Dart의 클래스는 Java나 Kotlin과 비슷하지만, 믹스인(mixin), 확장 메서드(extension), sealed class 같은 현대적 기능이 추가됩니다. 실무에서 자주 쓰는 패턴 위주로 핵심만 압축합니다.


클래스 기본과 생성자

class Person {
  final String name;
  int age;

  // 기본 생성자
  Person(this.name, this.age);

  // 명명된 생성자
  Person.anonymous() : name = 'Unknown', age = 0;

  // 팩토리 생성자 (캐싱, 서브타입 반환 가능)
  factory Person.fromJson(Map<String, dynamic> json) {
    return Person(json['name'] as String, json['age'] as int);
  }

  // 메서드
  String greet() => 'Hi, I am $name.';

  // toString 오버라이드
  @override
  String toString() => 'Person(name: $name, age: $age)';
}

void main() {
  final p1 = Person('Alice', 30);
  final p2 = Person.anonymous();
  final p3 = Person.fromJson({'name': 'Bob', 'age': 25});
  print(p1.greet());  // Hi, I am Alice.
}

초기화 리스트와 const 생성자

class Point {
  final double x;
  final double y;

  // 초기화 리스트: 생성자 본문 실행 전 필드 초기화
  const Point(this.x, this.y);

  // const 생성자: 컴파일 타임 상수 인스턴스 생성 가능
  static const origin = Point(0, 0);

  double distanceTo(Point other) {
    final dx = x - other.x;
    final dy = y - other.y;
    return (dx * dx + dy * dy);  // sqrt 생략
  }

  @override
  String toString() => 'Point($x, $y)';
}

const p = Point(3, 4);

상속

class Animal {
  final String name;
  Animal(this.name);

  void speak() => print('$name makes a sound.');
}

class Dog extends Animal {
  final String breed;

  Dog(super.name, this.breed);  // super 매개변수 (Dart 2.17+)

  @override
  void speak() => print('$name barks!');

  void fetch() => print('$name fetches the ball.');
}

class GuideDog extends Dog {
  GuideDog(super.name, super.breed);

  @override
  void speak() {
    super.speak();
    print('$name is a guide dog.');
  }
}

추상 클래스와 인터페이스

// 추상 클래스: 공통 인터페이스 + 부분 구현
abstract class Shape {
  double area();     // 추상 메서드
  double perimeter();

  // 구체 메서드
  bool isLargerThan(Shape other) => area() > other.area();
}

class Circle extends Shape {
  final double radius;
  Circle(this.radius);

  @override
  double area() => 3.14159 * radius * radius;

  @override
  double perimeter() => 2 * 3.14159 * radius;
}

// Dart의 모든 클래스는 암묵적으로 인터페이스
// implements로 인터페이스처럼 사용
class MockShape implements Shape {
  @override
  double area() => 0;
  @override
  double perimeter() => 0;
  @override
  bool isLargerThan(Shape other) => false;
}

믹스인 (Mixin)

다중 상속 없이 여러 클래스의 기능을 조합합니다.

mixin Flyable {
  void fly() => print('$runtimeType is flying!');
  int get altitude => 1000;
}

mixin Swimmable {
  void swim() => print('$runtimeType is swimming!');
}

class Bird with Flyable {}

class Duck extends Animal with Flyable, Swimmable {
  Duck() : super('Duck');
}

void main() {
  final duck = Duck();
  duck.fly();   // Duck is flying!
  duck.swim();  // Duck is swimming!
}

믹스인에 제약을 걸 수 있습니다.

mixin Logger on Animal {
  void log(String msg) => print('[$name] $msg');
}

// Animal 또는 Animal 하위 클래스에만 적용 가능
class VerboseDog extends Animal with Logger {
  VerboseDog(super.name);
  void bark() {
    log('Woof!');
  }
}

열거형 (Enum)

// 기본 열거형
enum Direction { north, south, east, west }

// 고급 열거형 (Dart 2.17+)
enum Planet {
  mercury(3.7),
  venus(8.87),
  earth(9.81);

  final double gravity;
  const Planet(this.gravity);

  String get description => '${name}: ${gravity}m/s²';
}

void main() {
  final d = Direction.north;
  print(d.name);   // north
  print(d.index);  // 0

  // switch와 함께 (exhaustive 체크)
  switch (d) {
    case Direction.north:
      print('Going north');
    case Direction.south:
      print('Going south');
    case Direction.east || Direction.west:
      print('Going east or west');
  }

  print(Planet.earth.description);  // earth: 9.81m/s²
}

확장 메서드 (Extension)

기존 클래스에 메서드를 추가합니다. 클래스 수정 없이 기능을 확장합니다.

extension StringUtils on String {
  bool get isEmail => contains('@') && contains('.');
  String truncate(int maxLength) {
    if (length <= maxLength) return this;
    return '${substring(0, maxLength)}...';
  }
  String get capitalize =>
      isEmpty ? this : '${this[0].toUpperCase()}${substring(1)}';
}

extension ListUtils<T> on List<T> {
  T? get secondOrNull => length >= 2 ? this[1] : null;
  List<List<T>> chunked(int size) {
    final result = <List<T>>[];
    for (var i = 0; i < length; i += size) {
      result.add(sublist(i, (i + size).clamp(0, length)));
    }
    return result;
  }
}

void main() {
  print('[email protected]'.isEmail);    // true
  print('Hello World'.truncate(5));  // Hello...
  print([1, 2, 3, 4, 5].chunked(2)); // [[1,2],[3,4],[5]]
}

정리

핵심 요점만 정리합니다.

  • factory 생성자는 캐싱, JSON 파싱, 서브타입 반환에 유용합니다.
  • abstract class는 공통 인터페이스와 부분 구현을, implements는 순수 인터페이스 계약을 표현합니다.
  • mixin은 다중 상속 없이 여러 기능을 조합할 때 사용합니다.
  • 고급 enum은 값과 메서드를 함께 가질 수 있어 상태 표현에 강력합니다.
  • extension으로 기존 클래스를 수정 없이 확장합니다.