🔌

Dart Extension

既存クラスにメソッド/プロパティを追加

Extensionは元のクラスを修正や継承せずにメソッド、プロパティ、演算子を追加できる機能です。Stringにcapitalize、intにisPrime、Listにdistinctのようなユーティリティを型に直接接続でき、コード可読性が大幅に向上します。ジェネリクスもサポートし、Flutterでのウィジェット拡張に非常に有用です。

Extension 기본 문법

Extension은 기존 클래스의 소스 코드를 수정하거나 상속하지 않고도 새로운 메서드, getter, setter, 연산자를 추가할 수 있는 기능입니다.

extension <ExtensionName> on <TargetType> { // methods, getters, setters, operators }

String 확장 예제

첫 글자 대문자 변환, 이메일 검증, 반복, 타이틀 케이스 등 문자열 유틸리티를 추가합니다.

extension StringExtension on String { String get capitalize => isNotEmpty ? '${this[0].toUpperCase()}${substring(1)}' : ''; bool get isValidEmail => RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(this); String repeat(int n) => List.filled(n, this).join(); String toTitleCase() { return split(' ') .map((word) => word.isNotEmpty ? '${word[0].toUpperCase()}${word.substring(1)}' : '') .join(' '); } } // 사용 예시 String name = 'john doe'; print(name.capitalize); // John doe print(name.toTitleCase()); // John Doe print('hello'.repeat(3)); // hellohellohello print('test@example.com'.isValidEmail); // true

int 확장 예제

초 단위를 시:분:초로 변환, 소수 판별, 팩토리얼 계산 등 정수에 수학 연산을 추가합니다.

extension IntExtension on int { String toTimeString() { int h = this ~/ 3600; int m = (this % 3600) ~/ 60; int s = this % 60; return '${h.toString().padLeft(2, '0')}:' '${m.toString().padLeft(2, '0')}:' '${s.toString().padLeft(2, '0')}'; } bool get isPrime { if (this <= 1) return false; if (this <= 3) return true; if (this % 2 == 0 || this % 3 == 0) return false; int i = 5; while (i * i <= this) { if (this % i == 0 || this % (i + 2) == 0) return false; i += 6; } return true; } int get factorial { if (this < 0) throw ArgumentError('음수의 팩토리얼은 정의되지 않습니다.'); if (this <= 1) return 1; return this * (this - 1).factorial; } } // 사용 예시 print(3665.toTimeString()); // 01:01:05 print(7.isPrime); // true print(5.factorial); // 120

List<T> 제네릭 확장 예제

제네릭 타입 파라미터를 사용하여 모든 리스트 타입에 적용 가능한 유틸리티를 만듭니다.

extension ListExtension<T> on List<T> { T? get firstOrNull => isEmpty ? null : first; T? get lastOrNull => isEmpty ? null : last; List<T> get distinct => toSet().toList(); List<List<T>> chunk(int size) { return List.generate( (length / size).ceil(), (i) => sublist( i * size, (i + 1) * size > length ? length : (i + 1) * size, ), ); } } // 사용 예시 List<int> numbers = [1, 2, 3, 4, 5, 1, 2]; print(numbers.distinct); // [1, 2, 3, 4, 5] List<String> fruits = ['사과', '바나나', '오렌지', '딸기', '포도']; print(fruits.chunk(2)); // [[사과, 바나나], [오렌지, 딸기], [포도]] List<int> empty = []; print(empty.firstOrNull); // null

Getter/Setter — 타입 파싱 확장

String에 안전한 타입 변환 getter를 추가하여 int, double, bool로 파싱합니다.

extension NumberParsing on String { int? get asIntOrNull => int.tryParse(this); double? get asDoubleOrNull => double.tryParse(this); bool get asBool { final lower = toLowerCase(); return lower == 'true' || lower == '1' || lower == 'yes' || lower == 'y'; } } print('42'.asIntOrNull); // 42 print('3.14'.asDoubleOrNull); // 3.14 print('abc'.asIntOrNull); // null print('YES'.asBool); // true

정적 멤버 (Static Members)

Extension에 static 메서드를 정의할 수 있습니다. 단, 호출 시 Extension 이름을 명시해야 합니다.

extension DateTimeExtension on DateTime { String get formattedDate => '$year-${month.toString().padLeft(2, '0')}-' '${day.toString().padLeft(2, '0')}'; static DateTime fromFormattedString(String s) { final parts = s.split('-'); if (parts.length != 3) { throw FormatException('잘못된 날짜 형식: $s'); } return DateTime( int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]), ); } static DateTime get tomorrow => DateTime.now().add(Duration(days: 1)); } // 인스턴스 메서드는 직접 호출 print(DateTime.now().formattedDate); // 2023-11-15 // static 메서드는 Extension 이름으로 호출 final date = DateTimeExtension.fromFormattedString('2023-11-15'); print(DateTimeExtension.tomorrow);

제네릭 Extension — Nullable 타입 확장

T? (nullable) 타입에 Extension을 적용하면 null 안전 유틸리티를 만들 수 있습니다.

extension OptionalExtension<T> on T? { T orDefault(T defaultValue) => this ?? defaultValue; R? mapIf<R>(R Function(T) mapper) => this != null ? mapper(this as T) : null; void ifPresent(void Function(T) action) { if (this != null) action(this as T); } } String? nullableString = null; print(nullableString.orDefault('기본값')); // 기본값 int? nullableNumber = 42; print(nullableNumber.orDefault(0)); // 42 String? name = '홍길동'; int? length = name.mapIf((n) => n.length); // 3 name.ifPresent((n) => print('안녕하세요, $n님!')); // 안녕하세요, 홍길동님!

이름 충돌 해결

동일 타입에 같은 이름의 메서드를 가진 Extension이 여러 개 있으면 컴파일 에러가 발생합니다. 이때 Extension 이름을 명시하여 해결합니다.

extension NumberParsing on String { int parseInt() => int.parse(this); } extension StringParsing on String { int parseInt() => int.parse(this) * 2; } void main() { // '42'.parseInt(); // 컴파일 에러! 어떤 Extension인지 모호함 // Extension 이름을 명시하여 해결 print(NumberParsing('42').parseInt()); // 42 print(StringParsing('42').parseInt()); // 84 }

핵심 정리

  • Extension은 원본 클래스를 수정하지 않고 기능을 추가하는 강력한 도구
  • getter, setter, 메서드, 연산자 모두 추가 가능
  • 제네릭 Extension으로 다양한 타입에 범용 유틸리티 구현
  • static 멤버는 Extension 이름으로만 호출 가능
  • dynamic 타입에서는 Extension 메서드 호출 불가
  • 이름 충돌 시 Extension 이름을 명시하거나 import show/hide로 제어

実装ステップ

1

String拡張 — capitalize、isValidEmail、repeat等ユーティリティメソッド追加

2

int拡張 — toTimeString()、isPrime、factorial等数学演算

3

List<T>拡張 — ジェネリクスでfirstOrNull、distinct、chunk(n)等安全なコレクションユーティリティ

4

名前衝突解決 — extension MyStringExt on Stringのように明示的命名、importでshow/hideで制御

メリット

  • 元のクラスを修正せず機能拡張可能
  • ユーティリティ関数を型に直接接続してコード可読性向上

デメリット

  • Extensionメソッドはdynamic型では呼び出し不可

ユースケース

FlutterでBuildContext拡張によるTheme、MediaQuery簡便アクセス プロジェクト全体で使うString/DateTimeユーティリティメソッド集