Dart에서 클래스는 객체(Object)를 만들기 위한 객체의 설계도 입니다.
클래스는 객체가 가져야 할 속성(attribute)와 메서드(method)를 정의합니다.
클래스의 역할
ex) 요리를 만들 요리 레시피
생성자는 객체를 생성하고 초기화 하는 역할을 합니다.
생성자는 객체가 생성될 때 자동으로 호출됩니다.
생성자는 객체의 인스턴스 변수에 초기값을 할당합니다.
특징
this 키워드를 사용하여 인스턴스 변수에 접근합니다.ex) 요리자
메서드는 객체(instance)가 수행할 행동을 정의합니다.
메서드는 객체가 생성된 후, 필요에 따라 호출합니다.
메서드는 객체의 상태를 변경하거나, 특정값을 반환합니다.
특징
this 키워드를 사용하여 인스턴스 변수에 접근합니다.ex) 요리 하는 과정 / 행동
객체는 클래스를 바탕으로 생성된 실제 인스턴스(instance) 입니다.
각 객체는 고유한 상태 (속성 값)을 가질 수 있으며,
객체를 생성하면 클래스에 정의된 속성과 메서드를 가진
실제 데이터가 메모리에 할당됩니다.
ex) 실제로 만들어진 음식
// 요리 재료 클래스
class Ingredient {
String name; // class 인스턴스 (name,quantity,unit)
double quantity; // quantity(양)
String unit; // unit(단위)
// 생성자 : 객체 값을 초기화
Ingredinet(this.name, this.quantity, this.unit);
// 메서드
String toString() {
return '$quantity $unit의 $name';
}
}
class Recipe {
String name;
List<Ingredient> ingredients; // ingredients(요리 재료)
String instructions; // instructions(만드는 법)
// 생성자 객체 값을 초기화
Recipe(this.name, this.ingredients, this.instructions);
// 메서드
void printRecipe() {
print('--- $name 레시피 ---');
print('재료');
for (var ingredient in ingredients) {
print('- ingredient');
}
print('만드는 법');
print('instructions');
}
}
// 메서드
void main() {
// 재료 생성
Ingredient egg = Ingredient('달걀', 2, '개');
Ingredient flour = Ingredient('밀가루', 100, 'g');
Ingredient suger = Ingredient('설탕', 50, 'g');
List<Ingredient> pancakeIngredients = [egg, flour, sugar];
Recipe pancakeRecipe = Recipe('팬케이크', pancakeIngredients, '달걀, 밀가루, 설탕을 섞고 팬에 부쳐줍니다.');
pancakeRecipe.printRecipe();
}
Dart에서 상속은 클래스 간의 계층 구조를 형성하여
코드의 재사용성을 높이고, 유지보수성을 향상 시키는 중요한 개념입니다.
부모 클래스(superclass)의 속성과 메서드를 정의하고
자식 클래스(subclass)는 필요한 부분은 물려받고,
필요에 따라 수정하거나 추가하여 활용할 수 있습니다. <- 다형성 부분
// 부모 클래스(superclass)
class Animal {
String name;
// 생성자 : 변수 초기화
Animal(this.name);
// 메서드
void eat() {
print('$name이 먹습니다.');
}
}
// 자식 클래스(subclass) 상속
class Dog extends Animal {
@override // 오버라이드 (다형성 부분)
void bark() {
print('$name'이 짖습니다.');
}
}
void main() {
Dog mydog = Dog('치와와');
myDog.eat(); // 부모 클래스 메서드
myDog.bark(); // 자식 클래스 메서드
}
상속의 종류
Mixin을 통해 유사한 기능을 제공합니다.믹스인은 클래스에 기능을 추가하는 방법입니다.
믹스인 자체는 인스턴스를 생성하지 않습니다.
믹스인을 사용할려면 믹스인을 사용하는 클래스를 인스턴스화 해야합니다.
믹스인을 다른 클래스에 추가할 때 with 키워드를 사용해야 합니다.
// 믹스인 정의 기능 추가
mixin Flyable {
void fly() {
print('날아다닙니다.');
}
}
class Animal {
void move() {
print('움직입니다.');
}
}
// 상속 및 믹스인
class Bird extends Animal with Flyable {
void sing() {
print('지저귀다.');
}
}
class Fish extends Animal {
void swim() {
print('헤엄칩니다.');
}
}
void main() {
Bird bird = Bird(); // 클래스 인스턴스화
bird.move(); // 움직입니다.
bird.fly(); // 날아다닙니다.
bird.sing(); // 지저귀다.
Fish fish = Fish();
fish.move(); // 움직입니다.
fish.swim(); // 헤엄칩니다.
}
상속 시 주의할 점
상속과 믹스인 차이점
| 특징 | 상속 | 믹스인 |
|---|---|---|
| 관계 | is-a 관계 (A 는 B 의 종류이다) | has-a (A 는 B 를 가지고 있다) |
| 목적 | 계층 구조 형성(자식 extends 부모) | 메서드 추가 |
| 제약 | 하나의 부모 클래스만 상속 가능 | 여러 믹스인을 혼합가능 |
Dart의 다형성은 다양한 객체가 하나의 타입으로 다루는 방법입니다.
같은 메서드를 통해 다양한 동작을 수행할 수 있도록 하는 것입니다.
다형성 예시)
부모 클래스: 조미료
자식 클래스 : 소금, 설탕
"만드는 방법" 공통적인 인터페이스(메서드)를 가지고 있지만,
각 조미료에 제조 방법 과정이 다릅니다.
다형성은 같은 이름의 메서드를 통해 다양한 객체의 고유한 특징을
나타낼 수 있도록 하는 것입니다.
Dart에서의 오버라이드는 자식 클래스가 부모 클래스의 메서드를 재정의하는 것을 의미합니다.
부모 클래스에서 이미 정의된 메서드를 자식 클래스에서
다시 구현하여 다른 동작을 수행하도록 만드는 것을 말합니다.
오버라이드 하는 메서드에 @override 어노테이션을 붙여
오버라이드의 의도를 명확하게 해주는 것이 좋습니다.
// 부모 클래스
class Animal {
void makeSound() {
print('makeSound');
}
}
// 자식 클래스
class Dog extends Animal {
@override
void makeSound() {
print('Wolf');
}
}
// 자식 클래스
class Cat extends Animal {
@override
void makeSound() {
print('Meow');
}
}
Dart에서 인터페이스는 계약서(Contract)라고 생각하시면 편합니다.
implements 키워드는
인터페이스는 클래스가 가져야 할 메서드의 형식만 정의하고,
실제 구현은 각 클래스에서 자유롭게 할 수 있도록 하는 것입니다.
Dart에서 인터페이스는 모든 메서드가 추상 메서드로 구성됩니다.
abstract class Animal {
void makeSound(); // 모든 동물은 소리를 낼 수 있어야 한다는 계약
}
class Dog implements Animal {
@override
void makeSound() {
print('bark');
}
}
추상 클래스란 직접 인스턴스를 생성할 수 없는 클래스 입니다.
추상 메서드는 구현체 없이 메서드의 선언으로만 존재하는 메서드 입니다.
추상 클래스는 완성된 클래스가 아니라,
다른 클래스들이 상속받아 완성할 수 있는 기본적인 구조를 제공합니다.
추상 클래스를 선언할 때 abstract 키워드는 반드시 필요합니다.
// 추상 클래스
abstract class Animal {
String name;
Animal(this.name);
void makeSound();
}
class Dog extends Animal {
Dog(String name) : super(name);
@override
void makeSound() {
print('$name'이 짖습니다.');
}
}
class Cat extends Animal {
Cat(String name) : super(name);
@override
void makeSound() {
print('$name'이 소리냅니다');
}
}
인터페이스와 추상 클래스의 차이점
| 특징 | 인터페이스 | 추상 클래스 |
|---|---|---|
| 관계 | 완전한 추상화 (모든 메서드가 추상 메서드) | 부분적인 구현 가능(일반 메서드 포함) |
| 목적 | 다중 상속 가능 | 단일 상속 |
| 제약 | 계약(contract) 정의, 다양한 클래스 간의 관계 설정 | 공통적인 특징 정의, 상속 구조 형성 |
제네릭은 컴파일 시 타입 안정성을 보장하기 위해 사용되는 강력한 도구입니다.
특정 자료형에 종속되지 않고, 다양한 자료형을 다룰 수 있도록 하는
매개변수화된 타입이라고 할 수 있습니다.
class Pair<K,V> {
K key;
V value;
Pair(this.key, this.value);
}
7-1. 제네릭 클래스와 메서드
class Box<T> {
T value;
Box(this.value);
T getValue() {
return value;
}
}
Box<int> numberBox = Box(42);
Box<String> stringBox = Box('Hello');
print(numberBox.getValue());
print(stringBox.getValue());
7-2. 제네릭 함수
T identity<T>(T value) {
return value;
}
print(identity<int>(42));
print(identity<String>('Hello'));
7-3. 제네릭 확장
extension InterableExtension<T> on Iterable<T> {
T? firstOrNull() {
return isEmpty ? null : first;
}
}
List<int> numbers = [1,2,3];
int? firstNumber = numbers.firstOrNull();
print(firstNumber);
7-4. 제네릭 인터페이스
abstract class Comparable<T> {
int compareTo(T other);
}
class Person implements Comparable<Person> {
final String name;
final int age;
Person(this.name, this.age);
@override
int compareTo(Person other) {
return name.compareTo(other.name);
}
}
7-5. 제네릭 컬렉션
List<int> numbers = [1,2,3];
Set<String> names = {'Alice', 'Bob', 'Charlie'};
Map<String, int> ages = {'Alice': 30, 'Bob':25};
Dart에서 static 키워드는 클래스 내에서 변수와 메서드를 선언할 때 사용됩니다.
static 으로 선언된 맴버(변수와 메서드)는 클래스 자체에 속하며,
모든 인스턴스가 공유되는 단 하나의 값을 가집니다.
static 변수 (클래스 변수)
static 변수를 참조합니다.static 변수 값을 변경하면 다른 모든 인스턴스의class Person {
static int population = 0;
String name;
Person(this.name) {
population++;
}
}
void main() {
Person person1 = Person('Alice');
Person person2 = Person('Bob');
print(Person.population); // 출력: 2
}
static 메서드 (클래스 메서드)
static 메서드는 인스턴스 변수에 직접 접근 할 수 없습니다.static 메서드는 다른 static 변수나 메서드에서만 접근할 수 있습니다.class MathUtils {
static int add(int a, int b) { // `add`는 `static` 메서드
return a + b;
}
}
void main() {
int result = MathUtils.add(3, 5); // 클래스 이름 직접 호출
print(result); // 출력: 8
}
Dart 에서 변수의 값을 변경하지 않고 고정시키고 싶을 때
사용하는 키워드가 바로 final 와 const 입니다.
두 키워드 모두 변수 값을 한번 할당하면 변경할 수 없도록 만들었지만,
사용시기는 약간 다릅니다.
| 특징 | final | const |
|---|---|---|
| 할당 시점 | 런타임 또는 생성자 | 컴파일 시점 |
| 객체 변경 가능성 | 객체 속성은 변경 가능 | 객체 자체 및 속성 모두 변경 불가 |
| 사용 시기 | 대부분의 상황에서 사용 가능 | 컴파일 시점에 값이 확정될 때 사용 |
final 키워드
final String name = 'Alice';
final List<int> numbers = [1,2,3];
final DateTime now = DateTime.now();
const 키워드
const pi = 3.14159;
const List<String> colors = ['red', 'green', 'blue'];
제가 공부한 Dart 객체지향 프로그래밍을 한번 정리해 봤습니다.
Dart 공식 문서 class부분
자세한 내용은 공식 문서를 참고해주시면 좋을거 같습니다.