🏗️

Dart 클래스 & OOP

생성자 5종, 상속, 인터페이스, Mixin, 팩토리 패턴

Dart는 완전한 객체 지향 언어로 모든 것이 객체입니다. 기본, 이름 있는, 초기화 리스트, 상수, 팩토리까지 5가지 생성자를 제공하며, extends로 상속, implements로 인터페이스, with로 Mixin을 구현합니다. 팩토리 생성자는 fromJson 직렬화, 싱글톤, 조건별 하위 클래스 반환 등 실전에서 자주 사용됩니다.

1. 클래스 기본 구조

Dart에서 모든 것은 객체이며, 모든 객체는 클래스의 인스턴스입니다. 클래스는 필드, 생성자, 메서드, getter/setter로 구성됩니다.

class Person { String name; int age; Person(this.name, this.age); void introduce() { print('안녕하세요, 저는 $name이고 $age살입니다.'); } bool get isAdult => age >= 18; set setAge(int newAge) { if (newAge >= 0) { age = newAge; } }}

2. Private 멤버 (_ 언더스코어)

언더스코어(_)로 시작하는 멤버는 라이브러리(파일) 수준 private입니다. public getter로 안전하게 노출합니다.

class Person { String name; int _age; Person(this.name, this._age); int get age => _age; void _privateMethod() { print('내부에서만 호출 가능'); } void publicMethod() { _privateMethod(); }}

3. 생성자 5종

3-1. 기본 생성자 (shorthand)

this.name으로 파라미터를 자동 할당

Person(this.name, this.age);

3-2. 이름 있는 생성자 (Named Constructor)

같은 클래스에 여러 생성자를 만들 수 있습니다.

Person.guest() { name = '손님'; age = 0; } Person.child(this.name) { age = 10; } Person.adult(this.name) { age = 18; } // 사용: Person.guest(), Person.child('아이'), Person.adult('성인')

3-3. 초기화 리스트 (Initializer List)

생성자 본문 실행 전 : 뒤에서 필드 초기화. final 필드에 유용.

Person(this.name, this.age) : birthDate = DateTime.now().subtract(Duration(days: 365 * age)); Person.custom(String userName, int userAge) : name = userName.toUpperCase(), age = userAge > 0 ? userAge : 0, birthDate = DateTime.now().subtract(Duration(days: 365 * userAge));

3-4. 상수 생성자 (Const Constructor)

불변 객체. 동일 값의 const 인스턴스는 같은 객체를 참조.

class ImmutablePoint { final int x; final int y; const ImmutablePoint(this.x, this.y); } var p1 = const ImmutablePoint(1, 2); var p2 = const ImmutablePoint(1, 2); print(identical(p1, p2)); // true

3-5. 리다이렉팅 생성자 (Redirecting)

: this(...)로 다른 생성자에 위임.

Person.adult(String name) : this(name, 18); Person.child(String name) : this(name, 10); Person.fromJson(Map<String, dynamic> json) : this(json['name'] as String, json['age'] as int);

4. 생성자 비교 테이블

종류문법용도
기본Person(this.name)자동 초기화
이름 있는Person.guest()여러 초기화
초기화 리스트: birthDate = ...파생값/final
상수const Person()불변 객체
리다이렉팅: this(name, 18)생성자 위임
팩토리factory Logger(name)캐싱/싱글톤

5. 팩토리 생성자 (Factory Constructor)

5-1. 캐싱 — Logger

class Logger { final String name; static final Map<String, Logger> _cache = {}; Logger._internal(this.name); factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); } } final l1 = Logger('UI'); final l2 = Logger('UI'); print(identical(l1, l2)); // true

5-2. 하위 클래스 반환 — Shape

abstract class Shape { factory Shape(String type) { switch (type) { case 'circle': return Circle(10); case 'rectangle': return Rectangle(10, 20); default: throw ArgumentError('지원하지 않는 도형: $type'); } } double get area; } class Circle implements Shape { final double radius; Circle(this.radius); @override double get area => 3.14 * radius * radius; } class Rectangle implements Shape { final double width, height; Rectangle(this.width, this.height); @override double get area => width * height; }

5-3. fromJson — User

Flutter API 응답 모델에 필수 패턴.

class User { final String name; final int age; final String email; User(this.name, this.age, this.email); factory User.fromJson(Map<String, dynamic> json) { return User(json['name'] as String, json['age'] as int, json['email'] as String); } Map<String, dynamic> toJson() => {'name': name, 'age': age, 'email': email}; }

6. 정적 멤버 (Static)

class MathUtils { static const double PI = 3.14159; static int calculationCount = 0; static double square(double num) { calculationCount++; return num * num; } } print(MathUtils.PI); print(MathUtils.square(5)); // 25.0

7. 추상 클래스

abstract class Animal { String name; Animal(this.name); void makeSound(); void sleep() { print('$name is sleeping'); } } class Dog extends Animal { Dog(String name) : super(name); @override void makeSound() { print('$name says Woof!'); } }

8. 인터페이스 (implements)

Dart는 별도 interface 키워드 없이, 모든 클래스가 인터페이스. implements 시 모든 멤버 재정의 필수.

class Car implements Vehicle { @override void move() { print('Car is driving'); } @override void stop() { print('Car stopped'); } } Vehicle vehicle = Car(); vehicle.move(); // 다형성

9. 상속 (extends)

class Student extends Person { String school; Student(String name, int age, this.school) : super(name, age); @override void introduce() { super.introduce(); print('저는 $school에 다니고 있습니다.'); } }

10. Mixin (with)

깊은 상속 없이 기능 재사용. with로 다중 Mixin 적용.

mixin Swimming { void swim() { print('수영 중'); } } mixin Flying { void fly() { print('비행 중'); } } class Duck extends Animal with Swimming, Flying { Duck(String name) : super(name); } final duck = Duck('오리'); duck.swim(); duck.fly();

mixin CanFly on Bird — 특정 클래스에서만 사용 가능하도록 제한

11. extends vs implements vs with

키워드다중구현 상속용도
extends단일Ois-a 상속
implements다중X인터페이스
with다중O기능 조합

12. Named Parameters

class Person { String name; int age; String? address; Person({required this.name, required this.age, this.address}); } final p = Person(name: '홍길동', age: 30);

13. 연산자 오버로딩

class Vector { final double x, y; const Vector(this.x, this.y); Vector operator +(Vector o) => Vector(x + o.x, y + o.y); @override bool operator ==(Object o) { if (identical(this, o)) return true; return o is Vector && o.x == x && o.y == y; } @override int get hashCode => x.hashCode ^ y.hashCode; } print(Vector(1,2) + Vector(3,4)); // Vector(4,6) print(Vector(1,2) == Vector(1,2)); // true

핵심 정리

  • Private — _ 접두사, getter로 노출
  • 생성자 5종 — 기본/이름/초기화리스트/상수/리다이렉팅
  • 팩토리 — 캐싱, 하위클래스 반환, fromJson
  • 상속 — extends/implements/with
  • 연산자 오버로딩 — operator +/==

구현 순서

1

기본 생성자 — Person(this.name, this.age)로 필드를 자동 초기화하는 간결한 문법

2

팩토리 생성자 — factory 키워드로 캐싱/싱글톤/fromJson/조건별 하위 클래스 반환 구현

3

Mixin — with 키워드로 다중 기능 조합, class Duck extends Animal with Swimming, Flying

4

abstract class — 추상 메서드를 정의하고 하위 클래스에서 구현 강제, 인터페이스 역할도 겸함

장점

  • Mixin으로 다중 상속의 장점을 안전하게 활용 가능
  • 팩토리 생성자로 유연한 객체 생성 패턴 구현

단점

  • 생성자 종류가 5개라 초보자에게 선택이 어려울 수 있음

사용 사례

API 응답 모델에서 factory fromJson() 패턴 사용 Flutter 위젯에서 Mixin으로 공통 기능(애니메이션, 제스처 등) 재사용

참고 자료