Dart에서 클래스는 객체 지향 프로그래밍의 기본 단위로, 객체의 속성과 동작을 정의하는 역할을 한다. Dart에서 클래스는 class
키워드를 사용하여 정의된다. 클래스를 정의할 때는 주로 객체의 특성에 해당하는 필드(fields)와 객체가 수행할 수 있는 메소드(methods)를 포함시킨다. 또한, 생성자를 통해 객체의 초기 상태를 정의할 수 있다.
클래스 정의의 기본 구조
Dart에서 클래스의 기본 구조는 다음과 같다:
class ClassName {
// 필드
var fieldName;
// 생성자
ClassName(this.fieldName);
// 메소드
void methodName() {
// 메소드의 내용
}
}
필드(Field)
필드는 클래스 내에서 객체가 가질 수 있는 데이터를 저장하는 변수이다. 필드는 여러 데이터 타입을 가질 수 있으며, 일반적으로 클래스 내부에서 선언된다. 예를 들어, 자동차 클래스라면 필드로 color
, model
, speed
와 같은 변수가 있을 수 있다. 필드는 객체의 상태를 나타낸다.
class Car {
String color; // 필드
String model; // 필드
int speed; // 필드
}
생성자(Constructor)
생성자는 클래스의 인스턴스가 생성될 때 호출되며, 주로 객체의 필드를 초기화하는 역할을 한다. Dart에서 생성자는 클래스 이름과 동일한 이름을 가지며, 클래스가 생성될 때 호출된다. Dart에서는 축약 생성자 문법을 제공하여 필드에 값을 쉽게 할당할 수 있다.
class Car {
String color;
String model;
int speed;
// 생성자
Car(this.color, this.model, this.speed);
}
위 예시에서 Car
클래스는 세 개의 필드를 가지고 있으며, Car
클래스의 인스턴스를 생성할 때 필드 값을 초기화할 수 있다.
void main() {
var myCar = Car('red', 'sedan', 100);
print(myCar.color); // 출력: red
}
기본 생성자
만약 클래스에 생성자를 명시적으로 정의하지 않으면, Dart는 기본 생성자를 제공한다. 기본 생성자는 인수 없이 호출되며, 객체를 생성할 때 필드를 초기화하지 않는다.
class Car {
String color;
String model;
int speed;
}
void main() {
var myCar = Car();
print(myCar.color); // null 출력
}
기본 생성자는 객체 생성 시 필드를 초기화하지 않기 때문에, 필드의 값은 기본적으로 null
이 된다.
Named Constructor (명명된 생성자)
Dart에서는 클래스에서 여러 생성자를 정의할 수 있도록 명명된 생성자 기능을 제공한다. 명명된 생성자를 사용하면 생성자의 이름을 붙여서 다른 방식으로 객체를 생성할 수 있다.
class Car {
String color;
String model;
int speed;
// 기본 생성자
Car(this.color, this.model, this.speed);
// 명명된 생성자
Car.empty() {
color = 'white';
model = 'sedan';
speed = 0;
}
}
명명된 생성자를 사용하면 특정한 조건에 맞게 객체를 생성할 수 있다.
void main() {
var emptyCar = Car.empty();
print(emptyCar.color); // 출력: white
}
상수 생성자
만약 클래스의 인스턴스를 상수로 만들고 싶다면 상수 생성자를 사용할 수 있다. 상수 생성자는 const
키워드를 사용하며, 객체가 불변 상태일 때 유용하다.
class Point {
final int x;
final int y;
const Point(this.x, this.y);
}
void main() {
var p1 = const Point(1, 2);
var p2 = const Point(1, 2);
print(identical(p1, p2)); // true
}
상수 생성자를 통해 생성된 객체는 동일한 값의 객체끼리 메모리에서 같은 인스턴스를 공유한다. 이는 메모리 절약과 성능 향상에 기여할 수 있다.
초기화 리스트 (Initializer List)
Dart에서 생성자는 클래스의 필드를 초기화하는 방법을 제공하는데, 때로는 생성자의 본문을 실행하기 전에 필드를 초기화해야 할 때가 있다. 이럴 때 초기화 리스트(Initializer List)를 사용할 수 있다. 초기화 리스트는 생성자의 본문이 실행되기 전에 실행된다. 이를 통해 final 필드를 초기화하거나, 상위 클래스의 생성자를 호출하는 데 유용하다.
class Point {
final int x;
final int y;
// 초기화 리스트를 사용하여 final 필드를 초기화
Point(int x, int y) : x = x, y = y;
}
초기화 리스트를 사용하면 생성자의 본문에서 별도로 필드를 초기화하지 않아도 된다. 이는 final 필드와 같이 생성 후 변경할 수 없는 필드를 초기화할 때 유용하다.
class Rectangle {
final int width;
final int height;
final int area;
// 초기화 리스트에서 넓이를 계산
Rectangle(int width, int height)
: width = width,
height = height,
area = width * height;
}
상속에서 초기화 리스트 사용
초기화 리스트는 상속 관계에서도 유용하게 사용된다. 상위 클래스의 생성자를 호출하기 전에 자식 클래스의 필드를 초기화할 수 있다. 예를 들어, 다음과 같이 상위 클래스와 하위 클래스가 있을 때, 하위 클래스의 생성자가 호출되면 초기화 리스트를 통해 상위 클래스의 생성자를 호출할 수 있다.
class Shape {
final String color;
Shape(this.color);
}
class Circle extends Shape {
final double radius;
// 상위 클래스 생성자를 호출하는 초기화 리스트
Circle(this.radius) : super('red');
}
위의 예에서 Circle
클래스는 Shape
클래스의 생성자를 초기화 리스트에서 호출하고 있다. 이는 상위 클래스가 먼저 초기화된 후 자식 클래스가 초기화됨을 보장한다.
팩토리 생성자 (Factory Constructor)
팩토리 생성자는 객체를 생성할 때 동일한 타입의 새로운 인스턴스를 생성하지 않고, 이미 존재하는 인스턴스를 반환하거나 다른 논리를 통해 객체를 생성하고자 할 때 사용된다. Dart에서 팩토리 생성자는 factory
키워드를 사용하여 정의된다. 팩토리 생성자는 주로 객체의 캐싱 또는 인스턴스의 유일성을 보장할 때 사용된다.
class Logger {
static final Map<String, Logger> _cache = <String, Logger>{};
final String name;
// 팩토리 생성자
factory Logger(String name) {
if (_cache.containsKey(name)) {
return _cache[name]!;
} else {
final logger = Logger._internal(name);
_cache[name] = logger;
return logger;
}
}
Logger._internal(this.name);
}
위의 예에서 Logger
클래스는 팩토리 생성자를 사용하여 동일한 이름의 인스턴스가 존재할 경우 그 인스턴스를 반환하고, 존재하지 않으면 새로운 인스턴스를 생성하여 반환한다.
void main() {
var logger1 = Logger('UI');
var logger2 = Logger('UI');
print(identical(logger1, logger2)); // true
}
팩토리 생성자를 통해 객체의 캐싱이 가능하며, 동일한 이름의 로거 인스턴스가 재사용된다.
클래스 정의 시 고려할 사항
클래스를 정의할 때, 몇 가지 고려해야 할 사항들이 있다.
final 필드
final
필드는 한 번 값이 할당되면 변경할 수 없는 필드이다. Dart에서는 이러한 필드를 초기화할 수 있는 기회를 생성자에서 제공한다. final
필드를 사용함으로써 불변성을 보장하고, 의도치 않은 값 변경을 방지할 수 있다.
class Employee {
final String name;
final int id;
Employee(this.name, this.id);
}
정적 필드와 메소드
클래스의 정적 필드와 정적 메소드는 인스턴스에 종속되지 않고, 클래스 자체에 속하는 속성 및 메소드이다. static
키워드를 사용하여 정의되며, 이는 모든 인스턴스가 동일한 값을 공유하거나, 특정한 공통 동작을 구현할 때 사용된다.
class Employee {
static int totalEmployees = 0;
Employee() {
totalEmployees++;
}
}
void main() {
var emp1 = Employee();
var emp2 = Employee();
print(Employee.totalEmployees); // 출력: 2
}
정적 메소드도 비슷한 방식으로 동작하며, 인스턴스 없이 호출될 수 있다.
class MathUtils {
static int square(int x) => x * x;
}
void main() {
print(MathUtils.square(4)); // 출력: 16
}
정적 필드와 메소드는 클래스의 범용적인 동작을 정의할 때 유용하다.