Pattern 패턴 매칭

Dart 언어 버전 3.0 이상부터 Patterns 기능을 사용할 수 있습니다.

1. Pattern의 핵심 역할

Pattern은 Dart의 독립적인 문법 분류로(문장, 표현식과 동일한 위계) 값의 구조 형태를 기술하는 문법입니다. 크게 매치(Match), 구조 분해(Destructure) 두 가지 핵심 기능을 제공하며 동시에 활용할 수 있습니다.

  1. 매치 Match:값이 예상한 형태와 일치하는지 검증
    • 지정한 상수와 일치하는지
    • 특정 타입에 부합하는지
    • 지정된 구조(List/Map/객체/레코드)를 가지고 있는지
    • 조건을 만족하는지
  2. 구조 분해 Destructure:매치에 성공하면 값 내부 필드·요소를 빠르게 추출해 변수에 바인딩하는 기능

1) 상수 매치 예시

void main() {
	int number = 1;
	switch (number) {
	  case 1:
		print('one'); // 상수 패턴으로 number == 1 검사
	  default:
		print('기타 숫자');
	}
}Code language: Dart (dart)

실행 결과

one

2. 중첩 서브 패턴 매치

패턴 안에 서브 패턴을 중첩할 수 있어 내부 구조를 단계별로 검사할 수 있습니다.

void main() {
  const a = 'a';
  const b = 'b';
  var obj = ['a', 'b'];
  switch (obj) {
    case [a, b]:
      print('$a, $b');
    default:
      print('매치되지 않음');
  }
}Code language: Dart (dart)

실행 결과

a, b

3) 기본 구조 분해 예시

var numList = [1, 2, 3];
// List 패턴으로 리스트 요소를 분해해 새 변수에 바인딩
var [x, y, z] = numList; 
print(x + y + z); // 6 x = 1, y = 2, z = 3Code language: PHP (php)

4) 중첩 패턴 매치 + 구조 분해

void main() {
  var list = ['b', 99];
  switch (list) {
    // 논리 OR 패턴: 첫 요소가 a 또는 b이고 두 번째 값은 변수 c에 바인딩
    case ['a' || 'b', var c]:
      print(c); // 99
  }
}Code language: Dart (dart)

2. 자주 사용하는 Pattern 예시

1) 지역 변수 선언

구조 분해 할당에 사용하며 여러 변수를 한 번에 생성할 수 있습니다.

// 레코드 + 리스트 중첩 패턴으로 분해
var (str, [n1, n2]) = ('text', [10, 20]);
print(str); //text
print(n1 + n2); //30Code language: Dart (dart)

2. 기존 변수 재할당 (임시 변수 없이 변수 값 교환 가능)

var (a, b) = ("왼쪽", "오른쪽");
(b, a) = (a, b); // 두 변수 값 교환
print('$a $b'); // 오른쪽 왼쪽Code language: Dart (dart)

3. switch /if-case 분기 (가장 자주 사용, when 가드 지원)

case 내부에는 반증 가능한 패턴을 모두 작성할 수 있으며 매치 성공 시 해당 분기를 실행하고 실패하면 다음 case로 넘어갑니다.

다중 타입 기본 case 예시

void testSwitch(dynamic obj) {
  switch (obj) {
    case 1:
      print('숫자 1');
    case >= 10 && <= 20:
      print('10~20 구간 숫자');
    case (var a, var b):
      print('레코드:$a,$b');
    default:
      print('매치되는 조건 없음');
  }
}Code language: Dart (dart)

논리 OR 패턴: 여러 분기에 동일 로직 적용

enum Color { red, yellow, blue, green }
void main() {
  Color color = Color.red;
  bool isPrimary = switch (color) {
    Color.red || Color.yellow || Color.blue => true,
    _ => false,
  };
  print(isPrimary); // true
}
Code language: PHP (php)

when 가드 구문 (패턴 매치 후 추가 조건 필터링)

sealed class Shape {}
class Square { final double size; Square(this.size); }
class Circle { final double size; Circle(this.size); }

void checkShape(Shape shape) {
  switch (shape) {
    // Square, Circle 한 번에 매치 후 size>0 조건 추가
    case Square(size: var s) || Circle(size: var s) when s > 0:
      print('유효한 대칭 도형');
    default:
      print('무효한 도형');
  }
}Code language: Dart (dart)

when과 일반 if의 차이점

void compareWhen((int, int) pair) {
  switch (pair) {
    case (int a, int b):
      if (a > b) print('앞 숫자가 더 큼');
      // if 조건 불만족 시 switch 전체가 종료되어 뒤 case는 실행되지 않음
    case (int a, int b) when a > b:
      print('【when 가드】앞 숫자가 더 큼');
      // when 조건 불만족 시 현재 case만 건너뛰고 다음 case 매치 진행
    case (int a, int b):
      print('앞 숫자가 더 작거나 같음');
  }
}

void main() {
  print("===== (5, 2) a > b 테스트 =====");
  compareWhen((5, 2));

  print("\n===== (2, 5) a < b 테스트 =====");
  compareWhen((2, 5));
}Code language: Dart (dart)

4. for /for-in 반복문 (MapEntry 분해 실용성 높음)


void main() {
	Map<String, int> hist = {'a': 23, 'b': 100};
	
	// 전체 표기
	for (var MapEntry(key: key, value: count) in hist.entries) {
	  print('$key$count');
	}
	
	// 축약 문법: key:key 를 :key로 간소화
	for (var MapEntry(:key, value: count) in hist.entries) {
	  print('$key$count');
	}
}Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
a:23
b:100
a:23
b:100
Code language: CSS (css)

3. Pattern 실무 활용 시나리오

1) 함수 다중 반환값 (레코드 구조 분해)

기존 작성 방식 (번거로움)

(String, int) userInfo() => ("Lily", 13);
void main() {
  var info = userInfo();
  var name = info.$1;
  var age = info.$2;
}
Code language: Dart (dart)

패턴으로 간편하게 분해

var (name, age) = userInfo();
// 이름 지정 필드 레코드 분해
final (:name, :age) = getData(); // 반환값 (name: "Doug", age:25)Code language: Dart (dart)

구체 예시


(String, int) userInfo() => ("Lily", 18);


({String name, int age}) getData() => (name: "Doug", age: 25);

void main() {
  {
    var (name, age) = userInfo();
    print("이름:$name, 나이:$age");
  }

  // 내부 name/age는 위 변수와 충돌하지 않음
  {
    final (:name, :age) = getData();
    print("이름:$name, 나이:$age");
  }
}Code language: Dart (dart)

2) 클래스 인스턴스 분해 (객체 패턴)

class Foo {
  final String one;
  final int two;
  Foo({required this.one, required this.two});
}

void main() {
  final Foo myFoo = Foo(one: "test", two: 66);
  var Foo(:one, :two) = myFoo;
  print('$one $two'); //test 66
}Code language: Dart (dart)

3) 대수 데이터 타입 (봉인 클래스 + switch 분기 연산)

연관된 서브클래스를 일괄 처리하며 분산된 인스턴스 메서드를 작성할 필요가 없습니다.

import 'dart:math';

sealed class Shape {}
class Square implements Shape { final double length; Square(this.length); }
class Circle implements Shape { final double radius; Circle(this.radius); }

double calculateArea(Shape shape) => switch (shape) {
  Square(length: var l) => l * l,
  Circle(radius: var r) => pi * r * r,
};
Code language: Dart (dart)

구현 예시

import 'dart:math';

// 같은 파일 내에서만 서브클래스를 구현할 수 있는 봉인 상위 클래스
sealed class Shape {}

// 정사각형 서브클래스
class Square implements Shape {
  final double length;
  Square(this.length);
}

// 원 서브클래스
class Circle implements Shape {
  final double radius;
  Circle(this.radius);
}

// switch 표현식 + 객체 패턴 매치로 면적 일괄 계산
double calculateArea(Shape shape) => switch (shape) {
  // Square 매치 후 length 필드를 변수 l에 추출
  Square(length: var l) => l * l,
  // Circle 매치 후 radius 필드를 변수 r에 추출
  Circle(radius: var r) => pi * r * r,
};

void main() {
  // 도형 인스턴스 생성
  Shape square = Square(5);
  Shape circle = Circle(3);

  // 면적 계산 실행
  double squareArea = calculateArea(square);
  double circleArea = calculateArea(circle);

  print("정사각형 면적:$squareArea");    // 25.0
  print("원 면적:${circleArea.toStringAsFixed(2)}"); // 28.27
}Code language: Dart (dart)

실행 결과

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
정사각형 면적:25.0
원 면적:28.27Code language: CSS (css)

4) JSON 데이터 검증 및 분해 (다중 중첩 판단 코드 크게 간소화)

(패턴 미사용 시) 다중 중첩 타입 판단으로 코드가 길어짐

void oldCheck(Map data) {
  if (data is Map<String, Object?> && data.containsKey('user')) {
    var user = data['user'];
    if (user is List && user.length == 2) {
      String name = user[0] as String;
      int age = user[1] as int;
      print('$name $age');
    }
  }
}Code language: Dart (dart)

if-case 패턴: 한 줄로 구조 검증 + 타입 검증 + 변수 바인딩 완료

void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('사용자 $name, 나이 $age');
  }
}Code language: Dart (dart)

해당 패턴은 아래 항목을 동시 검증합니다:

  1. data가 Map 타입이고 null이 아님
  2. user 키가 존재함
  3. user에 매칭되는 값이 요소 2개인 리스트임
  4. 리스트 첫 요소는 String, 두 번째 요소는 int 타입임
  5. name, age 지역 변수 자동 바인딩

실제 사용 예시

void main() {
  // 1. 유효한 JSON 구조: 매치 성공
  final validJson = {
    'user': ['Lily', 13]
  };
  newCheck(validJson); // 출력:사용자 Lily, 나이 13

  // 2. 리스트 내 타입 불일치: 나이가 문자열이라 매치 실패
  final wrongTypeJson = {
    'user': ['Tom', '16']
  };
  newCheck(wrongTypeJson); // 출력 없음

  // 3. user 리스트 요소 부족: 매치 실패 (나이 데이터 없음)
  final shortJson = {
    'user': ['Jack']
  };
  newCheck(shortJson); // 출력 없음

  // 4. user 키 자체가 없음: 매치 실패
  final noUserJson = {
    'info': ['Bob', 20]
  };
  newCheck(noUserJson); // 출력 없음
}

// if-case Map+List 패턴으로 구조·타입 검증과 분해 할당 한 번에 처리
void newCheck(Map data) {
  if (data case {'user': [String name, int age]}) {
    print('사용자 $name, 나이 $age');
  } else {
    print('데이터 형식이 올바르지 않습니다');
  }
}Code language: PHP (php)

Pattern 패턴 매칭

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다