다트의 시작 (2)

조미서·2023년 9월 29일
0
post-thumbnail

이 글은 노마드코더 - Dart 시작하기를 참고하여 작성하였습니다.
작성자 : 조미서
개발 환경 : Mac OS, Android Studio, Visual Studio Code

🧑🏻‍💻#4 CLASSES

#4.0 Your First Dart Class

먼저 기본적인 Player라는 기본적인 class를 만들어보자

class Player {			// class에서 property를 선언할 때는 타입을 꼭 명시해서 정의함
	String name = 'choms';
    int xp = 1500;
}

void main() {
	var player = Player(); // 객체 앞에 new를 쓸 순 있지만, 꼭 쓸 필요는 없다!
    print(player.name);
    player.name = 'horolro';
    print(player.name);
    print(player.xp);
}

출력:

choms
horolro
1500

만약 Player의 name을 바꾸지 못하게 고정하고 싶으면 어떻게 해야 할까?
-> 바로 타입 앞에 final을 추가하면 된다.

위 코드에서 final String name = 'choms'라고 선언해 주게 되면 아래의 player.name = 'horolro'는 오류가 생기게 된다.
-> final variable, property는 수정이 불가하기 때문이다.

player가 인사를 할 수 있는 method를 구현해 보기

class Player {
	final String name = 'choms';
    int xp = 1500;
    
    void sayHello(){
    	print("Hi my name is $name"); // dart의 클래스에서는 this를 사용할 필요가 없다
        // 사용은 가능하지만 같은 이름의 variable가 있어서 어쩔 수 없이 사용하는 게 아닌 이상 사용 필요 X
        }
}

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

#4.1 Constructors

class를 argument(실제로 함수가 호출될 때, 넘기는 변수값)로 player의 이름과 xp를 전달해서 새로운 Player를 생성할 수 있도록 하기 위해 -> constructor method를 작성

class Player{
	late final String name; // final property에 값이 없으면 오류가 생기므로 late를 작성
    late int xp;
    
    Player(String name, int xp){ // constructor method(생성자 함수) 만들기
    //constructor method의 이름은 class와 같아야 함
    
    this.name = name;
    this.xp = xp;
    }
    
    void sayHello(){
    	print("Hi my name is $name");
    }
}


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

출력:

Hi my name is choms
Hi my name is dart

하지만 위 코드에서 좀 더 단순하게 constuctor를 변형시킬 수 있다.

class Player{
	final String name; 
    int xp;
    
    Player(this.name, this.xp); // 훨씬 더 간결한 형태의 constructor가 생성되었다.
    // 주의할 점 : argument의 위치가 중요하다 여기서 (첫 번째는 name, 두 번째는 xp)인 것과 같이
    
    void sayHello(){
    	print("Hi my name is $name");
    }
}


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

#4.2 Named Constructor Parameters

만약 class에 더 많은 멤버 변수가 있을 때, argument의 위치를 정확히 지켜서 작성해야 하므로 혼란스러운 경우가 있을 수 있다 -> named argument를 가지는 constructor로 바꾸자!!

class Player{
	final String name; 
    int xp;
    String team;
    int age;
    
    Player(						
    	{required this.name, // named constructor parameters
        required this.xp, 
        required this.team, 
        required this.age,}); // {}로 감싸기!
    // constructor의 매개변수가 null 일 수 있으니 default 값을 정해주거나 required 키워드를 붙여준다
    
    void sayHello(){
    	print("Hi my name is $name");
    }
}


void main() {
	var player = Player(
    	name: "choms",
        xp: 1200,
        team: 'red',
        age: 21,
    );

    var player2 = Player("dart", 2500, 'blue', 15);

}

이제 데이터가 많은 클래스들을 만들 때도 argument들의 위치를 기억하지 않아도 된다!
(각각을 key와 value 쌍으로 작성하면 됨)


#4.3 Named Constructors

class Player{
	final String name; 
    int xp;
    String team;
    int age;
    
    Player(						
    	{required this.name, // named constructor parameters
        required this.xp, 
        required this.team, 
        required this.age,
    });
    
    Player.createBluePlayer({ 	// default로 team이 blue이고 xp는 0인 Player를 생성할 때
    	required String name, 		// named syntax 사용 (named parameter는 기본적으로 required 속성이 없음
        required int age,
        }) : this.age = age,		// : 후에 Player 클래스를 초기화
    		 this.name = name,
    		 this.team = 'blue',
    		 this.xp = 0;
             
    Player.createRedPlayer( 	// default로 team이 red이고 xp는 0인 Player를 생성할 때
    	String name, 			// positional syntax 사용 (positional parameter는 기본적으로 required 속성이 있다
        int age,
        ) : this.age = age,		// : 후에 Player 클래스를 초기화
    		 this.name = name,
    		 this.team = 'red',
    		 this.xp = 0;
    
    void sayHello(){
    	print("Hi my name is $name");
    }
}


void main() {
	var player = Player.createBluePlayer(
    	name: "choms",
        age: 21,
    );

    var redplayer = Player.createRedPlayer(
    	"dart", 
        15,
    );
}

주석 설명 읽기!


#4.4 Recap

API로부터 데이터를 받음 (Json format과 같이) -> 그 데이터를 class로 바꿔야 하는 경우가 있다

그로 하여 API로부터 여러 Player가 들어있는 목록을 받는다고 가정해 보자.

class Player{
	final String name;
    int xp;
    String team;
    
    Player.fromJson(Map<String, dynamic> playerJson) : // 구조화되지 않은 데이터를 map을 사용해 가져오기
    	name = playerJson['name'], 		// Json 속으로 들어가 각각을 초기화
        xp = playerJson['xp'],
        team = playerJson['team'];
        
    void sayHello(){
    	print("Hi my name is $name");
    }
}


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

출력:

Hi my name is choms
Hi my name is lynn
Hi my name is dart

#4.5 Cascade Notation

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

void main() {
	var choms = Player(name: 'choms', xp: 1200, team: 'red')	// 먼저 ;을 지운다
    ..name = 'dart'			// choms.부분을 모두 ..으로 변환
    ..xp = 1500				// ..부분은 choms를 가리켜준다
    ..team = 'blue'		
    ..sayHello();			// ;은 마지막에만 붙여준다
}

이는 훨씬 코드를 간결하게 작성하고 가독성 좋은 operator이다.


#4.6 Enums

Enum은 우리가 잘못한 실수들을 만들지 않게끔 도와주는 역할을 한다.
(flutter에는 color, margin, padding, style, linement 등과 같은 다양한 옵션이 존재하여 오타가 생길 수 있다.)

enum Team = { red, blue }	// 꼭 text 형태로 쓰지 않아도 괜찮다
enum XPLevel {beginner, medium, pro}

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

void main() {
	var choms = Player(name: 'choms', xp: XPLevel.medium, team: Team.red); 	// enum 사용
    ..name = 'dart'			
    ..xp = XPLevel.pro	// enum 사용			
    ..team = Team.blue 	// enum 사용		
    ..sayHello();			
}

#4.7 Abstract Classes

추상화 클래스로는 객체를 생성할 수 없다
추상화 클래스는 다른 클래스들이 직접 구현해야 하는 메소드들을 모아 놓은 전체적인 청사진 같은 역할

abstract class Human {
	void walk();	// 메소드의 이름과 반환 타입만 정해서 정의, parameter 설정 가능, 다른 클래스에서 (상속, 확장) 할 수 있다.
    // 추상화 클래스는 특정 메소드를 구현하도록 강제한다
}

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 walk(){		// walk 메소드
    	print('im walking');
    }
    
    void sayHello(){
    	print("Hi my name is $name");
    }
}

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

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

Human 추상화 클래스가 이를 상속받는 모든 클래스가 가지고 있어야 하는 메소드 (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;	// final로 변경 못하게
    
    Player({
    	required this.team,
        required String name
    }) : super(name);		// super라는 키워드르 통해 (확장 한) 부모 클래스와 상호작용을 가능하게 해줌
    
    		// Human에서 온 sayHello를 우리가 직접 만든 메소드로 override (대체) 하겠다
    void sayHello(){
    	super.sayHello();	// 확장 (상속) 한 부모 클래스의 프로퍼티에 접근하거나 메소드를 호출할 수 있게 해줌
        print('and I play for ${team}');
    }
}

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

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

코드 분석
main 함수 내 player에서 name(choms)를 받고 Human을 상속받은 Player 클래스 안 생성자의 매개변수 안(required String name)에 name(choms)를 넣은 다음 super, 즉 Human 클래스의 생성자에 즉시 전달해 준다.
Player 클래스 생성자 내의 데이터를 받아서 team은 그대로 놔두고 name은 super 클래스에 전달을 해주어 호출함.


#4.9 Mixins

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

Mixin 예

class Strong {					// 생성자 없음
	final double strengthLevel = 1500.99;
}

class QuickRunner {				// 생성자 없음
	void runQuick() {			
    	print('ruuuuuuuun!');
    }
}

class Tall {
	final double height = 1.99;
}


class Player with Strong, QuickRunner, Tall {		// 다른 클래스의 프로퍼티와 메소드를 모두 긁어옴
}

class Horse with Strong, QuickRunner {}		// 다른 클래스에 여러 번 재사용 가능

class Kid with QuickRunner {} 				// 다른 클래스에 여러 번 재사용 가능

void main() {
	player.runQuick();			// 클래스 내부의 메소드 사용도 가능
}

Mixin과 상속(extend)의 차이
extend를 하게 되면 확장한 클래스는 부모 클래스가 되고, 자식 클래스는 부모 클래스를 super를 통하여 접근할 수 있다 그 순간 부모 클래스의 인스턴스가 됨
Mixin은 with라는 키워드를 사용해서 단순히 mixin 내부의 프로퍼티와 메소드들을 가져오는 것뿐인 역할을 한다

Mixin의 조건은 생성자가 없는 클래스여야 한다는 것을 기억하기!

0개의 댓글