개발자/SupaBase && Flutter

05. (Dart 개념_노마드코더 강의)Classes

푸루닉 2024. 10. 23. 17:28

5.0 Your First Dart Class

Dart에서 클래스의 속성(Property) 을 선언할 때는 타입을 명시하여 정의합니다. 또한, final 키워드를 사용하면 해당 속성은 값을 한 번만 할당할 수 있게 되어, 이후에 변경되지 않도록 보장할 수 있습니다.

1. 클래스 속성 선언

class Player {
  final String name = 'jisoung';  // String 타입의 final 속성
  final int age = 17;             // int 타입의 final 속성

  void sayName() {
    // 클래스 메서드 안에서 this 사용은 권장되지 않음
    print("Hi my name is $name");
  }
}
  • final String name = 'jisoung';: name 속성은 String 타입이며, final로 선언되어 한 번 할당된 후에는 변경할 수 없습니다.
  • final int age = 17;: age 속성 역시 int 타입의 final 속성입니다.

2. 클래스 메서드에서 속성 사용

  • 클래스의 속성을 메서드 안에서 사용할 때는 보통 this 를 사용하지 않고 바로 속성 이름만 사용하는 것이 권장됩니다. Dart에서는 this 를 명시적으로 쓸 필요가 거의 없습니다.
void sayName() {
  print("Hi my name is $name");  // this 없이 속성을 사용
}

3. 인스턴스 생성

void main() {
  // new 키워드를 붙이지 않고 인스턴스를 생성할 수 있음
  var player = Player();
  player.sayName();  // 출력: Hi my name is jisoung
}
  • var player = Player();: Dart에서는 new 키워드를 사용하지 않아도 인스턴스를 생성할 수 있습니다.

4. 참고

인스턴스클래스에서 생성된 실제 객체를 의미함. 쉽게 말하면, 클래스는 일종의 설계도이고, 그 설계도를 바탕으로 만들어진 실물(객체)이 바로 인스턴스, 프로퍼티그 실물의 특징이나 속성에 비유할 수 있음. 메서드는 객체(자동차)가 실제로 생성되었을 때 어떤 행동을 할지를 정의하는 것.

예를 들어:
  • 클래스가 자동차 설계도라면,
  • 인스턴스는 실제 만들어진 자동차이고,
  • 프로퍼티는 자동차의 색상, 엔진 크기, 문 개수 등의 구체적인 특성을 뜻함.
  • 메소드는 "이동한다", "멈춘다", "속도를 올린다" 같은 동작을 의미함.

5.1 Constructors(생성자)

Dart에서 생성자 함수는 클래스를 통해 객체를 생성할 때 호출되는 특별한 함수입니다. 생성자 함수의 이름은 클래스 이름과 동일해야 합니다. 생성자는 클래스의 속성에 값을 할당하는 역할을 합니다.

1. 기본 생성자 정의

생성자를 사용하여 클래스의 속성에 값을 할당할 수 있습니다. late 키워드를 사용하면, 속성의 값을 나중에 할당할 수 있습니다.

class Player {
  late final String name;  // 나중에 값이 할당될 속성
  late final int age;

  // 생성자 함수는 클래스 이름과 같아야 한다
  Player(String name) {
    this.name = name;  // this를 사용하여 속성에 값을 할당
  }
}

void main() {
  // Player 클래스의 인스턴스 생성!
  var player = Player("lunick");
}
  • late final: 속성에 값을 나중에 할당할 수 있도록 하며, 할당 후에는 변경할 수 없습니다.
  • this.name = name;: 생성자 내부에서 this 키워드를 사용해 속성에 값을 할당할 수 있습니다.

2. 생성자 함수 단순화

위 코드는 더 간결하게 작성할 수 있습니다. this 키워드를 사용하여 생성자의 매개변수를 클래스의 속성에 자동으로 할당할 수 있습니다.

class Player {
  late final String name;
  late final int age;

  // 간결한 생성자 선언
  Player(this.name, this.age);
}

void main() {
  // Player 클래스의 인스턴스 생성
  var player = Player("lunick", 17);
  print(player.name);  // 출력: jisoung
  print(player.age);   // 출력: 17
}
  • Player(this.name, this.age);: Dart에서는 생성자에서 this 키워드를 사용하여 매개변수를 속성에 자동으로 할당할 수 있습니다. 이를 통해 생성자를 더 간결하게 작성할 수 있습니다.
    • 첫 번째 인자는 this.name에,
    • 두 번째 인자는 this.age에 할당됩니다.

3. 정리

  • 생성자 함수의 이름은 클래스 이름과 동일해야 합니다.
  • 생성자에서 속성 값을 할당할 때는 this 키워드를 사용해 매개변수를 속성에 바로 할당할 수 있습니다.
  • Player(this.name, this.age); 와 같이 작성하면 코드가 더 간결해집니다.

5.2 Named Constructor Parameters

Dart에서 클래스가 커지거나 많은 매개변수를 받아야 할 때, 생성자 함수가 복잡해지고 가독성이 떨어질 수 있습니다. 이 문제를 해결하는 방법으로 Named Parametersrequired 키워드를 사용하는 방법이 있습니다.

1. 문제점: 많은 인자로 인해 가독성 저하

다음과 같이 많은 인자를 받는 생성자는 가독성이 떨어지며, 각 인자의 의미를 헷갈릴 수 있습니다.

class Team {
  final String name;
  int age;
  String description;

  Team(this.name, this.age, this.description);
}

void main() {
  // 인자가 많고 각 인자의 의미가 불명확함
  var myTeam = Team("lunick", 17, "Happy coding is end coding");
}

2. 해결책 1: Named Parameters 사용

생성자에서 Named Parameters를 사용하면 함수 호출 시 각 인자의 이름을 명시할 수 있어 가독성이 좋아지고, 인자의 순서에 의존하지 않게 됩니다. 이를 위해 중괄호({}) 를 사용하여 매개변수를 명명할 수 있습니다.

class Team {
  final String name;
  int age;
  String description;

  // Named Parameters 사용
  Team({this.name, this.age, this.description});
}

void main() {
  // 매개변수의 이름을 명시하여 가독성 향상
  var myTeam = Team(
    name: "lunick",
    age: 17,
    description: "Happy coding is end coding"
  );
}

3. 문제점: Null 값 문제

Named Parameters를 사용하면 매개변수를 생략할 수 있어 유연하지만, null safety를 보장하기 위해서는 매개변수가 null이 될 수 없도록 required 키워드를 사용해야 합니다. 이를 통해 모든 인수가 반드시 제공되도록 강제할 수 있습니다.

4. 해결책 2: required 키워드 사용

required 키워드를 사용하여 매개변수가 반드시 필요하다는 것을 명시합니다. 이렇게 하면 필수 매개변수가 제공되지 않을 경우 컴파일 에러가 발생합니다.

class Team {
  final String name;
  int age;
  String description;

  // Required 키워드를 사용하여 필수 매개변수 지정
  Team({
    required this.name,
    required this.age,
    required this.description,
  });
}

void main() {
  // 필수 매개변수를 명시하여 호출
  var myTeam = Team(
    name: "lunick",
    age: 17,
    description: "Happy coding is end coding"
  );
}

5. 정리

  • Named Parameters를 사용하면 매개변수를 명시적으로 전달할 수 있어, 코드의 가독성을 크게 향상시킵니다.
  • required 키워드를 사용하여 매개변수가 null이 될 수 없도록 강제하고, 필수적으로 제공되어야 하는 값을 명확히 할 수 있습니다.
  • 이러한 방식은 클래스가 커지거나 많은 매개변수를 받아야 할 때 매우 유용하며, 가독성 및 유지보수성을 크게 향상시킵니다.

5.3 Named Constructors

Dart에서는 클래스에 여러 생성자를 정의할 수 있으며, 생성자마다 서로 다른 방식으로 객체를 초기화할 수 있습니다. 이때 Named Constructor콜론(:) 을 사용하여 특별한 생성자를 만들 수 있습니다.

1. 기본 생성자

일반적인 생성자는 Named Parameters를 사용하여 객체를 초기화할 수 있습니다.

class Car {
  late final String model;
  late final int year;

  // 기본 생성자
  Car({
    required this.model,
    required this.year,
  });
}

void main() {
  var myCar = Car(
    model: "Tesla Model S",
    year: 2022,
  );
  print("Car model: ${myCar.model}, year: ${myCar.year}");
}

2. Named Constructor

Named Constructor는 특별한 이름을 가진 생성자로, 클래스를 여러 방식으로 초기화할 수 있습니다. 콜론(:) 을 사용하여 속성을 초기화하는 작업을 할 수 있습니다.

class Car {
  final String model;
  final int year;
  final String color;
  final int mileage;

  // Named Constructor for new car
  Car.createNewCar({
    required this.model,
    required this.year,
  })  : this.color = 'black',
        this.mileage = 0;

  // Named Constructor for used car
  Car.createUsedCar({
    required this.model,
    required this.year,
    required this.color,
    required this.mileage,
  });
}

void main() {
  // 새 자동차 생성
  var newCar = Car.createNewCar(
    model: "Tesla Model X",
    year: 2023,
  );
  print("New Car: ${newCar.model}, Year: ${newCar.year}, Color: ${newCar.color}, Mileage: ${newCar.mileage}");

  // 중고 자동차 생성
  var usedCar = Car.createUsedCar(
    model: "BMW 5 Series",
    year: 2018,
    color: "white",
    mileage: 50000,
  );
  print("Used Car: ${usedCar.model}, Year: ${usedCar.year}, Color: ${usedCar.color}, Mileage: ${usedCar.mileage}");
}

3. 콜론(:)을 사용하는 생성자

콜론(:) 을 사용하면 생성자의 본문 실행 전에 클래스의 속성을 초기화할 수 있습니다. 이는 객체가 생성될 때 미리 초기화해야 하는 속성들을 설정할 때 유용합니다.

class Car {
  final String model;
  final int year;
  final String color;
  final int mileage;

  // Named Constructor with initial values using :
  Car.createNewCar({
    required String model,
    required int year,
  })  : this.model = model,
        this.year = year,
        this.color = 'black',  // 기본 색상
        this.mileage = 0;      // 새 차는 주행 거리가 0

  // 일반 생성자
  Car.createUsedCar({
    required this.model,
    required this.year,
    required this.color,
    required this.mileage,
  });
}

void main() {
  var newCar = Car.createNewCar(
    model: "Audi A6",
    year: 2024,
  );
  print("New Car: ${newCar.model}, Year: ${newCar.year}, Color: ${newCar.color}, Mileage: ${newCar.mileage}");

  var usedCar = Car.createUsedCar(
    model: "Ford Mustang",
    year: 2017,
    color: "red",
    mileage: 30000,
  );
  print("Used Car: ${usedCar.model}, Year: ${usedCar.year}, Color: ${usedCar.color}, Mileage: ${usedCar.mileage}");
}

4. Named Parameters에서 간소화된 방법

기존에는 생성자 내부에서 각 매개변수를 this를 이용해 명시적으로 할당했지만, 간소화된 방법(Sugar Syntax)에서는 this를 이용해 매개변수와 속성을 바로 연결할 수 있습니다.

// 일반적인 방법
Player.createBlue({
  required String name,
  required int xp,
}) : this.name = name,
     this.xp = xp,
     this.team = 'blue';

// 간소화된 방법
Player.createRed({
  required this.name,
  required this.xp,
  this.team = 'red',
});
  • this.name, this.xp 를 사용하면 Dart가 매개변수와 클래스 속성을 자동으로 연결해 줍니다.

5. Positional Parameters에서 간소화된 방법

Positional Parameters에서도 간소화된 방법을 사용할 수 있습니다. 이 방식은 순서대로 매개변수를 전달할 때 유용합니다.

// 일반적인 방법
Player.createRed(String name, int xp)
  : this.name = name,
    this.xp = xp,
    this.team = 'red';

// 간소화된 방법
Player.createRed(
  this.name,
  this.xp,
  [this.team = 'red'],
);
  • 여기서 대괄호 []선택적 매개변수를 의미하고, 기본값을 설정할 수 있습니다.

4. 정리

  • 기본 생성자: 일반적으로 클래스의 속성을 초기화할 때 사용되며, Named Parameters를 통해 가독성을 높일 수 있습니다.
  • Named Constructor: 여러 종류의 생성자를 정의할 수 있으며, 각 생성자가 서로 다른 방식으로 객체를 초기화합니다.
  • 콜론(:): 생성자 본문 전에 속성을 초기화할 때 사용되며, 객체가 생성될 때 바로 필요한 초기화 작업을 수행할 수 있습니다

4.4 Cascade Notation(연쇄 표기법)

Dart에서는 Cascade Notation을 사용하여 같은 객체에 대해 여러 작업을 연속적으로 수행할 수 있습니다. 이를 통해 객체를 반복해서 참조하지 않고도 간결한 코드를 작성할 수 있습니다. .. 연산자를 사용하여, 객체에 대한 속성 설정과 메서드 호출을 연쇄적으로 수행합니다.

1. 예시: 반복되는 작업

다음과 같이 자동차의 속성을 여러 번 수정할 때, 동일한 객체에 대해 반복적으로 참조해야 합니다.

class Car {
  String model;
  int year;
  String color;

  Car({required this.model, required this.year, required this.color});
}

void main() {
  var myCar = Car(model: "Tesla Model S", year: 2022, color: "Black");
  myCar.model = "Tesla Model X";
  myCar.year = 2023;
  myCar.color = "White";
}

위 코드에서는 myCar 객체에 대해 속성을 변경할 때마다 객체를 반복해서 참조하고 있습니다.

2. Cascade Notation으로 간단하게

Cascade Notation을 사용하면, 위와 같은 코드를 더 간단하게 작성할 수 있습니다. .. 연산자를 사용하여 객체에 대한 여러 작업을 연속적으로 수행할 수 있습니다.

class Car {
  String model;
  int year;
  String color;

  Car({required this.model, required this.year, required this.color});
}

void main() {
  var myCar = Car(model: "Tesla Model S", year: 2022, color: "Black")
    ..model = "Tesla Model X"
    ..year = 2023
    ..color = "White";
}
  • ..model = "Tesla Model X": myCar 객체의 model 속성을 변경합니다.
  • ..year = 2023: 이어서 myCaryear 속성을 변경합니다.
  • ..color = "White": 마지막으로 myCarcolor 속성을 변경합니다.
  • .. 연산자는 myCar 객체를 계속해서 가리킵니다.

3. 정리

  • Cascade Notation동일한 객체에 대해 여러 작업을 연속적으로 수행할 때 유용한 문법입니다.
  • .. 연산자를 사용하여 객체의 속성 수정이나 메서드 호출을 간결하게 처리할 수 있습니다.
  • 이를 통해 객체를 반복해서 참조하지 않아도 되며, 코드의 가독성과 유지보수성을 크게 향상시킬 수 있습니다.

4.5 Enums(열거형)

Enum(열거형)은 우리가 실수하지 않도록 도와주는 타입으로, 미리 정해진 값들의 집합을 정의하여 코드에서 사용할 수 있습니다. 이를 통해 특정 값들만을 선택할 수 있게 제한하고, 실수를 줄일 수 있습니다.

1. Enum 정의하기

Dart에서 Enum 타입을 만드는 방법은 다음과 같습니다. 예를 들어, CarColor라는 Enum을 만들어 자동차의 색상을 red 또는 blue로 지정할 수 있습니다.

enum CarColor {
  red,
  blue,
}

2. Enum을 클래스와 함께 사용하기

Enum을 클래스 속성으로 추가하여 특정 속성에 대해서만 제한된 값을 설정할 수 있습니다. 예를 들어, Car 클래스의 color 속성은 CarColor Enum에 의해 red 또는 blue만 선택 가능하게 할 수 있습니다.

class Car {
  String model;
  int year;
  CarColor color;

  Car({
    required this.model,
    required this.year,
    required this.color,
  });
}

3. Enum 값 사용 예시

이제 Car 인스턴스를 생성할 때, Enum을 사용하여 color를 지정할 수 있습니다. 각 인스턴스는 Enum 값인 CarColor.red 또는 CarColor.blue 중 하나를 선택해야 합니다.

void main() {
  var myCar = Car(model: "Tesla", year: 2022, color: CarColor.red);

  // Cascade Notation으로 값 수정
  var anotherCar = myCar
    ..model = "BMW"
    ..year = 2023
    ..color = CarColor.blue;
}
  • CarColor.red: Car 클래스의 color 속성에 red 색상을 지정합니다.
  • CarColor.blue: Car 클래스의 color 속성에 blue 색상을 지정합니다.
  • Cascade Notation을 사용해 한 번에 여러 속성을 수정할 수 있습니다.
  • 4. 정리
  • Enum은 미리 정의된 값들만 선택할 수 있도록 제한하여 실수를 방지할 수 있는 자료형입니다.
  • Enum은 특정 속성에서 고정된 값들만 사용할 수 있도록 하며, 가독성과 유지보수성을 높입니다.

4.8 Abstract Classes(추상화 클래스)

추상화 클래스는 다른 클래스들이 반드시 구현해야 하는 메소드들의 청사진을 제공하는 클래스입니다. 추상 클래스 자체에서는 기능을 구현하지 않고, 이를 상속받는 클래스에서 구현해야 합니다. 추상 클래스는 공통된 기능의 틀을 정의하고, 이를 기반으로 다양한 클래스들이 구체적인 기능을 구현하게 됩니다.

추상화란?(쉬운 예시와 함께)

  • 일반 클래스는 구체적인 설계도로, 예를 들어 자동차 설계도라고 생각할 수 있어. 이 설계도를 통해 자동차(인스턴스)를 만들면 바로 사용할 수 있지.
  • 그런데 추상화 클래스는 자동차의 더 큰 개념의 설계도야. 예를 들어, 모든 탈것(자동차, 자전거, 오토바이)의 공통적인 특징을 정의하는 거지. 탈것이라면 운전해야 하고, 이동해야 한다라는 것처럼 공통된 동작만 있고, 구체적으로 어떤 탈것인지에 대한 설명은 없어.
    abstract class Vehicle {
    void drive();  // 이 메서드는 구체적으로 구현되지 않음 (추상적)
    }
    

class Car extends Vehicle {
String model;

Car(this.model);

@override
void drive() {
print('$model is driving');
}
}

- **`Vehicle`** 클래스는 **추상화 클래스**로, **drive()**라는 메서드만 있고 **구체적인 동작**은 정의되지 않았어. 즉, 탈것이 어떻게 움직이는지는 여기서 설명하지 않지.
- **`Car`** 클래스는 **`Vehicle`** 클래스를 상속받아서, 구체적인 동작인 **자동차가 어떻게 움직이는지**를 정의했어.
#### 1. 추상 클래스 정의

추상 클래스는 `abstract` 키워드를 사용하여 정의합니다. 이 클래스는 **직접 인스턴스를 생성할 수 없고**, 그 안에 정의된 메소드들은 구체적인 구현 없이 틀만 제공합니다.

```dart
abstract class Vehicle {
  void drive();  // 구체적인 구현 없이 메소드 틀만 제공
}

2. 상속을 통한 구현

추상 클래스를 상속받은 클래스는 반드시 추상 클래스에서 정의된 메소드를 구체적으로 구현해야 합니다. 상속받은 클래스는 extends 키워드를 사용하여 추상 클래스의 기능을 확장합니다.

class Car extends Vehicle {
  @override
  void drive() {
    print("The car is driving.");
  }
}

3. 정리

  • 추상화 클래스(abstract class) 는 다른 클래스들이 구현해야 할 메소드들의 청사진 역할을 합니다.
  • 구현을 강제하는 메소드만을 정의하며, 상속받는 클래스에서 구체적인 동작을 구현합니다.
  • 상속extends 키워드를 사용하여 이루어지며, 상속받는 클래스는 반드시 추상 클래스의 메소드를 구현해야 합니다.

4.7 Inheritance(상속)

상속은 하위 클래스가 상위 클래스의 기능과 속성을 물려받는 것입니다. Dart에서는 super 키워드를 사용하여 부모 클래스의 생성자를 호출하거나, 부모 클래스의 메서드를 사용할 수 있습니다. 이와 함께 @override 키워드를 사용해 부모 클래스의 메서드를 재정의할 수 있습니다.

1. 부모 클래스의 생성자 호출 (super 사용)

하위 클래스에서 부모 클래스의 생성자를 호출할 때는 super 키워드를 사용합니다. 이를 통해 부모 클래스에서 정의된 속성들을 상속받아 초기화할 수 있습니다.

class Vehicle {
  final String model;

  Vehicle(this.model);  // 부모 클래스의 생성자
}

class Car extends Vehicle {
  final String color;

  // Car 생성자에서 super를 사용해 Vehicle의 생성자 호출
  Car({
    required this.color,
    required String model
  }) : super(model);  // 부모 클래스 Vehicle의 생성자 호출
}
  • super(model): 부모 클래스의 생성자에 model 값을 넘겨 부모 클래스의 속성을 초기화합니다.

2. 부모 클래스의 메서드 호출 (super 사용)

@override 키워드를 사용하면 하위 클래스에서 부모 클래스의 메서드를 재정의할 수 있습니다. 이때 부모 클래스의 메서드를 호출하고 싶다면 super 를 사용해 부모 클래스의 메서드를 호출할 수 있습니다.

class Vehicle {
  void drive() {
    print("The vehicle is driving.");
  }
}

class Car extends Vehicle {
  @override
  void drive() {
    super.drive();  // 부모 클래스의 drive() 메서드를 호출
    print("The car is driving with style.");
  }
}
  • @override: 부모 클래스의 메서드를 재정의할 때 사용합니다.
  • super.drive(): 부모 클래스에서 정의한 drive() 메서드를 호출합니다.

3. 정리

  • super 는 부모 클래스의 생성자메서드를 호출할 때 사용됩니다.
    • 하위 클래스에서 부모 클래스의 생성자를 호출할 때, 부모의 속성을 초기화하는 데 유용합니다.
    • 부모 클래스의 메서드를 하위 클래스에서 재정의한 후에도 부모 클래스의 원래 기능을 호출할 수 있습니다.
  • @override 를 사용해 부모 클래스의 메서드를 재정의하고, 필요한 경우 super 로 부모의 동작을 포함할 수 있습니다.

4.8 Mixins(생성자가 없는 클래스)

Mixin생성자가 없는 클래스를 의미하며, 여러 클래스에 공통된 기능을 재사용할 수 있게 해주는 방법입니다. Mixin 클래스는 상속을 하지 않고, with 키워드를 사용하여 다른 클래스에 기능을 혼합(mix)합니다. Mixin의 핵심여러 클래스에 재사용이 가능하다는 점입니다.

1. Mixin 사용 방법

Mixin 클래스를 정의한 후, 이를 다른 클래스에 with 키워드를 사용하여 추가할 수 있습니다. Mixin은 여러 클래스에서 재사용 가능하며, 각 클래스에 특정 기능을 추가할 수 있습니다.

class Tall {
  final double height = 190.00;
}

class Human with Tall {
  // Tall의 프로퍼티를 사용할 수 있음
}
  • with: Mixin을 추가할 때 사용합니다. 상속과 달리 부모 클래스와의 인스턴스 관계를 만들지 않고, 단순히 Mixin 내부의 프로퍼티와 메서드를 추가합니다.

2. extends와 차이점

  • extends는 클래스를 상속받아 부모-자식 관계를 형성하고, 부모 클래스의 속성과 메서드를 물려받습니다.
  • with부모-자식 관계가 아닌, 단순히 Mixin 내부의 프로퍼티와 메서드를 가져오는 방식입니다. 즉, 상속처럼 확장하지 않고, 재사용을 위한 기능으로 생각할 수 있습니다.

3. 정리

  • Mixin생성자가 없는 클래스로, 다른 클래스에 with 키워드를 사용해 추가할 수 있습니다.
  • Mixin은 상속과 달리 부모-자식 관계를 형성하지 않고, 단순히 프로퍼티와 메서드를 가져와 사용하는 방식입니다.
  • Mixin의 장점은 여러 클래스에 공통된 기능을 쉽게 재사용할 수 있다는 점입니다.