Nomadcoders - Dart 시작하기 #4

Class의 constructor, named constructor, cascade notation, enum, abstract class, inheritance, mixin에 대해 정리했습니다.
알게된 사실 👨🏻💻
Class에서는 타입을 꼭 명시하는 것이 코드의 안정성이나 유지보수 면에서 좋고, 만약 속성 값을 바꾸지 못하게 하려면 final
을 사용한다.
또한 Class 메서드 내에서의 this
는 어쩔 수 없는 경우(메서드 내에 같은 변수명이 있는 경우)가 아니면 사용하지 않는 것을 권장한다.
constructor
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");
}
}
class Player {
final String name;
int xp;
Player(this.name, this.xp)
void sayHello() {
print("Hi my name is $name");
}
}
class의 경우에도 positional constructor parameters 보다는 named constructor parameters를 사용하는 게 더 좋다. (parameter의 수가 많을수록 더 그렇다)
class Player {
final String name;
int xp;
Player({
required this.name,
required this.xp,
})
void sayHello() {
print("Hi my name is $name");
}
}
named constructors
class Player {
final String name;
int xp;
Player({
required this.name,
required this.xp,
})
Player.createBluePlayer({ // named syntax
required int age,
}) : this.name = 'blue', // <- 콜론(:)으로 Dart에게 여기서 Player 객체를 초기화하겠다~
this.age = age;
Player.createRedPlayer(int age) : // positional syntax
this.name = 'red',
this.age = age;
void sayHello() {
print("Hi my name is $name");
}
}
void main() {
var bluePlayer = Player.createBluePlayer(
age: 25,
);
var redPlayer = Player.createRedPlayer(25);
}
named constructor
는 syntax sugar로, 실제로 실무에서 많이 쓰인다.
콜론만 써서 argument
와 property
를 일대일 초기화하는 생성자를 만든다고 생각하면 된다.
// Player 클래스 생성 및 초기화 작업
class Player {
final String name;
int xp;
String team;
// named constructor
Player.fromJson(Map<String, dynamic> playerJson) :
name = playerJson['name'],
xp = playerJson['xp'],
team = playerJson['team'];
// class method
void sayHello() {
print("Hi my name is $name");
}
}
void main() {
var apiData = [
{
"name": "hardy0",
"team": "naver",
"xp": 0,
},
{
"name": "hardy1",
"team": "kakao",
"xp": 1,
},
{
"name": "hardy2",
"team": "line",
"xp": 2,
},
{
"name": "hardy3",
"team": "coupang",
"xp": 2,
},
{
"name": "hardy4",
"team": "baemin",
"xp": 2,
},
];
apiData.forEach((playerJson) {
var player = Player.fromJson(playerJson);
player.sayHello();
});
}
cascade notation
이전에 살펴보았던 Cascade 노테이션을 클래스에서 사용한 코드이다.
class Player {
final 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 hardy = Player(name: 'turtley', xp: 0, team: 'toss')
..name = 'hardy'
..xp = 1
..team = 'apple';
var kardy = hardy
..name = 'kardy'
..xp = 2
..team = 'google'
..sayHello();
}
enums
enum
을 사용하면 선택의 폭을 좁혀 실수를 방지하고, 안전성을 높일 수 있다.
enum Team { apple, google, naver, kakao, line, coupang, baemin, toss }
enum XPLevel {0, 1, 2}
class Player {
final 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 hardy = Player(name: 'turtley', xp: XPLevel.0, team: Team.toss)
..name = 'hardy'
..xp = XPLevel.1
..team = Team.apple;
var kardy = hardy
..name = 'kardy'
..xp = XPLevel.2
..team = Team.google
..sayHello();
}
abstract class
추상화 클래스로는 객체를 생성할 수 없지만(인스턴스화 X), 특정 메서드를 구현하도록 강제할 수 있다.
추상화 클래스는 다른 클래스들이 직접 구현해야하는 메서드들을 모아 놓은 일종의 청사진이라고 할 수 있고, 수많은 청사진에 메서드의 이름과 반환 타입만 정해서 정의할 수 있다.
추상화 클래스는 다른 클래스들이 어떤 청사진을 가지고 있어야 하는지 정의해주기 때문에 유용하다.
abstract class Human {
void walk() {}
}
class Developer extends Human {
void walk() {
print('developer walking...');
}
}
class Player extends Human {
final String name;
XPLevel xp;
Team team;
Player({
required this.name,
required this.xp,
required this.team,
})
void walk {
print('player walking...');
}
void sayHello() {
print("Hi my name is $name");
}
}
void main() {
var hardy = Player(name: 'turtley', xp: XPLevel.0, team: Team.toss)
..name = 'hardy'
..xp = XPLevel.1
..team = Team.apple;
var kardy = hardy
..name = 'kardy'
..xp = XPLevel.2
..team = Team.google
..sayHello();
}
Dart의 추상화 클래스가 특정한 작업이나 기능의 청사진으로 작용한다는 점에서 Swift의 Protocol
이 생각났다. 그러나 Dart의 abstract class
는 기본 구현을 포함할 수 있으며 다중 상속이 불가능한 반면, Swift의 protocol
은 기본 구현을 직접 포함할 수 없지만 extension을 통해 가능하며, 하나의 타입이 여러 protocol
을 동시에 채택할 수 있다는 차이가 있다.
StatelessWidget
이나 StatefulWidget
은 추상화 클래스일까?`
특정 메서드를 구현하도록 강제하는 것은 맞지만, 추상화 클래스는 아니다.
💡 StatelessWidget
과 StatefulWidget
이 직접적으로 추상화 클래스로 선언되지 않은 이유
정확한 타입 정의:
StatelessWidget
과StatefulWidget
은 Flutter 프레임워크에서 특정 타입의 객체로 인식되어야 한다. 만약 직접적으로 추상화 클래스로 정의된다면, 다른 개발자들이 이를 직접 상속 받아 실제 객체를 생성하는 것이 불가능해진다.프레임워크의 일관성:
StatelessWidget
과StatefulWidget
이라는 두 개의 주요 클래스는 Flutter의 핵심적인 구성요소이다. 이 두 클래스를 추상화 클래스로 만들 경우, 개발자들이 별도의 구체적인 클래스를 정의하고, 그 클래스에서 필요한 메소드와 속성을 구현해야 하는데 이는 Flutter의 위젯 트리 구조와의 일관성을 해칠 수 있다.간결한 API 디자인:
StatelessWidget
과StatefulWidget
을 직접 상속받아 사용하는 것은 Flutter의 API를 간결하게 유지하는 방법이다. 만약 추상화 클래스로 선언되면, 각 위젯에 대한 추가적인 구현 클래스가 필요하게 되므로, 위젯을 정의하고 사용하는 것이 복잡해질 수 있다.내부 메소드와 속성의 활용: 두 클래스는
build
메소드 외에도 다양한 내부 메소드와 속성을 가지고 있다. 이러한 내부 메소드와 속성은 상속받은 위젯에서 직접 사용할 수 있도록 설계되었기 때문에 위젯의 생명주기나 업데이트 과정 등을 더 잘 제어할 수 있다.
결론적으로, StatelessWidget
과 StatefulWidget
을 직접적인 추상화 클래스로 정의하지 않은 것은 Flutter 프레임워크의 디자인 철학, 사용자 경험, 그리고 API의 간결성을 위한 결정이었을 것이라고 생각한다.
Inheritance
class Human {
final String name;
Human(required 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: name); // super 클래스는 확장한 부모 클래스를 의미, name을 Human 클래스에 전달
@override
void sayHello() {
super.sayHello(); // super를 통해 부모 클래스의 메서드에 접근
print('and I play for ${team}')
}
}
Mixin
Mixin
은 생성자가 없는 클래스를 의미하며, 다른 클래스의 프로퍼티와 메서드를 그냥 긁어와서 추가하는 경우에 유용하게 사용된다.
Mixin
은 하나의 클래스에 단 한 번만 사용하는 것이 아니라 여러 클래스에 재사용을 하는 경우에 사용하는 것이 유의미하다.
class String {
final double strengthLevel = 1500.99;
}
class QuickRunner {
void runQuick() {
print("runnnn!");
}
}
class Player with Strong, QuickRunner {
final Team team;
Player({
required this.team,
required String name,
}) : super(name: name);
@override
void sayHello() {
super.sayHello();
print('and I play for ${team}')
}
}