My AI Smarteasy 사용자 정의 AI 에이전트 – 일타 강사 저스틴 – Dart 언어 완전 정복: 초보자도 쉽게 배우는 프로그래밍 기초!
안녕하세요, 일타 강사 저스틴입니다! 오늘은 프로그래밍 언어 Dart를 Visual Studio Code(이하 VS Code)와 함께 배우며 프로그래밍의 첫걸음을 내딛어볼 거예요. Dart는 초보자도 쉽게 접근할 수 있는 강력한 언어로, VS Code라는 편리한 도구와 함께라면 더 쉽게 시작할 수 있어요. 오늘 강의로 Dart의 기초부터 활용까지 확실히 잡아보는 게 목표예요! 자, 준비되셨죠? 😊
섹션 1: Dart와 VS Code란 무엇일까?
여러분, Dart가 뭘까요? Dart는 구글에서 만든 프로그래밍 언어로, 모바일 앱부터 웹까지 다양한 플랫폼에서 동작하는 만능 도구 같은 존재예요. 마치 요리에 어떤 재료든 잘 어울리는 만능 소스 같죠! 그리고 VS Code는 코드 작성을 도와주는 강력한 편집기로, 마치 요리사의 칼과 같아요. Dart를 설치하고 VS Code에 Dart 확장 프로그램을 추가하면 바로 코딩을 시작할 수 있어요. 자, 여기 별표 세 개! 이 조합은 초보자에게 최고예요. 이해되셨나요?
섹션 2: VS Code에서 첫걸음, ‘Hello, World!’
프로그래밍의 첫걸음은 역시 ‘Hello, World!’ 출력이죠. VS Code에서 새 파일을 만들고 확장자 dart로 해서, hello .dart로 저장하세요. Dart에서는 main() 함수가 프로그램의 시작점이에요. 이건 마치 자동차의 시동 키 같은 역할을 하죠. 아래 코드를 입력해보세요:
|
1 2 3 4 |
void main() { print('Hello, World!'); } |
VS Code에서 터미널을 열고 dart run ./hello.dart 명령어로 실행하면 콘솔에 “Hello, World!”가 출력돼요. 정말 간단하죠? 첫걸음부터 느낌 오시나요?
섹션 3: 변수, 데이터를 담는 그릇
여러분, 우리가 마트에 가서 뭘 담을 때 뭘 찾죠? 바로 그릇이죠! 🥣 프로그래밍에서도 데이터를 담아두는 ‘그릇’ 같은 존재가 있는데, 그걸 바로 변수라고 부릅니다!
Dart에서는 이 변수를 선언할 때 var 키워드를 사용해요. 🌟 이 var는 정말 똑똑해서, 우리가 굳이 “이 그릇에는 밥을 담을 거야!”, “저 그릇에는 국을 담을 거야!”라고 말해주지 않아도, 내용물만 딱! 넣으면 자동으로 그릇 모양과 크기를 맞춰준답니다! 와우! 마치 만능 그릇 같죠? 🤩
VS Code에서는 코드를 작성하는 중에 이런 변수들을 자동 완성 기능으로 아주 쉽게 입력할 수 있어요. 이건 마치 요리사가 필요한 도구를 척척 찾아주는 만능 주방장 같은 역할을 해주는 거예요! 🧑🍳
예를 한번 볼까요?
|
1 2 3 4 5 |
var name = 'Voyager I'; var year = 1977; var antennaDiameter = 3.7; var flybyObjects = <span style="color: #ff0000;">[</span>'Jupiter', 'Saturn', 'Uranus', 'Neptune'<span style="color: #ff0000;">]</span>; |
위 코드를 보시면, name에는 ‘Voyager I’이라는 글자를 넣었더니 자동으로 문자열(String) 그릇이 되고요, year에는 1977이라는 숫자를 넣었더니 숫자(int) 그릇이 되는 거죠! flybyObjects처럼 여러 개의 데이터를 담는 리스트(List) 그릇도 척척 만들어준답니다! 정말 신기하죠? ✨
자, 여기 별표 세 개! ⭐⭐⭐ 변수는 프로그래밍의 가장 기본적인 개념이니까요, 꼭! 꼭! 익혀두셔야 해요! 이건 시험에 무조건 나옵니다! 📝 이해되시죠? 😉
이제 이 담아둔 데이터들을 실제로 사용해볼까요? 가장 쉬운 방법은 역시 화면에 출력해보는 것이에요. 마치 그릇에 담긴 음식을 꺼내서 맛을 보는 것과 같죠! 😋
아래 코드를 보시면, 우리가 선언했던 변수들을 print() 함수 안에 넣어서 콘솔에 출력하는 예시입니다!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void main() { var name = 'Voyager I'; var year = 1977; var antennaDiameter = 3.7; var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune']; print('🌟 우주선 정보 🌟'); print('이름: ${name}'); // ${변수명}을 사용하면 문자열 안에 변수 값을 쉽게 넣을 수 있어요! print('발사 연도: ${year}년'); print('안테나 지름: ${antennaDiameter} 미터'); print('탐사 행성: ${flybyObjects}'); // 리스트 전체가 출력됩니다! // 특정 행성만 꺼내볼 수도 있어요! (리스트는 0부터 세는 게 포인트!) print('가장 먼저 탐사한 행성은 ${flybyObjects[0]}입니다!'); // Jupiter가 나오겠죠? print('탐사한 행성 수는 총 ${flybyObjects.length}개입니다!'); // .length를 쓰면 리스트의 개수를 알 수 있어요! // 숫자를 이용한 간단한 계산도 가능해요! var currentYear = 2025; var operatingYears = currentYear - year; print('보이저 1호는 현재 ${operatingYears}년째 우주를 여행 중이에요! 🚀'); } |
섹션 4: 흐름 제어, 길을 안내하는 표지판 🚦
여러분, 우리가 어딘가로 여행을 갈 때 어떻게 목적지까지 찾아가죠? 맞아요! 길을 가다가 표지판을 보고 이쪽으로 갈지, 저쪽으로 갈지 방향을 정하잖아요! 🗺️
프로그래밍에서도 마찬가지예요. 프로그램이 무조건 한 방향으로만 쭉 가는 게 아니라, 특정 조건에 따라 다른 길로 갈 수 있도록 방향을 정해주는 것이 바로 흐름 제어랍니다! 🚀
Dart에서는 if-else (만약 이렇다면 저렇게 해!), for (이 행동을 몇 번 반복해!), while (이 조건이 맞으면 계속 반복해!) 같은 문법들로 프로그램의 ‘방향’을 정할 수 있어요. 🔄
게다가 우리 똑똑한 VS Code의 코드 강조 기능 덕분에 이런 복잡해 보이는 구조도 한눈에 알아볼 수 있으니 걱정 마세요! 마치 복잡한 교차로에서도 눈에 띄는 큰 표지판 덕분에 헤매지 않는 것과 같아요. 🤩
예를 한번 들어볼까요? 우리가 어떤 연도가 몇 세기인지 알아보고 싶다고 해봅시다. 아래 코드를 한번 보세요!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void main() { var year = 1999; // 테스트할 연도 if (year >= 2001) { // 만약 연도가 2001년 이상이라면 print('${year}년은 21세기입니다!'); // '21세기입니다!' 출력 } else if (year >= 1901) { // 그게 아니고, 만약 연도가 1901년 이상이라면 print('${year}년은 20세기입니다!'); // '20세기입니다!' 출력 } else { // 위의 어떤 조건도 아니라면 print('${year}년은 19세기 또는 그 이전입니다!'); // '19세기 또는 그 이전입니다!' 출력 } // year 값을 바꿔가며 테스트해보세요! // year = 2025; // year = 1850; } |
이 코드는 year 변수에 담긴 연도를 보고 “만약 2001년 이상이라면 21세기 길로 가!”, “그게 아니라 1901년 이상이라면 20세기 길로 가!” 하고 방향을 정해주는 거예요. 정말 간단명료하죠? 마치 이정표를 따라가는 것과 같아요!
자, 여기 별표 세 개! ⭐⭐⭐ 흐름 제어는 프로그램이 똑똑하게 판단하고 행동하게 만드는 핵심이니까, 이 개념을 잘 익혀두시면 어떤 프로그램이든 자유자재로 만들 수 있을 거예요! 이해되셨나요? 감이 오시죠? 😉
섹션 5: 함수, 반복 작업을 간편하게 🔄
여러분, 요리할 때 매번 마늘을 다지고, 야채를 썰고… 귀찮지 않으세요? 😫 그럴 때 우리가 뭘 쓰죠? 맞아요! 바로 믹서기나 푸드 프로세서 같은 도구들이죠! 뚝딱! 하고 넣어주면 알아서 갈아주고 섞어주잖아요! 🥣
프로그래밍에서도 이렇게 반복되는 작업을 한 번에 처리해주는 만능 도구가 있는데, 그걸 바로 함수라고 부른답니다! ✨ 함수는 복잡한 코드 덩어리를 하나의 이름으로 묶어두고, 필요할 때마다 그 이름만 불러서 사용하는 거예요. 마치 필요한 요리 도구를 선반에서 꺼내 쓰는 것처럼요! 👩🍳
그리고 우리에게는 든든한 지원군, VS Code가 있잖아요? 🦸♂️ VS Code는 함수를 작성하는 도중에 혹시 오류가 있거나 문법이 틀리면 미리미리 빨간 줄로 경고를 띄워줘서, 우리가 실수를 바로잡을 수 있게 도와준답니다! 이건 마치 요리하다가 “이거 재료가 너무 적은데요?” 하고 미리 알려주는 똑똑한 조리사 같아요!
Dart에서는 함수의 입력값(인자)과 결과값(반환값)이 어떤 데이터 타입인지 명확하게 알려주는 걸 아주아주 권장해요. 🌟 예를 들어, “이 믹서기에는 과일만 넣어야 하고, 결과물은 주스로 나올 거야!” 하고 미리 정해두는 것과 같죠! 이렇게 하면 코드가 훨씬 더 튼튼해지고 이해하기 쉬워져요.
아래 fibonacci 함수 예시를 한번 볼까요?
|
1 2 3 4 5 |
int fibonacci(int n) { // 'int'는 정수를 반환하고, 'n'이라는 정수 인자를 받겠다는 뜻! if (n == 0 || n == 1) return n; // n이 0 또는 1이면 n을 바로 반환 return fibonacci(n - 1) + fibonacci(n - 2); // 자기 자신을 다시 호출하는 재귀 함수! } |
이 코드는 피보나치 수열을 계산하는 함수인데, int fibonacci(int n)이라고 써서 “나는 정수(int)를 돌려줄 거고, 너도 정수(int)를 넣어줘야 해!” 하고 딱 정해주는 거예요. 깔끔하죠? 👍
그리고, 만약 함수가 딱 한 줄짜리 코드로 끝난다면, 더 간편하게 => (화살표 문법)을 사용해서 쓸 수도 있어요! 이건 마치 긴 문장을 한 줄짜리 슬로건으로 만드는 것과 같죠!
|
1 2 3 4 |
// 예를 들어, 두 숫자를 더하는 한 줄짜리 함수는 이렇게! int add(int a, int b) => a + b; // 'a + b'의 결과값을 바로 반환!``` <strong>자, 여기 별표 세 개!</strong> ⭐⭐⭐ <strong>함수는 코드를 효율적으로 만들고, 재사용성을 높여주는 프로그래밍의 핵심 도구</strong>예요! 이 친구를 잘 활용하면 복잡한 작업도 아주 간편하게 해결할 수 있을 겁니다! 감 오시나요? 😉 |
섹션 6: 주석, 코드에 메모 남기기
Dart에서 주석은 코드를 설명하는 메모예요. 마치 책에 중요한 부분을 표시하는 포스트잇 같죠. VS Code에서는 주석이 다른 색으로 표시돼 가독성이 좋아요. 주석은 보통 //로 시작하고, 문서용 주석은 ///를 사용해요. 예를 들어:
|
1 2 3 |
// 이건 일반적인 한 줄 주석이에요. /// 이건 문서용 주석으로, 라이브러리나 클래스 설명에 사용돼요. |
주석은 코드를 읽기 쉽게 만들어주는 중요한 도구예요. 여러분도 꼭 활용해보세요!
섹션 7: Imports, 외부 도구 가져오기 🛠️
여러분, 우리가 요리를 하다가 갑자기 특별한 소스나, 평소에는 잘 쓰지 않는 특이한 조리 도구가 필요할 때가 있잖아요? 그럴 때 어떻게 하죠? 맞아요! 친구에게 “잠깐 빌려줄래?” 하고 부탁하거나, 마트에 가서 사 오잖아요! 🛒
프로그래밍에서도 똑같아요! 💡 Dart 프로그램에서 다른 사람이 만들어 놓은 유용한 기능이나 도구들을 내 코드 안으로 가져와서 쓰고 싶을 때가 있답니다. 이때 사용하는 마법의 주문이 바로 import예요! ✨ 이건 마치 필요한 도구를 빌려와서 내 주방에 세팅하는 것과 같죠! 🧑🍳
우리 똑똑한 VS Code는 또 얼마나 고마운지 몰라요! 🦸♀️ 여러분이 어떤 기능을 쓰려고 할 때, VS Code가 “어? 이 기능은 저기 있는 라이브러리에 있네? 내가 import 해줄까?” 하고 자동 완성 기능으로 미리 알려주고 척척 추가해준답니다! 이건 마치 주방에서 필요한 도구를 찾으려는데, 옆에서 누가 “여기요!” 하고 바로 건네주는 것과 똑같아요!
예시 코드를 한번 볼까요?
|
1 2 3 |
import 'dart:math'; // Dart 기본 라이브러리에서 '수학' 관련 기능 가져오기 import 'package:test/test.dart'; // 외부에서 설치한 '테스트' 패키지 기능 가져오기 |
첫 번째 줄 import 'dart:math';는 Dart 언어가 기본적으로 제공하는 ‘수학(math)’ 관련 기능들(예: 제곱근 계산, 난수 생성 등)을 내 코드에서 쓸 수 있게 해달라고 요청하는 거예요. 덧셈 뺄셈이야 직접 할 수 있지만, 복잡한 계산은 전문가에게 맡기는 게 빠르고 정확하죠? 📐
여기서 ‘dart:‘는 “이 도구는 Dart 언어가 기본적으로 제공하는 도구야!”라고 알려주는 표식이에요. 마치 주방 안에 있는 기본 도구 상자에 들어있는 것처럼요! 🎁
두 번째 줄 import 'package:test/test.dart';는 Flutter 프로젝트를 진행하다 보면 자주 보게 될 텐데요, 우리가 외부에서 설치한 ‘test’라는 패키지 안에 있는 기능들을 가져오겠다는 뜻이에요. 마치 아주 특별한 양념이 필요해서 전문 식료품점에서 사온 것을 내 요리에 사용하는 것과 같죠! 🌶️
여기서 package:라는 접두사는 “이 도구는 Dart나 Flutter의 기본으로 제공되는 게 아니라, 다른 개발자들이 만들어서 우리가 설치한 ‘외부 패키지’ 안에 들어있어!”라고 알려주는 표식이에요. 마치 유명한 요리사가 개발한 특별한 소스에 붙은 상표 같죠! 🏷️
test 패키지도 그중 하나인데, 사실 여러분이 Flutter 앱을 만들다 보면 http나 provider 같은 더 유명하고 자주 쓰이는 외부 패키지들을 훨씬 많이 만나게 될 거예요! 이 패키지들은 전 세계 개발자들이 “와, 이거 정말 편하고 좋다!” 하고 인정하고 사용하는 ‘공유 주방 도구’ 같은 거죠. 🧑🤝🧑
자, 그럼 가장 대표적인 외부 패키지 중 하나인 http 패키지를 예시로 들어 설명해볼게요!
🌍 ‘http’ 패키지, 인터넷 정보 가져오는 전문가!
여러분이 Flutter 앱을 만들 때, 인터넷에 있는 최신 날씨 정보나 뉴스 기사, 아니면 다른 웹사이트의 데이터를 가져와서 앱 화면에 보여주고 싶을 때가 많을 거예요. 🌐 그런데 이런 인터넷 통신 기능을 직접 처음부터 만들려면… 머리가 지끈지끈 아플 만큼 복잡하고 시간도 오래 걸린답니다. 😵💫
이럴 때 우리는 http라는 외부 패키지를 사용해요! 이 패키지는 “인터넷에서 데이터를 가져오거나 보내는 일은 내가 전문가야! 나한테 맡겨!” 하고 말하는 똑똑한 비서 같은 존재죠! 🧑💻
우리가 이 http 패키지를 사용하려면, 먼저 프로젝트에 이 패키지를 ‘설치’해야 합니다. (이건 pubspec.yaml 파일에 추가하고 flutter pub get 명령어로 할 수 있어요. 나중에 더 자세히 배울 거예요! 😉)
그리고 나서, 우리 코드에서 이 패 패키지의 기능을 쓰고 싶을 때 바로 이렇게 import를 하는 거죠!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import 'package:http/http.dart' as http; // http 패키지 가져오기 // 이제 'http'라는 이름으로 이 패키지의 기능을 사용할 수 있어요! void main() async { // 예를 들어, 어떤 웹사이트의 데이터를 가져오고 싶을 때 var url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1'); var response = await http.get(url); // http 패키지의 get 함수를 사용! if (response.statusCode == 200) { // 성공적으로 데이터를 가져왔다면 print('✅ 인터넷에서 데이터를 성공적으로 가져왔습니다!'); print('가져온 데이터: ${response.body}'); } else { // 문제가 생겼다면 print('❌ 데이터를 가져오는데 실패했습니다. 상태 코드: ${response.statusCode}'); } } |
위에 보이는 import ‘package:http/http.dart’ as http;문장이 바로 “나는 http라는 패키지 안에 있는 http.dart라는 파일을 가져와서, 앞으로 이 패키지의 기능들을 http.라고 줄여서 부를 거야!” 라고 선언하는 거예요. 📜
📜 pubspec.yaml 파일, 프로젝트의 설계도!
pubspec.yaml 파일은 프로젝트의 가장 중요한 설정 파일 중 하나예요. 이곳에 프로젝트의 이름, 설명, 사용할 외부 패키지 목록, 이미지나 폰트 같은 리소스 목록 등을 작성합니다.
가장 중요한 부분은 바로 dependencies 섹션인데요, 여기에 우리가 사용할 외부 패키지들을 명시해줘야 해요.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
name: my_awesome_app description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: '>=3.0.0 <4.0.0' dependencies: # 여기가 핵심! 사용할 패키지 목록을 여기에 적어요! flutter: sdk: flutter cupertino_icons: ^1.0.2 # iOS 스타일 아이콘 패키지 http: ^1.1.0 # HTTP 통신을 위한 패키지 (우리가 방금 예시로 든 친구!) provider: ^6.0.5 # 상태 관리를 위한 패키지 (아주 유명해요!) dev_dependencies: # 개발 환경에서만 필요한 패키지 (테스트 도구 등) flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg |
위 코드에서 dependencies: 아래에 http: ^1.1.0나 provider: ^6.0.5처럼 패키지 이름과 버전을 적어주는 부분이 보이시죠? 이게 바로 “나는 http 패키지의 1.1.0 버전 이상을 쓸 거야!”라고 선언하는 겁니다.
✍️ pubspec.yaml에 직접 작성하는 방법 (수동)
만약 어떤 패키지를 사용하고 싶을 때, 패키지 이름과 버전을 정확히 알고 있다면 pubspec.yaml 파일의 dependencies: 섹션 아래에 직접 타이핑해서 추가할 수 있어요.
- 사용하려는 패키지의 이름과 최신 버전을 확인합니다. (보통 pub.dev 사이트에서 확인할 수 있어요! 🔍)
- 예를 들어,
shared_preferences패키지를 쓰고 싶다면 pub.dev에서 검색 후Installing탭을 보면shared_preferences: ^2.2.0처럼 나와있어요.
- 예를 들어,
- VS Code에서
pubspec.yaml파일을 열고,dependencies:아래에 새로운 줄에 들여쓰기를 맞춰서 패키지 이름을 추가합니다.
123456dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.2shared_preferences: ^2.2.0 # 이렇게 추가! - 파일을 저장하면, VS Code나 터미널에서 flutter pub get (또는 dart pub get) 명령어가 자동으로 실행되면서 새로 추가된 패키지를 다운로드받을 거예요. 이건 마치 “새로운 재료를 추가했으니 마트에 가서 사 와!”라고 명령하는 것과 같아요. 🛒
자, 여기 별표 세 개! ⭐⭐⭐ pubspec.yaml 파일은 들여쓰기가 매우 중요해요! 스페이스바로 들여쓰기를 정확히 맞춰야 합니다! 안 그러면 에러가 날 수 있어요! 🚨
🪄 VS Code에서 자동 작성되게 하는 방법 (가장 추천!)
사실, 대부분의 경우 패키지를 직접 타이핑해서 추가할 필요 없이, VS Code의 똑똑한 기능을 활용해서 자동으로 추가할 수 있어요! 🤩 이건 마치 냉장고에 재료가 없으면 자동으로 마트 앱으로 주문해주는 스마트 냉장고 같아요! 🧊
방법 1: flutter pub add 명령어 사용 (가장 빠르고 정확!)
가장 깔끔하고 추천하는 방법입니다!
- VS Code에서 터미널을 엽니다 (
Ctrl +또는View > Terminal). - 여러분의 프로젝트 폴더 경로에서 다음 명령어를 입력하세요.
12flutter pub add [패키지_이름]- 예를 들어,
shared_preferences패키지를 추가하고 싶다면:
12flutter pub add shared_preferences
- 예를 들어,
이 명령어를 실행하면, flutter가 알아서 pubspec.yaml 파일에 해당 패키지의 최신 버전을 찾아 dependencies에 추가해주고, 필요한 파일을 다운로드( flutter pub get 자동 실행)까지 해줍니다!
방법 2: VS Code의 ‘빠른 수정(Quick Fix)’ 기능 활용 (코드를 작성하다가!)
이 방법은 여러분이 코드에서 어떤 패키지의 기능을 사용하려고 하는데, 아직 import도 안 하고 pubspec.yaml에도 추가 안 했을 때 유용해요.
.dart파일에서 사용하고 싶은 외부 패키지의 클래스나 함수를 그냥 타이핑합니다. (예:SharedPreferences.getInstance())- 그러면 VS Code가 “어? 이거 처음 보는 건데?” 하면서 해당 코드 아래에 빨간 밑줄을 띄울 거예요. 🔴
- 빨간 밑줄이 있는 코드 위에 마우스 커서를 올리거나,
Ctrl + .(점) 키를 누르면 (Mac은Cmd + .), ‘빠른 수정(Quick Fix)’ 옵션이 나타납니다. - 여기에 보통
Add dependency on '패키지명'이라는 옵션이 보일 거예요! 이걸 클릭하면, VS Code가 알아서pubspec.yaml파일에 해당 패키지를 추가하고import문까지 작성해준답니다! 🪄
자, 여기 별표 세 개! ⭐⭐⭐ VS Code의 ‘빠른 수정’ 기능은 코딩 속도를 엄청나게 올려주는 핵심 기능 중 하나이니 꼭 익혀두세요!
여러분, 우리가 사는 이 현실 세계에는 수많은 ‘물건’들이 있잖아요? 컵, 자동차, 스마트폰, 그리고 저 멀리 우주를 날아다니는 우주선까지! 🚀 프로그래밍에서도 이렇게 현실 세계에 존재하는 ‘객체(Object)’들을 코드 안으로 똑같이 가져와서 표현할 수 있답니다! 이걸 가능하게 해주는 마법 같은 도구가 바로 클래스(Class)예요!
🏗️ 클래스, 현실을 코드로 옮기는 설계도!
Dart에서 클래스는 마치 현실 세계의 물건을 만들기 위한 ‘설계도’ 또는 ‘붕어빵 틀’과 같아요! 🐟 이 설계도 한 장만 잘 만들어두면, 이 설계도에 따라 똑같은 모양의 물건들을 무한히 만들어낼 수 있는 거죠.
그리고 우리 똑똑한 VS Code는 이렇게 복잡해 보이는 클래스 구조도 접었다 펼 수 있는 기능을 제공해서, 코드가 길어져도 깔끔하게 관리할 수 있게 도와줘요. 마치 복잡한 설계도를 필요할 때만 펼쳐서 보고, 다 보면 다시 접어두는 것과 같죠! 📁
예를 들어, 우리가 우주선을 코드로 표현하고 싶다고 생각해봅시다. 우주선은 어떤 특징(속성)을 가지고 있고, 어떤 행동(동작)을 할 수 있을까요? 이런 것들을 클래스 안에서 정의할 수 있어요!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Spacecraft { // 🚀 속성 (Properties): 우주선이 가지고 있는 특징들 String name; // 우주선의 이름 (문자열) DateTime? launchDate; // 발사일 (날짜 및 시간, '?'는 없을 수도 있다는 뜻) // ⚡ 계산된 속성 (Getter): 기존 속성을 바탕으로 새로운 값을 계산해서 보여줘요! int? get launchYear => launchDate?.year; // 발사일에서 '년도'만 가져와서 보여줌 // (여기에 더 많은 속성들이 생략될 수 있어요... 예를 들어, 연료량, 승무원 수 등) // 🛠️ 생성자 (Constructor): 우주선을 만들 때 초기 값을 설정하는 부분 // Spacecraft(this.name, this.launchDate); // 이렇게 만들 수 있어요! // 🏃‍♂️ 동작 (Methods): 우주선이 할 수 있는 행동들 void describe() { // 우주선 정보를 설명하는 동작 print('✨ Spacecraft 정보 ✨'); print('이름: ${name}'); if (launchDate != null) { // 발사일이 있다면 print('발사일: ${launchDate!.year}년 ${launchDate!.month}월 ${launchDate!.day}일'); print('발사 후 ${DateTime.now().difference(launchDate!).inDays}일째 비행 중!'); } else { print('발사일: 미정'); } } } |
위 Spacecraft 클래스를 보시면, name, launchDate는 우주선이 ‘가지고 있는’ 속성들이에요. 그리고 describe()는 우주선이 ‘할 수 있는’ 동작(메서드)이죠. launchYear처럼 기존 데이터로 새로운 값을 ‘계산해서 보여주는’ 속성도 만들 수 있답니다!
이렇게 설계도를 만들어 두면, 나중에 우리는 이 설계도를 가지고 실제 우주선 객체들을 무한히 찍어낼 수 있어요!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void main() { // 설계도를 바탕으로 실제 '객체' 만들기! (붕어빵 찍어내듯!) var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5)); voyager.describe(); // 보이저 1호의 정보를 설명하는 동작 실행! print('---'); var apollo = Spacecraft('Apollo 11', DateTime(1969, 7, 16)); apollo.describe(); // 아폴로 11호 정보 설명! print('---'); var futureShip = Spacecraft('Future Explorer', null); // 아직 발사일이 정해지지 않은 우주선 futureShip.describe(); } |
어때요, 정말 신기하죠? 🤩 현실의 복잡한 우주선을 코드로 명확하게 정의하고, 그 설계도를 바탕으로 여러 우주선 객체를 만들어내는 느낌!
자, 여기 별표 세 개! ⭐⭐⭐ 클래스는 객체 지향 프로그래밍의 핵심 중의 핵심이에요! 현실 세계의 복잡한 문제들을 코드로 깔끔하게 구조화하고 해결하는 가장 강력한 도구랍니다! 이 개념을 잘 이해하면 어떤 복잡한 시스템이든 효율적으로 만들 수 있을 거예요! 현실을 코드로 옮기는 느낌, 감 오시나요? 😉
Spacecraft(this.name, this.launchDate); 에서 this를 사용하는 이유?
이 부분은 Dart 언어의 아주 편리하고 강력한 기능 중 하나인 ‘생성자 파라미터 단축 문법(Constructor Parameter Shorthand)’이랍니다! 🚀
프로그래밍에서 this 키워드는 “지금 내가 작업하고 있는 바로 이 객체 자신“을 가리킬 때 사용해요. 예를 들어, ‘우주선’이라는 설계도(클래스)로 ‘보이저 1호’라는 실제 우주선 객체를 만들었다면, ‘보이저 1호’ 객체 안에서 this는 ‘보이저 1호’ 자신을 뜻하는 거죠! 🛰️
일반적으로 클래스를 만들 때, 우리는 객체를 ‘생성(create)’하면서 필요한 초기 값들을 넘겨주곤 합니다. 이 초기 값들을 받아서 객체의 속성(변수)에 넣어주는 역할을 하는 게 바로 생성자(Constructor)인데요.
Spacecraft(this.name, this.launchDate); 이 코드는 다음 두 가지를 동시에 처리하는 아주 간편한 방법이에요!
- 생성자의 인자로 값을 받습니다:
name과launchDate라는 값을 외부에서 받아요. - 받은 값을 클래스 내부의 동일한 이름의 속성(변수)에 바로 할당합니다: 여기서
this.name은 “이 객체(this)의name이라는 속성에, 생성자로 넘어온name값을 넣어줘!” 라는 뜻이에요.this.launchDate도 마찬가지고요.
📜 만약 this를 사용하지 않았다면? (비교!)
만약 이 편리한 단축 문법이 없었다면, 우리는 생성자를 이렇게 길게 작성해야 했을 거예요.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Spacecraft { String name; DateTime? launchDate; // ... (다른 속성들) // ✨ this.name, this.launchDate를 사용한 간결한 생성자 // Spacecraft(this.name, this.launchDate); // 😵‍💫 this.name, this.launchDate를 사용하지 않은 생성자 (더 길죠?) Spacecraft(String name, DateTime? launchDate) { this.name = name; // 넘어온 name 값을 이 객체의 name 속성에 할당 this.launchDate = launchDate; // 넘어온 launchDate 값을 이 객체의 launchDate 속성에 할당 } void describe() { print('Spacecraft: $name'); // ... } } |
어때요? Spacecraft(this.name, this.launchDate);가 훨씬 더 간결하고 직관적이죠? 🤩 마치 “재료를 받으면 바로 내 이름표가 붙은 칸에 넣어줘!” 라고 말하는 것과 같아요.
섹션 9: Enum, 고정된 값 나열하기
여러분, 식당에 가서 메뉴판을 보면 어떠세요? 📋 주문할 수 있는 음식 종류가 딱 정해져 있죠? “김치찌개”, “된장찌개”, “부대찌개”… 이렇게 정해진 몇 가지 선택지 안에서만 고를 수 있잖아요! 고정된 값만 사용할 수 있어서 실수를 줄일 수 있죠.
프로그래밍에서도 이렇게 고정된 몇 가지 값들만 사용하고 싶을 때가 있답니다! 이때 사용하는 아주 유용한 도구가 바로 Enum (이넘)이에요! 🌟 Enum은 ‘열거형’이라고도 부르는데, 말 그대로 ‘값을 열거해서 정해두는’ 방식인 거죠.
🗒️ Enum, 고정된 메뉴판 만들기!
Dart의 Enum은 딱 정해진, 변경될 수 없는 값들을 목록으로 만들어 나열하는 방법이에요. 이건 마치 우리가 식당에서 “이 메뉴들 중에서만 골라주세요!” 하고 메뉴판을 보여주는 것과 같아요! 💁♀️
그리고 역시 우리 똑똑한 VS Code! 🦸♂️ Enum 값을 입력할 때도 자동 완성 기능을 제공해서, 우리가 실수할 일 없이 메뉴판에 있는 항목 중에서 정확히 골라 입력할 수 있도록 도와준답니다! 이건 마치 메뉴판을 보고 손가락으로 가리키면 점원이 “네, 알겠습니다!” 하고 바로 주문을 받아주는 것과 똑같죠!
예를 한번 볼까요? 우리가 우주에 있는 행성들을 분류하고 싶을 때, 행성의 종류를 딱 몇 가지로 정해둘 수 있겠죠?
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
enum PlanetType { terrestrial, // 지구형 행성 (암석으로 이루어진 행성들) gas, // 가스형 행성 (목성, 토성처럼 가스로 이루어진 행성들) ice // 얼음형 행성 (천왕성, 해왕성처럼 얼음 성분이 많은 행성들) } void main() { // 행성을 Enum으로 정의된 타입으로 지정 PlanetType earthType = PlanetType.terrestrial; PlanetType jupiterType = PlanetType.gas; PlanetType neptuneType = PlanetType.ice; print('지구는 ${earthType} 행성입니다.'); print('목성은 ${jupiterType} 행성입니다.'); print('해왕성은 ${neptuneType} 행성입니다.'); // 만약 다른 행성 종류를 넣으려고 하면 에러가 나겠죠? // PlanetType someType = 'unknown'; // 이렇게는 안 돼요! } |
이 코드는 행성의 종류를 terrestrial, gas, ice 딱 세 가지로 고정해놓은 거예요. 🌍 그럼 나중에 우리가 어떤 행성의 종류를 코드로 나타낼 때, 이 세 가지 중 하나만 선택해서 사용해야 하니, 오타를 내거나 잘못된 값을 입력할 일이 확 줄어들겠죠? 실수를 미연에 방지할 수 있는 아주 강력한 기능이랍니다! 💪
자, 여기 별표 세 개! ⭐⭐⭐ Enum은 프로그램의 안정성을 높여주고, 코드를 훨씬 명확하게 만들어주는 중요한 도구예요! 고정된 선택지가 필요한 곳이라면 어디든 이 친구를 불러주세요! 이해되셨나요? 감 오시나요? 😉
섹션 10: 상속과 믹스인, 코드 재사용의 기술
우리가 코드를 작성하다 보면 비슷한 기능이나 속성을 여러 클래스에서 반복해서 써야 할 때가 많아요. 🔄 이럴 때마다 매번 똑같은 코드를 다시 작성한다면… 시간 낭비는 물론이고, 코드도 너무 길고 복잡해지겠죠? 😫
하지만 걱정 마세요! Dart에는 이런 ‘코드 재사용’을 아주 스마트하게 할 수 있는 두 가지 강력한 기술이 있답니다! 바로 상속(Inheritance)과 믹스인(Mixin)이에요! 🚀 이건 마치 부모에게 좋은 유전자를 물려받거나, 여러 전문가에게 필요한 기술을 배우는 것과 같죠!
🤝 상속, 부모의 유산을 물려받기!
먼저 상속은 말 그대로 ‘부모 클래스의 기능을 자식 클래스가 물려받는’ 개념이에요. 👨👩👧 마치 부모님에게 키나 성격 같은 유전자를 물려받는 것처럼, 자식 클래스는 부모 클래스의 속성과 동작을 그대로 사용할 수 있게 되는 거죠!
우리 VS Code에서는 이런 상속 구조를 쉽게 탐색할 수 있어서, 어떤 클래스가 누구의 기능을 물려받았는지 한눈에 파악하기 좋답니다. 🗺️
예를 들어, 아까 만들었던 Spacecraft 클래스를 부모로 삼아, 지구 궤도를 도는 우주선 Orbiter 클래스를 만들어볼까요?
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Orbiter extends Spacecraft { // 'extends' 키워드로 Spacecraft의 기능을 물려받아요! double altitude; // 궤도선(Orbiter)만이 가진 고도(altitude) 속성 // Orbiter의 생성자 Orbiter(String name, DateTime? launchDate, this.altitude) : super(name, launchDate); // 부모(Spacecraft)의 생성자를 호출해서 초기화! // describe() 같은 Spacecraft의 모든 동작을 Orbiter도 사용할 수 있어요! // Orbiter만의 특별한 동작을 추가할 수도 있죠! void orbitDescription() { describe(); // 부모의 describe() 동작 사용! print('현재 고도: ${altitude} km'); } } void main() { var hubble = Orbiter('Hubble Space Telescope', DateTime(1990, 4, 24), 540.0); hubble.orbitDescription(); // Orbiter의 고유 동작 실행! // hubble.name; // 부모로부터 물려받은 name 속성도 사용 가능! } |
여기서 Orbiter extends Spacecraft라는 부분이 보이시죠? 🌟 이건 “내 Orbiter 클래스는 Spacecraft 클래스의 모든 기능을 물려받을 거야!”라고 선언하는 겁니다. 덕분에 Orbiter는 Spacecraft가 가진 name, launchDate 같은 속성과 describe() 같은 동작을 따로 만들지 않아도 바로 쓸 수 있게 되는 거죠! 효율적이죠? 👍
🥣 믹스인, 요리에 다양한 양념을 섞듯이!
그런데 말이에요! Dart는 ‘단일 상속’만 지원해요. 즉, 한 클래스는 딱 하나의 부모 클래스만 가질 수 있답니다. 👨👧👦 이건 마치 한 사람이 태어날 때 부모가 두 분인 것처럼, 클래스도 하나의 ‘본가’만 가질 수 있다는 거죠.
하지만 우리가 어떤 특별한 ‘능력’을 여러 클래스에 주고 싶을 때가 있잖아요? 예를 들어, “날아다니는” 능력, “뛰어다니는” 능력, “소리를 내는” 능력 같은 것들 말이죠. 🦅 이런 능력을 모두 상속으로만 처리하려면 복잡한 부모-자식 관계가 만들어지고 머리가 아파져요. 😵💫 이때 등장하는 히든카드! 바로 믹스인(Mixin)입니다!
믹스인은 마치 요리에 다양한 양념을 섞듯이, 특정 기능을 필요한 클래스에 유연하게 추가할 수 있는 방법이에요! 🧂 이것 때문에 우리는 상속을 사용하지 않고 믹스인을 활용하는 아주 중요한 이유들이 있답니다. 자, 여기 별표 세 개! ⭐⭐⭐ 이거 시험에 무조건 나옵니다!
💡 믹스인, 왜 상속 대신 사용할까요?
-
- 다중 상속 문제 회피 🚫:
- Dart는 단일 상속만 허용해요. 한 클래스는 하나의 부모 클래스만 가질 수 있죠. 마치 한 사람에게 친부모가 두 쌍일 수 없는 것처럼요.
- 하지만 믹스인은 여러 개를 한 클래스에 적용해서 다양한 기능을 한꺼번에 추가할 수 있게 해줘요! 🤩 이건 마치 한 사람에게 여러 명의 ‘전문 멘토’들이 각자 다른 기술을 가르쳐주는 것과 같죠. 복잡한 다중 상속에서 발생할 수 있는 ‘다이아몬드 문제’ 같은 골치 아픈 상황을 피할 수 있답니다!
- 코드 재사용성 강화 💪:**
- 믹스인은 특정 기능(예: ‘날아다니는’ 기능)을 따로 캡슐화해서, 필요한 어떤 클래스든 가져다 쓸 수 있도록 해요. 📦
- 상속은 “이것은 ~의 일종이다(is-a)”라는 계층 구조를 강제하지만, 믹스인은 “이것은 ~할 수 있는 능력을 가졌다(has-a ability)”는 식으로 더 유연하게 기능들을 조합할 수 있게 해줍니다. 🤸♀️ 예를 들어,
Flyable이라는 믹스인을 만들어서 새, 비행기, 심지어 날아다니는 자동차 클래스에도 쉽게 ‘날 수 있는’ 기능을 추가할 수 있는 거죠! 🚀
- 다중 상속 문제 회피 🚫:
- 계층 구조의 단순화 🌳:**
- 모든 관계가 상속처럼 계층적일 필요는 없어요. 믹스인은 굳이 부모-자식 관계를 만들지 않고도 특정 능력이나 동작을 부여할 때 아주 적합하답니다. 복잡하고 불필요한 상속 계층 구조를 만들 필요가 없으니 코드가 훨씬 깔끔해지겠죠! 🍃
- 제약과 유연성 🎯:**
- 믹스인은 생성자를 가질 수 없고, 독립적으로 혼자서 객체로 만들어질 수도 없어요. 이건 믹스인이 특정 기능을 제공하는 데에만 집중하도록 설계되었기 때문이랍니다. 💪 상속 대신 믹스인을 사용하면 클래스 설계가 더 명확하고 간결해질 수 있어요!
믹스인(Mixin)이 실제로 어떻게 사용되는지 예제로 직접 보여드릴게요! 🤩 마치 ‘만능 소스’ 레시피를 만드는 과정을 직접 보여드리는 것과 같죠! 👨🍳
🥣 믹스인(Mixin) 예제: ‘날 수 있는 능력’ 부여하기!
우리가 새, 비행기, 그리고 슈퍼히어로까지, 여러 종류의 ‘날 수 있는 것’들을 코드로 만들고 싶다고 상상해봅시다. 🦅🛩️🦸♂️ 이 모든 것들이 ‘날아다닌다’는 공통된 능력을 가지고 있지만, 그렇다고 새가 비행기의 부모 클래스도 아니고, 비행기가 슈퍼히어로의 부모 클래스도 아니죠? 이럴 때 믹스인이 빛을 발합니다!
1단계: 믹스인 정의하기 (만능 소스 레시피 만들기!)
먼저, ‘날 수 있는 능력’을 정의하는 믹스인을 만들어볼까요? 믹스인은 mixin 키워드를 사용해서 정의합니다.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
mixin Flyable { // 'Flyable'이라는 날 수 있는 능력을 가진 믹스인 정의! void fly() { print('하늘을 날아갑니다! 🚀'); } void land() { print('안전하게 착륙합니다. 🛬'); } // 날 수 있는 모든 객체가 공유할 속성도 가질 수 있어요! bool _isFlying = false; // 현재 날고 있는지 여부 void takeOff() { if (!_isFlying) { print('이륙합니다! 슈웅~'); _isFlying = true; } else { print('이미 날고 있어요!'); } } void stopFlying() { if (_isFlying) { print('비행을 멈추고 하강합니다.'); _isFlying = false; } else { print('이미 땅에 있어요!'); } } } |
어때요? Flyable 믹스인은 fly(), land(), takeOff(), stopFlying() 같은 ‘날 수 있는’ 행동들을 정의하고 있죠? 이건 마치 모든 요리에 두루 쓸 수 있는 ‘마법의 양념 레시피’를 만들어 둔 것과 같아요! ✨
2단계: 클래스에 믹스인 적용하기 (요리에 만능 소스 뿌리기!)
이제 이 Flyable 믹스인을 여러 클래스에 적용해볼까요? 클래스에 믹스인을 적용할 때는 with키워드를 사용합니다!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class Bird { // 새 클래스 String name; Bird(this.name); void chirp() { print('${name}이(가) 지저귑니다. 🎶'); } } class Airplane { // 비행기 클래스 String model; Airplane(this.model); void startEngine() { print('${model} 엔진을 가동합니다. ⚙️'); } } class Superhero { // 슈퍼히어로 클래스 String heroName; Superhero(this.heroName); void useSuperpower() { print('${heroName}이(가 특별한 능력을 사용합니다!'); } } // 이제 각각의 클래스에 'Flyable' 믹스인을 적용해봅시다! class Eagle extends Bird with Flyable { // 독수리는 새이면서 날 수 있는 능력도 가짐! Eagle(super.name); // 부모 클래스(Bird)의 생성자 호출 } class Jet extends Airplane with Flyable { // 제트기는 비행기이면서 날 수 있는 능력도 가짐! Jet(super.model); // 부모 클래스(Airplane)의 생성자 호출 } class Superman extends Superhero with Flyable { // 슈퍼맨은 슈퍼히어로이면서 날 수 있는 능력도 가짐! Superman(super.heroName); // 부모 클래스(Superhero)의 생성자 호출 } |
보이시나요? with Flyable이라는 마법의 주문 덕분에 Eagle, Jet, Superman 클래스들은 서로 다른 부모 클래스를 가지고 있음에도 불구하고, 모두 Flyable 믹스인의 fly(), land(), takeOff() 등의 동작들을 사용할 수 있게 되었어요! 🤩 이건 마치 김치찌개에도, 스테이크에도, 파스타에도 만능 소스를 뿌려서 특별한 맛을 내는 것과 똑같죠!
3단계: 믹스인 기능 사용하기 (만능 소스로 요리 맛보기!)
이제 실제로 이 클래스들의 기능을 사용해봅시다!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
void main() { var 독수리 = Eagle('하늘이'); print('--- 독수리 ---'); 독수리.chirp(); // Bird의 동작 독수리.takeOff(); // Flyable 믹스인의 동작 독수리.fly(); // Flyable 믹스인의 동작 독수리.stopFlying(); // Flyable 믹스인의 동작 독수리.land(); // Flyable 믹스인의 동작 print('\n--- 제트기 ---'); var 제트기 = Jet('F-16'); 제트기.startEngine(); // Airplane의 동작 제트기.takeOff(); // Flyable 믹스인의 동작 제트기.fly(); // Flyable 믹스인의 동작 print('\n--- 슈퍼맨 ---'); var 슈퍼맨 = Superman('클라크 켄트'); 슈퍼맨.useSuperpower(); // Superhero의 동작 슈퍼맨.takeOff(); // Flyable 믹스인의 동작 슈퍼맨.fly(); // Flyable 믹스인의 동작 } |
결과를 보시면, Eagle, Jet, Superman이 각자의 고유한 동작(chirp, startEngine, useSuperpower)뿐만 아니라, takeOff(), fly(), stopFlying(), land() 같은 Flyable 믹스인의 동작들도 모두 사용하고 있는 것을 확인할 수 있을 거예요! 🚀
implements는 ‘계약(Contract)’에 중점을 둔 코드 재사용 방식이고, with Mixin은 ‘구현(Implementation)’에 중점을 둔 코드 재사용 방식
섹션 11: 인터페이스와 추상 클래스, 설계도 만들기
여러분, 멋진 건물을 지을 때 뭘 제일 먼저 할까요? 🏗️ 바로 설계도를 그리는 일이죠! 어떤 모양으로 지을지, 방은 몇 개로 할지, 어디에 문을 낼지 등등 구체적으로 계획을 세우는 것이 중요하잖아요?
프로그래밍에서도 마찬가지예요! 복잡한 프로그램을 만들기 전에, 어떤 기능들이 필요하고 어떻게 작동해야 할지 미리 ‘설계도’를 그려두는 것이 아주 중요하답니다. Dart에는 이런 설계도를 만드는 데 아주 유용한 두 가지 개념이 있어요. 바로 인터페이스(Interface)와 추상 클래스(Abstract Class)입니다! 🚀
📜 인터페이스, 기능 명세서 만들기!
Dart에서는 아주 독특하게도, 모든 클래스가 자동으로 ‘인터페이스’ 역할을 할 수 있어요! 😲 이건 마치 모든 요리사가 자신만의 ‘레시피 목록’을 가지고 있는 것과 같아요. 다른 요리사는 이 레시피 목록을 보고 “아, 이 요리사는 이런 요리들을 만들 수 있구나!” 하고 알 수 있죠.
어떤 클래스가 다른 클래스를 ‘인터페이스’로 사용한다는 것은, “내가 저 클래스처럼 똑같은 기능들을 반드시 구현할게!“라고 약속하는 것과 같아요. 🤝 마치 “저 Master Chef의 모든 레시피를 내가 똑같이 만들어내겠습니다!” 하고 선언하는 것과 같습니다!
Dart에서 “내가 이 클래스의 기능을 반드시 구현할게!” 하고 약속하며 인터페이스를 구현할 때 사용하는 키워드가 있습니다! 바로 implements입니다! 🤝
1단계: 인터페이스 역할을 할 ‘클래스’ 정의하기
먼저, 우리가 ‘계약’으로 사용할 클래스를 하나 정의해봅시다. 여기서는 Vehicle이라는 클래스를 만들어서, 모든 차량이 가져야 할 기본적인 동작(메서드)을 정의해볼게요.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Vehicle { // 이 클래스가 인터페이스 역할을 할 거예요! void start() { // 차량이 시작하는 동작 print('차량이 시동을 겁니다.'); } void stop() { // 차량이 정지하는 동작 print('차량이 멈춥니다.'); } // 이 외에 Vehicle이 가질 수 있는 다른 속성이나 메서드들도 포함될 수 있어요. // 예를 들어, String model; 같은 속성도 가능합니다. } |
여기서 Vehicle 클래스는 구체적인 구현을 가지고 있지만, 다른 클래스가 이 Vehicle을 implements하면, Vehicle의 모든 공개(public) 메서드(start(), stop())를 반드시 구현해야 한다는 ‘계약’이 됩니다! 📜
2단계: implements 키워드로 인터페이스 구현하기!
이제 ElectricCar라는 새로운 클래스를 만들어서, 이 Vehicle 클래스의 ‘계약’을 따르겠다고 선언해볼게요. implements Vehicle이라고 써주는 거죠!
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
class ElectricCar implements Vehicle { // Vehicle의 인터페이스를 구현합니다! String batteryType; // 전기차만의 고유 속성 ElectricCar(this.batteryType); // 생성자 @override // Vehicle 인터페이스의 start() 메서드를 구현해야 합니다! void start() { print('전기차가 조용히 시동을 겁니다! (배터리: ${batteryType}) 🔋'); // 여기에 전기차만의 특별한 시동 로직을 넣을 수 있죠! } @override // Vehicle 인터페이스의 stop() 메서드를 구현해야 합니다! void stop() { print('전기차가 에너지 회수하며 정지합니다. ⚡'); // 여기에 전기차만의 특별한 정지 로직을 넣을 수 있어요! } // 전기차만의 추가 동작도 만들 수 있어요! void charge() { print('전기차를 충전합니다. 🔌'); } } class GasolineCar implements Vehicle { // 휘발유 차도 Vehicle 인터페이스를 구현합니다! String fuelType; GasolineCar(this.fuelType); @override void start() { print('휘발유 차가 시끄럽게 시동을 겁니다! (연료: ${fuelType}) ⛽'); } @override void stop() { print('휘발유 차가 부드럽게 정지합니다. 🛑'); } } void main() { var tesla = ElectricCar('리튬이온'); tesla.start(); // ElectricCar의 start() 동작 실행! tesla.stop(); // ElectricCar의 stop() 동작 실행! tesla.charge(); // ElectricCar의 고유 동작 실행! print('\n---'); var sonata = GasolineCar('무연 휘발유'); sonata.start(); // GasolineCar의 start() 동작 실행! sonata.stop(); // GasolineCar의 stop() 동작 실행! } |
보이시나요? ElectricCar와 GasolineCar 모두 implements Vehicle을 사용해서 Vehicle 클래스가 가진 start()와 stop() 메서드를 각자의 방식으로 구현하고 있죠? 🤩
ElectricCar는 전기차답게 조용히 시동을 걸고 에너지를 회수하며 정지하고,GasolineCar는 휘발유 차답게 시끄럽게 시동을 걸고 부드럽게 정지합니다.
🌟 implements 키워드의 중요성!
- 강력한 계약 🤝:
implements를 사용하면, 해당 클래스는 인터페이스로 사용된 클래스의 모든 공개 멤버(변수, 메서드)를 반드시 구현해야 한다는 강력한 계약을 맺게 돼요. 만약 하나라도 빠뜨리면 VS Code가 바로 에러를 띄워줍니다! 🚨 - 역할 정의 🎯:
implements는 “이 클래스는 이러이러한 역할을 수행할 수 있다”고 명확하게 선언하는 방법이에요. - 다형성 ✨:
ElectricCar와GasolineCar는 서로 다른 구현을 가지고 있지만, 둘 다Vehicle이라는 ‘인터페이스’ 역할을 수행하기 때문에, 나중에Vehicle타입으로 묶어서 처리할 수도 있답니다! (이건 ‘다형성’이라는 또 다른 멋진 개념이에요!)
여러 역할을 구현할 수 있어야 하기 때문에, class Amphibian implements Walkable, Swimmable 처럼, 콤마(,)로 구분해서 여러 개의 클래스(인터페이스)를 동시에 implements 할 수 있습니다.
🔑 Dart의 공개/비공개 구분 방식: 키워드 대신 ‘_’ (별표 세 개! ⭐⭐⭐)
다른 객체지향 프로그래밍 언어(예: Java의 public, private, protected나 C++의 public:, private:)와 달리, Dart는 ‘공개’와 ‘비공개’를 명시적으로 구분하는 별도의 키워드를 가지고 있지 않습니다! 😲
“어? 그럼 어떻게 구분하죠?” 하고 궁금해하실 텐데요! Dart는 아주 간단하고 직관적인 규칙을 사용합니다. 바로 ‘_’ (언더스코어) 접두사예요! underscore를 붙이느냐 안 붙이느냐에 따라 공개/비공개 여부가 결정된답니다!
1. 🌐 공개 (Public) 멤버: 언더스코어가 없으면 공개!
Dart에서 클래스나 믹스인, 함수 등의 멤버(변수나 메서드) 앞에 언더스코어(_)가 붙지 않으면, 기본적으로 ‘공개(Public)’ 멤버가 됩니다.
- 이 멤버들은 어떤 라이브러리(파일)에서든 자유롭게 접근할 수 있어요.
- 마치 누구나 볼 수 있는 ‘공개 게시판’에 글을 올리는 것과 같아요. 📣
2. 🤫 비공개 (Private) 멤버: 언더스코어가 붙으면 비공개!
반대로, 클래스나 믹스인, 함수 등의 멤버 앞에 언더스코어(_)가 붙으면, 그 멤버는 ‘비공개(Private)’ 멤버가 됩니다.
- 하지만 여기서 중요한 점! Dart의 비공개는 ‘라이브러리-레벨 비공개(Library-level private)’예요. 즉, 그 멤버가 정의된 같은 파일(같은 라이브러리) 안에서만 접근할 수 있고, 다른 파일에서는 접근할 수 없다는 뜻이에요! 🔒
- 이건 마치 ‘우리 가족만 볼 수 있는 앨범’ 같은 거죠. 같은 파일(가족) 안에서는 다 볼 수 있지만, 다른 파일(다른 가족)에서는 볼 수 없는 거예요. 다른 언어의 ‘클래스-레벨 비공개’와는 약간 다릅니다. 같은 파일이라는 ‘가족’ 안에 있기 때문에 서로의 ‘비공개’ 속 사정까지도 공유할 수 있는 거예요. 하지만 만약 부모 클래스가 정의된 파일과 자식 클래스가 정의된 파일이 서로 다르다면, 자식 클래스는 부모 클래스의 비공개(
_) 멤버에 접근할 수 없습니다! 🙅♂️ 이것이 바로protected와 가장 큰 차이점이에요.protected는 파일이 다르더라도 자식 클래스라면 접근을 허용하지만, Dart의_비공개는 ‘파일’을 기준으로 하기 때문에 다른 파일에 있는 자식 클래스는 부모의 비공개 멤버에 접근할 수 없답니다.
예제를 통해 확실히 이해해봅시다!
아까 만들었던 Spacecraft 클래스를 활용해서 공개/비공개를 구분해볼게요.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
// spacecraft_info.dart 파일이라고 가정해봅시다. // 이 파일 전체가 하나의 '라이브러리'가 됩니다. class Spacecraft { String name; // 공개 (언더스코어 없음) DateTime? launchDate; // 공개 (언더스코어 없음) // 비공개 속성: 이 파일(spacecraft_info.dart) 안에서만 접근 가능 int _fuelLevel = 100; // 연료 잔량 (언더스코어 있음) Spacecraft(this.name, this.launchDate); // 공개 메서드 void describe() { print('✨ Spacecraft: $name'); if (launchDate != null) { print('발사일: ${launchDate!.year}년 ${launchDate!.month}월 ${launchDate!.day}일'); } else { print('발사일: 미정'); } // 같은 파일 안에서는 비공개 멤버에 접근 가능! print('현재 연료: ${_fuelLevel}%'); } // 비공개 메서드: 이 파일(spacecraft_info.dart) 안에서만 호출 가능 void _refuel(int amount) { _fuelLevel += amount; if (_fuelLevel > 100) _fuelLevel = 100; print('연료 ${amount}% 충전 완료! 현재 ${_fuelLevel}%'); } // 공개 메서드를 통해 비공개 메서드에 간접적으로 접근 void performMaintenance() { print('점검 시작...'); _refuel(20); // 내부적으로 비공개 메서드를 호출 print('점검 완료!'); } } // 같은 파일(spacecraft_info.dart) 안에 있는 main 함수 void main() { var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5)); voyager.describe(); // 공개 멤버는 자유롭게 접근 가능 print('우주선 이름: ${voyager.name}'); // 비공개 멤버는 같은 파일 안에서는 접근 가능 (이 파일이 spacecraft_info.dart 라면) print('비공개 연료 레벨 (같은 파일 내): ${voyager._fuelLevel}'); voyager._refuel(10); // 비공개 메서드도 같은 파일 내에서는 호출 가능 voyager.describe(); voyager.performMaintenance(); // 공개 메서드를 통해 비공개 기능 활용 } |
🚨 다른 파일에서 접근 시도하면?
만약 다른 파일(예: main.dart 파일)에서 spacecraft_info.dart에 있는 Spacecraft 클래스를 import해서 사용하려고 할 때, _fuelLevel이나 _refuel() 같은 비공개 멤버에 직접 접근하려고 하면 에러가 발생합니다!
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// main.dart 파일 import 'package:your_project/spacecraft_info.dart'; // spacecraft_info.dart 파일을 import void main() { var voyager = Spacecraft('Voyager II', DateTime(1977, 8, 20)); voyager.describe(); // !!! 에러 발생 !!! // print(voyager._fuelLevel); // 에러! 다른 파일에서는 비공개 멤버에 접근 불가 // voyager._refuel(30); // 에러! 다른 파일에서는 비공개 메서드에 접근 불가 } |
🚧 추상 클래스, 미완성 설계도!
반면에 추상 클래스(Abstract Class)는 말 그대로 ‘추상적인 설계도’예요. 📄 이건 마치 건물 전체의 큰 그림은 그려져 있지만, “여기에는 어떤 벽돌을 쓸지, 어떤 창문을 달지는 아직 정하지 않았어. 나중에 구체적으로 지을 때 정해!” 하고 일부 구체적인 구현을 남겨둔 설계도와 같아요. 👷♂️
추상 클래스는 abstract 키워드를 사용해서 만드는데요, 이런 특징을 가지고 있어요!
- 구체적인 구현 없이 기능만 정의할 수 있어요: “여기에
describe()라는 기능이 있어야 해!”라고 정의만 해두고, 실제describe()안에 어떤 코드를 넣을지는 자식 클래스에게 맡기는 거죠. - 자신만의 객체를 직접 만들 수 없어요: 미완성 설계도만 가지고 건물을 지을 수 없듯이, 추상 클래스 자체로는 객체를 만들 수 없답니다. 반드시 이 추상 클래스를 상속받는 자식 클래스에서 나머지 부분을 완성해야 해요!
우리 VS Code는 이런 추상 클래스를 아주 잘 다룬답니다! 🦸♂️ 만약 여러분이 추상 클래스를 상속받았는데, 구현해야 할 추상 메서드를 빼먹으면 바로 빨간 줄로 “여기 빠졌어!” 하고 미리 경고를 띄워줘서 아주 편리해요! 이건 마치 설계도를 보다가 “어? 창문 설계가 빠졌네?” 하고 바로 알려주는 것과 똑같죠!
예시 코드를 한번 볼까요?
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
// 'Describable'라는 추상 클래스 (설계도)를 만듭니다. abstract class Describable { // 'describe()'라는 기능을 반드시 구현해야 한다고 정의만 해둡니다. // 이 안에는 어떤 코드를 넣을지 정하지 않았어요. (미완성!) void describe(); // 추상 클래스도 구체적인 구현을 가진 메서드를 가질 수 있어요! void describeWithEmphasis() { print('--- 아주 중요한 정보입니다! ---'); describe(); // 추상 메서드를 호출! print('--------------------------'); } } // 'Spacecraft' 클래스가 'Describable' 추상 클래스를 상속받아서 완성해볼게요! class Spacecraft extends Describable { // 'Describable' 설계도를 물려받아! String name; DateTime? launchDate; Spacecraft(this.name, this.launchDate); <span style="color: #ff0000;"> @override</span> // 추상 클래스의 'describe()' 메서드를 반드시 구현해야 합니다! void describe() { print('✨ 우주선: ${name}'); if (launchDate != null) { print('발사일: ${launchDate!.year}년 ${launchDate!.month}월 ${launchDate!.day}일'); } else { print('발사일: 미정'); } } } // 또 다른 클래스도 'Describable'을 상속받아 사용할 수 있어요! class Planet extends Describable { String planetName; Planet(this.planetName); @override void describe() { // Planet에 맞는 describe()를 구현! print('🪐 행성: ${planetName}'); print('이 행성은 태양계에 속해 있습니다.'); } } void main() { var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5)); voyager.describe(); // Spacecraft의 describe 호출 voyager.describeWithEmphasis(); // 추상 클래스에서 물려받은 구현된 메서드 호출! print('\n---'); var mars = Planet('Mars'); mars.describe(); // Planet의 describe 호출 mars.describeWithEmphasis(); } |
위 코드를 보시면, abstract class Describable는 describe()라는 기능의 ‘존재’만 정의하고, Spacecraft나 Planet 같은 자식 클래스들이 각자의 방식으로 describe()를 ‘구현’하고 있죠? 그리고 describeWithEmphasis()처럼 구체적인 구현이 포함된 메서드도 가질 수 있어요!
섹션 12: 비동기, 기다리지 않고 효율적으로
Dart의 async와 await는 비동기 작업을 쉽게 처리하는 도구예요. VS Code에서는 비동기 코드의 흐름을 쉽게 파악할 수 있죠. 마치 요리하면서 설거지도 동시에 하는 것과 같아요. 예를 들어:
|
1 2 3 4 5 |
Future<void> printWithDelay(String message) async { await Future.delayed(Duration(seconds: 1)); print(message); } |
이건 1초 뒤 메시지를 출력하는 코드인데, 기다리는 동안 다른 일을 할 수 있어요. 효율적이지 않나요?
섹션 13: 예외 처리, 문제에 대처하기
Dart에서는 throw로 예외를 발생시키고, try-catch로 문제를 처리할 수 있어요. VS Code에서는 오류를 미리 감지해줘서 실수를 줄일 수 있죠. 마치 비가 올 때 우산을 준비하는 것과 같아요. 예를 들어:
|
1 2 3 4 |
if (astronauts == 0) { throw StateError('No astronauts.'); } |
문제가 생기면 미리 대비하는 게 중요하죠. 이해되셨나요?
섹션 14: Dart의 중요한 개념들
Dart에는 몇 가지 중요한 개념이 있어요. 모든 것이 객체라는 점, 타입을 명시하지 않아도 되는 타입 추론, 그리고 널 안전성 같은 기능들이죠. VS Code에서는 이런 개념을 쉽게 이해할 수 있도록 코드 힌트를 제공해요. 마치 게임의 기본 규칙을 아는 것과 같아요. 규칙을 알면 더 쉽게 프로그래밍을 할 수 있답니다. 자, 여기 별표 세 개! 이 개념들은 Dart의 핵심이에요!
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart와 VS Code의 핵심을 3줄로 요약하면:
- Dart는 초보자도 쉽게 접근 가능한 만능 프로그래밍 언어예요.
- VS Code는 코드 작성과 디버깅을 편리하게 해주는 강력한 도구죠.
- 변수, 함수, 클래스, 비동기 처리 등 다양한 문법을 익히며 효율적인 코드를 만들 수 있어요.
이 중 가장 중요한 건 기본 문법과 VS Code 사용법을 익히는 것이에요. 여러분, 오늘 배운 ‘Hello, World!’ 코드를 VS Code에서 직접 쳐보고 결과를 확인해보세요. 작은 실천이 큰 성장을 만듭니다! 다음 강의에서 더 재미있는 내용으로 찾아뵐게요. 감사합니다! 😊
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어에서 변수를 어떻게 다루는지, 그 핵심을 함께 파헤쳐 볼게요. 변수는 코딩의 기본 중 기본, 이걸 잘 이해하면 코드 작성 속도가 쑥쑥 올라간다는 사실! 자, 그럼 바로 시작해 볼까요? 🚀
변수란 무엇일까? 기본부터 잡기
여러분, 변수가 뭘까요? 쉽게 말해 변수는 데이터를 담는 작은 상자예요. 예를 들어, “Bob”이라는 이름을 저장하고 싶다면 상자에 “Bob”을 넣고 이름표로 ‘name’이라고 붙이는 거죠. Dart에서는 이렇게 쓰면 됩니다: var name = 'Bob';. 이 상자는 “Bob”이라는 값을 참조하고 있는 거예요. 타입을 명시하고 싶다면 String name = 'Bob';처럼 쓰거나, 좀 더 유연하게 Object name = 'Bob';처럼 사용할 수도 있답니다. 자, 여기 별표 세 개! 변수는 데이터를 저장하고 필요할 때 꺼내 쓰는 핵심 도구라는 점, 꼭 기억하세요!
널 안전성(Null Safety), 이건 뭐야?
Dart에서는 널 안전성이라는 멋진 기능이 있어요. 널(NULL)이 뭐냐고요? 데이터가 없는 상태, 즉 빈 상자를 생각하면 돼요. 널 안전성은 이 빈 상자를 잘못 열어서 에러가 나는 걸 미리 막아주는 안전장치예요. 예를 들어, 숫자 변수 i가 널인데 i.abs()를 호출하면 큰일 나죠. 다른 언어에서는 실행 중에 에러가 터질 수 있지만, Dart는 컴파일러가 미리 경고를 줘요.
타입 뒤에 ?를 붙이면 널이 될 수 있다고 선언하는 거예요. String? name은 상자가 비어 있을 수도 있다는 뜻이고, String name은 반드시 내용물이 있어야 한다는 뜻이에요. 자, 이거 시험에 나와요! 널 안전성은 런타임 에러를 줄이고 코드 안정성을 높여주는 Dart의 강력한 무기라는 점, 감 오시나요?
초기값과 초기화, 놓치면 안 돼!
변수를 선언했는데 값을 안 넣으면 어떻게 될까요? 널이 가능한 변수(int? lineCount)는 기본값이 널로 설정돼요. 하지만 널이 불가능한 변수는 반드시 값을 넣어야 해요. 예를 들어, int lineCount = 0;처럼요. Dart는 초기화하지 않은 변수를 사용하는 걸 막아주니 안심하세요.
조건문 안에서 값을 설정해도 Dart가 그걸 알아채요. if문으로 값을 설정하고 print(lineCount)를 호출해도 문제없는 거죠. 상자가 비어 있는지, 내용물이 있는지 미리 확인하는 셈이에요. 이해되시죠? 😊
Late 변수, 나중에 초기화하기
가끔은 변수 선언 후 나중에 값을 넣고 싶을 때가 있죠. 그럴 때 쓰는 게 late 키워드예요. 예를 들어, late String description;이라고 선언하고 나중에 description = 'Feijoada!';처럼 값을 넣을 수 있어요. 이건 초기화 비용이 큰 변수나, 초기화가 필요 없는 경우 유용해요.
비유하자면, 요리를 할 때 재료를 미리 준비하지 않고 손님이 오면 그때 재료를 꺼내는 것과 같아요. 단, 주의점! late 변수를 초기화하지 않고 사용하면 런타임 에러가 터지니 조심해야 해요. 자, 여기 별표 세 개! late는 나중에 초기화하는 똑똑한 방법이지만, 반드시 값이 들어가야 한다는 점 잊지 마세요!
Final과 Const, 변경 불가 변수의 비밀
변수를 절대 바꾸고 싶지 않다면? final과 const를 사용하세요. final은 한 번 설정하면 다시 못 바꾸는 변수예요. 예를 들어, final name = 'Bob';은 ‘Bob’을 다른 이름으로 바꿀 수 없어요. const는 컴파일 타임에 값이 고정되는 상수로, 더 엄격하죠. const bar = 1000000;처럼 숫자나 문자열 같은 고정값에 사용돼요.
비유하자면, final은 한 번 쓴 다이어리 페이지를 고정하는 핀 같고, const는 아예 책에 인쇄된 글자처럼 영원히 바뀌지 않는 거예요. 단, final 객체의 필드는 변경 가능하지만, const는 완전히 불변이라는 점, 기억하세요!
와일드카드 변수, 이름 없는 조력자
마지막으로 와일드카드 변수라는 재미있는 개념이 있어요. _라는 이름으로 선언하는데, 이건 값을 저장하지만 그 값을 사용할 수 없는 자리 표시자예요. 예를 들어, for (var _ in list) {}처럼 반복문에서 사용하거나, 에러를 잡을 때 catch (_) {}처럼 쓰죠.
비유하자면, 파티에서 이름 없는 손님이 와서 도와주고 사라지는 것과 같아요. 이름은 없지만 역할은 해내는 거죠. 이건 코드에서 불필요한 이름 충돌을 피할 때 유용하니 알아두세요!
오늘의 정리
자, 오늘 배운 내용을 3줄로 요약해 볼게요.
- 변수는 데이터를 담는 상자이며, Dart는 널 안전성으로 에러를 미리 잡아줍니다.
late,final,const로 초기화와 변경 여부를 조절하고, 와일드카드 변수_로 이름 충돌을 피할 수 있어요.- 초기화와 타입 선언을 잘 활용하면 안정적인 코드를 작성할 수 있습니다.
이 핵심을 머릿속에 꼭 담아두세요! 오늘의 실천 과제는 간단해요. Dart로 간단한 변수를 선언하고, final과 const를 사용해 보는 코드를 작성해 보세요. 여러분의 코드가 더 단단해질 거예요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어에서 사용하는 연산자(Operator)를 샅샅이 파헤쳐 볼게요. 연산자는 코딩의 기본 도구로, 숫자를 더하고 빼고, 조건을 확인하는 데 필수예요. 이 강의를 통해 연산자를 자유자재로 다룰 수 있게 되실 거예요. 자, 준비되셨나요? 출발! 🚀
연산자란 무엇일까? 기본 개념 잡기
여러분, 연산자가 뭘까요? 연산자는 쉽게 말해 계산기 버튼 같은 거예요. 숫자나 값을 넣고 버튼을 누르면 원하는 결과를 얻는 도구죠. Dart에서는 덧셈(+), 뺄셈(-), 곱셈(*) 같은 기본 연산자부터 조건 확인(==, >)까지 다양한 연산자를 지원해요. 연산자는 우선순위와 결합 방향이 있어서 어떤 순서로 계산되는지 알아야 해요. 자, 별표 세 개! 연산자는 코드에서 값을 다루는 핵심 열쇠라는 점, 꼭 기억하세요!
산술 연산자: 숫자 다루기의 기본
Dart의 산술 연산자는 우리가 일상에서 쓰는 계산과 똑같아요. +는 더하기, -는 빼기, *는 곱하기, /는 나누기예요. 특별히 ~/는 나누기 후 정수 부분만 가져오고, %는 나머지를 구해요. 예를 들어, 5 / 2는 2.5지만, 5 ~/ 2는 2, 5 % 2는 1이에요.
비유하자면, 케이크를 자르는 것과 같아요. /는 소수점까지 다 자르고, ~/는 큰 조각만 가져가고, %는 남은 조각을 확인하는 거죠. 또한 ++와 --로 값을 1씩 늘리거나 줄일 수 있어요. a++는 값을 먼저 사용한 후 증가, ++a는 증가 후 사용이에요. 이해되시죠? 😊
비교와 관계 연산자: 조건 확인의 핵심
비교 연산자는 두 값을 비교할 때 쓰는 도구예요. ==는 같음을, !=는 다름을 확인하고, >, <, >=, <=는 크기 비교를 해요. 예를 들어, 2 == 2는 참이고, 3 > 2도 참이에요.
이건 마치 친구 키를 비교하는 것과 같아요. 누가 더 크고, 같은지 바로 알 수 있죠. Dart에서 ==는 두 객체가 같은 값을 가지는지 확인하는 메서드 호출이에요. 자, 이거 시험에 나와요! 비교 연산자는 조건을 판단하는 데 필수라는 점, 감 오시나요?
논리 연산자: 참과 거짓을 조합하기
논리 연산자는 참(true)과 거짓(false)을 다룰 때 사용해요. &&는 AND(그리고), ||는 OR(또는), !는 NOT(부정)이에요. 예를 들어, !done && (col == 0 || col == 3)은 done이 거짓이고, col이 0이거나 3일 때 참이에요.
비유하자면, 친구 약속을 잡을 때를 생각해보세요. “비가 안 오고( !rain ), 친구가 시간이 되거나( friend1 || friend2 ) 내가 시간이 되면( me )” 만날 수 있는 거죠. 논리 연산자는 복잡한 조건을 쉽게 만들 수 있어요. 이 점, 꼭 기억하세요!
비트 연산자와 시프트 연산자: 숫자의 비밀 조작
비트 연산자는 숫자의 0과 1 단위를 조작하는 도구예요. &는 AND, |는 OR, ^는 XOR, ~는 보수예요. 시프트 연산자인 <<, >>, >>>는 비트를 왼쪽이나 오른쪽으로 밀어요. 예를 들어, 0x22 & 0x0f는 비트 단위로 AND 연산을 해서 0x02가 돼요.
이건 마치 레고 블록을 조립하거나 위치를 옮기는 것과 같아요. 작은 조각을 조합해서 새로운 모양을 만드는 거죠. 자, 별표 세 개! 비트 연산자는 정밀한 숫자 조작에 유용하다는 점, 놓치지 마세요!
조건부 연산자: 간결한 선택의 기술
Dart에는 조건부 연산자라는 멋진 도구가 있어요. ? :는 조건에 따라 값을 선택하고, ??는 널일 때 대체 값을 제공해요. 예를 들어, isPublic ? 'public' : 'private'는 조건이 참이면 ‘public’, 거짓이면 ‘private’을 반환해요. name ?? 'Guest'는 name이 널이면 ‘Guest’를 사용해요.
비유하자면, 메뉴를 고를 때 “햄버거 있으면 햄버거 먹고, 없으면 피자 먹자” 같은 선택이에요. 코드가 간결해지니 정말 편리하죠. 이해되셨나요?
캐스케이드 표기법: 연속 작업의 마법
캐스케이드 표기법인 ..와 ?..는 같은 객체에 여러 작업을 연속으로 할 때 사용해요. 예를 들어, Paint() 객체에 색상과 스타일을 설정하려면 Paint()..color = Colors.black..strokeWidth = 5.0;처럼 쓰면 돼요. ?..는 객체가 널일 수 있을 때 안전하게 작업을 해요.
이건 마치 그림을 그릴 때 붓에 색을 묻히고, 두께를 조절하고, 바로 선을 긋는 것과 같아요. 한 번에 여러 작업을 처리하니 코드가 깔끔해져요. 정말 멋지지 않나요?
오늘의 정리
자, 오늘 배운 내용을 3줄로 요약해 볼게요.
- Dart 연산자는 산술, 비교, 논리, 비트, 조건부 등 다양한 기능을 제공해요.
- 연산자 우선순위와 결합 방향을 이해하면 복잡한 표현도 쉽게 작성할 수 있어요.
- 캐스케이드 표기법(
..,?..)으로 연속 작업을 간결하게 처리할 수 있습니다.
이 핵심 메시지를 꼭 기억하세요! 오늘의 실천 과제는 간단해요. Dart에서 산술 연산자와 조건부 연산자를 사용해 간단한 계산 코드를 작성해 보세요. 연산자 활용이 몸에 익을 거예요.
문서화 주석: 전문가처럼 코드 설명하기
문서화 주석은 ///로 시작하는 한 줄 주석이나 /**로 시작하는 여러 줄 주석을 사용해요. 연속된 ///는 여러 줄 문서화 주석과 같은 효과를 내죠. 이 주석은 코드의 클래스, 메서드, 변수 등을 설명하고, 자동으로 HTML 문서를 생성하는 데 사용돼요.
특히 대괄호 [ ] 안에 클래스나 메서드 이름을 넣으면, 생성된 문서에서 링크로 연결돼요. 예를 들어:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/// 남아메리카의 가축화된 낙타과 동물 (Lama glama). /// /// 안데스 문화에서는 히스패닉 이전 시대부터 라마를 고기와 짐 운반용으로 사용했어요. /// 다른 동물처럼, 라마도 먹어야 하니 [feed]로 [Food]를 주는 걸 잊지 마세요. class Llama { String? name; /// 라마에게 [food]를 먹입니다. /// /// 일반적인 라마는 일주일에 건초 한 묶음을 먹어요. void feed(Food food) { // ... } } |
비유하자면, 책에 상세한 해설과 참조 링크를 추가해 독자가 더 쉽게 이해하도록 돕는 것과 같아요. 자, 이거 시험에 나와요! 문서화 주석은 코드의 공식 설명서 역할을 한다는 점, 꼭 알아두세요!
안녕하세요, 일타 강사 저스틴입니다! 오늘은 프로그래밍 언어 Dart의 내장 타입(Built-in Types)을 함께 파헤쳐 볼게요. Dart를 처음 접하는 분들도, 이미 익숙한 분들도 오늘 강의로 핵심을 확실히 잡아보세요. 자, Dart의 내장 타입이 왜 중요하냐고요? 이건 Dart로 코딩할 때 우리가 데이터를 다루는 가장 기본적인 도구들이기 때문이에요. 바로 시작합니다! 🚀
1. 내장 타입이란? Dart의 기본 도구 상자!
여러분, Dart의 내장 타입이 뭘까요? 이건 마치 요리할 때 사용하는 기본 재료와 같아요. 고기, 채소, 양념처럼 Dart에는 숫자, 문자열, 불리언 같은 기본 데이터 타입이 준비되어 있거든요. Dart는 이런 타입들을 특별히 지원해서 우리가 쉽게 객체를 만들고 사용할 수 있어요. 예를 들어, ‘안녕하세요’라는 문자열이나 true라는 불리언 값을 바로 쓸 수 있는 거죠. 자, 여기 별표 세 개! 내장 타입은 Dart의 기초! 이해되시죠?
Dart에서 지원하는 주요 내장 타입은 숫자(int, double), 문자열(String), 불리언(bool), 리스트(List), 세트(Set), 맵(Map), 그리고 null(Null) 등이 있어요. 이들은 모두 객체로 다뤄지며, 필요하면 생성자를 통해 초기화할 수도 있어요. 예를 들어, Map() 생성자로 맵을 만들 수 있는 거예요.
2. 숫자 타입: int와 double로 모든 숫자 해결!
자, 숫자 타입을 살펴볼게요. Dart의 숫자는 크게 두 가지로 나뉘어요. int는 정수, double은 소수점이 있는 실수예요. 이건 마치 계산기를 사용할 때 정수 계산과 소수 계산을 구분하는 것과 같아요. int는 플랫폼에 따라 최대 64비트까지 지원하고, double은 IEEE 754 표준에 따라 64비트 부동소수점 숫자를 다룰 수 있어요.
예를 들어, int는 1, 100, 심지어 16진수 0xDEADBEEF 같은 값을 다룰 수 있어요. double은 1.1이나 1.42e5 같은 지수 표현도 가능하죠. 그리고 재미있는 점! 숫자 리터럴에 밑줄(_)을 넣어 가독성을 높일 수 있어요. 예를 들어, 1_000_000은 백만을 의미하는 거예요. 자, 여기 별표 세 개! 숫자는 int와 double로 나눠서 기억하세요! 감 오시나요?
또, 문자열을 숫자로 바꾸거나 숫자를 문자열로 바꾸는 것도 가능해요. int.parse(‘1’) 하면 문자열 ‘1’이 숫자 1로 변신! 반대로 3.14159.toStringAsFixed(2)는 ‘3.14’로 바뀌는 거예요.
3. 문자열(String): 텍스트 다루기의 달인!
이제 문자열, 즉 String 타입을 알아볼게요. 문자열은 텍스트를 다루는 도구로, 마치 우리가 편지를 쓸 때 사용하는 문장과 같아요. Dart에서는 작은따옴표(”)나 큰따옴표(“”)로 문자열을 만들 수 있어요. 예를 들어, ‘안녕’이나 “Hello” 모두 문자열이에요.
특히 재미있는 건 문자열 보간(String Interpolation)이에요. 문자열 안에 ${표현식}을 넣어서 값을 바로 삽입할 수 있는 거죠. 예를 들어, ‘Dart has $s’라고 쓰면 변수 s의 값이 들어가요. 이건 마치 편지 쓸 때 빈칸에 이름 넣는 것처럼 편리하죠. 자, 여기 별표 세 개! 문자열 보간은 초강력 기능! 이거 시험에 나와요!
또, 여러 줄 문자열은 삼중 따옴표(”’ 또는 “””)로 만들고, r을 붙이면 \n 같은 특수 문자를 무시하는 ‘raw’ 문자열도 만들 수 있어요. 유니코드 문자도 지원해서 이모지 같은 특수 문자를 표현할 수 있는 거예요.
4. 불리언(bool): 참과 거짓의 세계!
불리언 타입은 간단해요. true와 false, 딱 두 가지 값만 가지는 타입이에요. 이건 마치 스위치를 켜고 끄는 것과 같아요. 켜짐이 true, 꺼짐이 false인 거죠. Dart는 타입 안전성을 엄격히 지키기 때문에, 다른 값을 불리언으로 바로 사용할 수 없어요. 예를 들어, 문자열이 비어있는지 확인하려면 fullName.isEmpty로 직접 체크해야 해요.
이런 명확한 체크는 코드의 안정성을 높여주는 Dart만의 특징이에요. 자, 불리언은 단순하지만 강력한 도구라는 점, 꼭 기억하세요!
5. 기타 타입: Dart의 숨은 조력자들!
Dart에는 리스트(List), 세트(Set), 맵(Map) 같은 컬렉션 타입도 내장되어 있어요. 이건 마치 서랍장, 상자, 정리함 같은 역할을 해서 데이터를 체계적으로 보관할 수 있게 해줘요. 또, 함수(Function), 레코드(Records), 심볼(Symbol) 같은 특별한 타입도 지원하죠. 심볼은 주로 API에서 식별자를 다룰 때 유용해요.
그리고 Object, Null, Future, Stream 같은 특수 타입도 Dart의 클래스 계층에서 중요한 역할을 해요. 특히 Null은 값이 없음을 나타내는 타입으로, 널 안전성(Null Safety)을 이해하는 데 핵심이에요.
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart의 내장 타입을 3줄로 요약하면:
- Dart는 숫자(int, double), 문자열(String), 불리언(bool) 등 다양한 내장 타입을 지원해요.
- 각 타입은 객체로 다뤄지며, 리터럴이나 생성자를 통해 쉽게 사용할 수 있어요.
- 문자열 보간, 숫자 변환, 불리언 체크 등 실용적인 기능이 가득하죠!
이 중 숫자와 문자열은 정말 자주 쓰이니 확실히 익혀두세요. 오늘의 실천 과제! 간단한 Dart 코드를 작성하면서 int, double, String 타입을 각각 사용해보세요. 어떤 상황에서 유용한지 느껴보는 거예요. 그럼 다음 강의에서 더 재미있는 주제로 찾아뵙겠습니다.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 Records라는 멋진 기능을 함께 탐구해볼게요. Records가 뭔지, 왜 유용한지, 어떻게 쓰는지 확실히 잡아보세요. 자, Records의 핵심 가치는 간단하게 여러 데이터를 한 번에 묶어서 다룰 수 있다는 점이에요. 바로 시작합니다! 🚀
1. Records란? 데이터 묶음의 새 친구!
여러분, Records가 뭘까요? 이건 마치 하나의 상자에 여러 물건을 담는 것과 같아요. Dart에서 Records는 익명, 불변, 고정 크기의 데이터 묶음이에요. 리스트나 맵 같은 컬렉션과 비슷하지만, 크기가 고정되고 서로 다른 타입의 데이터를 담을 수 있다는 점이 다르죠. 즉, 상자 크기를 미리 정하고 안에 책, 연필, 사탕을 같이 넣는 느낌이에요.
Records는 변수에 저장하거나 함수에 전달하고, 리스트나 맵 안에 넣을 수도 있어요. Dart 언어 버전 3.0 이상에서 지원되니 최신 버전을 사용하셔야 해요. 자, 여기 별표 세 개! Records는 데이터 묶음의 강력한 도구! 이해되시죠?
2. Records 문법: 간단하게 데이터 꾸러미 만들기!
Records는 어떻게 만들까요? 이건 마치 쇼핑 리스트를 작성하는 것처럼 간단해요. 괄호 안에 쉼표로 구분된 필드들을 넣으면 돼요. 위치 기반 필드나 이름이 있는 필드를 사용할 수 있어요. 예를 들어, var record = ('first', a: 2, b: true, 'last');처럼 쓰는 거죠.
타입을 명시할 때는 (String, int)처럼 위치 기반 필드나 ({int a, bool b})처럼 이름이 있는 필드를 정의해요. 이름이 있는 필드는 타입에 영향을 주지만, 위치 기반 필드의 이름은 단순히 문서화용이라 타입에 영향을 주지 않아요. 자, 여기 별표 세 개! Records 문법은 괄호와 쉼표가 핵심! 감 오시나요?
3. Records 필드 접근: 데이터 꺼내기 쉬워요!
Records의 데이터는 어떻게 꺼낼까요? 이건 상자에서 물건을 꺼내는 것과 같아요. Records는 불변이라 수정은 불가능하지만, getter를 통해 값을 읽을 수 있어요. 위치 기반 필드는 $1, $2처럼 접근하고, 이름이 있는 필드는 record.a처럼 이름으로 접근해요.
예를 들어, var record = ('first', a: 2, b: true, 'last');에서 record.$1은 ‘first’를, record.a는 2를 반환해요. 이건 정말 직관적이라 바로 익숙해질 거예요. 자, 접근 방법 꼭 기억하세요!
4. Records 타입과 동등성: 구조가 중요해요!
Records의 타입은 어떻게 결정될까요? 이건 마치 상자의 모양과 내용물로 상자를 구분하는 것과 같아요. Records는 구조적 타입(Structural Typing)을 따르기 때문에 필드의 타입과 이름(이름이 있는 경우)에 따라 타입이 결정돼요. 두 Records가 같은 필드 구조를 가지면 같은 타입으로 간주돼요.
동등성도 마찬가지예요. 같은 구조와 값을 가지면 두 Records는 같다고 판단돼요. 이름이 있는 필드의 순서는 상관없다는 점도 중요해요. 자, 여기 별표 세 개! 구조가 같으면 타입도 같아요! 이거 시험에 나와요!
5. Records의 활용: 여러 값 반환과 데이터 구조!
Records의 진짜 매력은 뭘까요? 이건 함수에서 여러 값을 한 번에 반환할 때 정말 유용해요. 마치 한 번에 여러 선물을 주는 것과 같죠. 예를 들어, (String name, int age) userInfo() 같은 함수는 이름과 나이를 함께 반환해요. 반환값은 var (name, age) = userInfo(json);처럼 패턴 매칭으로 바로 분해할 수 있어요.
또, Records는 간단한 데이터 구조를 선언 없이 바로 사용할 수 있어요. 버튼 정의 리스트처럼 동일한 구조의 데이터를 묶을 때 클래스 없이 바로 Records로 처리할 수 있는 거죠. 이건 코드 작성 속도를 엄청나게 높여줘요!
6. Records와 typedef: 이름 붙여서 더 편리하게!
Records에 이름을 붙이고 싶다면 어떻게 할까요? 이건 마치 상자에 라벨을 붙이는 것과 같아요. typedef를 사용하면 Records 타입에 별칭을 붙여서 가독성을 높일 수 있어요. 예를 들어, typedef ButtonItem = ({String label, Icon icon, void Function()? onPressed});처럼 정의하면 코드를 더 깔끔하게 재사용할 수 있어요.
이 별칭은 구조적 타입을 참조하는 것일 뿐이지만, 나중에 Records를 클래스나 확장 타입으로 바꿔도 코드 수정이 최소화되는 장점이 있어요. 자, typedef 활용법, 정말 유용하죠?
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart의 Records를 3줄로 요약하면:
- Records는 익명, 불변, 고정 크기의 데이터 묶음으로 여러 값을 한 번에 다룰 수 있어요.
- 위치 기반과 이름 기반 필드로 구성되며, getter로 접근하고 구조적 타입을 따라요.
- 여러 값 반환, 간단한 데이터 구조, typedef로 재사용성까지 높이는 강력한 기능이죠!
이 중 여러 값 반환 기능은 정말 자주 쓰이니 꼭 익혀두세요. 오늘의 실천 과제! 간단한 Dart 함수를 작성하면서 Records로 두 개 이상의 값을 반환해보세요. 어떤 상황에서 유용한지 느껴보는 거예요. 그럼 다음 강의에서 더 재미있는 주제로 찾아뵙겠습니다.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 컬렉션(Collections)을 함께 탐구해볼게요. 리스트, 세트, 맵부터 다양한 제어 요소까지, 데이터를 묶고 다루는 방법을 확실히 잡아보세요. 자, 컬렉션의 핵심 가치는 데이터를 효율적으로 정리하고 활용할 수 있다는 점이에요. 바로 시작합니다! 🚀
1. 컬렉션이란? 데이터 정리 상자!
여러분, 컬렉션이 뭘까요? 이건 마치 책상 위 물건을 정리하는 서랍장과 같아요. Dart에서는 리스트(List), 세트(Set), 맵(Map)이라는 세 가지 기본 컬렉션을 지원해요. 각각의 컬렉션은 데이터를 다루는 방식이 달라서, 상황에 맞게 골라 쓸 수 있는 거죠. 자, 여기 별표 세 개! 컬렉션은 데이터 정리의 핵심 도구! 이해되시죠?
2. 리스트(List): 순서 있는 데이터 모음!
리스트는 무엇일까요? 이건 마치 우리가 쇼핑 리스트를 작성하는 것과 같아요. Dart에서 리스트는 순서가 있는 데이터 모음으로, 배열(Array)이라고도 불려요. 대괄호 [] 안에 쉼표로 구분된 요소를 넣어 만들 수 있어요. 예를 들어, var list = [1, 2, 3];처럼 말이죠.
리스트는 0부터 시작하는 인덱스로 요소에 접근하고, .length로 길이를 확인할 수 있어요. 값을 수정하는 것도 가능하고, const를 붙이면 변경 불가능한 상수 리스트를 만들 수 있어요. 자, 여기 별표 세 개! 리스트는 순서와 접근이 핵심! 감 오시나요?
3. 세트(Set): 중복 없는 데이터 모음!
세트는 어떤 특징이 있을까요? 이건 마치 중복된 물건을 제외하고 정리하는 상자와 같아요. Dart의 세트는 순서가 없고, 중복된 요소를 허용하지 않아요. 중괄호 {} 안에 요소를 넣어 만들며, 타입을 명시하거나 <String>{}처럼 빈 세트를 만들 수 있어요. 예를 들어, var halogens = {'fluorine', 'chlorine'};처럼 말이죠.
add()나 addAll()로 요소를 추가하고, .length로 개수를 확인할 수 있어요. const를 붙여 상수 세트로 만들 수도 있죠. 자, 세트는 중복 제거가 핵심이라는 점, 꼭 기억하세요!
4. 맵(Map): 키-값 쌍의 데이터 정리!
맵은 뭘까요? 이건 마치 이름표를 붙인 서랍처럼, 키와 값의 쌍으로 데이터를 저장하는 컬렉션이에요. Dart에서 맵은 중괄호 {} 안에 키: 값 형태로 요소를 넣어 만들어요. 예를 들어, var gifts = {'first': 'partridge'};처럼 말이죠.
키로 값을 찾거나, 새로운 키-값 쌍을 추가할 수 있고, .length로 쌍의 개수를 확인할 수 있어요. 없는 키를 찾으면 null이 반환되는 것도 특징이에요. 자, 여기 별표 세 개! 맵은 키-값 쌍으로 데이터 연결! 이거 시험에 나와요!
5. 컬렉션 요소: 데이터 넣기의 마법!
컬렉션에 데이터를 넣는 방법이 더 있나요? 네, 마치 상자에 물건을 넣는 다양한 기술이 있는 것처럼, Dart는 여러 컬렉션 요소를 제공해요. 표현식 요소, 맵 엔트리 요소, 널 인식 요소, 스프레드 요소, if 요소, for 요소 등이 있어요.
예를 들어, 스프레드 요소 ...는 다른 컬렉션의 모든 요소를 삽입하고, 널 인식 스프레드 ...?는 널일 경우 무시해요. if 요소는 조건에 따라 요소를 추가하고, for 요소는 반복적으로 요소를 삽입하죠. 이건 마치 조건문과 반복문을 상자에 넣는 것과 같아요!
6. 제어 흐름 요소 중첩: 컬렉션의 고급 기술!
제어 흐름 요소를 중첩해서 쓰면 어떻게 될까요? 이건 마치 상자 안에 작은 상자를 넣는 것처럼, 더 복잡한 데이터 구성을 할 수 있어요. 예를 들어, for 안에 if를 넣어 짝수만 골라내거나, if 안에 스프레드를 넣어 조건에 따라 여러 요소를 추가할 수 있어요.
이런 중첩은 코드의 유연성을 엄청 높여주죠. Dart에서는 이런 고급 기술을 자연스럽게 지원하니, 여러분도 연습해보시면 금방 익숙해질 거예요. 자, 중첩 활용법, 정말 강력하죠?
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart의 컬렉션을 3줄로 요약하면:
- Dart는 리스트(순서 있음), 세트(중복 없음), 맵(키-값 쌍) 세 가지 컬렉션을 지원해요.
- 각 컬렉션은 데이터를 정리하고 접근하는 방식이 다르며, 상수로 만들 수도 있어요.
- 스프레드, if, for 같은 제어 요소와 중첩으로 유연한 데이터 구성이 가능하죠!
이 중 리스트와 맵은 정말 자주 쓰이니 확실히 익혀두세요. 오늘의 실천 과제! 간단한 Dart 코드를 작성하면서 리스트, 세트, 맵을 각각 만들어보세요. 어떤 상황에서 유용한지 느껴보는 거예요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 제네릭(Generics)을 함께 파헤쳐 볼게요. 제네릭이 뭔지, 왜 쓰는지, 어떻게 활용하는지 확실히 잡아보세요. 자, 제네릭의 핵심 가치는 코드의 안전성과 재사용성을 높이는 데 있다는 점이에요. 바로 시작합니다! 🚀
1. 제네릭이란? 타입의 유연한 도구!
여러분, 제네릭이 뭘까요? 이건 마치 하나의 틀을 만들어 여러 재료로 요리할 수 있게 하는 레시피와 같아요. Dart에서 제네릭은 타입 파라미터를 사용해 클래스나 함수를 일반화하는 기술이에요. 예를 들어, List<E>에서 E는 어떤 타입이든 될 수 있는 자리 표시자예요. 보통 E, T, K 같은 한 글자 이름으로 쓰죠.
제네릭을 쓰면 타입 안전성을 높이고, 코드 중복을 줄일 수 있어요. 자, 여기 별표 세 개! 제네릭은 타입 안전성과 재사용의 열쇠! 이해되시죠?
2. 제네릭의 장점: 안전하고 간결한 코드!
제네릭을 왜 써야 할까요? 이건 마치 요리할 때 정확한 재료를 사용하는 것과 같아요. 예를 들어, List<String>으로 선언하면 문자열 리스트만 다룰 수 있어서 숫자를 추가하려는 실수를 컴파일러가 잡아줘요. 또, 제네릭은 하나의 인터페이스를 여러 타입에 맞게 재사용할 수 있게 해줘요.
Cache<T> 같은 인터페이스를 만들면 T를 문자열, 숫자 등 원하는 타입으로 지정해 쓸 수 있는 거죠. 이건 코드 중복을 줄이고 더 나은 성능의 코드를 생성하게 해줘요. 자, 여기 별표 세 개! 제네릭으로 실수 방지와 코드 절약! 감 오시나요?
3. 컬렉션 리터럴과 제네릭: 타입 명시의 힘!
컬렉션에서 제네릭은 어떻게 쓰나요? 이건 마치 상자에 라벨을 붙이는 것처럼 타입을 명시하는 거예요. 리스트, 세트, 맵 리터럴에 <타입>을 추가해요. 예를 들어, var names = <String>['Seth', 'Kathy'];처럼 쓰면 문자열 리스트를 명확히 선언하는 거죠.
맵은 <키타입, 값타입> 형태로 쓰는데, var pages = <String, String>{'index.html': 'Homepage'};처럼 말이죠. 이렇게 하면 타입 오류를 미리 잡을 수 있어요. 자, 타입 명시는 정말 중요해요!
4. 생성자와 제네릭: 타입 지정의 또 다른 방법!
생성자에서도 제네릭을 쓸 수 있나요? 네, 마치 맞춤 제작 상자를 만드는 것처럼 가능해요. 클래스 이름 뒤에 <타입>을 붙여 생성자에서 타입을 지정할 수 있어요. 예를 들어, var nameSet = Set<String>.of(names);처럼 쓰는 거죠.
이렇게 하면 컬렉션의 타입을 명확히 설정할 수 있어요. Dart는 실행 중에도 타입 정보를 유지하기 때문에, names is List<String> 같은 타입 체크도 가능해요. 자, 여기 별표 세 개! 생성자에서도 제네릭 활용 가능! 이거 시험에 나와요!
5. 제네릭 타입 제한: 타입의 경계 설정!
제네릭 타입에 제한을 걸 수 있나요? 이건 마치 상자에 넣을 물건의 종류를 제한하는 것과 같아요. extends 키워드로 타입 파라미터에 경계를 설정할 수 있어요. 예를 들어, class Foo<T extends Object>는 T가 반드시 널이 아닌 타입이어야 한다는 뜻이에요.
또, 특정 클래스의 하위 타입으로 제한할 수도 있어요. class Foo<T extends SomeBaseClass>처럼 쓰면 T는 SomeBaseClass의 하위 타입만 될 수 있는 거죠. 자, 타입 제한으로 더 안전한 코드 작성 가능해요!
6. 제네릭 메서드: 함수에서도 타입 유연성!
메서드나 함수에서도 제네릭을 쓸 수 있나요? 당연하죠! 이건 마치 요리법을 여러 재료에 맞게 바꾸는 것과 같아요. 함수에 <T> 같은 타입 파라미터를 추가해요. 예를 들어, T first<T>(List<T> ts)는 리스트의 첫 번째 요소를 반환하는 제네릭 함수예요.
이렇게 하면 반환 타입, 매개변수 타입, 로컬 변수 타입에 모두 T를 사용할 수 있어요. 제네릭 메서드는 코드의 유연성을 엄청나게 높여주는 도구예요. 자, 함수에서도 제네릭 활용, 정말 강력하죠?
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart의 제네릭을 3줄로 요약하면:
- 제네릭은 타입 파라미터를 사용해 클래스와 함수를 일반화하며 타입 안전성을 높여요.
- 컬렉션 리터럴, 생성자, 메서드에서 타입을 명시해 코드 중복을 줄이고 오류를 방지하죠.
- 타입 제한(
extends)으로 특정 타입만 허용하며 더 안전한 코드를 작성할 수 있어요!
이 중 컬렉션에서의 제네릭 사용은 정말 자주 쓰이니 확실히 익혀두세요. 오늘의 실천 과제! 간단한 Dart 코드를 작성하면서 List<String>과 같은 제네릭 타입을 사용해보세요. 어떤 상황에서 유용한지 느껴보는 거예요. 그럼 다음 강의에서 더 재미있는 주제로 찾아뵙겠습니다.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 Typedef를 함께 탐구해볼게요. Typedef가 뭔지, 왜 유용한지, 어떻게 쓰는지 확실히 잡아보세요. 자, Typedef의 핵심 가치는 복잡한 타입을 간단하게 참조할 수 있게 해준다는 점이에요. 바로 시작합니다! 🚀
1. Typedef란? 타입의 별명 짓기!
여러분, Typedef가 뭘까요? 이건 마치 긴 이름을 짧은 별명으로 부르는 것과 같아요. Dart에서 Typedef는 타입 별칭(Type Alias)을 만드는 방법으로, typedef 키워드를 사용해요. 복잡한 타입을 간결하게 표현할 수 있어서 코드 가독성을 높여주는 도구예요.
예를 들어, typedef IntList = List<int>;라고 선언하면 IntList라는 이름으로 List<int>를 사용할 수 있어요. IntList il = [1, 2, 3];처럼 쓰는 거죠. 자, 여기 별표 세 개! Typedef는 타입의 별명! 이해되시죠?
2. 타입 파라미터와 Typedef: 더 유연하게!
Typedef에서 타입 파라미터를 쓸 수 있나요? 네, 마치 별명을 더 일반적으로 만드는 것과 같아요. typedef ListMapper<X> = Map<X, List<X>>;처럼 타입 파라미터 X를 사용하면 다양한 타입에 맞게 별칭을 정의할 수 있어요.
예를 들어, ListMapper<String> m2 = {};라고 쓰면 Map<String, List<String>>를 간단히 표현한 거예요. 이건 코드가 훨씬 깔끔해지고 이해하기 쉬워지게 해줘요. 자, 여기 별표 세 개! 타입 파라미터로 유연성 UP! 감 오시나요?
3. 함수 타입과 Typedef: 과거와 현재!
Typedef는 원래 함수 타입에만 사용됐었나요? 맞아요, 이건 마치 예전 규칙을 업데이트한 것과 같아요. Dart 2.13 이전에는 Typedef가 함수 타입에만 제한됐었지만, 2.13 이후로는 모든 타입에 사용할 수 있게 됐어요. 최신 언어 버전을 사용해야 한다는 점, 기억하세요!
함수 타입에서는 typedef Compare<T> = int Function(T a, T b);처럼 정의할 수 있어요. 하지만 대부분의 경우 인라인 함수 타입을 쓰는 게 더 간편하다는 점도 알아두세요. 자, 함수 타입에서도 유용하지만 상황에 맞게 선택하는 게 중요해요!
4. Typedef의 활용: 코드 가독성 높이기!
Typedef는 언제 쓰면 좋을까요? 이건 마치 긴 설명을 짧게 줄이는 것과 같아요. 복잡한 타입이 반복적으로 등장할 때 Typedef를 사용하면 코드를 더 읽기 쉽게 만들 수 있어요. 예를 들어, 특정 타입의 리스트나 맵을 자주 쓰면 별칭으로 정의해두는 거죠.
함수 타입에서도 특정 시그니처를 자주 확인하거나 사용할 때 유용하게 쓰일 수 있어요. 자, 여기 별표 세 개! Typedef로 코드 깔끔하게! 이거 시험에 나와요!
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart의 Typedef를 3줄로 요약하면:
- Typedef는 타입 별칭을 만들어 복잡한 타입을 간결하게 참조할 수 있게 해줘요.
- 타입 파라미터를 사용해 유연하게 정의할 수 있으며, Dart 2.13 이후 모든 타입에 적용 가능해요.
- 코드 가독성을 높이고 반복을 줄이는 데 유용하지만, 함수 타입은 인라인 방식도 고려하세요!
이 중 타입 별칭 정의는 자주 쓰이니 확실히 익혀두세요. 오늘의 실천 과제! 간단한 Dart 코드를 작성하면서 자주 사용하는 타입에 Typedef를 적용해보세요. 코드가 얼마나 깔끔해지는지 느껴보는 거예요. 그럼 다음 강의에서 더 재미있는 주제로 찾아뵙겠습니다.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 타입 시스템(Type System)을 함께 파헤쳐 볼게요. Dart의 타입 시스템이 뭔지, 왜 중요한지, 어떻게 활용하는지 확실히 잡아보세요. 자, 타입 시스템의 핵심 가치는 코드의 안정성과 버그를 미리 잡아내는 데 있다는 점이에요. 바로 시작합니다! 🚀
1. Dart 타입 시스템이란? 코드 안전의 수호자!
여러분, Dart의 타입 시스템이 뭘까요? 이건 마치 도로의 교통 규칙처럼 코드가 올바른 길을 가도록 안내하는 시스템이에요. Dart는 타입 안전(Type Safe) 언어로, 정적 타입 체크(컴파일 타임)와 런타임 체크를 조합해 변수의 값이 항상 선언된 타입과 일치하도록 보장해요. 이를 사운드 타이핑(Sound Typing)이라고 하죠.
타입 선언은 필수지만, 타입 주석은 선택 사항이에요. Dart는 타입 추론(Type Inference)을 통해 자동으로 타입을 알아내거든요. 자, 여기 별표 세 개! 타입 시스템은 코드 안전의 핵심! 이해되시죠?
2. 사운드니스(Soundness)의 의미: 타입이 거짓말하지 않아요!
사운드니스가 뭘까요? 이건 마치 약속이 지켜지는 것과 같아요. 사운드 타입 시스템은 프로그램이 잘못된 상태에 빠지지 않도록 보장해요. 예를 들어, String 타입으로 선언된 표현식은 런타임에서도 반드시 문자열 값만 가지게 돼요. Dart는 Java나 C#처럼 사운드 타입 시스템을 갖춰서 컴파일 타임 오류와 런타임 체크로 이를 엄격히 지키죠.
이건 타입 관련 버그를 컴파일 타임에 잡아내고, 코드 가독성과 유지보수를 쉽게 만들며, 더 효율적인 AOT(앞서 컴파일) 코드를 생성하는 장점이 있어요. 자, 여기 별표 세 개! 사운드니스는 코드 신뢰의 기반! 감 오시나요?
3. 정적 분석 통과 팁: 타입 오류 미리 잡기!
정적 분석을 통과하려면 어떻게 해야 하나요? 이건 마치 시험 준비를 철저히 하는 것과 같아요. Dart의 정적 분석기는 컴파일 타임에 버그를 잡아내는 강력한 도구예요. 몇 가지 중요한 규칙을 알아볼게요.
- 메서드 오버라이딩 시 반환 타입은 동일하거나 하위 타입이어야 해요.
- 매개변수 타입은 동일하거나 상위 타입이어야 해요. 하위 타입으로 좁히는 건 금지!
- 동적 리스트를 타입이 지정된 리스트로 사용하지 마세요.
예를 들어, Animal 클래스의 chase(Animal) 메서드를 오버라이딩할 때 매개변수를 Object로 넓히는 건 OK지만, Mouse로 좁히는 건 오류예요. 자, 규칙 잘 지키는 게 중요해요!
4. 런타임 체크와 동적 타입: 실행 중 안전 보장!
런타임 체크는 뭘까요? 이건 마치 도로에서 실시간으로 차량을 점검하는 것과 같아요. 컴파일 타임에 잡히지 않는 타입 안전 문제는 런타임에 체크돼요. 예를 들어, List<Animal>을 List<Cat>으로 캐스팅하는 건 런타임 오류를 일으킬 수 있어요.
또, dynamic 타입에서의 암시적 다운캐스트는 런타임에 타입이 맞지 않으면 오류를 던져요. 분석기의 strict-casts 모드를 활성화하면 이런 문제를 미리 방지할 수 있어요. 자, 여기 별표 세 개! 런타임 체크로 추가 안전 확보! 이거 시험에 나와요!
5. 타입 추론(Type Inference): Dart가 알아서 척척!
타입 추론은 뭘까요? 이건 마치 퍼즐을 자동으로 맞춰주는 것과 같아요. Dart 분석기는 필드, 메서드, 로컬 변수, 제네릭 타입 인수를 자동으로 추론해요. 정보가 부족하면 dynamic 타입을 사용하죠.
예를 들어, var arguments = {'argA': 'hello', 'argB': 42};는 Map<String, Object>로 추론돼요. 초기값이 있는 필드나 로컬 변수도 초기값에 따라 타입이 결정되죠. 자, 타입 추론은 코드를 간결하게 만들어주는 마법이에요!
6. 타입 대체와 공변성: 소비자와 생산자의 규칙!
타입 대체는 어떻게 하나요? 이건 마치 대체 선수를 투입하는 것과 같아요. 타입을 대체할 때 소비자(Consumer)와 생산자(Producer)의 개념을 생각해야 해요. 소비자는 상위 타입으로, 생산자는 하위 타입으로 대체할 수 있어요.
예를 들어, Cat을 소비하는 위치에서는 Animal로 대체 가능하지만, MaineCoon으로 좁히는 건 안 돼요. 반대로 생산 위치에서는 Cat을 MaineCoon으로 대체할 수 있어요. 제네릭 타입에서도 동일한 규칙이 적용돼요. 자, 소비자와 생산자 규칙, 꼭 기억하세요!
7. 공변(Covariant) 매개변수: 예외적 타입 좁히기!
매개변수를 하위 타입으로 좁혀야 할 때는 어떻게 하나요? 이건 마치 특별 허가를 받는 것과 같아요. 드물게 타입을 좁혀야 하는 경우 covariant 키워드를 사용하면 정적 오류를 없애고 런타임 체크로 전환할 수 있어요.
예를 들어, Animal의 chase(Animal)을 Cat에서 chase(covariant Mouse)로 오버라이딩하면 허용돼요. 자, covariant는 특별 상황에서 유용한 도구예요!
오늘의 정리
자, 오늘 강의 여기서 마무리할게요! Dart의 타입 시스템을 3줄로 요약하면:
- Dart는 정적 타입 체크와 런타임 체크로 사운드 타입 시스템을 보장해요.
- 타입 추론으로 코드 간결성을 유지하며, 정적 분석으로 버그를 미리 잡아내죠.
- 소비자와 생산자 규칙, 공변성으로 타입 대체를 안전하게 처리할 수 있어요!
이 중 정적 분석 규칙은 정말 중요하니 확실히 익혀두세요. 오늘의 실천 과제! 간단한 Dart 코드를 작성하면서 타입 선언과 추론을 활용해보세요. 타입 오류가 어떻게 잡히는지 느껴보는 거예요. 그럼 다음 강의에서 더 재미있는 주제로 찾아뵙겠습니다.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 강력한 기능 중 하나인 패턴(Patterns)에 대해 함께 파헤쳐 볼게요. 이 패턴이라는 친구, 복잡한 데이터를 쉽게 쪼개고 확인하는 데 정말 유용한 도구인데요. 여러분의 코딩 효율을 10배는 높여줄 핵심 비법입니다! 자, 그럼 지금부터 패턴의 세계로 들어가 봅시다! 🚀
패턴이 뭘까? 데이터를 쪼개는 마법 도구!
여러분, 패턴이 뭘까요? 쉽게 말해 패턴은 데이터를 특정 형태로 맞춰보고, 맞으면 그 안의 내용을 쪼개서 꺼내 쓰는 기술이에요. 마치 레고 블록을 보고 “이건 네모 두 개, 동그라미 하나야!” 하며 분해하는 것과 비슷하죠. 패턴은 두 가지 큰 역할을 해요. 첫째, 데이터가 내가 원하는 형태인지 확인(매칭)하고, 둘째, 그 데이터를 분해(구조 분해)해서 필요한 부분만 쏙쏙 뽑는 거예요. 자, 여기 별표 세 개! 이 두 개념만 기억하면 패턴의 절반은 이해한 거예요! ⭐⭐⭐
매칭(Matching): 데이터가 맞는지 확인하기
패턴의 첫 번째 능력, 매칭! 이건 데이터를 보고 “너, 내가 원하는 모양이니?” 하고 확인하는 과정이에요. 예를 들어, 숫자가 1인지 확인하려면 이렇게 쓰면 돼요. switch (number) { case 1: print('one'); } 만약 숫자가 1이라면 ‘one’이 출력되는 거죠. 이게 바로 상수 패턴 매칭이에요. 또 리스트처럼 복잡한 데이터도 가능해요. [a, b]라는 패턴은 리스트가 두 개 요소를 가졌는지, 그 요소가 특정 값과 맞는지 확인하는 거예요. 이해되시죠? 마치 퍼즐 조각이 딱 맞는지 확인하는 것과 똑같아요!
구조 분해(Destructuring): 데이터 쪼개서 활용하기
자, 이제 두 번째 능력! 매칭이 끝났다면 데이터를 쪼개서 필요한 부분을 꺼내 쓸 수 있어요. 예를 들어, [1, 2, 3]이라는 리스트가 있다면 var [a, b, c] = numList; 이렇게 쓰면 1, 2, 3이 각각 a, b, c에 쏙 들어가요. 마치 박스를 열어서 안에 든 물건을 하나씩 꺼내는 느낌! 심지어 중첩된 데이터도 가능해요. 리스트 안의 특정 값만 확인하고 싶다면 case ['a' || 'b', var c]:처럼 쓰면 첫 번째 요소가 ‘a’나 ‘b’인지 확인하고 나머지를 c에 저장하는 거예요. 감 오시나요? 😊
어디서 쓰나? 패턴의 활용 장소
패턴은 Dart 코드 곳곳에서 쓸 수 있는 만능 도구예요. 어디서 쓰는지 보면, 변수 선언, 반복문, switch-case, if-case 같은 제어 흐름에서 다 등장해요. 예를 들어, 변수 선언에서 var (a, [b, c]) = ('str', [1, 2]);처럼 쓰면 문자열과 리스트를 한 번에 쪼갤 수 있어요. 또 for-in 반복문에서는 맵 데이터를 for (var MapEntry(:key, value: count) in hist.entries)처럼 분해해서 key와 value를 바로 꺼낼 수 있죠. 자, 여기 별표 세 개! 패턴은 단순히 확인만이 아니라 데이터를 편리하게 다루는 데 최고라는 점! ⭐⭐⭐
실전 활용 사례: 패턴으로 문제 해결하기
패턴의 진짜 힘은 실전에서 발휘돼요. 예를 들어, JSON 데이터가 올바른 구조인지 확인하고 값을 뽑아내는 작업을 해볼게요. 일반 코드는 엄청 길어요. if로 이것저것 확인하고 타입 맞는지 체크하고… 그런데 패턴 쓰면 단 한 줄로 끝! if (data case {'user': [String name, int age]}) {} 이걸로 데이터가 맵인지, ‘user’ 키가 있는지, 값이 리스트인지, 타입이 맞는지 한 번에 확인하고 name과 age에 값을 넣을 수 있어요. 이거 시험에 나와요! 패턴 쓰면 코드가 짧고 깔끔해진다는 점, 꼭 기억하세요! 📝
더 멋진 활용: 클래스와 함수 결과 분해
패턴은 클래스 인스턴스나 함수 반환값도 분해할 수 있어요. 예를 들어, 클래스 Foo의 객체가 있다면 var Foo(:one, :two) = myFoo;처럼 속성을 바로 뽑아낼 수 있어요. 함수가 여러 값을 반환할 때도 var (name, age) = userInfo(json);처럼 한 줄로 필요한 값을 변수에 저장! 마치 여러 개의 선물을 한 번에 뜯는 기분이에요. 이런 식으로 코드가 간결해지고 실수도 줄어드는 거예요. 이 점, 정말 중요하죠!
오늘의 정리
자, 오늘 Dart의 패턴에 대해 배운 내용을 3줄로 정리해볼게요.
- 패턴은 데이터를 매칭하고 구조 분해하는 강력한 도구예요.
- 변수 선언, 반복문, switch-case 등 코드 곳곳에서 활용 가능해요.
- JSON 검증, 클래스 분해 등 실전에서 코드 효율을 엄청 높여주는 기술이죠!
마지막으로, 패턴의 핵심은 간결함과 정확성이라는 점, 꼭 기억해주세요! 오늘 배운 패턴을 실제 코드에 한 번 적용해보는 과제를 드릴게요. 여러분의 코딩이 한 단계 업그레이드될 거예요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어의 패턴 타입(Pattern Types)을 하나씩 뜯어보며 완벽히 이해해보는 시간 가져볼게요. 패턴은 데이터를 확인하고 쪼개는 데 엄청 유용한 도구인데, 그 종류가 많아서 헷갈릴 수 있어요. 하지만 걱정 마세요! 여러분이 쉽게 이해할 수 있도록 일상 비유로 풀어보며 끝까지 함께할게요. 자, 패턴 타입의 세계로 출발! 🚀
패턴 우선순위: 누가 먼저 맞는지 알아보자!
여러분, 패턴에도 순서가 있어요. 마치 수학에서 곱셈이 덧셈보다 먼저 계산되듯, 패턴도 우선순위(Precedence)에 따라 평가 순서가 정해져 있어요. 예를 들어, 논리적 OR 패턴은 AND 패턴보다 우선순위가 낮고, 관계형 패턴은 또 그보다 높아요. 필요하면 괄호로 묶어서 순서를 바꿀 수도 있어요. 마치 요리할 때 재료를 순서대로 넣는 것처럼, 패턴도 순서를 잘 맞춰야 결과가 제대로 나온다는 점! 자, 여기 별표 세 개! 우선순위만 이해하면 패턴 평가가 한결 쉬워져요! ⭐⭐⭐
논리적 OR 패턴: 이거나 저거나 맞으면 OK!
논리적 OR 패턴은 ||로 두 개의 하위 패턴을 연결해서 둘 중 하나만 맞아도 통과하는 패턴이에요. 예를 들어, 색상이 빨강, 노랑, 파랑 중 하나인지 확인하려면 Color.red || Color.yellow || Color.blue처럼 쓰면 돼요. 왼쪽부터 차례로 확인해서 하나라도 맞으면 나머지는 체크 안 해요. 마치 친구한테 “피자 먹을래? 아니면 햄버거?” 하고 물어보는 것과 같아요. 하나만 대답해도 끝! 이해되시죠? 😊
논리적 AND 패턴: 둘 다 맞아야 통과!
논리적 AND 패턴은 &&로 연결해서 두 하위 패턴이 모두 맞아야 통과하는 패턴이에요. 왼쪽이 안 맞으면 오른쪽은 아예 보지도 않아요. 예를 들어, 숫자가 특정 범위 안에 있는지 확인하려면 > space && < zero처럼 쓰는 거죠. 마치 시험에서 “문법 맞고, 철자도 맞아야 정답!”이라고 하는 것과 비슷해요. 두 조건이 동시에 충족돼야 하는 상황에서 딱이에요!
관계형 패턴: 숫자 비교의 달인
관계형 패턴은 ==, <, > 같은 연산자로 값을 비교하는 패턴이에요. 숫자가 특정 값과 같은지, 큰지, 작은지 확인할 때 유용하죠. 예를 들어, asciiCharType 함수에서 숫자가 32보다 작은지 확인하려면 < space처럼 쓰면 돼요. 마치 키 제한이 있는 놀이기구에서 “너 키 140cm 넘니?” 하고 확인하는 것과 같아요. 숫자 범위 체크할 때 이 패턴이 최고라는 점, 기억하세요!
캐스트, 널 체크, 널 어설션: 타입과 널의 경비원
자, 이제 타입과 널 관련 패턴들을 만나볼게요. 캐스트 패턴(as String)은 값을 특정 타입으로 강제 변환하는 거예요. 마치 “이 사람은 학생이어야 해!” 하고 신분증 확인하는 것과 같죠. 널 체크 패턴(?)은 값이 널이 아니어야 통과하고, 널 어설션 패턴(!)은 널이면 아예 오류를 던져요. 널 체크는 “이 상자 비어있지 않지?” 하고 확인하는 것, 널 어설션은 “비어있으면 큰일 나!” 하고 경고하는 것과 비슷해요. 자, 여기 별표 세 개! 널 처리할 때 이 패턴들이 생명줄이에요! ⭐⭐⭐
상수와 변수 패턴: 값 고정과 저장의 기술
상수 패턴은 고정된 값과 비교하는 거예요. 예를 들어, case 1:은 숫자가 1인지 확인하는 거죠. 반면 변수 패턴은 값을 새로운 변수에 저장해요. var a처럼 쓰면 매칭된 값을 a에 담는 거예요. 상수는 “이건 딱 이 값이어야 해!” 하는 고집쟁이, 변수는 “뭐든 받아들일게!” 하는 열린 마음과 같아요. 이 두 패턴은 패턴의 기본 중 기본이니 꼭 익혀두세요!
리스트와 맵 패턴: 데이터 묶음 쪼개기
리스트 패턴은 리스트를 위치별로 쪼개는 거예요. [a, b]는 리스트의 첫 두 요소를 매칭해요. 맵 패턴은 {"key": subpattern}처럼 맵의 키-값 쌍을 확인하고 쪼개죠. 리스트는 마치 도시락 칸을 하나씩 여는 것, 맵은 서랍에서 특정 물건 찾는 것과 같아요. 리스트에는 ... 같은 나머지 요소(Rest Element)도 쓸 수 있어서 길이가 달라도 매칭 가능하다는 점, 유용하죠!
레코드와 객체 패턴: 복잡한 데이터도 문제없어!
레코드 패턴은 (a, b)처럼 레코드의 필드를 쪼개는 거예요. 객체 패턴은 Rect(width: var w)처럼 특정 클래스의 속성을 뽑아내죠. 레코드는 가족 사진에서 각 사람 이름 붙이는 것, 객체는 스마트폰에서 특정 앱 정보 꺼내는 것과 비슷해요. 이 패턴들 덕분에 복잡한 데이터도 한 번에 분해할 수 있어요. 이거 시험에 나와요! 복잡한 구조 다룰 때 필수라는 점! 📝
와일드카드 패턴: 뭐든지 통과시키는 만능키
마지막으로 와일드카드 패턴 _는 뭐든지 통과시키는 패턴이에요. 값을 저장하거나 비교할 필요 없을 때 쓰죠. 예를 들어, [_, two, _]는 리스트의 첫 번째와 세 번째는 무시하고 두 번째만 가져오는 거예요. 마치 “이건 신경 안 써, 이거만 줘!” 하는 것과 같아요. 불필요한 부분 무시할 때 딱 좋은 패턴이에요!
오늘의 정리
자, 오늘 Dart의 패턴 타입을 배운 내용을 3줄로 정리해볼게요.
- 패턴 타입은 논리적 OR/AND, 관계형, 널 처리, 리스트, 맵 등 다양하게 데이터를 매칭하고 쪼개요.
- 각 패턴은 우선순위에 따라 평가되며, 상황에 맞는 패턴을 골라 쓰는 게 중요해요.
- 복잡한 데이터도 쉽게 다룰 수 있는 레코드, 객체 패턴까지, 패턴은 코딩 효율의 핵심이에요!
마지막으로, 패턴 타입은 상황별 맞춤 도구라는 점, 꼭 기억해주세요! 오늘 배운 패턴 중 하나를 실제 코드에 써보는 과제를 드릴게요. 여러분의 코딩이 한층 더 간결해질 거예요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 언어에서 코드의 흐름을 제어하는 데 필수인 반복문(Loops)에 대해 함께 배워볼게요. 반복문은 같은 작업을 여러 번 자동으로 처리해 주는 마법 같은 도구예요. 여러분의 코딩 효율을 10배 높여줄 핵심 기술이니, 자, 지금부터 반복문의 세계로 들어가 봅시다! 🚀
for 반복문: 규칙적으로 반복하는 기본기!
여러분, for 반복문이 뭘까요? 이건 반복 횟수를 정확히 정해서 작업을 반복하는 방법이에요. 마치 매일 아침 5번 팔굽혀펴기를 하는 것과 비슷하죠. 예를 들어, for (var i = 0; i < 5; i++) { message.write('!'); }는 메시지에 ‘!’를 5번 추가하는 코드예요. 또, 리스트나 세트 같은 데이터 묶음을 돌 때는 for-in 반복문을 쓰면 더 깔끔해요. for (var candidate in candidates) { candidate.interview(); }처럼 쓰면 각 항목을 하나씩 꺼내 처리하는 거죠. 자, 여기 별표 세 개! for 반복문은 반복의 기본 중 기본이라는 점! ⭐⭐⭐
while과 do-while 반복문: 조건 맞을 때까지 돌려!
while 반복문은 조건이 맞는 동안 계속 반복하는 거예요. while (!isDone()) { doSomething(); }는 ‘끝났니?’라는 조건이 거짓일 때까지 작업을 반복해요. 마치 “배고프면 계속 먹어!”라고 하는 것과 같죠. 반면, do-while은 일단 한 번 실행하고 조건을 확인해요. do { printLine(); } while (!atEndOfPage());는 페이지 끝에 도달할 때까지 출력하는 코드예요. 이건 “일단 먹고 배부른지 확인해!” 같은 느낌이에요. 조건에 따라 반복을 제어하고 싶을 때 딱 좋은 방법이죠!
break와 continue: 반복문 제어의 비밀 병기
반복문을 하다가 멈추거나 건너뛰고 싶을 땐 어떻게 할까요? 바로 break와 continue를 쓰면 돼요. break는 반복을 완전히 멈추는 거예요. if (shutDownRequested()) break;는 종료 요청이 오면 반복을 끝내는 코드예요. 마치 “이제 그만!” 하고 게임을 끄는 것과 같아요. 반면 continue는 그 회차만 건너뛰고 다음 반복으로 넘어가요. if (candidate.yearsExperience < 5) continue;는 경력이 5년 미만이면 인터뷰 없이 다음 사람으로 넘어가는 거죠. 이 두 친구는 반복문의 흐름을 자유자재로 조절하는 데 최고예요!
레이블(Label): 반복문의 내비게이션 시스템
레이블은 반복문에 이름을 붙여서 더 세밀하게 제어하는 방법이에요. 예를 들어, 중첩된 반복문에서 바깥 반복문을 멈추고 싶다면 break outerLoop;처럼 레이블을 지정하면 돼요. 마치 “이 길로 나가!” 하고 내비게이션이 특정 출구를 알려주는 것과 같아요. continue도 마찬가지로 continue outerLoop;를 쓰면 특정 반복문의 다음 단계로 바로 이동해요. 예를 들어, i=2, j=2일 때 바깥 반복문의 다음 i로 넘어가게 할 수 있죠. 자, 여기 별표 세 개! 레이블은 중첩 반복문 다룰 때 필수라는 점! ⭐⭐⭐
레이블 활용 사례: for, while, do-while에서 제어하기
레이블은 for, while, do-while 반복문 모두에서 쓸 수 있어요. for 반복문에서 break outerLoop;를 쓰면 중첩된 반복문을 한 번에 빠져나가요. 출력 결과로 i=2, j=2에서 반복이 끝나는 걸 볼 수 있죠. while에서도 마찬가지로 continue outerLoop;를 쓰면 특정 조건에서 바깥 반복문의 다음 단계로 넘어가요. do-while에서도 동일하게 작동해요. 마치 여러 층 건물에서 특정 층으로 바로 이동하는 엘리베이터 같은 느낌! 복잡한 반복문 제어할 때 레이블이 정말 유용하다는 점, 감 오시나요? 😊
오늘의 정리
자, 오늘 Dart의 반복문에 대해 배운 내용을 3줄로 정리해볼게요.
- for, while, do-while 반복문으로 다양한 상황에서 코드를 반복 실행할 수 있어요.
- break와 continue로 반복 흐름을 멈추거나 건너뛰고, 레이블로 중첩 반복문을 세밀하게 제어해요.
- 반복문은 코딩의 효율을 높이는 핵심 도구예요!
마지막으로, 반복문은 상황에 맞게 선택하는 게 중요하다는 점, 꼭 기억해주세요! 오늘 배운 반복문 중 하나를 실제 코드에 적용해보는 과제를 드릴게요. 여러분의 코드가 훨씬 더 스마트해질 거예요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍에서 코드의 흐름을 제어하는 브랜치(Branches)에 대해 함께 알아볼 거예요. 여러분의 코드를 원하는 방향으로 이끄는 기술, 바로 이 흐름 제어가 초보에서 고수로 가는 첫걸음이랍니다! 자, 준비되셨나요? 😊
1. if 문: 선택의 갈림길
여러분, 길을 걷다가 갈림길을 만난 적 있죠? Dart의 if 문은 바로 그런 갈림길이에요. 조건을 보고 어느 길로 갈지 결정하는 거예요. 예를 들어, 비가 오는지 확인하고 우비를 챙길지, 아니면 눈이 오는지 확인하고 재킷을 입을지 결정할 수 있죠.
|
1 2 3 4 5 6 7 8 |
if (isRaining()) { you.bringRainCoat(); // 비 오면 우비 챙겨! } else if (isSnowing()) { you.wearJacket(); // 눈 오면 재킷 입어! } else { car.putTopDown(); // 날씨 좋으면 차 지붕 열고 드라이브! } |
이해 되시죠? 조건이 참이면 첫 번째 길, 아니면 다음 길로 가는 식이에요. 자, 여기 별표 세 개! 조건은 반드시 true/false로 평가되어야 한다는 점, 기억하세요!
2. if-case 문: 패턴 매칭으로 더 똑똑하게
이제 좀 더 똑똑한 갈림길, if-case 문을 만나볼게요. 이건 단순히 조건을 보는 게 아니라, 값의 패턴을 확인해서 길을 선택하는 거예요. 마치 친구 얼굴을 보고 바로 알아보는 것과 같아요.
|
1 2 3 4 5 6 |
if (pair case [int x, int y]) { print('좌표는 $x,$y'); } else { throw FormatException('잘못된 좌표야!'); } |
pair라는 값이 [x, y] 패턴과 맞는지 확인하고, 맞으면 x와 y를 꺼내서 쓰는 거예요. 패턴이 안 맞으면 else로 넘어가는 거고요. 이거 시험에 나와요! 패턴 매칭으로 변수까지 뽑아낼 수 있다는 점, 감 오시나요?
3. switch 문: 여러 갈림길 한 번에 관리
여러분, 여러 개의 길이 동시에 펼쳐진 교차로를 상상해보세요. switch 문은 바로 그런 교차로예요. 하나의 값을 여러 패턴과 비교해서 딱 맞는 길로 가는 거죠.
|
1 2 3 4 5 6 7 8 9 10 |
var command = 'OPEN'; switch (command) { case 'CLOSED': executeClosed(); case 'OPEN': executeOpen(); default: executeUnknown(); // 아무것도 안 맞으면 이 길로! } |
특히 재밌는 건, 빈 케이스는 다음 케이스로 넘어간다는 점이에요. 마치 중간에 휴게소 없이 다음 길로 쭉 가는 것과 같아요. 자, 여기 별표 세 개! default는 모든 경우를 커버하는 안전망이라는 점, 꼭 기억하세요!
4. switch 표현식: 값을 뱉어내는 똑똑한 교차로
이제 switch 표현식을 볼게요. 이건 switch 문과 비슷하지만, 값을 직접 반환한다는 점이 달라요. 마치 교차로에서 길을 고르고 바로 목적지로 가는 느낌이죠.
|
1 2 3 4 5 6 |
token = switch (charCode) { slash || star => operator(charCode), comma || semicolon => punctuation(charCode), _ => throw FormatException('잘못됨!'), }; |
케이스마다 값을 뱉어내니 코드가 더 간결해져요. 표현식은 값으로 사용할 수 있다는 점, 이거 정말 유용하다는 거 아시죠?
5. 완전성 검사: 놓친 길이 없도록!
여러분, 길을 가다 한 곳을 빠뜨리면 큰일 나죠? Dart의 완전성 검사(Exhaustiveness Checking)는 switch에서 모든 경우를 다 다뤘는지 확인해주는 기능이에요. 예를 들어, bool? 타입에서 null을 빠뜨리면 컴파일러가 경고를 주죠.
|
1 2 3 |
sealed class Shape {} // Shape 타입의 switch에서 모든 하위 클래스를 다루지 않으면 경고! |
이건 마치 여행 계획을 짤 때 모든 경로를 확인하는 것과 같아요. 자, 여기 별표 세 개! 완전성 검사는 실수를 줄여주는 든든한 조력자라는 점, 꼭 기억해요!
오늘의 정리
자, 오늘 배운 내용을 3줄로 정리해볼게요.
- Dart의 흐름 제어는 if, if-case, switch 문/표현식으로 코드를 원하는 방향으로 이끌어요.
- 패턴 매칭과 완전성 검사로 더 똑똑하고 안전하게 코딩할 수 있어요.
- 각 도구의 특성을 이해하고 적절히 쓰는 게 핵심이에요!
마지막으로, 여러분께 드리는 실천 과제! 오늘 배운 if나 switch를 사용해서 간단한 코드를 짜보세요. 예를 들어, 날씨에 따라 옷을 추천하는 코드를 만들어보면 어떨까요?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍에서 코드가 예상치 못한 상황에 부딪혔을 때 어떻게 대처할 수 있는지, 바로 예외 처리(Error Handling)에 대해 배워볼 거예요. 코드를 안전하게 지키는 비법, 함께 알아보시죠! 😊
1. 예외란? 갑작스러운 문제 상황!
여러분, 길을 가다 갑자기 돌부리에 걸려 넘어질 뻔한 적 있죠? Dart에서의 예외(Exception)는 바로 그런 돌부리 같은 거예요. 코드 실행 중 예상치 못한 문제가 생기면 예외가 발생하고, 이걸 처리하지 않으면 프로그램이 멈춰버릴 수 있어요. Dart에서는 모든 예외가 ‘unchecked’라서, 어떤 예외가 발생할지 미리 선언할 필요는 없다는 점이 특징이에요. 자유롭지만 책임감이 필요한 거죠!
2. 예외 던지기(Throw): 문제 알리기
문제를 발견했는데 그냥 넘어갈 수는 없겠죠? Dart에서는 throw를 사용해 예외를 던질 수 있어요. 마치 “문제 발생!”이라고 큰 소리로 외치는 것과 같아요.
|
1 2 |
throw FormatException('최소 1개의 섹션이 필요해요!'); |
심지어 단순한 문자열 같은 임의의 객체도 던질 수 있어요.
|
1 2 |
throw '라마가 다 떨어졌어요!'; |
자, 여기 별표 세 개! throw는 표현식이라서 어디서든 사용할 수 있다는 점, 기억하세요! 하지만 실제 코드에서는 보통 Exception이나 Error 타입을 던지는 게 좋다는 점도 잊지 마세요.
3. 예외 잡기(Catch): 문제 해결하기
예외가 던져졌다면, 그걸 받아서 처리해야겠죠? try-catch는 마치 안전망 같은 거예요. 문제가 생길 만한 코드를 try 안에서 실행하고, 예외가 발생하면 catch에서 잡아내는 거죠.
|
1 2 3 4 5 6 |
try { breedMoreLlamas(); // 라마를 더 키워보자! } on OutOfLlamasException { buyMoreLlamas(); // 라마가 없으면 더 사와! } |
여러 종류의 예외를 처리하려면 catch를 여러 번 쓸 수도 있어요. 특정 예외를 먼저 잡고, 나머지는 일반적으로 처리하는 식이죠. 이해 되시죠? catch는 예외 객체를 받아 세부 정보를 확인할 수 있다는 점, 감 오시나요?
4. 예외 재던지기(Rethrow): 책임 넘기기
가끔은 예외를 일부 처리하고, 나머지 책임을 다른 곳에 넘기고 싶을 때가 있어요. 이때 rethrow를 사용하면 돼요. 마치 “이건 나 혼자 해결 못 해, 위로 넘길게!”라고 말하는 것과 같아요.
|
1 2 3 4 5 6 7 8 9 10 |
void misbehave() { try { dynamic foo = true; print(foo++); // 실행 중 오류 발생 } catch (e) { print('misbehave()에서 일부 처리: ${e.runtimeType}'); rethrow; // 상위로 예외 넘기기 } } |
자, 여기 별표 세 개! rethrow는 예외를 완전히 해결하지 않고 상위로 전달하는 방법이라는 점, 꼭 알아두세요!
5. Finally: 무조건 실행되는 정리 작업
예외가 있든 없든 반드시 해야 할 일이 있다면? finally를 사용하면 돼요. 이건 마치 여행을 끝내고 집에 돌아와서 무조건 짐을 정리하는 것과 같아요.
|
1 2 3 4 5 6 |
try { breedMoreLlamas(); } finally { cleanLlamaStalls(); // 예외 여부와 상관없이 항상 정리! } |
이거 시험에 나와요! finally는 catch가 끝난 후 실행되거나, catch가 없어도 예외 전파 전에 실행된다는 점, 기억하셨죠?
6. Assert: 개발 중 문제 잡아내기
개발 중에 코드가 이상한 방향으로 가는 걸 막으려면 assert를 써보세요. 이건 마치 경고등 같은 거예요. 조건이 false면 예외를 던져서 문제를 알려주는 거죠.
|
1 2 3 |
<span style="color: #3366ff;"><code class="language-dart">assert(text != null); // text가 null이면 안 돼! assert(number < 100, '숫자는 100보다 작아야 해요!'); </code></span> |
assert는 디버그 모드에서만 작동하고, 프로덕션 코드에서는 무시돼요. 자, 여기 별표 세 개! assert는 개발 중 실수를 잡아내는 데 유용하다는 점, 꼭 기억하세요!
오늘의 정리
자, 오늘 배운 내용을 3줄로 정리해볼게요.
- Dart에서 예외는 코드의 예상치 못한 문제를 알리는 신호로, throw로 던지고 try-catch로 잡을 수 있어요.
- rethrow로 예외를 전달하거나, finally로 무조건 실행되는 정리를 할 수 있죠.
- assert는 개발 중 문제를 잡아내는 경고등 같은 역할을 해요!
마지막으로, 여러분께 드리는 실천 과제! 오늘 배운 try-catch를 사용해 간단한 코드를 짜보세요. 예를 들어, 숫자를 입력받아 0으로 나누는 상황에서 예외를 처리하는 코드를 만들어보면 어떨까요?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 함수를 완벽히 파헤쳐 볼 거예요. 함수는 Dart에서 모든 것을 객체로 다루는 진정한 객체 지향 언어의 핵심 요소인데요, 함수를 제대로 이해하면 코드가 한층 더 강력해진다는 사실! 자, 그럼 함께 출발해 볼까요? 🚀
함수가 뭔가요? 기본부터 잡기
여러분, 함수가 뭘까요? 함수는 마치 요리 레시피 같은 거예요. 재료를 넣으면 정해진 방식으로 요리를 만들어주는 과정이죠. Dart에서는 함수도 객체로 취급돼서 변수에 저장하거나 다른 함수에 전달할 수 있어요. 심지어 클래스의 인스턴스를 함수처럼 호출할 수도 있답니다.
예를 들어, 원소 번호를 받아서 그게 귀족 기체인지 알려주는 함수를 만들어 보죠:
|
1 2 |
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null; |
이 코드는 한 줄로 끝나는 짧은 레시피예요. 화살표(=>) 문법을 사용해서 간단하게 “이거 맞아? 확인하고 결과 내놔!” 하는 거죠. 자, 여기 별표 세 개! 화살표 문법은 한 줄 표현식에만 사용 가능하다는 점, 기억하세요!
매개변수: 함수의 재료 넣기
함수는 재료, 즉 매개변수를 받아서 요리를 시작해요. Dart에서는 필수 매개변수, 선택적 위치 매개변수, 이름 붙은 매개변수까지 다양하게 사용할 수 있어요. 이게 뭐냐고요? 식당에서 주문한다고 생각해 보세요. 기본 메뉴는 꼭 선택해야 하고(필수), 추가 토핑은 넣어도 되고 말아도 되는(선택적) 거죠.
이름 붙은 매개변수는 주문할 때 “이건 꼭 넣어주세요!”라고 이름표를 붙이는 느낌이에요:
|
1 2 |
void enableFlags({bool bold = false, bool hidden = false}) { ... } |
호출할 때는 enableFlags(bold: true)처럼 이름과 값을 함께 전달하면 되는 거예요. 이해 되시죠? 그리고 required 키워드를 붙이면 “이건 꼭 넣어야 해!”라고 강제할 수도 있답니다.
함수도 일급 객체: 어디서든 자유롭게
Dart의 함수는 일급 객체예요. 이게 무슨 말이냐? 함수를 마치 물건처럼 여기저기 들고 다닐 수 있다는 거죠. 가방에 넣어 다른 함수에 전달하거나 변수에 저장할 수 있어요. 예를 들어, 리스트의 각 요소를 출력하는 함수를 전달하는 코드를 보죠:
|
1 2 3 |
var list = [1, 2, 3]; list.forEach(printElement); |
이건 마치 “이 리스트를 이 사람(printElement)에게 맡겨서 처리해!”라고 말하는 것과 같아요. 자, 여기 별표 세 개! 함수를 변수에 저장하거나 전달하는 게 Dart의 강력한 기능이라는 점, 시험에 나와요!
익명 함수: 이름 없는 요리사
익명 함수는 이름 없는 요리사 같은 거예요. 특정 작업을 위해 잠깐 고용해서 일을 시키는 거죠. Dart에서는 이런 함수를 만들어서 바로 사용할 수 있어요:
|
1 2 |
var uppercaseList = list.map((item) => item.toUpperCase()).toList(); |
여기서 (item) => item.toUpperCase()는 이름 없이 바로 동작하는 함수예요. 리스트의 각 요소를 대문자로 바꾸는 임시 요리사인 셈이죠. 감 오시나요?
클로저와 스코프: 기억력 좋은 함수
Dart의 함수는 기억력이 좋아요. 클로저라는 개념인데, 함수가 자신이 태어난 환경의 변수들을 기억하고 언제 어디서든 그 값을 사용할 수 있다는 거예요. 마치 집에서 배운 요리법을 어디서든 똑같이 재현하는 셰프 같죠.
예를 들어, 숫자를 더하는 함수를 만들어 보죠:
|
1 2 3 4 5 |
<span style="color: #3366ff;"><code class="language-dart">Function makeAdder(int addBy) { return (int i) => addBy + i; } var add2 = makeAdder(2); </code></span> |
add2는 항상 2를 더하는 함수가 되는 거예요. addBy 값을 기억하고 있어서 어디서 호출하든 2를 더해준답니다. 이거 멋지지 않나요?
게터와 세터: 속성의 비밀 요원
게터와 세터는 속성에 접근할 때 사용하는 비밀 요원 같은 존재예요. 겉으로는 단순한 변수처럼 보이지만, 실제로는 값을 계산하거나 보호하는 역할을 하죠. 마치 냉장고 문을 열 때마다 안에 뭐가 있는지 확인하고 조작하는 보안 요원 같아요.
예를 들어, 비밀 메시지를 다루는 코드를 보죠:
|
1 2 3 4 5 6 |
String _secret = 'Hello'; String get secret => _secret.toUpperCase(); set secret(String newMessage) { if (newMessage.isNotEmpty) _secret = newMessage; } |
여기서 get은 값을 읽을 때, set은 값을 설정할 때 호출돼요. 값을 대문자로 바꿔 보여주거나 빈 값은 저장하지 않도록 보호하는 거죠. 자, 여기 별표 세 개! 게터와 세터는 데이터 보호와 계산을 숨겨주는 강력한 도구라는 점, 꼭 기억하세요!
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 함수는 객체로, 변수에 저장하거나 전달할 수 있는 일급 객체예요.
- 매개변수, 익명 함수, 클로저, 게터/세터 등 다양한 기능을 통해 유연한 코딩이 가능하죠.
- 함수의 스코프와 기억력을 활용하면 강력한 로직을 만들 수 있답니다.
이 모든 게 Dart 함수의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 직접 코드로 작성해보는 과제를 드릴게요. 간단한 익명 함수를 만들어서 리스트를 변환해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 클래스를 완벽히 파헤쳐 볼 거예요. 클래스는 객체 지향 프로그래밍의 핵심으로, 모든 객체가 클래스의 인스턴스라는 점에서 Dart의 뼈대를 이루는 요소죠. 클래스를 이해하면 코드가 훨씬 체계적이고 강력해진다는 사실! 자, 함께 시작해 볼까요? 🚀
클래스가 뭘까요? 객체의 설계도
여러분, 클래스가 뭔지 아시나요? 클래스는 마치 건물을 짓기 위한 설계도 같은 거예요. 이 설계도에는 건물의 구조(데이터)와 기능(메서드)이 정의되어 있고, 이 설계도를 바탕으로 실제 건물(객체)을 만들 수 있죠. Dart에서는 모든 클래스가 Object라는 최상위 클래스에서 비롯되며, 믹스인 기반 상속으로 여러 계층에서 재사용이 가능해요.
예를 들어, 좌표를 다루는 Point 클래스를 통해 객체를 만들어보죠:
|
1 2 3 |
var p = Point(2, 2); double distance = p.distanceTo(Point(4, 4)); |
이건 마치 설계도를 보고 건물을 짓고, 그 건물 사이의 거리를 측정하는 것과 같아요. 이해 되시죠?
생성자: 객체를 만드는 공장
생성자는 설계도를 실제 객체로 만드는 공장 같은 거예요. Dart에서는 클래스 이름으로 기본 생성자를 호출하거나, ClassName.identifier 형식으로 이름이 붙은 생성자를 사용할 수 있죠:
|
1 2 3 |
var p1 = Point(2, 2); // 기본 생성자 var p2 = Point.fromJson({'x': 1, 'y': 2}); // 이름 붙은 생성자 |
상수 생성자를 사용할 수도 있는데, const 키워드를 붙이면 컴파일 시점에 고정된 객체를 만들어요. 이건 마치 똑같은 설계도로 만든 건물이 하나만 존재하게 보장하는 것과 같죠:
|
1 2 3 4 |
var a = const ImmutablePoint(1, 1); var b = const ImmutablePoint(1, 1); assert(identical(a, b)); // 동일한 인스턴스! |
자, 여기 별표 세 개! const 생성자는 동일한 객체를 재사용한다는 점, 기억하세요!
인스턴스 변수: 객체의 속성
인스턴스 변수는 객체가 가진 고유한 데이터예요. 이건 마치 건물의 방 크기나 색상 같은 속성과 같죠. Dart에서는 변수 선언 시 초기값을 설정하거나 null을 기본값으로 둘 수 있어요:
|
1 2 3 4 5 |
class Point { double? x; // 초기값 null double z = 0; // 초기값 0 } |
모든 인스턴스 변수는 자동으로 getter를 제공하고, final이 아닌 경우 setter도 제공돼요. final 변수는 한 번만 설정 가능하니, 마치 건물의 기초 공사처럼 처음에 확정해야 하는 속성이라고 생각하면 돼요. 감 오시나요?
암시적 인터페이스: 클래스의 약속
Dart의 모든 클래스는 자동으로 인터페이스를 정의해요. 이건 마치 건물 설계도가 “이런 기능은 꼭 있어야 해!”라는 약속을 포함하는 것과 같아요. 다른 클래스가 이 약속을 지키며 구현할 수 있죠:
|
1 2 3 4 5 6 7 8 9 10 11 |
class Person { final String _name; Person(this._name); String greet(String who) => 'Hello, $who. I am $_name.'; } class Impostor implements Person { String get _name => ''; String greet(String who) => 'Hi $who. Do you know who I am?'; } |
Impostor 클래스는 Person의 인터페이스를 구현하면서 약속된 메서드를 제공하는 거예요. 이건 설계도의 규칙만 따르면 다른 스타일로 건물을 지어도 되는 것과 같아요.
정적 변수와 메서드: 클래스 전체의 공유 자원
정적 변수와 메서드는 클래스 전체가 공유하는 자원이에요. 이건 마치 아파트 단지에서 모든 건물이 공유하는 주차장이나 관리 사무소 같은 거죠. static 키워드로 정의하면 인스턴스가 아닌 클래스 자체에 속하게 돼요:
|
1 2 3 4 5 6 7 8 9 |
class Point { static double distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } var distance = Point.distanceBetween(Point(2, 2), Point(4, 4)); |
정적 메서드는 this에 접근할 수 없지만, 클래스 단위의 공통 기능을 제공하는 데 유용하죠. 자, 여기 별표 세 개! 정적 멤버는 클래스 전체의 자원이라는 점, 시험에 나와요!
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 클래스는 객체를 만드는 설계도로, 데이터와 기능을 정의하며 모든 클래스는
Object에서 시작돼요. - 생성자로 객체를 만들고, 인스턴스 변수와 메서드로 고유 속성과 동작을 설정할 수 있죠.
- 암시적 인터페이스와 정적 멤버를 통해 유연한 설계와 공유 자원을 활용할 수 있어요.
이 모든 게 Dart 클래스의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 클래스를 만들어서 인스턴스 변수와 메서드를 정의해보세요. 할 수 있죠?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 생성자(Constructor)를 완벽히 파헤쳐 볼 거예요. 생성자는 클래스의 인스턴스를 만드는 특별한 함수로, 객체를 어떻게 만들고 초기화할지를 결정하는 설계도의 첫 페이지 같은 존재죠. 생성자를 제대로 이해하면 객체 생성이 한결 쉬워진다는 사실! 자, 함께 시작해 볼까요? 🚀
생성자가 뭘까요? 객체 만드는 공장
여러분, 생성자가 뭔지 아시나요? 생성자는 클래스의 인스턴스를 만드는 공장 같은 거예요. 이 공장에서 객체를 만들 때 필요한 재료(매개변수)를 넣고, 설계도에 맞게 초기 설정을 해주는 역할을 하죠. Dart에서는 기본 생성자부터 다양한 종류의 생성자를 제공해서 객체를 유연하게 만들 수 있어요.
기본적으로 생성자는 클래스 이름과 같고, 여러 가지 형태로 제공돼요. 이건 마치 건물을 지을 때 다양한 공법을 선택할 수 있는 것과 같아요. 이해 되시죠?
생성자의 종류: 다양한 공장 라인
Dart에는 여러 가지 생성자가 있어요. 이건 마치 공장에서 다양한 생산 라인을 두는 것과 같죠. 하나씩 살펴볼게요.
1. 생성 생성자(Generative Constructor): 기본 생산 라인
생성 생성자는 객체를 새로 만드는 가장 기본적인 방법이에요. 클래스에 속한 인스턴스 변수를 초기화하면서 객체를 만들죠:
|
1 2 3 4 5 6 |
class Point { double x; double y; Point(this.x, this.y); } |
이건 마치 “x와 y 좌표를 입력받아 새로운 좌표 객체를 만들어라!”라고 지시하는 것과 같아요.
2. 기본 생성자(Default Constructor): 자동 생산 라인
만약 생성자를 따로 정의하지 않으면, Dart는 자동으로 기본 생성자를 제공해요. 이건 매개변수 없이 객체를 만드는 아주 간단한 라인이죠. 마치 “아무 설정 없이 기본 건물을 지어라!”라고 하는 것과 같아요.
3. 이름 붙은 생성자(Named Constructor): 특화된 생산 라인
이름 붙은 생성자는 특정 목적에 맞게 객체를 만들 때 사용해요. 클래스에 여러 생성자를 두고 싶을 때 유용하죠:
|
1 2 3 4 5 6 7 |
class Point { final double x; final double y; Point(this.x, this.y); Point.origin() : x = 0, y = 0; } |
Point.origin()은 원점(0, 0)을 만드는 특화된 라인인 셈이에요. 자, 여기 별표 세 개! 이름 붙은 생성자는 목적을 명확히 드러내는 데 유용하다는 점, 기억하세요!
4. 상수 생성자(Constant Constructor): 고정된 제품 만들기
상수 생성자는 컴파일 시점에 고정된 객체를 만들어요. 이건 마치 한 번 만든 제품이 절대 변하지 않는 것과 같죠:
|
1 2 3 4 5 |
class ImmutablePoint { final double x, y; const ImmutablePoint(this.x, this.y); } |
const 키워드를 사용하면 동일한 값으로 만든 객체는 하나의 인스턴스로 공유돼요. 효율적이죠!
5. 팩토리 생성자(Factory Constructor): 스마트 공장
팩토리 생성자는 좀 더 똑똑한 공장 라인 같아요. 새 객체를 만들지 않고 캐시에서 기존 객체를 반환하거나, 하위 타입의 객체를 만들 수도 있죠:
|
1 2 3 4 5 6 7 8 9 |
class Logger { final String name; static final Map<String, Logger> _cache = <String, Logger>{}; factory Logger(String name) { return _cache.putIfAbsent(name, () => Logger._internal(name)); } Logger._internal(this.name); } |
이건 마치 “이미 만든 제품이 있으면 그걸 재사용해!”라고 지시하는 것과 같아요. 감 오시나요?
6. 리다이렉팅 생성자(Redirecting Constructor): 다른 라인으로 넘기기
리다이렉팅 생성자는 다른 생성자로 호출을 넘기는 거예요. 마치 “이 작업은 다른 라인에서 처리해!”라고 말하는 것과 같죠:
|
1 2 3 4 5 6 |
class Point { double x, y; Point(this.x, this.y); Point.alongXAxis(double x) : this(x, 0); } |
Point.alongXAxis는 x축에만 값을 설정하고 y는 0으로 넘기는 방식이에요.
인스턴스 변수 초기화: 공장 설정값 넣기
Dart에서는 객체를 만들 때 인스턴스 변수를 초기화하는 여러 방법을 제공해요. 이건 마치 공장에서 제품을 조립하기 전에 필요한 부품을 설정하는 것과 같아요.
1. 선언 시 초기화: 기본값 설정
변수 선언과 함께 초기값을 설정할 수 있어요:
|
1 2 3 4 5 |
class PointA { double x = 1.0; double y = 2.0; } |
이건 마치 “모든 제품은 기본적으로 이런 값으로 시작해!”라고 설정하는 것과 같죠.
2. 초기화 형식 매개변수: 입력값 바로 연결
생성자 매개변수를 바로 변수에 연결하는 방식이에요:
|
1 2 3 4 5 6 |
class PointB { final double x; final double y; PointB(this.x, this.y); } |
this.x로 입력값을 바로 변수에 할당하는 거예요. 간단하고 효율적이죠.
3. 초기화 리스트: 조립 전 설정
생성자 본문이 실행되기 전에 변수를 초기화할 수 있는 리스트를 사용할 수도 있어요:
|
1 2 3 4 5 6 7 |
class Point { double x, y; Point.fromJson(Map<String, double> json) : x = json['x']!, y = json['y']! { print('Initialized!'); } } |
이건 마치 “조립 시작 전에 이 부품들을 먼저 맞춰놔!”라고 지시하는 것과 같아요. 자, 여기 별표 세 개! 초기화 리스트는 복잡한 설정에 유용하다는 점, 시험에 나와요!
생성자 상속과 슈퍼 파라미터: 부모 공장 활용하기
하위 클래스는 상위 클래스의 생성자를 상속받지 않아요. 이건 마치 자식 공장이 부모 공장의 생산 라인을 그대로 가져오지 않는 것과 같죠. 대신, 상위 클래스의 생성자를 호출해서 초기화를 할 수 있어요:
|
1 2 3 4 5 |
class Vector3d extends Vector2d { final double z; Vector3d(super.x, super.y, this.z); } |
super.x, super.y로 부모 공장에 값을 전달하는 방식이에요. 부모 공장의 설계도를 활용하면서 추가 부품(z)을 붙이는 거죠.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 생성자는 클래스의 인스턴스를 만드는 공장으로, 생성 생성자, 상수 생성자, 팩토리 생성자 등 다양한 종류가 있어요.
- 인스턴스 변수는 선언 시, 초기화 형식 매개변수, 초기화 리스트로 설정할 수 있죠.
- 상위 클래스의 생성자를 호출하거나 슈퍼 파라미터로 값을 전달해 유연한 초기화가 가능해요.
이 모든 게 Dart 생성자의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 클래스를 만들고 이름 붙은 생성자와 초기화 리스트를 사용해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어에서 클래스 확장(Extend)에 대해 완벽히 파헤쳐 볼 거예요. 클래스를 확장하면 기존 클래스의 기능을 물려받아 더 멋진 클래스를 만들 수 있는데, 이건 객체 지향 프로그래밍의 핵심 중 하나죠. 클래스 확장을 이해하면 코드 재사용성이 확 올라간다는 사실! 자, 함께 시작해 볼까요? 🚀
클래스 확장이 뭘까요? 부모의 설계도 물려받기
여러분, 클래스 확장이 뭔지 아시나요? 클래스 확장은 마치 부모의 설계도를 물려받아 새로운 건물을 짓는 것과 같아요. Dart에서는 extends 키워드를 사용해 하위 클래스(자식 클래스)를 만들고, super 키워드로 부모 클래스(상위 클래스)의 기능을 호출할 수 있죠.
예를 들어, TV 클래스를 확장해서 스마트 TV를 만들어볼게요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Television { void turnOn() { _illuminateDisplay(); _activateIrSensor(); } } class SmartTelevision extends Television { void turnOn() { super.turnOn(); // 부모의 turnOn 호출 _bootNetworkInterface(); _initializeMemory(); _upgradeApps(); } } |
이건 마치 기본 TV 설계도에 스마트 기능을 추가한 것과 같아요. super.turnOn()으로 부모의 기능을 먼저 실행하고, 추가 기능을 붙이는 거죠. 이해 되시죠?
멤버 오버라이딩: 부모의 기능 재정의하기
하위 클래스는 부모 클래스의 메서드, 게터, 세터를 재정의(오버라이드)할 수 있어요. 이건 마치 부모의 설계도에서 특정 부분을 내 스타일로 바꾸는 것과 같죠. @override 어노테이션을 사용해 의도적으로 재정의한다고 명시할 수 있어요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Television { set contrast(int value) { // ... } } class SmartTelevision extends Television { @override set contrast(num value) { // 새로운 로직으로 재정의 } } |
재정의할 때는 몇 가지 규칙을 지켜야 해요. 반환 타입은 같거나 하위 타입이어야 하고, 매개변수 타입은 같거나 상위 타입이어야 하죠. 이건 마치 부모 설계도의 기본 구조는 유지하면서 세부 사항을 조정하는 것과 같아요. 자, 여기 별표 세 개! 오버라이딩은 부모의 기능을 커스터마이즈하는 강력한 도구라는 점, 기억하세요!
타입 좁히기와 covariant: 세밀한 조정하기
때로는 메서드 매개변수나 변수의 타입을 더 구체적으로 좁히고 싶을 때가 있어요. 이건 일반적인 규칙에는 어긋나지만, 런타임 오류가 발생하지 않는다는 보장이 있다면 covariant 키워드로 가능하죠. 이건 마치 “이 부분은 더 구체적으로 설계해도 문제없어!”라고 선언하는 것과 같아요. Dart 언어 사양에서 더 자세한 내용을 확인할 수 있으니 참고하세요.
noSuchMethod(): 없는 기능에 대응하기
만약 존재하지 않는 메서드나 변수를 호출하려고 할 때 어떻게 할까요? Dart에서는 noSuchMethod()를 오버라이드해서 이런 상황에 대응할 수 있어요. 이건 마치 “이런 기능은 없지만, 대신 이렇게 반응할게!”라고 로봇에게 지시하는 것과 같아요:
|
1 2 3 4 5 6 7 |
class A { @override void noSuchMethod(Invocation invocation) { print('You tried to use a non-existent member: ${invocation.memberName}'); } } |
이 메서드를 사용하면 없는 멤버에 접근할 때 오류 대신 원하는 동작을 정의할 수 있죠. 단, 특정 조건(수신자가 dynamic 타입이거나, 수신자의 동적 타입이 noSuchMethod()를 구현한 경우)이 충족되지 않으면 호출할 수 없다는 점을 주의하세요. 감 오시나요?
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart에서
extends로 클래스를 확장하고,super로 부모 클래스의 기능을 호출할 수 있어요. @override로 메서드나 게터/세터를 재정의하며 부모의 기능을 커스터마이즈할 수 있죠.noSuchMethod()를 사용해 존재하지 않는 멤버 호출에 대응할 수 있어요.
이 모든 게 Dart 클래스 확장의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 부모 클래스를 만들고, 이를 확장한 하위 클래스에서 메서드를 오버라이드해보세요. 할 수 있죠?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 믹스인(Mixin)을 완벽히 파헤쳐 볼 거예요. 믹스인은 여러 클래스 계층에서 재사용 가능한 코드를 정의하는 방법으로, 객체 지향 프로그래밍에서 엄청 유용한 도구죠. 믹스인을 이해하면 코드 중복을 줄이고 효율성을 높일 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
믹스인이 뭘까요? 재사용 가능한 코드 블록
여러분, 믹스인이 뭔지 아시나요? 믹스인은 마치 여러 요리에 공통으로 들어가는 양념 세트 같은 거예요. 이 양념을 여러 클래스에 뿌려서 공통 기능을 쉽게 추가할 수 있죠. Dart에서는 with 키워드를 사용해 믹스인을 클래스에 적용할 수 있어요.
예를 들어, 음악적 재능을 가진 믹스인을 두 클래스에 적용해보죠:
|
1 2 3 4 5 6 7 8 9 10 11 |
class Musician extends Performer with Musical { // ... } class Maestro extends Person with Musical, Aggressive, Demented { Maestro(String maestroName) { name = maestroName; canConduct = true; } } |
이건 마치 “이 요리에는 음악적 양념을, 저 요리에는 음악적+공격적+광적인 양념을 추가해!”라고 말하는 것과 같아요. 이해 되시죠?
믹스인 정의하기: 공통 양념 만들기
믹스인은 mixin 키워드로 정의해요. 드물게 클래스와 믹스인을 동시에 정의해야 할 때는 mixin class를 사용할 수도 있죠. 믹스인은 extends 절을 가질 수 없고, 생성 생성자도 선언할 수 없다는 점이 특징이에요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
mixin Musical { bool canPlayPiano = false; bool canCompose = false; bool canConduct = false; void entertainMe() { if (canPlayPiano) { print('Playing piano'); } else if (canConduct) { print('Waving hands'); } else { print('Humming to self'); } } } |
이건 마치 “음악적 양념에는 이런 재료와 조리법이 들어가!”라고 정의한 것과 같아요. 감 오시나요?
믹스인이 의존하는 멤버 지정하기: 필요한 재료 확보
믹스인이 제대로 작동하려면 때로는 특정 메서드나 필드가 필요해요. 이건 마치 양념을 만들 때 특정 재료가 반드시 있어야 하는 것과 같죠. Dart에서는 몇 가지 방법으로 이런 의존성을 보장할 수 있어요.
1. 추상 멤버 정의: 필수 재료 강제하기
믹스인에 추상 메서드를 선언하면, 믹스인을 사용하는 클래스는 반드시 그 메서드를 구현해야 해요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
mixin Musician { void playInstrument(String instrumentName); // 추상 메서드 void playPiano() { playInstrument('Piano'); } } class Virtuoso with Musician { @override void playInstrument(String instrumentName) { print('Plays the $instrumentName beautifully'); } } |
이건 마치 “이 양념을 쓰려면 이 재료는 네가 꼭 준비해!”라고 요구하는 것과 같아요.
2. 하위 클래스의 상태 접근: 재료 가져오기
추상 게터를 선언해서 하위 클래스의 상태에 접근할 수도 있어요:
|
1 2 3 4 5 6 7 8 9 10 11 |
mixin NameIdentity { String get name; @override int get hashCode => name.hashCode; } class Person with NameIdentity { final String name; Person(this.name); } |
이건 마치 “이름이라는 재료를 가져와서 양념에 섞을게!”라고 말하는 것과 같죠.
3. 인터페이스 구현: 규칙 지키기
implements 절을 사용해 믹스인이 특정 인터페이스를 요구하게 할 수도 있어요. 믹스인을 사용하는 클래스는 그 인터페이스를 구현해야 하죠:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
abstract interface class Tuner { void tuneInstrument(); } mixin Guitarist implements Tuner { void playSong() { tuneInstrument(); print('Strums guitar majestically.'); } } class PunkRocker with Guitarist { @override void tuneInstrument() { print("Don't bother, being out of tune is punk rock."); } } |
이건 마치 “이 양념을 쓰려면 조율법을 반드시 알아야 해!”라고 규칙을 정하는 것과 같아요.
4. on 절 사용: 상위 클래스 요구하기
on 절을 사용하면 믹스인이 특정 상위 클래스를 요구하게 만들 수 있어요. 이건 super 호출이 필요한 경우 유용하죠:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Musician { musicianMethod() { print('Playing music!'); } } mixin MusicalPerformer on Musician { performerMethod() { print('Performing music!'); super.musicianMethod(); } } class SingerDancer extends Musician with MusicalPerformer { } |
이건 마치 “이 양념은 음악가라는 기본 재료가 있어야만 쓸 수 있어!”라고 조건을 걸어놓은 것과 같아요. 자, 여기 별표 세 개! on 절은 믹스인의 상위 클래스 의존성을 보장한다는 점, 시험에 나와요!
클래스, 믹스인, 믹스인 클래스: 뭐가 다를까?
Dart에서는 mixin, class, 그리고 mixin class라는 세 가지 선언이 있어요. mixin class는 클래스와 믹스인 둘 다로 사용할 수 있는 특별한 선언이죠. 단, 믹스인과 관련된 제약(예: extends나 with 절 사용 불가)은 여전히 적용돼요:
|
1 2 3 4 5 6 7 8 9 10 |
mixin class Musician { // ... } class Novice with Musician { // 믹스인으로 사용 // ... } class Novice extends Musician { // 클래스로 사용 // ... } |
이건 마치 “이 양념은 요리로도 쓰고, 다른 요리에 첨가물로도 쓸 수 있어!”라고 다용도로 준비한 것과 같아요.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 믹스인은 여러 클래스 계층에서 재사용 가능한 코드를 정의하는 방법으로,
with키워드로 적용해요. - 추상 멤버, 인터페이스,
on절 등을 사용해 믹스인이 필요한 멤버를 보장할 수 있죠. mixin,class,mixin class선언을 통해 다양한 상황에 맞는 코드를 설계할 수 있어요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 열거형(Enum)을 완벽히 파헤쳐 볼 거예요. 열거형은 고정된 상수 값을 표현하는 특별한 클래스인데, 코드를 더 명확하고 안전하게 만들어주는 강력한 도구죠. 열거형을 이해하면 선택지를 깔끔하게 정리할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
열거형이 뭘까요? 선택지의 정리 상자
여러분, 열거형이 뭔지 아시나요? 열거형은 고정된 선택지를 나열한 상자 같은 거예요. 마치 메뉴판에서 “빨강, 초록, 파랑” 중 하나만 고를 수 있게 정해놓은 것과 같죠. Dart에서는 enum 키워드로 열거형을 정의하며, 모든 열거형은 자동으로 Enum 클래스를 확장해요. 이건 하위 클래스를 만들거나 인스턴스를 직접 생성할 수 없다는 뜻이에요.
이건 마치 “이 선택지 외에는 절대 추가 안 돼!”라고 규칙을 정해놓는 것과 같아요. 이해 되시죠?
간단한 열거형 선언: 기본 메뉴판 만들기
간단한 열거형을 선언하려면 enum 키워드를 사용하고, 나열할 값을 적으면 돼요. 예를 들어, 색상을 선택지로 만들어볼게요:
|
1 2 |
enum Color { red, green, blue } |
이건 마치 메뉴판에 “빨강, 초록, 파랑” 세 가지 옵션만 적어놓은 것과 같아요. 추가 선택지는 없죠. 자, 여기 별표 세 개! 간단한 열거형은 고정된 상수를 표현하는 데 최적이라는 점, 기억하세요!
향상된 열거형 선언: 메뉴판에 정보 추가하기
Dart에서는 열거형에 필드, 메서드, 상수 생성자를 추가해 더 풍성하게 만들 수 있어요. 이건 마치 메뉴판에 각 음식의 칼로리나 재료 정보를 추가하는 것과 같죠. 몇 가지 규칙이 있지만, 엄청 유용해요:
- 인스턴스 변수는
final이어야 해요. - 모든 생성 생성자는 상수(
const)여야 하죠. values라는 이름은 자동 생성되는 getter와 충돌하니 사용할 수 없어요.
예를 들어, 교통수단을 정의하면서 탄소 발자국을 계산하는 열거형을 만들어볼게요:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
enum Vehicle implements Comparable<Vehicle> { car(tires: 4, passengers: 5, carbonPerKilometer: 400), bus(tires: 6, passengers: 50, carbonPerKilometer: 800), bicycle(tires: 2, passengers: 1, carbonPerKilometer: 0); const Vehicle({ required this.tires, required this.passengers, required this.carbonPerKilometer, }); final int tires; final int passengers; final int carbonPerKilometer; int get carbonFootprint => (carbonPerKilometer / passengers).round(); bool get isTwoWheeled => this == Vehicle.bicycle; @override int compareTo(Vehicle other) => carbonFootprint - other.carbonFootprint; } |
이건 마치 “자동차는 타이어 4개, 승객 5명, 탄소 배출량 이 정도야!”라고 각 메뉴에 상세 정보를 추가한 것과 같아요. 감 오시나요?
열거형 사용하기: 선택지 활용하기
열거형 값은 정적 변수처럼 접근할 수 있어요. 마치 메뉴판에서 원하는 음식을 고르는 것과 같죠:
|
1 2 3 4 5 |
final favoriteColor = Color.blue; if (favoriteColor == Color.blue) { print('Your favorite color is blue!'); } |
각 열거형 값은 index getter를 통해 선언 순서를 알 수 있어요. 첫 번째 값은 0, 두 번째는 1 이런 식으로요. 또한 values 상수를 통해 모든 값의 리스트를 얻을 수도 있죠:
|
1 2 3 |
List<Color> colors = Color.values; assert(colors[2] == Color.blue); |
switch 문에서도 열거형을 사용할 수 있는데, 모든 값을 처리하지 않으면 경고를 받으니 주의하세요:
|
1 2 3 4 5 6 7 8 9 10 |
var aColor = Color.blue; switch (aColor) { case Color.red: print('Red as roses!'); case Color.green: print('Green as grass!'); default: print(aColor); } |
자, 여기 별표 세 개! 열거형은 switch 문에서 모든 경우를 다루는 게 안전하다는 점, 시험에 나와요!
열거형 값의 이름과 멤버 접근: 세부 정보 확인하기
열거형 값의 이름을 알고 싶다면 name 속성을 사용하면 돼요. 예를 들어, Color.blue.name은 “blue”라는 문자열을 반환하죠. 또한 향상된 열거형에서는 멤버에 접근할 수도 있어요:
|
1 2 |
print(Vehicle.car.carbonFootprint); |
이건 마치 메뉴판에서 특정 음식의 칼로리를 확인하는 것과 같아요.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 열거형은 고정된 상수 값을 표현하는 특별한 클래스로, 간단한 선택지를 정의할 수 있어요.
- 향상된 열거형은 필드와 메서드를 추가해 더 풍성한 정보를 제공하죠.
- 열거형 값은 정적 변수처럼 접근하고,
index,values,name으로 세부 정보를 활용할 수 있어요.
이 모든 게 Dart 열거형의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 열거형을 만들고 switch 문으로 각 값을 처리해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 확장 메서드(Extension Methods)를 완벽히 파헤쳐 볼 거예요. 확장 메서드는 기존 라이브러리에 새로운 기능을 추가하는 멋진 방법으로, 코드를 더 직관적이고 편리하게 만들어주는 도구죠. 확장 메서드를 이해하면 기존 코드에 손대지 않고도 강력한 기능을 붙일 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
확장 메서드가 뭘까요? 기존 클래스에 기능 추가하기
여러분, 확장 메서드가 뭔지 아시나요? 확장 메서드는 마치 기존 요리에 새로운 양념을 더하는 것과 같아요. 다른 사람의 API나 널리 사용되는 라이브러리를 수정할 수 없을 때, 여기에 새로운 기능을 추가하고 싶을 때 사용하죠.
예를 들어, 문자열을 정수로 변환하는 기능을 추가해보죠:
|
1 2 |
int.parse('42') // 기본 방식 |
이걸 더 직관적으로 '42'.parseInt()처럼 쓰고 싶다면, 확장 메서드를 사용하면 돼요. 이건 마치 “문자열에 직접 정수로 바꾸는 기능을 붙이자!”라고 말하는 것과 같아요. 이해 되시죠?
확장 메서드 사용하기: 새로운 기능 바로 활용
확장 메서드는 라이브러리에 정의되어 있어요. 사용하려면 해당 라이브러리를 임포트하고, 마치 원래 메서드인 것처럼 호출하면 돼요:
|
1 2 3 4 5 6 |
import 'string_apis.dart'; void main() { print('42'.parseInt()); // 확장 메서드 사용 } |
이건 마치 새로운 양념이 담긴 병을 열어서 요리에 바로 뿌리는 것과 같아요. IDE에서 코드 완성 기능을 사용할 때 확장 메서드가 일반 메서드와 함께 제안되는 경우도 많죠. 감 오시나요?
정적 타입과 동적 타입: 확장 메서드의 규칙
확장 메서드는 정적 타입(Static Type)에 따라 작동해요. 이건 마치 요리법이 재료의 종류에 따라 달라지는 것과 같죠. dynamic 타입의 변수에서는 확장 메서드를 호출할 수 없어요:
|
1 2 3 |
dynamic d = '2'; print(d.parseInt()); // 런타임 오류 발생 |
하지만 타입 추론은 잘 작동해서, 타입이 명확히 추론되면 문제없어요:
|
1 2 3 |
var v = '2'; print(v.parseInt()); // 정상 작동 |
이건 정적 타입을 기반으로 메서드가 결정되기 때문에 속도도 빠르다는 장점이 있죠. 자, 여기 별표 세 개! 확장 메서드는 정적 타입에 의존한다는 점, 기억하세요!
API 충돌 해결: 겹치는 기능 정리하기
만약 확장 메서드가 기존 인터페이스나 다른 확장과 충돌한다면 어떻게 할까요? 이건 마치 두 가지 양념이 겹칠 때 어떤 걸 사용할지 결정하는 것과 같아요. 몇 가지 해결책이 있어요.
첫 번째는 show나 hide를 사용해 임포트하는 기능을 제한하는 거예요:
|
1 2 3 |
import 'string_apis.dart'; import 'string_apis_2.dart' hide NumberParsing2; |
두 번째는 확장 이름을 명시적으로 사용해 호출하는 거예요:
|
1 2 |
print(NumberParsing('42').parseInt()); |
이름이 같은 경우 접두어를 사용해 임포트할 수도 있죠:
|
1 2 3 |
import 'string_apis_3.dart' as rad; print(rad.NumberParsing('42').parseInt()); |
이건 마치 “이 양념은 이 병에서, 저 양념은 저 병에서 가져와!”라고 구분하는 것과 같아요.
확장 메서드 구현하기: 나만의 기능 만들기
확장 메서드는 extension 키워드로 정의해요. 이건 마치 새로운 양념 레시피를 만드는 것과 같죠:
|
1 2 3 4 5 6 7 8 9 |
extension NumberParsing on String { int parseInt() { return int.parse(this); } double parseDouble() { return double.parse(this); } } |
확장에는 메서드뿐만 아니라 게터, 세터, 연산자도 포함될 수 있어요. 정적 필드나 정적 메서드도 추가할 수 있죠. 이름 없는 확장은 선언된 라이브러리 내에서만 보이고, 명시적 호출이 불가능하다는 점도 알아두세요. 자, 여기 별표 세 개! 확장은 다양한 멤버를 추가할 수 있는 유연한 도구라는 점, 시험에 나와요!
제네릭 확장 구현: 유연한 양념 만들기
확장 메서드는 제네릭 타입 파라미터를 가질 수도 있어요. 이건 마치 모든 재료에 맞는 양념을 만드는 것과 같아요:
|
1 2 3 4 5 6 |
extension MyFancyList<T> on List<T> { int get doubleLength => length * 2; List<T> operator -() => reversed.toList(); List<List<T>> split(int at) => [sublist(0, at), sublist(at)]; } |
이렇게 하면 리스트의 타입에 따라 적절히 동작하는 확장 메서드를 만들 수 있죠.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 확장 메서드는 기존 클래스에 새로운 기능을 추가하는 방법으로, API 수정 없이도 유용한 메서드를 붙일 수 있어요.
- 정적 타입에 기반해 작동하며, API 충돌은
show,hide, 명시적 호출 등으로 해결할 수 있죠. extension키워드로 메서드, 게터, 세터, 연산자를 정의하고, 제네릭 타입으로 유연하게 활용할 수 있어요.
이 모든 게 Dart 확장 메서드의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 확장 메서드를 만들어서 문자열이나 리스트에 새로운 기능을 추가해보세요. 할 수 있죠?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 확장 타입(Extension Types)을 완벽히 파헤쳐 볼 거예요. 확장 타입은 기존 타입을 컴파일 시점에 새로운 인터페이스로 감싸는 추상화 방법으로, 성능 저하 없이 인터페이스를 변경할 수 있는 강력한 도구죠. 확장 타입을 이해하면 기존 타입에 맞춤형 인터페이스를 추가할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
확장 타입이 뭘까요? 타입에 새로운 인터페이스 입히기
여러분, 확장 타입이 뭔지 아시나요? 확장 타입은 마치 기존 옷에 새로운 스타일의 겉옷을 입히는 것과 같아요. 기존 타입(표현 타입)을 감싸서 정적 인터페이스를 변경하거나 제한하는 방법이죠. 특히 JS 상호 운용성과 같은 상황에서 유용하며, 런타임 오버헤드 없이 인터페이스를 조정할 수 있어요.
예를 들어, int 타입을 ID 번호로 사용하면서 불필요한 연산을 막는 확장 타입을 만들어볼게요:
|
1 2 3 4 |
extension type IdNumber(int id) { operator <(IdNumber other) => id < other.id; } |
이건 마치 “ID 번호로는 비교만 가능하고, 덧셈 같은 연산은 안 돼!”라고 규칙을 정하는 것과 같아요. 이해 되시죠?
확장 타입의 특징: 컴파일 시점의 규칙
확장 타입은 컴파일 시점에만 존재하는 추상화예요. 이건 마치 겉옷이 실제 옷을 바꾸는 게 아니라 보기만 다르게 만드는 것과 같죠. 런타임에서는 표현 타입으로 돌아가며, 확장 타입의 흔적은 사라져요. 따라서 성능 저하 없이 인터페이스를 변경할 수 있는 ‘제로 비용’ 도구라고 할 수 있죠.
확장 타입은 표현 타입의 인터페이스를 기본적으로 상속하지 않아요. 특정 멤버를 사용하려면 직접 선언해야 하죠. 자, 여기 별표 세 개! 확장 타입은 컴파일 시점에서만 인터페이스를 변경한다는 점, 기억하세요!
확장 타입 선언과 문법: 새로운 겉옷 디자인하기
확장 타입은 extension type 키워드로 선언하며, 표현 타입을 괄호 안에 지정해요:
|
1 2 3 4 |
extension type E(int i) { // 인터페이스 정의 } |
여기서 i는 표현 객체에 접근하는 getter 이름이에요. 암시적 생성자 E(int i)도 자동으로 제공되죠. 생성자를 추가로 정의할 수도 있어요:
|
1 2 3 4 5 |
extension type E(int i) { E.n(this.i); E.m(int j, String foo) : i = j + foo.length; } |
멤버로는 메서드, 게터, 세터, 연산자를 선언할 수 있지만, 추상 멤버나 인스턴스 변수는 불가능해요. 이건 마치 겉옷에 새로운 장식을 추가하는 것과 같아요.
implements 절: 인터페이스 확장하기
implements 절을 사용하면 표현 타입이나 그 상위 타입의 인터페이스를 가져올 수 있어요. 이건 마치 기존 옷의 일부 디자인을 그대로 가져오는 것과 같죠:
|
1 2 3 4 |
extension type NumberI(int i) implements int { // int의 모든 멤버 사용 가능 } |
또한 다른 확장 타입을 구현하거나, 표현 타입의 상위 타입(예: Object, Iterable)을 구현할 수도 있어요. 감 오시나요?
@redeclare 어노테이션: 멤버 재선언하기
확장 타입에서는 상위 타입의 멤버를 재선언할 수 있어요. 이건 오버라이드가 아니라 완전한 대체예요. @redeclare 어노테이션을 사용하면 의도적 재선언임을 명시할 수 있죠:
|
1 2 3 4 5 6 7 |
import 'package:meta/meta.dart'; extension type MyString(String _) implements String { @redeclare int operator [](int index) => codeUnitAt(index); } |
이건 마치 “기존 디자인을 완전히 새로 바꿀게!”라고 선언하는 것과 같아요.
사용 사례: 두 가지 주요 활용법
확장 타입은 두 가지 주요 용도로 나뉘어요. 이건 마치 겉옷을 입는 목적이 다른 것과 같죠.
1. 기존 타입에 인터페이스 확장하기
표현 타입을 implements로 구현하면 ‘투명한’ 확장 타입이 돼요. 이건 기존 인터페이스를 그대로 사용하면서 추가 기능을 붙이는 방식이죠:
|
1 2 3 4 |
extension type NumberT(int value) implements int { NumberT get i => this; } |
이렇게 하면 int의 모든 멤버를 호출할 수 있어요. 마치 기존 옷에 장식만 더하는 것과 같죠.
2. 기존 타입에 완전히 다른 인터페이스 제공하기
implements를 사용하지 않으면 표현 타입과 완전히 다른 인터페이스를 만들 수 있어요. 이건 기존 타입을 새로운 타입처럼 보이게 만드는 방식이에요:
|
1 2 3 4 |
extension type NumberE(int value) { NumberE operator +(NumberE other) => NumberE(value + other.value); } |
이렇게 하면 int의 멤버는 기본적으로 보이지 않고, 정의한 멤버만 사용 가능해요. 자, 여기 별표 세 개! 확장 타입으로 인터페이스를 제한하거나 확장할 수 있다는 점, 시험에 나와요!
타입 고려사항: 런타임의 현실
확장 타입은 런타임에서 사라져요. 런타임 타입 테스트나 캐스트는 표현 타입을 기준으로 작동하죠:
|
1 2 3 |
var n = NumberE(1); if (n is int) print(n); // 작동함, int로 인식 |
이건 마치 겉옷을 벗으면 원래 옷이 드러나는 것과 같아요. 확장 타입은 컴파일 시점에서만 유효하다는 점을 항상 기억해야 해요. 안전한 캡슐화를 위해서는 실제 래퍼 클래스를 사용하는 게 나을 수도 있지만, 성능이 중요한 경우 확장 타입이 훨씬 유리하죠.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 확장 타입은 기존 타입에 새로운 정적 인터페이스를 입히는 컴파일 시점 추상화로, 성능 저하 없이 인터페이스를 변경할 수 있어요.
implements로 기존 인터페이스를 확장하거나, 완전히 다른 인터페이스를 제공할 수 있죠.- 런타임에서는 표현 타입으로 돌아가니, 컴파일 시점의 제한임을 기억해야 해요.
이 모든 게 Dart 확장 타입의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 확장 타입을 만들어서 기존 타입에 새로운 인터페이스를 추가해보세요. 할 수 있죠?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 호출 가능 객체(Callable Objects)를 완벽히 파헤쳐 볼 거예요. 호출 가능 객체는 클래스의 인스턴스를 함수처럼 호출할 수 있게 해주는 특별한 기능으로, 코드의 유연성을 한층 높여주는 도구죠. 이걸 이해하면 객체를 함수처럼 사용할 수 있다는 재미있는 사실! 자, 함께 시작해 볼까요? 🚀
호출 가능 객체가 뭘까요? 객체를 함수처럼 사용하기
여러분, 호출 가능 객체가 뭔지 아시나요? 호출 가능 객체는 마치 로봇을 버튼처럼 누르면 특정 동작을 하게 만드는 것과 같아요. Dart에서는 클래스가 call() 메서드를 구현하면, 그 클래스의 인스턴스를 함수처럼 호출할 수 있죠. 이 메서드는 일반 함수처럼 매개변수와 반환 타입을 지원해요.
이건 마치 “이 로봇은 버튼을 누르면 이런 일을 할 거야!”라고 설정하는 것과 같아요. 이해 되시죠?
call() 메서드 구현: 객체에 함수 기능 부여하기
call() 메서드를 구현하면 객체가 함수처럼 동작해요. 예를 들어, 문자열을 연결하는 기능을 가진 클래스를 만들어볼게요:
|
1 2 3 4 5 6 7 |
class WannabeFunction { String call(String a, String b, String c) => '$a $b $c!'; } var wf = WannabeFunction(); var out = wf('Hi', 'there,', 'gang'); |
이 코드는 wf라는 객체를 함수처럼 호출해서 세 문자열을 연결하고 느낌표를 붙이는 거예요. 마치 “이 버튼을 누르면 세 단어를 붙여서 출력해!”라고 지시한 것과 같죠. 자, 여기 별표 세 개! call() 메서드는 객체를 함수처럼 사용하게 해주는 마법이라는 점, 기억하세요!
호출 가능 객체의 활용: 유연한 코드 작성
호출 가능 객체는 코드를 더 유연하게 만들어줘요. 예를 들어, 특정 동작을 캡슐화한 객체를 만들어서 함수처럼 호출할 수 있으니, 상황에 따라 다른 로직을 쉽게 적용할 수 있죠. 이건 마치 로봇 버튼을 누를 때마다 다른 행동을 설정할 수 있는 것과 같아요. 감 오시나요?
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 호출 가능 객체는
call()메서드를 구현해 클래스의 인스턴스를 함수처럼 호출할 수 있게 해요. call()메서드는 일반 함수처럼 매개변수와 반환 타입을 지원하죠.- 이를 통해 객체에 함수적 기능을 부여해 코드의 유연성을 높일 수 있어요.
이 모든 게 Dart 호출 가능 객체의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 클래스를 만들고 call() 메서드를 구현해 함수처럼 호출해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 클래스 수정자(Class Modifiers)를 완벽히 파헤쳐 볼 거예요. 클래스 수정자는 클래스나 믹스인의 사용 방식을 제어하는 키워드로, 라이브러리 안팎에서 어떻게 사용할 수 있는지를 결정하는 중요한 도구죠. 클래스 수정자를 이해하면 코드의 접근성과 상속을 세밀하게 관리할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
클래스 수정자가 뭘까요? 클래스 사용 규칙 정하기
여러분, 클래스 수정자가 뭔지 아시나요? 클래스 수정자는 마치 건물에 접근 권한을 설정하는 규칙 같은 거예요. 특정 건물(클래스)을 누가 사용할 수 있고, 어떻게 확장하거나 구현할 수 있는지를 정하는 거죠. Dart에서는 abstract, base, final, interface, sealed, mixin 같은 수정자를 클래스나 믹스인 선언 앞에 붙여서 사용해요.
이건 마치 “이 건물은 특정 사람만 들어올 수 있어!”라고 규칙을 정하는 것과 같아요. 이해 되시죠?
수정자 없는 클래스: 누구나 접근 가능
수정자가 없는 클래스는 제한 없이 사용 가능해요. 이건 마치 문이 활짝 열려 있는 건물처럼, 누구나 들어와서 새 건물을 짓거나(상속), 내부 구조를 따라 하거나(구현), 기능을 추가할 수 있는(믹스인) 상태예요. 라이브러리 안팎에서 자유롭게 인스턴스를 만들고 서브타입을 생성할 수 있죠.
abstract: 완전하지 않은 설계도
abstract 수정자는 클래스가 완전한 구현을 가지지 않아도 되게 해요. 이건 마치 설계도만 있고 실제 건물은 짓지 않은 상태와 같아요. 추상 클래스는 인스턴스를 만들 수 없으며, 종종 추상 메서드를 포함하죠:
|
1 2 3 4 |
abstract class Vehicle { void moveForward(int meters); } |
추상 클래스는 다른 라이브러리에서도 상속하거나 구현할 수 있지만, 직접 인스턴스를 만드는 건 불가능해요. 자, 여기 별표 세 개! abstract는 미완성 클래스를 정의하는 데 유용하다는 점, 기억하세요!
base: 구현 상속 강제하기
base 수정자는 클래스의 구현을 상속하도록 강제해요. 이건 마치 “이 건물 설계도를 따라야만 새 건물을 지을 수 있어!”라고 규칙을 정하는 것과 같아요. 외부 라이브러리에서는 구현할 수 없으며, 상속하는 클래스는 반드시 base, final, sealed 중 하나로 표시해야 하죠:
|
1 2 3 4 5 6 |
base class Vehicle { void moveForward(int meters) { // ... } } |
이렇게 하면 생성자 호출, 프라이빗 멤버 상속, 새로운 멤버 추가의 안정성을 보장할 수 있어요. 감 오시나요?
interface: 인터페이스만 제공하기
interface 수정자는 클래스를 인터페이스로 정의해요. 이건 마치 건물의 외관만 보여주고 내부 설계도는 숨기는 것과 같아요. 외부 라이브러리에서는 구현은 가능하지만 상속은 불가능하죠:
|
1 2 3 4 5 6 |
interface class Vehicle { void moveForward(int meters) { // ... } } |
이렇게 하면 내부 메서드 호출이 항상 동일한 라이브러리 내 구현을 보장하며, 예상치 못한 오버라이드를 방지할 수 있어요.
abstract interface: 순수 인터페이스 정의
abstract와 interface를 결합하면 순수 인터페이스를 만들 수 있어요. 이건 마치 외관 설계도만 있고 구체적인 건 없는 상태와 같죠. 구현은 가능하지만 상속은 불가능하며, 추상 멤버를 포함할 수 있어요.
final: 타입 계층 닫기
final 수정자는 타입 계층을 완전히 닫아요. 이건 마치 “이 건물은 더 이상 확장하거나 모방할 수 없어!”라고 선언하는 것과 같아요. 외부 라이브러리에서 상속이나 구현이 불가능하며, 동일 라이브러리 내에서는 가능하죠:
|
1 2 3 4 5 6 |
final class Vehicle { void moveForward(int meters) { // ... } } |
이렇게 하면 API에 점진적 변경을 안전하게 추가하거나, 메서드 오버라이드 걱정 없이 호출할 수 있어요. 자, 여기 별표 세 개! final은 타입 계층을 완전히 봉쇄한다는 점, 시험에 나와요!
sealed: 고정된 서브타입 집합 만들기
sealed 수정자는 고정된 서브타입 집합을 만들어요. 이건 마치 “이 건물의 변형은 딱 정해진 몇 가지뿐이야!”라고 규칙을 정하는 것과 같아요. 외부 라이브러리에서 상속이나 구현이 불가능하며, 암시적으로 추상 클래스예요:
|
1 2 3 4 |
sealed class Vehicle {} class Car extends Vehicle {} class Truck implements Vehicle {} |
컴파일러는 동일 라이브러리 내의 서브타입만 알 수 있으니, switch 문에서 모든 경우를 다루지 않으면 경고를 줘요. 완전한 스위칭이 필요한 경우에 유용하죠.
수정자 조합: 층층이 규칙 적용하기
수정자는 일부 조합이 가능해요. 이건 마치 여러 규칙을 층층이 적용하는 것과 같아요. 선언 순서는 abstract(선택), base/interface/final/sealed 중 하나(선택), mixin(선택), 그리고 class 키워드예요. 하지만 상충되거나 중복되는 조합(예: abstract와 sealed)은 불가능하죠.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 클래스 수정자는 클래스나 믹스인의 사용 방식을 제어하며, 접근성과 상속을 제한해요.
abstract,base,interface,final,sealed로 다양한 제한을 설정할 수 있죠.- 수정자 조합으로 더 세밀한 제어를 할 수 있지만, 상충되는 조합은 피해야 해요.
이 모든 게 Dart 클래스 수정자의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 클래스를 만들고 base나 final 같은 수정자를 적용해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 3.0에서 새롭게 추가된 클래스 수정자(Class Modifiers)를 API 유지 관리자 입장에서 완벽히 파헤쳐 볼 거예요. 라이브러리 패키지 작성자라면 이 수정자를 통해 사용자가 타입을 어떻게 사용할 수 있는지 더 세밀하게 제어할 수 있고, 코드 변경이 사용자에게 미치는 영향을 최소화할 수 있죠. 자, 함께 시작해 볼까요? 🚀
클래스 수정자가 API에 미치는 영향: 사용자와의 약속 관리
여러분, 클래스 수정자가 API 유지 관리자에게 왜 중요한지 아시나요? 이 수정자는 마치 사용자와의 계약서를 작성하는 것과 같아요. Dart 3.0에서는 mixin, interface, base, final, sealed 같은 수정자를 통해 사용자가 클래스를 어떻게 사용할 수 있는지, 그리고 내가 어떻게 코드를 발전시킬 수 있는지를 결정할 수 있죠. 또한, 클래스 수정자는 코드 변경이 사용자 코드를 깨뜨리지 않도록 보장하는 데 큰 역할을 해요. 이해 되시죠?
mixin 수정자: 믹스인 사용 명시하기
Dart 3.0 이전에는 특정 조건(비-팩토리 생성자 없음, Object 외 상속 없음)을 만족하면 어떤 클래스든 믹스인으로 사용할 수 있었어요. 이건 마치 문이 열려 있어 누구나 들어올 수 있는 건물 같았죠. 하지만 이로 인해 생성자를 추가하거나 상속을 변경하는 순간 사용자 코드가 깨질 위험이 있었어요.
Dart 3.0에서는 기본적으로 클래스를 믹스인으로 사용할 수 없게 바뀌었어요. 대신 mixin class로 명시적으로 선언해야 하죠:
|
1 2 3 4 |
mixin class Both {} class UseAsMixin with Both {} class UseAsSuperclass extends Both {} |
만약 Dart 3.0으로 업데이트하면서 코드를 변경하지 않으면, 기존에 믹스인으로 사용하던 사용자의 코드가 깨질 수 있어요. 자, 여기 별표 세 개! mixin 수정자는 기존 믹스인 사용을 보장하는 데 필수라는 점, 기억하세요!
믹스인 클래스 마이그레이션: 사용자 영향 최소화
기존 클래스가 믹스인으로 사용될 수 있는지 확인하고, 어떻게 마이그레이션할지 결정해야 해요. 이건 마치 건물의 문을 계속 열어둘지, 잠글지를 결정하는 것과 같아요. 다음 질문을 고려해보세요:
- 사용자 코드를 깨뜨릴 위험을 감수하고 싶지 않다면? 모든 믹스인 가능 클래스에
mixin을 붙여 기존 동작을 유지하세요. - 사용자가 직접 인스턴스를 만들 수 있게 하고 싶나요? 클래스가 추상이 아니어야 하죠.
- 믹스인으로 사용하게 하고 싶나요?
with절에서 사용 가능해야 하죠.
두 질문에 모두 “예”라면 mixin class로 선언하세요. 믹스인 사용을 막고 싶다면 그냥 class로 두고, 인스턴스 생성은 막고 믹스인만 허용하려면 mixin 선언으로 변경하세요. 후자의 두 옵션은 API를 깨뜨리는 변경이니 주요 버전 업데이트가 필요해요. 감 오시나요?
interface 수정자: 순수 인터페이스 제공
Dart에는 순수 인터페이스 문법이 없었죠. 대신 추상 클래스로 대체했지만, 사용자는 이 클래스가 상속용인지 인터페이스용인지 알기 어려웠어요. interface 수정자는 이를 명확히 해줘요. 이건 마치 “이 설계도는 따라 만들기만 해, 확장은 안 돼!”라고 말하는 것과 같아요. 외부 라이브러리에서는 구현만 가능하고 상속은 불가능하죠.
상속은 강력한 결합을 만들지만, 패키지 경계를 넘을 때 깨지기 쉬워요. interface를 사용하면 코드 재사용을 막고, 인터페이스 구현만 허용해요. 동일 라이브러리 내에서는 이 제한을 무시할 수 있으니, 내부 확장은 자유롭죠.
base 수정자: 구현 상속 보장
base 수정자는 interface와 반대예요. 이건 마치 “이 설계도를 반드시 따라야 해, 다른 방식은 안 돼!”라고 말하는 것과 같아요. 외부 라이브러리에서 구현은 불가능하고, 상속이나 믹스인(with)만 허용되죠. 이렇게 하면 모든 인스턴스가 내 구현과 프라이빗 멤버를 상속받아 런타임 오류를 방지할 수 있어요.
base는 전염성이 있어요. 하위 타입도 반드시 base, final, sealed 중 하나로 선언해야 하죠. 이건 마치 설계도를 따른 모든 건물이 동일한 규칙을 지켜야 하는 것과 같아요. 자, 여기 별표 세 개! base는 구현 상속을 강제하며 하위 타입에도 영향을 줌을 기억하세요!
final 수정자: 모든 서브타이핑 차단
final 수정자는 가장 강력한 제한이에요. 이건 마치 “이 건물은 절대 확장하거나 모방할 수 없어!”라고 선언하는 것과 같아요. 외부 라이브러리에서 상속, 구현, 믹스인 모두 불가능하며, 오직 인스턴스 생성만 허용되죠(추상이 아닌 경우). API 유지 관리자로서 새로운 메서드나 생성자 변경을 자유롭게 할 수 있어요.
sealed 수정자: 고정된 서브타입과 완전성 검사
sealed 수정자는 고정된 서브타입 집합을 만들고, 패턴 매칭에서 완전성 검사를 가능하게 해요. 이건 마치 “이 건물의 변형은 딱 정해진 몇 가지뿐이야!”라고 선언하는 것과 같아요. 외부 라이브러리에서 서브타이핑이 불가능하며, 암시적으로 추상 클래스예요. 컴파일러는 동일 라이브러리 내 서브타입만 인식하니, switch 문에서 모든 경우를 다루지 않으면 경고를 줘요:
|
1 2 3 4 5 |
sealed class Amigo {} class Lucky extends Amigo {} class Dusty extends Amigo {} class Ned extends Amigo {} |
sealed는 하위 타입에 전염성 제한을 두지 않으니, 하위 타입의 추가 서브타이핑은 가능해요. 하지만 완전성 검사를 위해 새 서브타입 추가는 API를 깨뜨리는 변경이 되죠.
sealed vs final: 어떤 걸 사용할까?
sealed와 final 중 고민된다면, 몇 가지 기준을 고려하세요. 이건 마치 문을 잠글지, 제한된 입구만 둘지를 결정하는 것과 같아요:
- 인스턴스 생성을 허용하려면
sealed는 불가능해요(암시적 추상이므로). - 서브타입이 없으면
sealed의 완전성 검사 이점이 없으니final을 사용하세요. - 서브타입이 있고, 완전성 검사가 필요하면
sealed를, 나중에 서브타입 추가를 자유롭게 하고 싶다면final을 선택하세요.
sealed는 새 서브타입 추가가 깨뜨리는 변경이 되지만, 사용자에게 필요한 업데이트 지점을 알려주는 장점이 있어요. 반면 final은 기본적으로 default 케이스를 강제하니, 새 서브타입 추가가 비-깨뜨리는 변경이 되죠.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 3.0 클래스 수정자는 API 유지 관리자에게 타입 사용 제어와 코드 진화를 돕는 도구로,
mixin,interface,base,final,sealed가 있어요. mixin으로 믹스인 사용을 명시하고, 다른 수정자로 상속, 구현, 완전성 검사를 세밀히 관리할 수 있죠.- 수정자 적용은 대부분 API를 깨뜨리는 변경이니, 주요 버전 업데이트와 함께 신중히 사용해야 해요.
이 모든 게 Dart 클래스 수정자의 API 유지 관리 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 기존 라이브러리의 클래스에 적절한 수정자를 적용해 사용 제어를 설정해보세요. 할 수 있죠?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 클래스 수정자(Class Modifiers)에 대한 참조 정보를 함께 파헤쳐 볼 거예요. 클래스 수정자는 클래스나 믹스인의 사용 방식을 제어하는 도구로, 어떤 조합이 가능한지, 어떤 기능이 허용되는지를 명확히 이해하는 게 중요하죠. 이 가이드를 통해 수정자의 조합과 제약을 한눈에 파악할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
클래스 수정자란? 클래스 사용 규칙 정하기
여러분, 클래스 수정자가 뭔지 아시나요? 클래스 수정자는 마치 건물에 접근 권한을 설정하는 규칙 같은 거예요. Dart에서는 abstract, base, final, interface, sealed, mixin 같은 키워드를 클래스나 믹스인 선언 앞에 붙여서 사용 제어를 설정할 수 있죠. 이건 마치 “이 건물은 누가 들어오고, 어떻게 사용할 수 있는지” 규칙을 정하는 것과 같아요. 이해 되시죠?
유효한 수정자 조합: 어떤 규칙을 만들 수 있을까?
Dart에서는 수정자를 조합해 다양한 사용 제어를 할 수 있어요. 이건 마치 건물에 여러 보안 장치를 층층이 설치하는 것과 같죠. 아래 표는 각 조합이 어떤 기능을 허용하는지 보여줘요. 각 열은 특정 동작(인스턴스 생성, 상속, 구현, 믹스인, 완전성 검사)을 허용하는지 나타냅니다:
- Declaration: 선언 방식
- Construct?: 인스턴스 생성 가능 여부
- Extend?: 상속 가능 여부
- Implement?: 구현 가능 여부
- Mix in?: 믹스인으로 사용 가능 여부
- Exhaustive?: 완전성 검사 지원 여부
| Declaration | Construct? | Extend? | Implement? | Mix in? | Exhaustive? |
|---|---|---|---|---|---|
class |
Yes | Yes | Yes | No | No |
base class |
Yes | Yes | No | No | No |
interface class |
Yes | No | Yes | No | No |
final class |
Yes | No | No | No | No |
sealed class |
No | No | No | No | Yes |
abstract class |
No | Yes | Yes | No | No |
abstract base class |
No | Yes | No | No | No |
abstract interface class |
No | No | Yes | No | No |
abstract final class |
No | No | No | No | No |
mixin class |
Yes | Yes | Yes | Yes | No |
base mixin class |
Yes | Yes | No | Yes | No |
abstract mixin class |
No | Yes | Yes | Yes | No |
abstract base mixin class |
No | Yes | No | Yes | No |
mixin |
No | No | Yes | Yes | No |
base mixin |
No | No | No | Yes | No |
이 표를 보면 각 수정자 조합이 어떤 제어를 제공하는지 한눈에 알 수 있어요. 예를 들어, sealed class는 인스턴스 생성, 상속, 구현, 믹스인 모두 불가능하지만 완전성 검사(switch 문에서 모든 서브타입 처리 보장)를 지원하죠. 자, 여기 별표 세 개! 수정자 조합으로 다양한 제어 가능하다는 점, 기억하세요!
유효하지 않은 조합: 사용할 수 없는 규칙들
모든 수정자 조합이 가능한 건 아니에요. 이건 마치 상충되는 보안 장치를 동시에 설치할 수 없는 것과 같아요. 아래는 사용할 수 없는 조합과 그 이유예요:
| Combination | Reasoning |
|---|---|
base, interface, final 함께 사용 |
상속과 구현 가능 여부를 제어하는 기능이 중복되므로 상호 배타적임 |
sealed와 abstract 함께 사용 |
둘 다 인스턴스 생성이 불가능하니 중복됨 |
sealed와 base, interface, final 함께 사용 |
sealed는 이미 외부 라이브러리에서 믹스인, 상속, 구현을 막으니 중복됨 |
mixin과 abstract 함께 사용 |
둘 다 인스턴스 생성이 불가능하니 중복됨 |
mixin과 interface, final, sealed 함께 사용 |
mixin은 믹스인으로 사용되도록 설계되었으나, 나머지 수정자는 이를 막음 |
enum과 모든 수정자 |
enum은 상속, 구현, 믹스인이 불가능하고 항상 인스턴스 생성 가능하니 수정자 적용 불가 |
extension type과 모든 수정자 |
extension type은 상속, 믹스인이 불가능하고 제한된 구현만 허용되니 수정자 적용 불가 |
이렇게 상충되거나 중복되는 조합은 Dart에서 허용되지 않아요. 감 오시나요?
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 클래스 수정자는
abstract,base,interface,final,sealed,mixin등의 조합으로 클래스와 믹스인의 사용 방식을 제어해요. - 각 조합은 인스턴스 생성, 상속, 구현, 믹스인, 완전성 검사 여부를 결정하죠.
- 상충되거나 중복되는 조합(예:
base와final함께 사용)은 사용할 수 없어요.
이 모든 게 Dart 클래스 수정자 참조 정보의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 클래스를 만들고 유효한 수정자 조합(예: abstract base class)을 적용해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 동시성(Concurrency)에 대해 완벽히 파헤쳐 볼 거예요. 동시성은 Dart에서 비동기 API와 아이솔레이트를 통해 여러 작업을 동시에 처리하는 기술로, 현대 애플리케이션의 성능과 반응성을 높이는 데 필수적이죠. 동시성을 이해하면 앱이 더 부드럽고 효율적으로 동작한다는 사실! 자, 함께 시작해 볼까요? 🚀
동시성이 뭘까요? 여러 작업을 동시에 처리하기
여러분, 동시성이 뭔지 아시나요? 동시성은 마치 여러 요리를 동시에 준비하는 것과 같아요. Dart에서는 비동기 API(Future, Stream)와 아이솔레이트(Isolates)를 통해 동시성을 지원하죠. 모든 Dart 코드는 아이솔레이트라는 독립된 실행 단위에서 동작하며, 기본적으로 메인 아이솔레이트에서 시작해 필요 시 추가 아이솔레이트를 생성할 수 있어요. 이해 되시죠?
이벤트 루프: Dart의 심장 박동
Dart의 실행 모델은 이벤트 루프(Event Loop)를 기반으로 해요. 이건 마치 주방에서 요리 순서를 관리하는 셰프 같은 거예요. 이벤트 루프는 프로그램 코드를 실행하고, 이벤트를 수집하며 처리하는 역할을 하죠. 이벤트는 UI 재绘制 요청, 사용자 탭, 디스크 I/O 등 다양하며, 이벤트 큐에 순서대로 쌓여 처리돼요:
|
1 2 3 4 |
while (eventQueue.waitForEvent()) { eventQueue.processNextEvent(); } |
이벤트 루프는 단일 스레드에서 동기적으로 작동하지만, 비동기 작업을 통해 여러 일을 동시에 처리할 수 있어요. 예를 들어, HTTP 요청을 보내면 이벤트 루프는 요청을 처리하고, 응답이 올 때까지 다른 이벤트를 처리한 뒤 나중에 콜백을 실행하죠. 자, 여기 별표 세 개! 이벤트 루프는 비동기와 동시성의 핵심이라는 점, 기억하세요!
비동기 프로그래밍: 기다리지 않고 작업하기
Dart의 비동기 프로그래밍은 마치 요리를 준비하면서 다른 재료를 자르는 것과 같아요. 주요 비동기 도구를 살펴볼게요.
Future: 미래의 결과 약속
Future는 비동기 작업의 결과를 나타내요. 이건 마치 “이 요리가 완성되면 알려줄게!”라는 약속과 같죠:
|
1 2 3 4 5 6 7 |
Future<String> _readFileAsync(String filename) { final file = File(filename); return file.readAsString().then((contents) { return contents.trim(); }); } |
async-await: 자연스러운 비동기 흐름
async와 await 키워드는 비동기 코드를 동기 코드처럼 읽기 쉽게 만들어줘요. 이건 마치 요리 중간에 “이 재료가 준비될 때까지 기다려!”라고 말하는 것과 같아요:
|
1 2 3 4 5 |
void main() async { final fileData = await _readFileAsync(); print('Data: $fileData'); } |
await는 다른 Dart 코드가 CPU를 사용할 수 있게 해주며, 작업이 완료되면 결과를 반환하죠.
Stream: 연속적인 데이터 흐름
Stream은 시간이 지남에 따라 반복적으로 값을 제공해요. 이건 마치 계속 재료가 들어오는 컨베이어 벨트와 같죠:
|
1 2 |
Stream<int> stream = Stream.periodic(const Duration(seconds: 1), (i) => i * i); |
await-for와 yield: 스트림 처리
await-for는 스트림의 값을 순회하며 처리하고, yield는 스트림 값을 방출해요. 이건 마치 벨트에서 재료를 하나씩 꺼내 요리하는 것과 같아요:
|
1 2 3 4 5 6 7 |
Stream<int> sumStream(Stream<int> stream) async* { var sum = 0; await for (final value in stream) { yield sum += value; } } |
아이솔레이트: 독립된 작업 공간
Dart는 스레드 대신 아이솔레이트(Isolates)를 사용해 동시성을 지원해요. 이건 마치 여러 주방에서 독립적으로 요리하는 것과 같아요. 각 아이솔레이트는 고유한 메모리와 이벤트 루프를 가지며, 다른 아이솔레이트와 상태를 공유하지 않아요. 메시지 전달로만 소통하죠.
메인 아이솔레이트: 기본 주방
대부분의 Dart 프로그램은 메인 아이솔레이트에서 실행돼요. 이건 마치 주방 하나에서 모든 요리를 시작하는 것과 같아요. 메인 아이솔레이트는 main() 함수를 실행하고 이벤트를 처리한 뒤 종료되죠.
백그라운드 워커: 추가 주방 활용
UI가 느려질 수 있는 작업(예: 대용량 JSON 파싱)은 백그라운드 워커 아이솔레이트로 옮길 수 있어요. 이건 마치 무거운 요리를 별도 주방에서 처리하는 것과 같아요:
Isolate.run(): 단일 계산을 별도 스레드에서 실행.Isolate.spawn(): 지속적인 메시지 처리를 위한 아이솔레이트 생성.
Isolate.run() 예시:
|
1 2 3 4 5 6 |
int slowFib(int n) => n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2); void fib40() async { var result = await Isolate.run(() => slowFib(40)); print('Fib(40) = $result'); } |
자, 여기 별표 세 개! 아이솔레이트는 독립 메모리로 안전한 동시성 제공이라는 점, 시험에 나와요!
아이솔레이트의 한계: 스레드와의 차이
아이솔레이트는 스레드가 아니에요. 이건 마치 각 주방이 독립적으로 재료를 관리하는 것과 같아요. 공유 상태가 없으니 뮤텍스나 데이터 레이스 같은 복잡성이 없지만, 전역 변수는 각 아이솔레이트마다 별개예요. 메시지 전달도 특정 타입(예: Socket, ReceivePort)은 지원되지 않죠. 감 오시나요?
웹에서의 동시성: 아이솔레이트 대신 웹 워커
Dart 웹 플랫폼은 아이솔레이트를 지원하지 않아요. 대신 웹 워커(Web Workers)를 사용해 백그라운드 스레드에서 스크립트를 실행하죠. 이건 마치 별도 주방을 빌리는 것과 같지만, 데이터 복사로 인해 속도가 느릴 수 있어요. 웹 워커는 Isolate.spawnUri()와 유사하게 별도 엔트리포인트를 컴파일해야 하죠.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 동시성은 비동기 API(
Future,Stream,async-await)와 아이솔레이트로 여러 작업을 동시에 처리해요. - 이벤트 루프는 모든 비동기 작업의 중심이며, 아이솔레이트는 독립 메모리로 안전한 동시성을 제공하죠.
- 웹에서는 아이솔레이트 대신 웹 워커를 사용하며, 성능과 사용 방식이 다르니 주의해야 해요.
이 모든 게 Dart 동시성의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 Future와 async-await를 사용한 비동기 코드를 작성하거나, Isolate.run()으로 백그라운드 작업을 실행해보세요. 할 수 있죠?
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 비동기 프로그래밍(Asynchronous Programming)을 완벽히 파헤쳐 볼 거예요. Dart는 Future와 Stream 같은 비동기 객체를 반환하는 함수로 가득 차 있으며, async와 await 키워드를 통해 비동기 코드를 동기 코드처럼 자연스럽게 작성할 수 있죠. 비동기 프로그래밍을 이해하면 시간 소모적인 작업을 효율적으로 처리할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
비동기 프로그래밍이 뭘까요? 기다리지 않고 작업하기
여러분, 비동기 프로그래밍이 뭔지 아시나요? 비동기 프로그래밍은 마치 요리를 준비하면서 다른 재료를 자르는 것과 같아요. Dart 라이브러리의 많은 함수는 I/O 같은 시간 소모적인 작업을 설정한 후, 그 작업이 완료되기를 기다리지 않고 바로 반환돼요. 이 함수들은 Future나 Stream 객체를 반환하며, async와 await를 사용해 코드 흐름을 자연스럽게 관리할 수 있죠. 이해 되시죠?
Future 처리하기: 미래의 결과 기다리기
Future는 비동기 작업의 결과를 나타내요. 이 결과를 얻는 방법은 두 가지예요:
async와await사용하기FutureAPI 직접 사용하기
async와 await를 사용하면 코드가 동기 코드처럼 보이면서도 비동기적으로 동작해요. 예를 들어, 버전 정보를 조회하는 함수를 기다리는 코드를 보죠:
|
1 2 3 4 5 |
Future<void> checkVersion() async { var version = await lookUpVersion(); // 버전 정보로 작업 수행 } |
await는 lookUpVersion()의 결과가 완료될 때까지 실행을 일시 중지하고, 완료되면 결과를 반환해요. 이건 마치 “이 요리가 완성될 때까지 기다렸다가 다음 단계로 넘어가!”라고 말하는 것과 같아요.
오류 처리와 정리 작업은 try, catch, finally로 할 수 있어요:
|
1 2 3 4 5 6 |
try { version = await lookUpVersion(); } catch (e) { // 버전 조회 실패 시 대응 } |
한 함수 내에서 await를 여러 번 사용할 수도 있죠:
|
1 2 3 4 |
var entrypoint = await findEntryPoint(); var exitCode = await runExecutable(entrypoint, args); await flushThenExit(exitCode); |
await는 보통 Future를 대상으로 하지만, Future가 아닌 값도 자동으로 Future로 감싸져요. 자, 여기 별표 세 개! await는 async 함수 내에서만 사용 가능하다는 점, 기억하세요! 만약 컴파일 오류가 난다면 async 키워드가 있는지 확인해보세요:
|
1 2 3 4 5 |
void main() async { checkVersion(); print('In main: version is ${await lookUpVersion()}'); } |
비동기 함수 선언하기: Future 반환 설정
비동기 함수는 async 키워드로 표시돼요. 이건 마치 “이 요리는 시간이 걸릴 수 있으니 기다려야 해!”라고 선언하는 것과 같아요. async를 추가하면 함수는 Future를 반환하죠:
|
1 2 |
Future<String> lookUpVersion() async => '1.0.0'; |
함수 본문에서 Future API를 직접 사용할 필요는 없어요. Dart가 자동으로 Future 객체를 만들어주며, 반환 값이 없으면 Future<void>로 설정하면 되죠. 감 오시나요?
Stream 처리하기: 연속적인 데이터 흐름 관리
Stream은 시간이 지남에 따라 반복적으로 값을 제공하는 비동기 데이터 흐름이에요. 이건 마치 컨베이어 벨트에서 재료가 계속 들어오는 것과 같아요. Stream 값을 얻는 방법도 두 가지예요:
async와await for사용하기StreamAPI 직접 사용하기
await for는 스트림의 값을 순회하며 처리하는 비동기 반복문이에요:
|
1 2 3 4 |
await for (final request in requestServer) { handleRequest(request); } |
이건 마치 “벨트에서 재료가 올 때마다 요리해!”라고 말하는 것과 같아요. 스트림이 닫힐 때까지 반복하며, break나 return으로 중단할 수 있죠. await for도 async 함수 내에서만 사용 가능하니, 컴파일 오류가 나면 async 키워드를 확인하세요:
|
1 2 3 4 5 6 |
void main() async { await for (final request in requestServer) { handleRequest(request); } } |
자, 여기 별표 세 개! await for는 스트림 순회에 최적하다는 점, 시험에 나와요! UI 이벤트 리스너처럼 끝없는 스트림에는 사용하지 않는 게 좋으니, 정말 모든 결과를 기다려야 하는지 확인하세요.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 비동기 프로그래밍은
Future와Stream객체를 반환하는 함수로, 시간 소모 작업을 효율적으로 처리해요. async와await로Future를,await for로Stream을 자연스럽게 다룰 수 있죠.- 비동기 함수는
async로 선언하며,await와await for는async함수 내에서만 사용 가능해요.
이 모든 게 Dart 비동기 프로그래밍의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 async 함수를 작성하고 await로 Future 결과를 처리해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 아이솔레이트(Isolates)를 완벽히 파헤쳐 볼 거예요. 아이솔레이트는 Dart에서 동시성을 구현하는 강력한 도구로, 무거운 계산이 다른 작업을 방해하지 않도록 별도의 실행 단위에서 처리할 수 있게 해주죠. 아이솔레이트를 이해하면 UI 반응성을 유지하며 복잡한 작업을 처리할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
아이솔레이트가 뭘까요? 독립된 작업 공간
여러분, 아이솔레이트가 뭔지 아시나요? 아이솔레이트는 마치 여러 주방에서 독립적으로 요리하는 것과 같아요. Dart에서는 모든 코드가 아이솔레이트라는 독립된 실행 단위에서 동작하며, 각 아이솔레이트는 고유한 메모리와 이벤트 루프를 가지고 있죠. 다른 작업을 방해할 수 있는 무거운 계산(예: 대규모 JSON 파싱, 이미지 압축)을 처리할 때 아이솔레이트를 사용하면 메인 작업이 멈추지 않아요. 이해 되시죠?
아이솔레이트 사용 시기: 무거운 작업 분리하기
아이솔레이트는 UI를 멈추게 할 수 있는 작업에 특히 유용해요. 이건 마치 무거운 요리를 별도 주방으로 옮기는 것과 같아요. 다음 상황에서 사용을 고려하세요:
- 대규모 JSON 파싱
- 사진, 오디오, 비디오 처리
- 복잡한 검색 및 필터링
- 대량의 네트워크 요청 처리
- 데이터베이스 I/O 작업
아이솔레이트 사용 여부에 대한 엄격한 규칙은 없지만, 작업이 다른 계산을 잠시라도 막을 가능성이 있다면 고려하는 게 좋아요.
간단한 워커 아이솔레이트 구현: 기본 작업 분리
Dart의 Isolate.run() 메서드는 간단한 워커 아이솔레이트를 쉽게 설정할 수 있어요. 이건 마치 별도 주방을 잠깐 빌려 요리를 완성하는 것과 같죠. 주요 단계는 다음과 같아요:
- 새 아이솔레이트 생성
- 함수 실행
- 결과 캡처 및 반환
- 작업 완료 후 아이솔레이트 종료
예를 들어, JSON 파일을 읽고 파싱하는 작업을 메인 아이솔레이트에서 분리해보죠:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const String filename = 'with_keys.json'; void main() async { final jsonData = await Isolate.run(_readAndParseJson); print('Number of JSON keys: ${jsonData.length}'); } Future<Map<String, dynamic>> _readAndParseJson() async { final fileData = await File(filename).readAsString(); final jsonData = jsonDecode(fileData) as Map<String, dynamic>; return jsonData; } |
Isolate.run()은 결과를 메인 아이솔레이트로 반환하며, 데이터 복사가 아닌 메모리 전송으로 효율적으로 작동해요. 자, 여기 별표 세 개! Isolate.run()은 간단한 동시 작업에 최적이라는 점, 기억하세요!
클로저와 함께 아이솔레이트 사용: 즉석 작업 정의
기존 메서드뿐만 아니라 클로저(함수 리터럴)도 Isolate.run()에 전달할 수 있어요. 이건 마치 별도 주방에서 즉석 요리법을 실행하는 것과 같아요:
|
1 2 3 4 5 6 7 8 9 |
void main() async { final jsonData = await Isolate.run(() async { final fileData = await File(filename).readAsString(); final jsonData = jsonDecode(fileData) as Map<String, dynamic>; return jsonData; }); print('Number of JSON keys: ${jsonData.length}'); } |
클로저는 코드 내에서 바로 정의할 수 있어 유연성이 높죠. 감 오시나요?
포트를 사용한 다중 메시지 교환: 지속적인 소통
단기 아이솔레이트는 편리하지만, 반복적인 계산에는 성능 오버헤드가 발생해요. 이 경우 Isolate.spawn()과 포트(ReceivePort, SendPort)를 사용해 장기 아이솔레이트를 설정하는 게 더 효율적이에요. 이건 마치 두 주방 간에 계속 대화를 나누는 것과 같아요.
포트 설정: 소통 채널 만들기
ReceivePort는 메시지를 받는 객체이고, SendPort는 메시지를 보내는 객체예요. 메인 아이솔레이트에서 ReceivePort를 만들고, SendPort를 새 아이솔레이트에 전달한 뒤, 새 아이솔레이트도 자신의 SendPort를 메인으로 보내면 양방향 소통이 가능해져요.
기본 포트 예제: 간단한 양방향 소통
기본적인 장기 아이솔레이트 설정을 살펴볼게요. (이 예제는 오류 처리 등은 생략한 기본 예제예요.)
- 워커 클래스 정의:
12345678class Worker {Future<void> spawn() async {final receivePort = ReceivePort();receivePort.listen(_handleResponsesFromIsolate);await Isolate.spawn(_startRemoteIsolate, receivePort.sendPort);}} - 워커 아이솔레이트에서 실행할 코드:
1234567891011static void _startRemoteIsolate(SendPort port) {final receivePort = ReceivePort();port.send(receivePort.sendPort);receivePort.listen((dynamic message) async {if (message is String) {final transformed = jsonDecode(message);port.send(transformed);}});} - 메시지 처리 및 전송 메서드 추가:
12345678910111213void _handleResponsesFromIsolate(dynamic message) {if (message is SendPort) {_sendPort = message;_isolateReady.complete();} else if (message is Map<String, dynamic>) {print(message);}}Future<void> parseJson(String message) async {await _isolateReady.future;_sendPort.send(message);}
강력한 포트 예제: 실전 기능 추가
실제 애플리케이션에서는 오류 처리, 포트 종료, 메시지 순서 보장 등이 필요해요. 이를 위해 Completer, 메시지 ID, 포트 종료 로직을 추가한 강력한 예제를 고려해야 하죠. 주요 개선 사항은 다음과 같아요:
- 메시지 ID와
Completer로 순서 보장 - 포트 종료 로직 추가
- 오류 처리 강화
이렇게 하면 반복 작업에서도 성능과 안정성을 유지할 수 있어요. 자, 여기 별표 세 개! 장기 아이솔레이트는 반복 작업에 효율적이라는 점, 시험에 나와요!
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 아이솔레이트는 무거운 계산을 별도 실행 단위에서 처리해 메인 작업을 방해하지 않게 해요.
Isolate.run()으로 간단한 워커 아이솔레이트를,Isolate.spawn()과 포트로 장기 아이솔레이트를 설정할 수 있죠.- 포트(
ReceivePort,SendPort)를 사용한 양방향 소통으로 지속적인 메시지 교환이 가능하며, 강력한 설정으로 실전 문제를 해결할 수 있어요.
이 모든 게 Dart 아이솔레이트의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. Isolate.run()을 사용해 간단한 무거운 작업을 별도로 실행해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 키워드(Keywords)를 완벽히 파헤쳐 볼 거예요. 키워드는 Dart 언어가 자체적으로 사용하는 예약어로, 코드에서 특정한 의미를 가지며 대부분 식별자로 사용할 수 없죠. 키워드를 이해하면 코드 작성 시 실수를 줄이고 언어의 규칙을 잘 지킬 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
키워드가 뭘까요? 언어의 기본 규칙
여러분, 키워드가 뭔지 아시나요? 키워드는 마치 언어의 기본 규칙을 정하는 단어들 같아요. Dart에서는 이 키워드들을 예약어로 사용하며, 대부분 변수나 함수 이름 같은 식별자로 사용할 수 없어요. 키워드를 식별자로 사용하면 다른 개발자들에게 혼란을 줄 수 있으니 피하는 게 좋죠. 이해 되시죠?
Dart 키워드 목록: 예약어 확인하기
Dart 언어에서 예약된 키워드 목록을 아래 표에 정리해봤어요. 이 키워드들은 특정 상황을 제외하고는 식별자로 사용할 수 없으며, 각 키워드에는 사용 제약이 있어요. 표 아래에 제약 조건을 번호로 표시했으니 참고하세요.
| 키워드 | 제약 조건 | 키워드 | 제약 조건 | 키워드 | 제약 조건 |
|---|---|---|---|---|---|
abstract |
2 | as |
2 | assert |
– |
async |
3 | await |
1 | base |
3 |
break |
– | case |
– | catch |
– |
class |
– | const |
– | continue |
– |
covariant |
2 | default |
– | deferred |
2 |
do |
– | dynamic |
2 | else |
– |
enum |
– | export |
2 | extends |
– |
extension |
2 | external |
2 | factory |
2 |
false |
– | final (var) |
– | final (class) |
– |
finally |
– | for |
– | Function |
2 |
get |
2 | hide |
3 | if |
– |
implements |
2 | import |
2 | in |
– |
interface |
2 | is |
– | late |
2 |
library |
2 | mixin |
2 | new |
– |
null |
– | of |
3 | on |
3 |
operator |
2 | part |
2 | required |
2 |
rethrow |
– | return |
– | sealed |
3 |
set |
2 | show |
3 | static |
2 |
super |
– | switch |
– | sync |
3 |
this |
– | throw |
– | true |
– |
try |
– | type |
2 | typedef |
2 |
var |
– | void |
– | when |
3 |
with |
– | while |
– | yield |
1 |
제약 조건 설명:
- 1번: 이 키워드는 특정 상황에서 식별자로 사용할 수 있어요. 예를 들어,
await와yield는 특정 문맥에서만 예약어로 작동하죠. - 2번: 이 키워드는 타입 이름(클래스, 믹스인, 열거형, 확장 타입, 타입 별칭), 확장 이름, 또는 임포트 접두어로는 사용할 수 없지만, 다른 상황에서는 식별자로 사용 가능해요.
- 3번: 이 키워드는 제한 없이 식별자로 사용할 수 있어요. 예를 들어,
async,base,sealed등은 자유롭게 사용 가능하죠. - – (빈칸): 이 키워드는 어떤 상황에서도 식별자로 사용할 수 없어요.
자, 여기 별표 세 개! 키워드는 대부분 식별자로 사용 불가하다는 점, 기억하세요! 특정 상황에서만 예외적으로 사용할 수 있으니, 혼란을 피하려면 키워드를 식별자로 쓰지 않는 게 좋아요. 감 오시나요?
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 키워드는 언어가 예약한 단어로, 대부분 식별자로 사용할 수 없는 예약어예요.
- 일부 키워드는 특정 상황(타입 이름 제외)에서 식별자로 사용 가능하며, 일부는 제한 없이 사용 가능하죠.
- 코드 가독성과 혼란 방지를 위해 키워드를 식별자로 사용하는 것은 피하는 게 좋아요.
이 모든 게 Dart 키워드의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. Dart 코드에서 키워드를 식별자로 사용하지 않도록 주의하며 간단한 프로그램을 작성해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 사운드 널 세이프티(Sound Null Safety)를 완벽히 파헤쳐 볼 거예요. 널 세이프티는 Dart에서 null로 인해 발생하는 런타임 오류를 방지하는 강력한 기능으로, 코드의 안정성을 한층 높여주는 도구죠. 널 세이프티를 이해하면 버그를 줄이고 더 안전한 코드를 작성할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
널 세이프티가 뭘까요? null 오류로부터 코드 보호하기
여러분, 널 세이프티가 뭔지 아시나요? 널 세이프티는 마치 코드에 안전망을 설치하는 것과 같아요. Dart에서는 변수가 의도치 않게 null이 되어 발생하는 런타임 오류(널 참조 오류)를 방지하기 위해 모든 변수를 기본적으로 널 불가(non-nullable)로 설정했어요. null이 될 수 있는 변수는 타입 뒤에 ?를 붙여 명시적으로 선언해야 하죠. 예를 들어, int? i는 null이 될 수 있는 정수형 변수를 의미해요. 이해 되시죠?
널 세이프티의 기본 원칙: 안전이 기본값
Dart의 널 세이프티는 두 가지 핵심 원칙을 따릅니다. 이건 마치 건물 설계에서 안전을 최우선으로 고려하는 것과 같아요:
- 기본적으로 널 불가: 명시적으로
null을 허용하지 않으면 변수는null이 될 수 없어요. - 완전한 사운드 보장: 타입 시스템이 변수가 널 불가라고 판단하면 런타임에서 절대
null이 될 수 없음을 보장하죠.
이 원칙 덕분에 Dart는 컴파일 시점에서 많은 오류를 잡아내고, 더 적은 버그와 더 작은 바이너리, 더 빠른 실행 속도를 제공해요. 자, 여기 별표 세 개! 널 세이프티는 안전을 기본으로 하며 런타임 오류를 컴파일 시점으로 이동시킨다는 점, 기억하세요!
널 세이프티 예제: 기본 개념 익히기
널 세이프티에서는 변수가 기본적으로 null이 될 수 없어요. 다음 코드를 보죠:
|
1 2 3 4 |
var i = 42; // int로 추론, null 불가 String name = getFileName(); // null 불가 final b = Foo(); // null 불가 |
null이 될 수 있는 변수는 명시적으로 ?를 붙여 선언해야 해요:
|
1 2 |
int? aNullableInt = null; // null 가능 |
이렇게 하면 코드 작성 시 null 가능성을 명확히 알 수 있어요. 감 오시나요?
Dart 3와 널 세이프티: 필수 조건
Dart 3에서는 사운드 널 세이프티가 기본이에요. 널 세이프티를 지원하지 않는 코드는 실행할 수 없죠. 만약 종속성이 널 세이프티를 지원하지 않으면 의존성 해결에 문제가 생기거나 분석/컴파일 오류가 발생해요. 이를 해결하려면:
pub.dev에서 널 세이프티 지원 버전의 패키지를 확인하세요.- 모든 소스 코드를 널 세이프티로 마이그레이션하세요.
Dart 3 호환성을 테스트하려면 Dart 3 이상 버전을 사용하고 dart pub get 및 dart analyze를 실행해보세요:
|
1 2 3 4 |
$ dart --version # 3.0.0-417.1.beta 이상 확인 $ dart pub get # 의존성 해결 확인 $ dart analyze # 오류 없이 통과 확인 |
Dart 2.x와 널 세이프티: 활성화 필요
Dart 2.12에서 2.19까지는 널 세이프티를 활성화해야 해요. pubspec.yaml에서 SDK 제약을 2.12.0 이상으로 설정하세요:
|
1 2 3 |
environment: sdk: '>=2.12.0 <3.0.0' |
기존 코드 마이그레이션: 널 세이프티로 전환
Dart 3에서는 dart migrate 도구가 제거되었으니, 2.19 SDK에서 마이그레이션을 완료한 후 업그레이드하세요. 마이그레이션 기본 단계는 다음과 같아요:
- 종속성이 마이그레이션될 때까지 기다리세요.
- 패키지 코드를 마이그레이션하세요(도구 사용 권장).
- 정적 분석 수행.
- 테스트로 변경 사항 확인.
pub.dev에 게시된 경우 널 세이프 버전을 프리릴리스로 게시.
마이그레이션 도구 사용
Dart 2.12~2.19에서는 dart migrate 도구를 사용해 마이그레이션을 쉽게 할 수 있어요:
|
1 2 3 |
$ cd my_app $ dart migrate |
도구는 널 가능성을 추론하고 수정 제안을 제공하며, 힌트 마커(/*?*/, /*!*/)로 조정 가능해요. 적용 후 코드 분석과 테스트를 진행하세요.
수동 마이그레이션
도구 없이 수동으로 마이그레이션하려면 pubspec.yaml의 SDK 제약을 설정하고, dart pub get으로 패키지 구성을 업데이트한 뒤, IDE에서 오류를 수정하며 ?, !, required, late를 추가하세요.
널 세이프티 이해: 주요 개념
널 세이프티는 Dart 2.0 이후 가장 큰 변화로, 런타임 널 참조 오류를 컴파일 시점 오류로 전환해요. 주요 원칙은 다음과 같아요:
- 기본 안전: 명시적 기능 사용 없이는 런타임 오류가 없어야 해요.
- 작성 용이성: 기존 Dart 코드를 크게 변경하지 않아도 안전성을 유지.
- 완전 사운드: 널 불가 타입은 런타임에서 절대
null이 되지 않음을 보장.
타입 시스템 내 널 가능성
널 세이프티는 타입 계층을 변경해 Null 타입이 더 이상 모든 타입의 하위 타입이 아니에요. 기본적으로 모든 타입은 널 불가이며, ?를 추가해 널 가능 타입을 선언해야 하죠:
|
1 2 3 |
String? nullableString = null; // null 가능 String nonNullableString = "text"; // null 불가 |
널 세이프티 FAQ: 자주 묻는 질문
런타임 변화는 무엇인가요?
!연산자와late키워드는 모든 모드에서 런타임 체크를 추가하니,null이 흐를 수 없는 곳에만 사용하세요.
테스트에서만 null인 경우는?
- 널 불가로 선언하고 테스트에서 널이 아닌 값을 전달하도록 개선하세요.
required와 @required의 차이는?
required는 널 세이프티에서 필수 매개변수를 강제하며, 널 세이프 코드 간 호출 시 오류 발생.@required는 힌트 수준.
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 널 세이프티는
null로 인한 런타임 오류를 컴파일 시점 오류로 전환하며, 기본적으로 모든 변수는 널 불가예요. - Dart 3에서는 널 세이프티가 필수이며, 2.12~2.19에서는 활성화 필요, 마이그레이션 도구로 전환 가능해요.
- 널 가능 타입(
?),!,late,required등으로 유연하게null을 관리하며, 사운드 보장으로 코드 안정성을 높일 수 있죠.
이 모든 게 Dart 널 세이프티의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. 간단한 코드를 작성하며 널 불가 변수와 널 가능 변수를 선언하고 사용해보세요.
안녕하세요, 일타 강사 저스틴입니다! 오늘은 Dart 프로그래밍 언어의 핵심 라이브러리(Core Libraries)를 완벽히 파헤쳐 볼 거예요. Dart는 일상적인 프로그래밍 작업을 지원하는 풍부한 핵심 라이브러리를 제공하며, 컬렉션 작업, 수학 계산, 데이터 인코딩/디코딩 등 다양한 기능을 손쉽게 사용할 수 있죠. 핵심 라이브러리를 이해하면 Dart로 효율적인 코드를 작성할 수 있다는 사실! 자, 함께 시작해 볼까요? 🚀
Dart 핵심 라이브러리가 뭘까요? 기본 도구 상자
여러분, Dart 핵심 라이브러리가 뭔지 아시나요? Dart 핵심 라이브러리는 마치 프로그래머를 위한 기본 도구 상자와 같아요. Dart는 dart:core, dart:async, dart:math, dart:convert 같은 내장 라이브러리를 통해 다양한 기능을 제공하며, 이들은 모든 Dart 프로그램에서 필수적인 역할을 하죠. 이 도구들을 사용하면 컬렉션 관리, 비동기 프로그래밍, 수학 연산, 데이터 변환 등을 쉽게 할 수 있어요. 이해 되시죠?
Dart 핵심 라이브러리 둘러보기: 주요 기능 탐색
Dart의 주요 핵심 라이브러리를 간단히 살펴볼게요. 이건 마치 도구 상자에서 자주 사용하는 도구를 확인하는 것과 같아요. 각 라이브러리의 주요 기능만 개요로 다루니, 자세한 내용은 Dart API 참조를 확인하세요.
dart:core – 모든 프로그램의 기본
dart:core는 모든 Dart 프로그램에 자동으로 임포트되는 라이브러리로, 기본 타입(int, String, bool), 컬렉션(List, Map, Set), 그리고 핵심 기능(예: print, DateTime)을 제공해요. 이건 마치 도구 상자의 망치나 드라이버 같은 기본 도구예요.
dart:async – 비동기 작업 지원
dart:async는 비동기 프로그래밍을 위한 도구로, Future와 Stream 같은 클래스를 제공해요. 이건 마치 요리를 준비하면서 다른 재료를 기다리는 타이머와 같아요. 비동기 작업으로 앱의 반응성을 유지할 수 있죠.
dart:math – 수학 계산 도구
dart:math는 수학 상수(예: pi), 함수(예: sin, cos), 그리고 난수 생성기를 제공해요. 이건 마치 계산기나 측정 도구 같은 거예요. 복잡한 수학 연산을 쉽게 처리할 수 있죠.
dart:convert – 데이터 변환 전문가
dart:convert는 JSON, UTF-8 같은 다양한 데이터 표현 간 변환을 위한 인코더와 디코더를 제공해요. 이건 마치 언어 번역기 같은 거예요. 데이터를 다른 형식으로 쉽게 변환할 수 있답니다.
dart:io – 입출력 처리 (비-웹)
dart:io는 Flutter 앱, 서버, 명령줄 스크립트에서 파일, 소켓, HTTP 같은 I/O 작업을 지원해요. 이건 마치 주방의 수도꼭지나 가스레인지 같은 거예요. 외부 리소스와의 상호작용을 가능하게 하죠.
dart:js_interop – 웹 플랫폼과의 상호작용
dart:js_interop는 웹 플랫폼과의 상호작용을 위한 API를 제공하며, package:web과 함께 dart:html을 대체해요. 이건 마치 웹과의 통신을 위한 무전기 같은 거예요. 자바스크립트와의 인터롭을 지원하죠.
자, 여기 별표 세 개! Dart 핵심 라이브러리는 모든 프로그래밍 작업의 기반이라는 점, 기억하세요! 이 라이브러리들은 Dart의 기본 기능을 제공하며, 더 자세한 내용은 API 문서를 참고하세요. 감 오시나요?
다중 플랫폼 라이브러리: 모든 Dart 플랫폼에서 사용
Dart는 모든 플랫폼에서 사용할 수 있는 다중 플랫폼 라이브러리를 제공해요. 이건 마치 어디서나 사용할 수 있는 만능 도구 같은 거예요. 주요 라이브러리는 다음과 같아요:
dart:core: 기본 타입 및 컬렉션dart:async및package:async: 비동기 프로그래밍 지원dart:collection및package:collection: 추가 컬렉션 기능dart:convert및package:convert: 데이터 변환dart:developer: 개발자 도구 상호작용dart:math: 수학 기능dart:typed_data및package:typed_data: 고정 크기 데이터 처리
네이티브 플랫폼 라이브러리: Dart VM에서 사용
Dart 네이티브 플랫폼(AOT 및 JIT 컴파일 코드)에서 사용할 수 있는 라이브러리는 다음과 같아요. 이건 마치 특정 주방에서만 사용 가능한 특수 도구 같은 거예요:
dart:ffi및package:ffi: 네이티브 C API 사용dart:io및package:io: 파일 및 네트워크 I/Odart:isolate: 동시 프로그래밍용 아이솔레이트dart:mirrors: 리플렉션 지원 (실험적, 네이티브 JIT 전용)
웹 플랫폼 라이브러리: JavaScript로 컴파일
Dart 웹 플랫폼(JavaScript로 컴파일된 코드)에서 사용할 수 있는 라이브러리는 다음과 같아요. 최신 권장 도구는 굵게, 레거시 도구는 기울임체로 표시했어요:
package:web: 경량 브라우저 API 바인딩dart:js_interop: JavaScript 및 브라우저 API 인터롭dart:js_interop_unsafe: 동적 JavaScript 객체 조작dart:html: HTML 요소 지원 (대신package:web사용 권장)dart:indexed_db: 클라이언트 측 키-값 저장소 (대신package:web사용 권장)dart:js,dart:js_util,package:js: JS 인터롭용 저수준 도구 (대신dart:js_interop사용 권장)dart:svg,dart:web_audio,dart:web_gl: 웹 그래픽 및 오디오/3D 프로그래밍 (대신package:web사용 권장)
오늘의 정리
자, 오늘 배운 내용 3줄로 정리해 볼게요:
- Dart 핵심 라이브러리는
dart:core,dart:async,dart:math,dart:convert등으로 기본 프로그래밍 작업을 지원해요. - 다중 플랫폼, 네이티브, 웹 플랫폼별로 다양한 라이브러리가 제공되며, 각 플랫폼에 맞는 기능을 활용할 수 있죠.
- 최신 권장 도구(
package:web,dart:js_interop)를 사용하고, 레거시 도구는 피하는 게 좋아요.
이 모든 게 Dart 핵심 라이브러리의 핵심이에요. 여러분, 오늘 배운 내용 중 하나를 실천해보는 과제를 드릴게요. dart:math를 사용해 간단한 수학 계산 코드를 작성하거나, dart:async로 비동기 작업을 구현해보세요. 할 수 있죠?
