레코드 & 패턴매칭
레코드(Records)
Section titled “레코드(Records)”레코드는 Dart 3.0에서 도입된 새로운 컬렉션 타입으로, 여러 필드를 그룹화하여 단일 객체로 전달할 수 있게 해줍니다. 클래스와 달리 명시적인 정의가 필요 없고, 불변(immutable)이며, 구조적으로 타입이 지정됩니다.
레코드 기본
Section titled “레코드 기본”레코드는 괄호(()
)를 사용하여 생성하며, 쉼표로 구분된 여러 값을 담을 수 있습니다:
// 간단한 레코드var person = ('홍길동', 30);print(person); // (홍길동, 30)
// 위치 기반 필드 접근print(person.$1); // 홍길동print(person.$2); // 30
명명된 필드가 있는 레코드
Section titled “명명된 필드가 있는 레코드”레코드에 이름이 있는 필드를 포함할 수 있습니다:
// 명명된 필드가 있는 레코드var person = (name: '홍길동', age: 30);
// 명명된 필드 접근print(person.name); // 홍길동print(person.age); // 30
// 혼합 사용var data = ('홍길동', age: 30, active: true);print(data.$1); // 홍길동print(data.age); // 30print(data.active); // true
레코드 타입 지정
Section titled “레코드 타입 지정”레코드에 명시적인 타입을 지정할 수 있습니다:
// 타입이 명시된 레코드(String, int) person = ('홍길동', 30);
// 명명된 필드가 있는 레코드 타입({String name, int age}) person = (name: '홍길동', age: 30);
// 혼합 타입(String, {int age, bool active}) data = ('홍길동', age: 30, active: true);
레코드의 유용성
Section titled “레코드의 유용성”1. 여러 값 반환
Section titled “1. 여러 값 반환”함수에서 여러 값을 반환할 때 유용합니다:
// 여러 값 반환(String, int) getUserInfo() { return ('홍길동', 30);}
void main() { var (name, age) = getUserInfo(); print('이름: $name, 나이: $age'); // 이름: 홍길동, 나이: 30}
2. 구조 분해 할당
Section titled “2. 구조 분해 할당”레코드는 구조 분해 할당을 지원합니다:
var person = (name: '홍길동', age: 30);
// 구조 분해var (name: userName, age: userAge) = person;print('이름: $userName, 나이: $userAge'); // 이름: 홍길동, 나이: 30
// 간단한 구조 분해var (:name, :age) = person;print('이름: $name, 나이: $age'); // 이름: 홍길동, 나이: 30
3. 그룹화된 데이터 전달
Section titled “3. 그룹화된 데이터 전달”여러 값을 그룹화하여 전달할 때 유용합니다:
void printPersonInfo((String, int) person) { print('이름: ${person.$1}, 나이: ${person.$2}');}
void printUserDetails({String name, int age, String? address}) { print('이름: $name, 나이: $age, 주소: ${address ?? '정보 없음'}');}
void main() { printPersonInfo(('홍길동', 30)); // 이름: 홍길동, 나이: 30
var userDetails = (name: '김철수', age: 25, address: '서울시'); printUserDetails(userDetails); // 이름: 김철수, 나이: 25, 주소: 서울시}
레코드 비교
Section titled “레코드 비교”레코드는 값 기반 비교를 지원합니다:
void main() { var person1 = (name: '홍길동', age: 30); var person2 = (name: '홍길동', age: 30); var person3 = (name: '김철수', age: 25);
print(person1 == person2); // true (동일한 값) print(person1 == person3); // false (다른 값)
// 위치 기반 레코드도 같음 var p1 = ('홍길동', 30); var p2 = ('홍길동', 30); print(p1 == p2); // true}
레코드 사용 예제
Section titled “레코드 사용 예제”1. 통계 계산
Section titled “1. 통계 계산”(double min, double max, double average) calculateStats(List<double> values) { if (values.isEmpty) { return (0, 0, 0); } double sum = 0; double min = values[0]; double max = values[0];
for (var value in values) { sum += value; if (value < min) min = value; if (value > max) max = value; }
return (min, max, sum / values.length);}
void main() { var numbers = [10.5, 25.3, 17.2, 8.7, 30.1]; var (min, max, avg) = calculateStats(numbers);
print('최소값: $min'); // 최소값: 8.7 print('최대값: $max'); // 최대값: 30.1 print('평균값: $avg'); // 평균값: 18.36}
2. API 응답 처리
Section titled “2. API 응답 처리”(bool success, {String? data, String? error}) fetchUserData(String userId) { // 서버 요청 시뮬레이션 if (userId == 'user123') { return (true, data: '{"name": "홍길동", "email": "hong@example.com"}', error: null); } else { return (false, data: null, error: '사용자를 찾을 수 없습니다.'); }}
void main() { // 성공 케이스 var (success: isSuccess, data: userData, error: _) = fetchUserData('user123');
if (isSuccess && userData != null) { print('사용자 데이터: $userData'); }
// 실패 케이스 var result = fetchUserData('unknown');
if (!result.success) { print('오류: ${result.error}'); // 오류: 사용자를 찾을 수 없습니다. }}
패턴 매칭(Pattern Matching)
Section titled “패턴 매칭(Pattern Matching)”패턴 매칭은 Dart 3.0에서 도입된 기능으로, 데이터 구조에서 특정 패턴을 검색하고 추출하는 강력한 방법을 제공합니다.
패턴 매칭 기본
Section titled “패턴 매칭 기본”패턴 매칭을 사용하면 복잡한 데이터 구조에서 데이터를 쉽게 추출하고 검사할 수 있습니다:
// 레코드 패턴 매칭var person = ('홍길동', 30);
var (name, age) = person;print('이름: $name, 나이: $age'); // 이름: 홍길동, 나이: 30
// 리스트 패턴 매칭var numbers = [1, 2, 3];
var [first, second, third] = numbers;print('$first, $second, $third'); // 1, 2, 3
// 맵 패턴 매칭var user = {'name': '홍길동', 'age': 30};
var {'name': userName, 'age': userAge} = user;print('이름: $userName, 나이: $userAge'); // 이름: 홍길동, 나이: 30
switch 문에서의 패턴 매칭
Section titled “switch 문에서의 패턴 매칭”패턴 매칭은 switch
문에서 특히 강력합니다:
void describe(Object obj) { switch (obj) { case int i when i > 0: print('양수: $i'); case int i when i < 0: print('음수: $i'); case int i when i == 0: print('0'); case String s when s.isEmpty: print('빈 문자열'); case String s: print('문자열: $s'); case List<int> list: print('정수 리스트: $list'); case (String, int) pair: print('문자열-정수 쌍: ${pair.$1}, ${pair.$2}'); case (String name, int age): print('이름: $name, 나이: $age'); default: print('기타 객체: $obj'); }}
void main() { describe(42); // 양수: 42 describe(-10); // 음수: -10 describe(0); // 0 describe(''); // 빈 문자열 describe('안녕하세요'); // 문자열: 안녕하세요 describe([1, 2, 3]); // 정수 리스트: [1, 2, 3] describe(('홍길동', 30)); // 이름: 홍길동, 나이: 30 describe(true); // 기타 객체: true}
if-case 문
Section titled “if-case 문”패턴 매칭은 if-case
문에서도 사용할 수 있습니다:
void processValue(Object value) { if (value case String s when s.length > 5) { print('긴 문자열: $s'); } else if (value case int n when n > 10) { print('큰 정수: $n'); } else if (value case (String name, int age)) { print('이름: $name, 나이: $age'); } else { print('처리할 수 없는 값: $value'); }}
void main() { processValue('안녕하세요, 반갑습니다'); // 긴 문자열: 안녕하세요, 반갑습니다 processValue(42); // 큰 정수: 42 processValue(('홍길동', 30)); // 이름: 홍길동, 나이: 30 processValue(true); // 처리할 수 없는 값: true}
논리 OR 패턴
Section titled “논리 OR 패턴”여러 패턴을 |
연산자로 결합할 수 있습니다:
Object value = 42;
switch (value) { case String s | int i: print('문자열 또는 정수: $value'); case List<dynamic> l | Map<dynamic, dynamic> m: print('리스트 또는 맵'); default: print('기타 타입');}
중첩 패턴 매칭
Section titled “중첩 패턴 매칭”패턴은 중첩될 수 있어 복잡한 데이터 구조에서도 사용할 수 있습니다:
// 복잡한 데이터 구조var person = { 'name': '홍길동', 'age': 30, 'address': { 'city': '서울', 'zipcode': '12345' }, 'hobbies': ['독서', '여행', '음악']};
// 중첩 패턴 매칭if (person case {'name': String name, 'address': {'city': String city}}) { print('$name은(는) $city에 살고 있습니다.'); // 홍길동은(는) 서울에 살고 있습니다.}
// 리스트와 레코드의 중첩 패턴var data = [('홍길동', 30), ('김철수', 25)];
if (data case [(String s, int i), var rest]) { print('첫 번째 사람: $s, $i살'); // 첫 번째 사람: 홍길동, 30살 print('나머지: $rest'); // 나머지: (김철수, 25)}
var 패턴과 와일드카드 패턴
Section titled “var 패턴과 와일드카드 패턴”변수에 값을 캡처하거나 값을 무시할 수 있습니다:
// var 패턴 (값 캡처)var list = [1, 2, 3, 4, 5];
if (list case [var first, var second, ...]) { print('처음 두 값: $first, $second'); // 처음 두 값: 1, 2}
// 와일드카드 패턴 (값 무시)var record = ('홍길동', 30, '서울');
if (record case (String name, _, var city)) { print('$name은(는) $city에 살고 있습니다.'); // 홍길동은(는) 서울에 살고 있습니다.}
일정한 리스트 패턴
Section titled “일정한 리스트 패턴”리스트의 특정 위치에 있는 값을 추출할 수 있습니다:
var numbers = [1, 2, 3, 4, 5];
switch (numbers) { case [var first, var second, ...var rest]: print('첫 번째: $first'); // 첫 번째: 1 print('두 번째: $second'); // 두 번째: 2 print('나머지: $rest'); // 나머지: [3, 4, 5] default: print('빈 리스트 또는 다른 형태');}
// 특정 패턴 검색var list = [1, 2, 3, 4, 5];
if (list case [_, _, 3, ..., 5]) { print('리스트가 패턴과 일치합니다.'); // 리스트가 패턴과 일치합니다.}
클래스 인스턴스의 속성을 추출할 수도 있습니다:
class Person { final String name; final int age;
Person(this.name, this.age);}
void describePerson(Person person) { switch (person) { case Person(name: 'Unknown', age: var a): print('알 수 없는 사람, 나이: $a'); case Person(name: var n, age: > 18): print('성인: $n'); case Person(name: var n): print('미성년자: $n'); }}
void main() { describePerson(Person('홍길동', 30)); // 성인: 홍길동 describePerson(Person('김영희', 15)); // 미성년자: 김영희}
타입 패턴과 조건 패턴
Section titled “타입 패턴과 조건 패턴”void process(dynamic value) { switch (value) { // 타입 패턴 case int(): print('정수: $value');
// 타입 패턴과 조건 패턴 case String() when value.length > 5: print('긴 문자열: $value'); case String(): print('짧은 문자열: $value');
// 리스트 + 조건 패턴 case List<int> l when l.every((e) => e > 0): print('양수 리스트: $value');
// 레코드 + 조건 패턴 case (String n, int a) when a >= 18: print('성인: $n, $a살'); case (String n, int a): print('미성년자: $n, $a살');
default: print('기타 값: $value'); }}
void main() { process(42); // 정수: 42 process('Hello'); // 짧은 문자열: Hello process('안녕하세요, 반갑습니다'); // 긴 문자열: 안녕하세요, 반갑습니다 process([1, 2, 3]); // 양수 리스트: [1, 2, 3] process([-1, 2, 3]); // 기타 값: [-1, 2, 3] process(('홍길동', 30)); // 성인: 홍길동, 30살 process(('김영희', 15)); // 미성년자: 김영희, 15살}
1. REST API 응답 처리
Section titled “1. REST API 응답 처리”// API 응답 시뮬레이션Map<String, dynamic> fetchUserResponse() { return { 'status': 'success', 'data': { 'user': { 'id': 123, 'name': '홍길동', 'email': 'hong@example.com', 'addresses': [ {'type': 'home', 'city': '서울', 'zipcode': '12345'}, {'type': 'work', 'city': '부산', 'zipcode': '67890'}, ] } } };}
void main() { var response = fetchUserResponse();
// 패턴 매칭으로 응답 처리 switch (response) { case {'status': 'success', 'data': {'user': var user}}: if (user case {'name': String name, 'email': String email, 'addresses': var addresses}) { print('사용자 이름: $name, 이메일: $email');
if (addresses case List<Map<String, dynamic>> addressList) { for (var address in addressList) { if (address case {'type': 'home', 'city': String city}) { print('집 주소: $city'); } } } } case {'status': 'error', 'message': String message}: print('오류: $message'); default: print('알 수 없는 응답'); }}
2. 데이터 변환 및 검증
Section titled “2. 데이터 변환 및 검증”// 데이터 변환 및 검증(bool isValid, {String? data, String? error}) validateUserData(Object input) { return switch (input) { Map<String, dynamic> map when map.containsKey('name') && map.containsKey('age') => map['age'] is int && map['age'] > 0 ? (true, data: '유효한 사용자 데이터', error: null) : (false, data: null, error: '나이는 양의 정수여야 합니다.'),
(String name, var age) when age is int && age > 0 => (true, data: '유효한 사용자 레코드', error: null),
String s when RegExp(r'^[a-zA-Z0-9]+,\s*\d+$').hasMatch(s) => (true, data: '유효한 사용자 문자열', error: null),
_ => (false, data: null, error: '지원하지 않는 형식') };}
void main() { // 다양한 입력 형식 테스트 var result1 = validateUserData({'name': '홍길동', 'age': 30}); var result2 = validateUserData(('김철수', 25)); var result3 = validateUserData('이영희, 20'); var result4 = validateUserData({'name': '박지성', 'age': -5}); var result5 = validateUserData(42);
for (var result in [result1, result2, result3, result4, result5]) { if (result.isValid) { print('검증 성공: ${result.data}'); } else { print('검증 실패: ${result.error}'); } }}
레코드와 패턴 매칭은 Dart 3에서 도입된 강력한 기능으로, 코드를 더 간결하고 표현력 있게 만들 수 있습니다. 레코드는 여러 값을 그룹화하고 반환하는 간편한 방법을 제공하며, 패턴 매칭은 복잡한 데이터 구조에서 값을 쉽게 추출하고 분석할 수 있게 해줍니다.
이러한 기능은 다음과 같은 상황에서 특히 유용합니다:
- 함수에서 여러 값 반환
- 데이터 구조에서 특정 패턴 검색
- 조건부 로직 간소화
- API 응답 처리
- 데이터 변환 및 검증
Dart의 레코드와 패턴 매칭을 활용하면 더 선언적이고 안전한 코드를 작성할 수 있으며, 이는 Flutter 애플리케이션에서도 큰 도움이 됩니다.
다음 장에서는 Dart의 비동기 프로그래밍에 대해 알아보겠습니다.