본문 바로가기
Tech/Flutter&Dart

[Dart] 기본 개념

by uyoo 2024. 1. 8.
Flutter 프레임워크를 알려면 자연스럽게 알아야 하는 언어가 있다.(세트라고 생각하면 편할 듯)
”Dart”라는 언어인데, 객체 지향 언어를 기반으로 이루어져 있다.

Flutter를 위해 필수적으로 필요한 부분을 우선적으로 다룬 강의를 기반으로 했고, 예제를 통해 기본적인 개념을 익혀보고자 한다.

 

(강의 출처: 노마드 코더)

 

플랫폼

출처: https://dart-ko.dev/overview

  • Dart Web
    • dart로 작성한 코드 → javascript로 변환
  • Dart Native
    • dart로 작성한 코드 → 여러 CPU 아키텍처로 변환
    • JIT(Just In Time)와 AOT(Ahead Of Time)
      • JIT
        • 주로 개발과 디버깅 목적
        • Dart VM을 활용해 작성한 코드를 실시간으로 변환하여 결과를 바로 보여줌
        • Java의 JIT와는 다름
      • AOT
        • 프로그램 실행 전에 전체 코드를 기계 코드로 컴파일
        • 기계어로 컴파일 후 원하는 os 환경에 배포하는 데 사용 (ex. C++)
        • 시간이 오래 소요됨

 

 

🔥 Tour with examples 🔥

💡 예제를 통해 기본적인 문법들(클래스, 변수, 타입, 함수 등)을 이해해 보고 적용해보자.

💡 [Case]
  - 헬스(근력 운동)를 하는 사람의 3대 운동(Squat, Bench Press, Deadlift) 정보를 관리하고, 중량 조절 및 운동을 수행하도록 해보자.

 

Class, Abstract Class, Enum

  • 헬스(근력 운동)를 하는 사람을 class로 정의
  • 보다 상위 개념이면서 공통된 기능을 제공하기 위해 Exercise라는 abstract class 정의
  • 3대 운동을 관리하는 enum 정의

 

1) abstract class 정의

  • 상위 개념이면서 공통된 기능을 제공
  • 아래 (3)번에 정의한 class에서 상속받고자 한다면 extends 키워드 사용
  • 공통된 기능만 제공하고자 한다면 Mixins를 활용해도 좋음 (하단 Mixins 내용 참고)
abstract class Exerciser {
  void workout();
}

 

 

2) enum 정의

enum Exercise { squat, benchPress, deadlift }

 

 

아래 class의 경우 생성자 정의를 위한 여러 방법이 존재하며, 가급적 "권장"이라고 표기된 스타일을 준수해보자.

 

 

3-1) late 키워드, constructor(생성자), positional parameter 적용

  • late
    • 값을 바로 할당하지 못하는 경우 사용
    • 예제 기준으로는 생성자 호출 시 값을 할당하겠다 의미
  • constructor - positional parameter
    • 생성자를 호출할 때 순서를 지키면서 parameter 값을 넣어줘야함
    • parameter가 많아질수록 번거로워지고 잘못된 입력이 발생할 수 있음
class StrengthExerciser extends Exerciser {
  late String name;
  late int age;
  late Map<Exercise, int> strengthInfo;

  // 1. constructor - positional parameter
  StrengthExerciser(String name, int age, Map<Exercise, int> strengthInfo) {
    this.name = name;
    this.age = age;
    this.strengthInfo = strengthInfo;
  }

  @override
  void workout() {
    print("$name workout!!");

    // 현재 3대 운동 중량 표기
    strengthInfo.forEach((key, value) {
      print("$key : $value");
    });
    
    // 개행
    print("");
  }

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

 

 

3-2) late 키워드, constructor(생성자), named parameter 적용 (권장)

  • late
    • 값을 바로 할당하지 못하는 경우 사용
    • 예제 기준으로는 생성자 호출시 값을 할당하겠다 의미
  • constructor - named parameter
    • 생성자 호출시 순서에 신경 쓸 필요 없이 parameter 이름과 매핑
    • positional parameter 방식이 가지는 단점 보완
class StrengthExerciser extends Exerciser {
  late String name;
  late int age;
  late Map<Exercise, int> strengthInfo;

  // 2. constructor - named parameter (권장)
  StrengthExerciser(
      {required String name,
      required int age,
      required Map<Exercise, int> strengthInfo}) {
    this.name = name;
    this.age = age;
    this.strengthInfo = strengthInfo;
  }

  @override
  void workout() {
    print("$name workout!!");
    strengthInfo.forEach((key, value) {
      print("$key : $value");
    });

    // 개행
    print("");
  }

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

 

 

3-3) late 키워드(X), constructor(생성자), named parameter 적용 (권장)

  • this.name → this.name = name과 동일한 기능
  • (3-2) 보다 더 간결한 작성 가능
class StrengthExerciser extends Exerciser {
  String name;
  int age;
  Map<Exercise, int> strengthInfo;

  StrengthExerciser(
      {required this.name, required this.age, required this.strengthInfo});

  @override
  void workout() {
    print("$name workout!!");
    strengthInfo.forEach((key, value) {
      print("$key : $value");
    });

    // 개행
    print("");
  }

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

 

extends, @override, function

  • extends
    • Exerciser라는 추상 클래스를 상속하고자 할 때 사용
    • 추상 클래스 말고도 상위 클래스 상속도 가능
  • @override
    • extends를 통해 상속받고 상위 클래스에 정의된 추상 메서드를 재정의할 때 사용
  • function
    • {리턴 타입} {함수명} (매개변수) 구조
    • 아래 예시로는 increaseWeight, decreaseWeight 함수 참조
    • 함수 역시 positional parameter과 named parameter 방식이 존재하며, named parameter 권장
class StrengthExerciser extends Exerciser {
  String name;
  int age;
  Map<Exercise, int> strengthInfo;

  // 3. constructor - named parameter (권장)
  //    (위 멤버 변수들의 late 키워드를 추가하지 않는 방법)
  StrengthExerciser(
      {required this.name, required this.age, required this.strengthInfo});

  @override
  void workout() {
    print("$name workout!!");
    strengthInfo.forEach((key, value) {
      print("$key : $value");
    });

    // 개행
    print("");
  }

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

 

전체 코드

enum Exercise { squat, benchPress, deadlift }

abstract class Exerciser {
  void workout();
}

class StrengthExerciser extends Exerciser {
  String name;
  int age;
  Map<Exercise, int> strengthInfo;

  StrengthExerciser(
      {required this.name, required this.age, required this.strengthInfo});

  @override
  void workout() {
    print("$name workout!!");

    strengthInfo.forEach((key, value) {
      print("$key : $value");
    });

    // 개행
    print("");
  }

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

// main 메서드에서 실행
void main() {
  var exerciser1 = StrengthExerciser(name: "user1", age: 25, strengthInfo: {
    Exercise.squat: 70,
    Exercise.benchPress: 60,
    Exercise.deadlift: 70,
  });

  exerciser1.workout();
  exerciser1.increaseWeight(exercise: Exercise.squat, weight: 10);

  exerciser1.workout();
}

 


 

위 예제를 통해 아주 기본적인 개념들을 다뤄봤다. 아래는 알아두면 유용할 내용을 예제와 함께 남겨봤다.

Named Constructors

  • 기본 생성자 이외에도 여러 생성자를 정의할 수 있게 해줌
  • 각각의 생성자에 이름을 부여하여 호출 가능
  • 생성자이기 때문에 named parameter와 positional parameter 방식이 존재 (위에 class 설명 참조)
  • api를 통해 json으로 받은 데이터를 해당 클래스로 매핑할 때도 유용

중급자(?) 단계인 사람이 있을 때, 객체를 생성할 때 미리 설정한 중급 레벨의 중량값을 가질 수 있도록 하는 생성자를 만들어보자.

(예시는 예시니까 값은 가볍게 봐주는 걸로😅)

class StrengthExerciser {
  String name;
  int age;
  Map<Exercise, int> strengthInfo;

  // 기본 생성자
  StrengthExerciser(
      {required this.name, required this.age, required this.strengthInfo});

  // named constructors - named parameter
  StrengthExerciser.createMiddleLevelExerciser(
      {required String name, required int age})
      : this.name = name,
        this.age = age,
        this.strengthInfo = {
          Exercise.squat: 140,
          Exercise.benchPress: 80,
          Exercise.deadlift: 120,
        };

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

// main 메서드에서 실행
void main() {
	var midLevelExerciser = StrengthExerciser.createMiddleLevelExerciser(
	    name: 'user2',
	    age: 30,
	  );
  midLevelExerciser.workout();
}

 

Mixins

  • 생성자가 없는 클래스
  • extends 대신 with 사용 (2개 이상도 적용 가능)
  • abstract class의 단위까지는 아니더라도 공통된 기능(함수) 또는 공통된 프로퍼티를 제공하고 싶을 때 사용

abstract class로 적용했던 부분을 Mixins로 적용해보자 (용도에 맞게 잘 쓰면 좋을 듯?!)

mixin Exerciser {
  void workout() {
    print("workout!!!");
  }
}

class StrengthExerciser with Exerciser {
  String name;
  int age;
  Map<Exercise, int> strengthInfo;

  StrengthExerciser(
      {required this.name, required this.age, required this.strengthInfo});

  void increaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight + weight);
  }

  void decreaseWeight({required Exercise exercise, required int weight}) {
    strengthInfo.update(exercise, (currentWeight) => currentWeight - weight);
  }
}

 

 

회고

  • 객체 지향 언어를 기반으로 해서 그런지 몰라도 Java와 Kotlin과 유사한 느낌을 받았다.
  • 개발 환경에서 VM을 통해 컴파일하는 JIT 방식이 인상 깊었다.