[플러터 인 액션] C2 : 다트

다트 프로그램 내부

  • 모든 다트 프로그램은 main 함수를 반드시 정의해야 한다.
  • 모든 변수는 형식을 가져야 하며, 형식(or void)를 반환해야 한다.

입출력과 다트 라이브러리

  • 다양한 라이브러리를 제공하지만, dart:core 라이브러리만 자동 로드된다.
  • dart:html, dart:async, dart:math 등 다양한 라이브러리를 사용할 수 있다.
  • dart:io를 활용하여 서버 응용프로그램이나 명령줄 응용프로그램을 구현할 수 있다.
import 'dart:io';

void main() {
  stdout.writeln('Greet Somebody');
  String? input = stdin.readLineSync();
  return helloDart(input);
}

void helloDart(String? name){
  if (name == null) return;
  print('Hello, $name');
}

다트 프로그래밍 개념

  • 다트는 객체지향 언어이며, 단일 상속을 지원한다.
  • 모든 객체는 Object 클래스를 상속받는다.
  • 다트는 형식을 가진다.
    • 문자열을 반환한다고 선언한 함수에서 숫자를 반환할 수 없다.
  • 최상위 수준 함수와 변수를 지원하며 이를 라이브러리 멤버라고 부른다.

형식을 갖는 프로그래밍 언어

  • 변수의 형식을 컴파일 타임에서 알 수 있거나 추론할 수 있다면 형식을 갖는 언어이다.
  • 런타임에서 형식을 추론한다면 이는 동적 언어이다.
  • 형식을 사용하는 코드는 일반적으로 안전하다.
    • 컴파일 오류 발생 가능성이 적음
  • 다트는 컴파일 타임에 형식을 확인한다.

다트의 형식 시스템

  • 다트는 변수를 선언할 때 형식을 지정해야 한다.
  • dynamic을 사용하면 동적 형식도 활용 가능하며, 컴파일러가 해당 변수에 모든 형식을 허용한다.
// 선택적 형식 선언
dynamic myNumber = 'Hello';
  • 주로 맵에 dynamic 형식을 활용한다.
Map<String, dynamic> json = Map();
  • var 키워드는 코드 형식에 도움을 주는 키워드이다.
    • 범위가 한정적이며, 어떠한 변수형도 받을 수 있지만 변경할 수 없다.
var boo = 123;

변수와 할당

  • 일반적으로 다트에서는 값을 할당하지 않으면, null을 값으로 가진다.
  • 스타일 가이드에서는 객체에 null을 할당하지 않는 것을 추천한다.

final, const, static

  • 위 세 키워드는 변수의 형식을 확장한다.
  • [final] : 한 번만 할당할 수 있으며, 클래스 수준에서 변수를 할당하기 전에 선언한다.
    • 클래스의 생성자에서 할당하는 모든 변수에 final을 사용한다.
  • [const] : 할당하기 전에 선언하지 않는다.
    • 컴파일 이후로 항상 같은 값을 가지는 상수를 선언하며, 성능이 개선된다.
const String name = 'Nora';
  • [static] : 클래스에서만 사용된다.

널 인지 연산자

  • 다트에서는 널 인지 연산자로 null Safe 환경을 지원한다.
  • null이면 오류를 발생하지 않고, 아무것도 하지 말라고 지시한다.

?. 연산자

  • null이 아니라면 할당하고, null이면 오류 없이 null을 할당하라는 의미의 연산자이다.
class User {
  int? age;
  
  User(this.age);
}

void main() {
  User? user = User(25);
  
  print(user?.age);
}
  • 코드가 간결하며 가독성이 높다.

?? 연산자

  • 값이 존재하지 않는 상황에 할당할 백업값을 지정할 수 있다.
  • 변수가 null이라면 백업값을 활용하라는 의미이다.
int? age;
int defaultAge = 18;

int userAge = age ?? defaultAge ;
print(userAge);

??= 연산자

  • ??와 비슷하지만 반대의 작업을 수행한다.
  • null일 때만 백업값을 할당한다.
// ??=
int? number;
number ??= 10;
print(number); //10
number ??= 20;
print(number); // 10

제어 흐름

  • 다트는 대부분 고수준 언어와 마찬가지로 제어 흐름 기능을 사용한다.
void main(){
  // if - else
  bool isTrue = false;
  if(isTrue && isTrue || isTrue){
    print(true);
  }else{
    print(false);
  }
  // switch - case
  int number = 1;
  switch(number){
    case 0:
      print('zero');
      break; //break를 수행하지 않으면 모든 case 수행
    default:
      print("edefault");
  }
}

삼항 연산자

  • 삼항 연산자로 if/else를 대신할 수 있다.
//ternary
int? name;
var nametag  = name == null ? "null" : "username";

다트와 함수

  • 함수 시그니처는 [반환 형식] [함수명](인수 형식arg)의 패턴으로 구성된다.
  • 다트는 함수를 인수로 전달하거나 함수에서 함수를 반한하는 고차 함수를 지원한다.
  • 함수 바디에 하나의 행의 코드가 있을 때 단축 표현식을 활용할 수 있다.
//단축 표현식 : 함수 바디에 한 행의 코드를 구현
String makeGreeting(String name) => 'Hello, $name';

이름 지정 파라미터

  • 이름 지정이란 함수를 호출할 때 인수를 레이블과 쌍으로 제공한다는 의미이다.
//이름 지정
void debugger({required String message,required int lineNum}) {
  debugger(message:'A',lineNum:44);
}
  • 중괄호로 이름 지정 파라미터를 감싸고, required 키워드로 반드시 이름을 지정하도록 정의한다.

고급 함수 개념

  • 함수는 코드 재사용의 핵심이며, 고차 함수를 이용해 코드의 추상 계층을 추가하면 이해하기 쉬운 코드를 만들 수 있다.
  • 함수를 쪼개서 자신만의 기능을 구현하는 행위를 추상화라고 한다.
  • 고차 함수를 활용하면 로직을 추상화 할 수 있고, 함수를 인수로 받거나 함수를 반환할 수 있다.
//고차 함수와 추상화
List<int> nums = [1,2,3];

void printNum(int num) => print(num);

void printNumbers(List<int> nums){
  nums.forEach(printNum); 
}
  • forEach와 같은 Iterable 객체는 모든 멤버에게 고차 함수를 제공한다.

다트의 객체지향 프로그래밍

  • 요즘 앱은 큰 데이터셋을 처리할 수 있도록 돕는 역할을 주로 수행한다.
  • 모든 앱은 사용자가 데이터와 쉽게 상호작용할 수 있도록 돕는다.
  • 데이터는 현실을 반영함, 컴퓨터가 우리가 원하는 작업을 수행하도록 데이터에 추상화를 추가해야 한다.
    • 이는 읽고, 이해하기 쉬우며 재사용성 높은 코드를 제공한다.
  • 모든 클래스는 현실의 사물을 표현할 수 있다.

생성자

  • 새 인스턴스를 만들 때 수행할 동작을 지정할 수 있는데, 이 역할을 하는 함수를 생성자라고 한다.
  • 기본 생성자는 클래스와 같은 이름을 갖는다.
    • 함수로 전달하려는 인수(클래스의 프로퍼티로 할당하려는 값)는 함수의 인수처럼 정의한다.
class Animal {
  String name;
  String type;

  Animal(this.name, this.type);
}

상속

  • 한 클래스가 다른 클래스를 상속받거나 다른 클래스의 슈퍼 클래스가 될 수 있다.
class Animal {
  late String name;
  late String type;

  Animal();
}

class Cat extends Animal {
  Cat();
}

void main(){
  Cat cat = Cat();
  cat.name = 'name';
  cat.type = '4';
}

factory와 지정 생성자

  • factory와 지정 생성자는 항상 클래스의 새 인스턴스를 반환한다.
  • factory 메서드는 캐시된 인스턴스 또는 서브 형식의 인스턴스를 반환하기 때문에, 조금 더 유연하다.
class Energy {
  late int power;

  Energy(this.power); //기본 생성자

  //지정 생성자 : 새로운 객체를 항상 생성
  //여러 개의 생성자 제공
  Energy.fromWind(int windBlows)
  : power = windBlows*2;
  
  // private 생성자 
  Energy._internal(this.power);
  //싱글톤 객체 
  static final Energy _instance= Energy._internal(0);
  //factory 생성자 : return을 사용해 새로운 객체 또는 기존 객체 반환
  //싱글톤 패턴, 캐싱, JSON 변환 등에 사용
  // 기존 객체 재사용
  factory Energy.singleton(){
    return _instance;
  }
}

열거자

  • 상수 집합을 표현하기 위해 enum(열거 클래스)를 사용할 수 있다.
enum Color{red,blue}

void updateColor(Color color){
  switch(color){
    case Color.red:
      print("red");
    case Color.blue:
      print("blue");
  }
}