카테고리 없음

Dart 시작하기 - #4 Classes

샤크데브 2025. 7. 30. 15:10

 

 

 

#4.0 Your First Dart Class

 

flutter에서는 class가 많이 쓰이기 때문에 중요하다.

class에서 property를 선언할때는 타입을 사용해서 정의한다.

 

class Player {
  String name = 'dart';
  int xp = 1500;
}

void main(){
  var player = Player();
  print(player.name);
  player.name = 'awdawd';
  print(player.name);
}

 

다음과 같은 코드처럼 property의 값을 바꿀 수도 있고, 가져오기만 할 수도 있다.

 

 

 

예를 들어 Player의 name을 바꾸지 못하게 하고 싶다면?

 

class Player {
  final String name = 'dart';
  int xp = 1500;
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var player = Player();
  player.sayHello();
}

 

String 앞에 final을 추가하기만 해주면 된다. 이 경우에는 name을 바꿀 수 없기 때문에 player.name = 'awdawd'; 는 오류가 나게된다.

 

class Player {
  final String name = 'dart';
  int xp = 1500;
  
  void sayHello(){
    var name = '123';
    print("Hi my name is ${this.name}");
  }
}

 

다음과 같이 method 내에서 같은 이름의 variable가 있을때 Player의 name에 접근 하기 위해서는 this.name을 써주어야 한다. 만약 아니라면 굳이 사용할 필요가 없다.

 

#4.1 Constructors

 

class Player {
  late final String name;
  late int xp;
  
  Player(String name, int xp){
    this.name = name;
    this.xp = xp;
    
  }
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var player = Player("dart",1500);
  player.sayHello();
  var player2 = Player("lynn",2500);
  player2.sayHello();
}

 

class에 parameter(argument)를 넘겨주면 위쪽에 있는 Player()에서 parameter을 받아 정해진 자리에 할당한다.

 

 

constructor 코드를 작성하는 더 나은 방법은, 일단 late를 지운다.

class Player {
   final String name;
   int xp;
  
  Player(this.name, this.xp){
  }
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var player = Player("dart",1500);
  player.sayHello();
  var player2 = Player("lynn",2500);
  player2.sayHello();
}

 

위에서 이미 type을 선언해주었기 때문에, Player(this.name, this xp) 만으로도 값을 불러올 수 있다.

 

 

#4.2 Named Constructor Parameters

 

#4.1에서 위치에 따라 달라지는 positional arguments를 사용하고 있다. (첫 번째는 name, 두 번째는 xp)

예를 들어 team과 age라는 정보를 추가하여 코드를 작성하면

 

class Player {
   final String name;
   int xp;
   String team;
   int age;
  
  Player(this.name, this.xp, this.team, this.age){
  }
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var player = Player("dart",1500, 'red', 12);
  player.sayHello();
  var player2 = Player("lynn",2500, 'blue', 12);
  player2.sayHello();
}

 

입력받아야 하는 값이 너무 많아지기 때문에 헷갈리기 쉽다. 그렇다면 이를 named constructor parameters 로 변경하려면 아래와 같이 변경해주면 된다.

 

class Player {
   final String name;
   int xp;
   String team;
   int age;
  
  Player({
    required this.name, 
    required this.xp, 
    required this.team, 
    required this.age
    }){
  }
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var player = Player(
    name: "dart",
    xp: 1500, 
    team: 'red',
    age: 12,
  );
  player.sayHello();
  var player2 = Player(
    name: "lynn",
    xp: 2500,
    team: 'blue',
    age: 12,
  );
  player2.sayHello();
}

 

Player()에 중괄호를 추가해주고, 아래의 var값을 다음과 같이 변경하면 되는데, 값이 null인 경우를 dart는 걱정하기 때문에 앞에 required를 추가 해주면 된다.

 

 

#4.3 Named Constructors

 

 

조금 다르게 적용하는 constructor

 

하나는 xp를 기본값 0으로 초기화 시킨 blue팀을 가진 Player를 만들고, 다른 하나는 xp를 0으로 초기화 시킨 red팀의 Player를 만들게 하고 싶다고 한다면...

 

 

class Player {
   final String name;
   int xp,age;
   String team;
  
  Player({
    required this.name, 
    required this.xp, 
    required this.team, 
    required this.age
    });
  
  Player.createBluePlayer({required String name, required int age}) : 
  this.age = age,
  this.name = name,
  this. team = 'blue',
  this.xp = 0;
  
  Player.createRedPlayer(String name, int age) : 
  this.age = age,
  this.name = name,
  this. team = 'red',
  this.xp = 0;
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var player = Player.createBluePlayer(
    name: "dart",
    age: 12,
  );
  var redplayer = Player.createRedPlayer("dart", 12);  
}

 

Player.createBluePlayer을 봤을 때 :(콜론) 뒤에 들어간 property들은 초기화 된다. 즉 team, xp는 설정한 'blue', 0 으로 계속 초기화 되고 age와 name만 사용자에게 받아오게 된다. 

 

#4.4 Recap

 

class Player {
   final String name;
   int xp;
   String team;
  
   Player.fromJson(Map<String, dynamic> playerJson) :
    name = playerJson['name'],
    xp = playerJson['xp'],
    team = playerJson['team'];
  
 
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var apiData = [
    {
    "name": "dart",
    "team": "red",
    "xp": 0,
   },
    {
    "name": "lynn",
    "team": "red",
    "xp": 0,
   },
    {
    "name": "dat",
    "team": "red",
    "xp": 0,
   }
  ];
  
  apiData.forEach((playerJson){
    var player = Player.fromJson(playerJson);
    player.sayHello();
  });
}

 

구조화되지 않은 데이터를 가져올 수 있다. string을 key로 dynamic value를 values로 갖는 Map을 가져오면 다음과 같은 결과가 나온다.

 

 

 

 

#4.5 Cascade Notation

 

class Player {
   String name;
   int xp;
   String team;
  
  Player({required this.name, required this.xp, required this.team});
  
 
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var nic = Player(name: 'nic', xp: 1200, team: 'red');
  nic.name = 'las';
  nic.xp = 120000;
  nic.team = 'blue';
}

 

nic이라는 Player가 있을 때 값을 바꿔주는 경우 아래와 같이 nic.name 형식을 이용하여 변경하게 된다. 

하지만 이는 nic.xxx를 반복하기 때문에 이를 줄이도록 Cascade operator을 사용 할 수 있다.

 

void main(){
  var nic = Player(name: 'nic', xp: 1200, team: 'red')
  ..name = 'las'
  ..xp = 120000
  ..team = 'blue';
}

 

방법은 먼저 세미콜론(;)을 지우고 nic 대신 . 을 하나 더 입력하면 전과 같은 기능을 한다.

 

void main(){
  var nic = Player(name: 'nic', xp: 1200, team: 'red')
  var potato = nic
    ..name = 'las'
    ..xp = 120000
    ..team = 'blue'
    ..sayHello();
}

 

앞에 class가 있다면 아래의 코드들은 바로 class를 가리키게 된다.

 

 

#4.6 Enums

 

Enum은 실수를 줄여주는 역할을 한다.

예를 들어 팀 색깔을 나타낼때 blue를 bule라고 쓴다거나 flex를 felx라고 쓰는 등 실수하기가 쉽다. 이 경우 선택의 폭을 줄여는 Enums를 이용하여 실수를 줄일 수 있다.

 

enum Team {red, blue}
enum XPLevel {beginner, medium, pro}

class Player {
   String name;
   XPLevel xp;
   Team team;
  
  Player({required this.name, required this.xp, required this.team});
  
 
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

void main(){
  var nic = Player(name: 'nic', xp : XPLevel.medium , team: Team.red);
  var potato = nic
    ..name = 'las'
    ..xp = XPLevel.pro
    ..team = Team.blue
    ..sayHello();
}

 

enum 을 이용하여 어떤 값이 들어갈 수 있는지 먼저 정의해주고, 사용할때는 더 이상 직접 값을 넣어주는 것이 아닌 enum을 통해서 값을 넣어줄 수 있다. 예를들어 team : 'red' 는 team : Team.red 로 표현이 가능하다.

 

 

 

#4.7 Abstract Classes (추상화 메소드)

 

abstract class(추상화 클래스)로는 객체를 생성할 수 없다. 추상화 클래스는 다른 클래스들이 직접 구현 해야하는 메소드들을 모아놓은 청사진(blueprint)이라고 볼 수 있다.

 

예를 들어 Human이라는 추상적 메소드를 만들고 walk이라는 메소드를 가질 때 이 메소드의 시그니쳐가 무엇인지 정의한다. 이 메소드가 반환하는 값이 무엇인지 확인하는 것이다. 여기서는 void가 될 것이고,

 

abstract class Human {
  void walk();
}

enum Team {red, blue}
enum XPLevel {beginner, medium, pro}

class Player extends Human{
   String name;
   XPLevel xp;
   Team team;
  
  Player({required this.name, required this.xp, required this.team});
  
 
  
  void sayHello(){
    print("Hi my name is $name");
  }
}

 

클래스의 형태를 사용하고 싶을 때는 class 옆에 extends 를 붙여 사용 할 수 있다. 하지만 이 경우에는 Player에서 오류가 나는데, 그 이유는 walk이라는 메소드가 없기 때문이다.

 

추상화 클래스는 메소드의 이름과 반환 타입만 정해서 정의가 가능하고, 파라미터도 성정이 가능하다. 

 

 

abstract class Human {
  void walk();
}

enum Team {red, blue}
enum XPLevel {beginner, medium, pro}

class Player extends Human{
   String name;
   XPLevel xp;
   Team team;
  
  Player({required this.name, required this.xp, required this.team});
  
 
  
  void sayHello(){
    print("Hi my name is $name");
  }
  void walk(){
    print("i'm walkin");
  }
}

class Coach extends Human{
  void walk(){
    print('the coach is walking');
  }
}

void main(){
  var nic = Player(name: 'nic', xp : XPLevel.medium , team: Team.red);
  var potato = nic
    ..name = 'las'
    ..xp = XPLevel.pro
    ..team = Team.blue
    ..sayHello();
}

 

예를 들어 Coach라는 새로운 클래스를 만들 때 위 코드처럼 walk 메소드를 무조건 만들어주어야 한다.

 

 

 

#4.8 Inheritance

 

 

class Human {
  final String name;
  Human(this.name);
  void sayHello(){
    print("Hi my name is $name");
  }
}

enum Team {blue, red}

class Player extends Human {
  final Team team;
  
  Player({
    required this.team,
    required String name
  }) : super(name);

  
  @override
  void sayHello(){
    super.sayHello();
    print('and I play for ${team}');
  }
  
}

void main(){
  var player = Player(team : Team.red, name: 'nic');
  player.sayHello();
}

 

@override 는 Human에서 온 sayHello를 직접 만든 메소드로 대체한다.

super은 확장(상속)한 부모 클래스의 property에 접근하게 하거나 메소드를 호출할 수 있게 해준다.

 

확장한 부모 클래스가 생성자를 포함하고 있는데, 그 클래스를 다른 곳에서 사용하려면 필요한 값을 전달해야 하고 부모 클래스의 생성자를 호출해주어야 한다.

 

 

#4.9 Mixin

 

Mixin은 생성자가 없는 클래스를 의미한다. 

 

mixin Strong{
  final double strengthLevel = 1500.99;
}

mixin QuickRunner{
  void runQuick(){
    print("ruuuuun");
  }
}

mixin Tall {
  final double height = 1.99;
}

  
enum Team {blue, red}

class Player with Strong, QuickRunner, Tall{
  final Team team;
  
  Player({
    required this.team,
  });
}

class Horse with Strong, QuickRunner {}

class Kid with QuickRunner {}

void main(){
  var player = Player(
    team: Team.red
  );
}

 

다른 클래스의 preoperty와 메소드를 긁어오는 것 뿐이기 때문에 상속받을 필요가 없어서 편리하다.

Mixin의 조건은 생성자가 없는 클래스여야 한다.