패턴(Patterns)은 Dart 3.0부터 쓸 수 있는 문법 범주야. 쉽게 말해, 값의 형태를 서술해서 그 값이 기대한 모양인지 매칭해 보고, 맞으면 그 값을 분해(디스트럭처링) 해서 구성 요소를 꺼내오는 방식이야. 어디서 쓸 수 있는지, 뭐가 좋은지 감만 잡자.
패턴이 하는 일
패턴은 상황과 생김새에 따라 두 가지를 해:
- 매칭: 값이 특정 모양인지, 특정 상수와 같은지, 특정 타입인지 등을 검사.
- 디스트럭처링: 매칭에 성공하면 그 값을 선언적으로 쪼개서 필요한 조각들을 변수로 바로 받기.
매칭은 패턴 종류에 따라 달라. 예를 들어 상수 패턴은 값이 그 상수와 같으면 매칭돼:
switch (number) {
// Constant pattern matches if 1 == number.
case 1:
print('one');
}
여러 패턴은 하위 패턴을 재귀적으로 포함할 수 있어. 예를 들어 리스트 패턴의 각 칸에 상수 패턴이나 변수 패턴이 들어갈 수 있지:
const a = 'a';
const b = 'b';
switch (obj) {
// List pattern [a, b] matches obj first if obj is a list with two fields,
// then if its fields match the constant subpatterns 'a' and 'b'.
case [a, b]:
print('$a, $b');
}
무시하고 싶은 부분은 와일드카드로 건너뛰면 되고, 리스트면 rest 요소로 남은 걸 흡수할 수도 있어.
디스트럭처링
매칭이 되면 패턴이 그 값을 구성 요소로 분해해서 변수에 담을 수 있어:
var numList = [1, 2, 3];
// List pattern [a, b, c] destructures the three elements from numList...
var [a, b, c] = numList;
// ...and assigns them to new variables.
print(a + b + c);
패턴 안에 또 다른 패턴을 중첩할 수도 있어. 예를 들어 첫 번째 요소가 'a' 또는 'b' 인 두 칸짜리 리스트를 매칭하고 분해하는 패턴:
switch (list) {
case ['a' || 'b', var c]:
print(c);
}
패턴을 쓸 수 있는 곳
패턴은 지역 변수 선언/대입, for·for-in 루프, if-case와 switch의 case, 컬렉션 리터럴의 제어 흐름 요소에 쓸 수 있어. 자주 쓰는 케이스만 콕 집어 보자.
변수 선언
오른쪽 값과 매칭 + 분해해서 새 변수로 바인딩해:
// Declares new variables a, b, and c.
var (a, [b, c]) = ('str', [1, 2]);
항상 var 또는 final 다음에 패턴이 와야 해.
변수 대입
이미 있는 변수에 패턴 대입으로 값들을 한 번에 갱신할 수 있어. 임시 변수 없이 스왑하기도 쉽고:
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left".
switch 문/식
모든 case는 패턴이야. 매칭되면 분해된 값들이 그 case 본문에서만 보이는 지역 변수로 잡혀.
switch (obj) {
// Matches if 1 == obj.
case 1:
print('one');
// Matches if the value of obj is between the
// constant values of 'first' and 'last'.
case >= first && <= last:
print('in range');
// Matches if obj is a record with two fields,
// then assigns the fields to 'a' and 'b'.
case (var a, var b):
print('a = $a, b = $b');
default:
}
여러 케이스를 하나의 본문으로 묶고 싶으면 논리 OR 패턴이 좋아:
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false,
};
또, 여러 케이스에 공통 가드(when) 를 쓰고 싶을 때도 유용해:
switch (shape) {
case Square(size: var s) || Circle(size: var s) when s > 0:
print('Non-empty symmetric shape');
}
가드는 case의 일부로 조건을 평가하는데, 거짓이면 switch를 탈출하지 않고 다음 케이스로 넘어가게 할 수 있어:
switch (pair) {
case (int a, int b):
if (a > b) print('First element greater');
// If false, prints nothing and exits the switch.
case (int a, int b) when a > b:
// If false, prints nothing but proceeds to next case.
print('First element greater');
case (int a, int b):
print('First element not greater');
}
for / for-in 루프
루프 변수 자리에 패턴을 써서 컬렉션의 요소를 반복하면서 분해할 수 있어:
Map<String, int> hist = {'a': 23, 'b': 100};
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}
게터 이름과 같은 변수로 바인딩하는 건 너무 흔해서, 축약 표기도 돼:
for (var MapEntry(:key, value: count) in hist.entries) {
print('$key occurred $count times');
}
패턴이 빛나는 쓰임새
여러 값 반환(Records) 즉시 분해
레코드가 여러 값을 묶어서 반환해 주면, 패턴으로 바로 풀어 받으면 돼:
var info = userInfo(json);
var name = info.$1;
var age = info.$2;
대신 한 줄로:
var (name, age) = userInfo(json);
이름 있는 레코드도 바로 분해 가능:
final (:name, :age) =
getData(); // For example, return (name: 'doug', age: 25);
클래스 인스턴스 분해
오브젝트 패턴은 특정 타입을 매칭하고, 그 타입이 노출하는 게터로 프로퍼티를 분해해:
final Foo myFoo = Foo(one: 'one', two: 2);
var Foo(:one, :two) = myFoo;
print('one $one, two $two');
대수적 데이터 타입(ADT) 스타일
관련된 타입 군을 한꺼번에 다루고, 한 함수에서 서브타입별 동작을 스위치로 분기하는 패턴이 잘 맞아:
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) => math.pi * r * r,
};
들어오는 JSON 검증 + 분해
맵/리스트 패턴으로 구조를 검증하면서 동시에 분해할 수 있어:
var data = {
'user': ['Lily', 13],
};
var {'user': [name, age]} = data;
외부 데이터는 보통 신뢰할 수 없으니, 패턴으로 한 번에 선언적으로 확인하자. 장황한 if 연쇄 대신:
if (data case {'user': [String name, int age]}) {
print('User $name is $age years old.');
}
이 한 줄로
- 맵인지 확인(= null 아님),
'user'키 존재,- 값이 길이 2인 리스트,
- 첫 값
String, 둘째 값int, - 그리고
name,age변수 바인딩
을 전부 만족시켜야 통과해.
'다트🎯' 카테고리의 다른 글
| 관리 흐름 - 반복 (0) | 2025.08.31 |
|---|---|
| 패턴의 종류 (0) | 2025.08.31 |
| 타입 - 타입 시스템 (0) | 2025.08.31 |
| 타입 - 타입 별칭 (0) | 2025.08.31 |
| 타입 - 제너릭 (0) | 2025.08.31 |