본문 바로가기
Dart 기초

Dart 언어 맛보기

by 서기애 2021. 9. 9.

Dart 훑어보기

Dart 언어를 본격적으로 배우기 전에, 간략하게 언어를 훑어보고 어떤 느낌인지 알아보도록 하겠습니다. DartPad에서 별도의 설치 없이 Dart와 Flutter 코드를 작성하고 실행해볼 수 있으니 아래 코드들을 따라 작성해보면 좋을 것 같습니다. 😊 DartPad 사이트 주소는 여기를 클릭해주세요.

Hello World!

새 언어를 배울 때는 역시 Hello World!를 찍어봐야죠. 모든 App은 main() 함수를 가지고 있습니다. 이 main 함수 안에서 상위 레벨 함수인 print() 함수를 사용해 콘솔에 text를 출력해봅니다.

 

void main() {
  print("Hello, World!");
}

변수

Dart언어에서는 타입 추론 덕분에 대부분의 변수들은 타입을 명시하지 않아도 됩니다. var String name = 'Voyager I';라고 쓰지 않아도 String이라 추론해냅니다.

 

var name = 'Voyager I';
var year = 1977;
var antennaDiameter = 3.7;
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var image = {
  'tags': ['saturn'],
  'url': '//path/to/saturn.jpg'
};

흐름제어문

Dart 언어는 일반적인 흐름제어문을 지원하고 있습니다. 

 

if (year >= 2001) {
  print('21st century');
} else if (year >= 1901) {
  print('20th century');
}

for (var object in flybyObjects) {
  print(object);
}

for (int month = 1; month <= 12; month++) {
  print(month);
}

while (year < 2016) {
  year += 1;
}

함수

int fibonacci(int n) {
  if (n == 0 || n == 1) return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
}

var result = fibonacci(20);

 

함수의 매개변수와 반환값을 명시하는 것이 좋다고 하네요. 

또한 Dart에서는 => 구문을 제공합니다. 이 구문은 단일 명령문을 가진 함수에 유용합니다. 특히 익명 함수를 인수로 넘겨줄 때 유용하다네요. 어떻게 사용하는 건지 볼까요?

 

flybyObjects.where((name) => name.contains('turn')).forEach(print);

 

이 코드를 보면 where 함수에 익명함수를 인수로 넘겨주고 있는 것 외에도 forEach문에 상위레벨 함수인 print 함수를 인수로 넘겨주고 있는 것까지 보여주고 있네요.

주석

Dart에서는 보통 //로 주석을 답니다.

 

// This is a normal, one-line comment.

/// This is a documentation comment, used to document libraries,
/// classes, and their members. Tools like IDEs and dartdoc treat
/// doc comments specially.

/* Comments like these are also supported. */

 

흔하게 쓰는 //와 /* */ 외에도 문서화를 위한 /// 주석도 있네요.

Imports

다른 라이브러리에 정의된 API에 접근하기 위해서는 import를 사용합니다.

 

// Importing core libraries
import 'dart:math';

// Importing libraries from external packages
import 'package:test/test.dart';

// Importing files
import 'path/to/my_other_file.dart';

클래스

3개의 속성, 2개의 생성자, 1개의 메소드를 가진 클래스 예시입니다. 속성 launchYear는 직접 값을 세팅할 수 없고 variable이 아닌 getter method를 사용해 정의되었네요. 

 

class Spacecraft {
  String name;
  DateTime? launchDate;

  // Read-only non-final property
  int? get launchYear => launchDate?.year;

  // Constructor, with syntactic sugar for assignment to members.
  Spacecraft(this.name, this.launchDate) {
    // Initialization code goes here.
  }

  // Named constructor that forwards to the default one.
  Spacecraft.unlaunched(String name) : this(name, null);

  // Method.
  void describe() {
    print('Spacecraft: $name');
    // Type promotion doesn't work on getters.
    var launchDate = this.launchDate;
    if (launchDate != null) {
      int years =
          DateTime.now().difference(launchDate).inDays ~/ 365;
      print('Launched: $launchYear ($years years ago)');
    } else {
      print('Unlaunched');
    }
  }
}

 

눈에 띄는 점은 이름이 있는 생성자가 있네요. 보통 클래스 이름과 동일한 이름으로 생성자를 만들고 매개변수만 달리 써서 여러 개의 생성자를 만드는데요. Dart에서는 이름 있는 생성자를 지원하나봅니다. 편리하게 쓸 수 있을 것 같습니다. Spacecraft.unlaunched(String name) 으로 시작하는 라인을 참고하세요.

 

만든 Spacecraft 클래스를 아래와 같이 사용할 수 있습니다.

 

var voyager = Spacecraft('Voyager I', DateTime(1977, 9, 5));
voyager.describe();

var voyager3 = Spacecraft.unlaunched('Voyager III');
voyager3.describe();

상속

Dart에서는 단일 상속을 합니다.

 

class Orbiter extends Spacecraft {
  double altitude;

  Orbiter(String name, DateTime launchDate, this.altitude)
      : super(name, launchDate);
}

Mixins

Mixin은 여러 클래스 계층에서 코드를 재사용할 수 있는 방법입니다. mixin 선언은 아래와 같이 합니다.

 

mixin Piloted {
  int astronauts = 1;

  void describeCrew() {
    print('Number of astronauts: $astronauts');
  }
}

 

mixin 기능을 클래스에 추가하려면 'with' 키워드를 사용해 상속 클래스 옆에 작성해주면 됩니다.

 

class PilotedCraft extends Spacecraft with Piloted {
  // ···
}

 

PilotedCraft 클래스는 이제 astronauts 필드와 describeCrew() 메소드를 가지고 있습니다.

인터페이스와 추상 클래스

Dart에는 interface 키워드가 없습니다. 그렇다고 인터페이스를 만들 수 없는 것은 아니고요. 모든 클래스들은 암시적으로 인터페이스를 정의한다고 합니다. 그러니까 어떤 클래스든 구현이 가능한 것이죠. 아까 위에서 만들었던 Spacecraft를 구현하려면 아래와 같이 작성합니다.

 

class MockSpaceship implements Spacecraft {
  // ···
}

 

추상 클래스는 abstract class 키워드로 만들 수 있습니다. 그리고 추상클래스는 추상 메소드를 포함할 수 있습니다.

 

abstract class Describable {
  void describe();

  void describeWithEmphasis() {
    print('=========');
    describe();
    print('=========');
  }
}

 

Describable 추상 클래스를 상속한 클래스는 describe 추상 메소드를 구현해야 합니다. 그리고 describeWithEmphasis 함수에서는 구현된 describe 함수를 호출하게 되겠네요.

비동기

asyncawait을 사용해서 비동기 작업을 한다면 읽기 더 쉬운 코드를 작성할 수 있고 콜백 지옥에서 벗어날 수 있습니다.

 

const oneSecond = Duration(seconds: 1);
// ···
Future<void> printWithDelay(String message) async {
  await Future.delayed(oneSecond);
  print(message);
}

 

위의 함수를 아래와 같이 표현할 수도 있습니다.

 

Future<void> printWithDelay(String message) {
  return Future.delayed(oneSecond).then((_) {
    print(message);
  });
}

 

아래 코드에서 보여주는 것처럼 async와 await을 사용하면 비동기 코드를 훨씬 읽기 편한 코드로 작성할 수 있습니다.

 

Future<void> createDescriptions(Iterable<String> objects) async {
  for (var object in objects) {
    try {
      var file = File('$object.txt');
      if (await file.exists()) {
        var modified = await file.lastModified();
        print(
            'File for $object already exists. It was modified on $modified.');
        continue;
      }
      await file.create();
      await file.writeAsString('Start describing $object in this file.');
    } on IOException catch (e) {
      print('Cannot create description for $object: $e');
    }
  }
}

 

또한 스트림을 생성한다면 async*라는 훌륭한 방법을 사용할 수 있습니다.

 

Stream<String> report(Spacecraft craft, Iterable<String> objects) async* {
  for (var object in objects) {
    await Future.delayed(oneSecond);
    yield '${craft.name} flies by $object';
  }
}

예외

예외를 만들어야 한다면 throw를 사용할 수 있습니다.

 

if (astronauts == 0) {
  throw StateError('No astronauts.');
}

 

예외를 포착하려면, on 또는 catch(혹은 둘다)와 함께 try 구문을 사용하면 됩니다. (아래 코드 블럭에서 on이 하이라이트되지 않았는데, catch 앞에 위치해 있습니다.)

 

try {
  for (var object in flybyObjects) {
    var description = await File('$object.txt').readAsString();
    print(description);
  }
} on IOException catch (e) {
  print('Could not describe object: $e');
} finally {
  flybyObjects.clear();
}

 

어떤가요?

준비한 Dart 훑어보기는 여기까지입니다. 어떠신가요? 다른 언어와 구별되는 몇 가지 특징이 눈에 띄네요. 저는 Dart 언어를 처음 사용해봤을 때, Java와 kotlin을 섞은 것 같은 느낌을 받았는데요. 그만큼 익숙한 표현방식이라 금방 적응해서 사용할 수 있지 않을까 싶어요. kotlin에서는 세미콜론을 쓰지 않아 참 편했는데, 다시 세미콜론을 쓰게 됐네요 😅 

다음 번에는 Dart 언어에 대해서 하나씩 상세하게 알아보는 글을 작성해볼게요. 읽어주셔서 감사합니다! 

 

참고

https://dart.dev/samples

댓글