반응형
제네릭 유연하고 타입 안전한 프로그래밍의 핵심을 알아보자.
- 플러터를 시작하기 전 다트(Dart) 언어의 개념에 대해 정리를 해보고자 합니다.
- 다트(Dart) 언어 개념 정리 포스팅 후 플러터(Flutter) 개념 정리로 넘어갈 예정입니다.
- 플러터(Flutter) 개념 정리 후 실습이 시작 된다고 보시면 될 것 같습니다.
소개
- 프로그래밍 세계에서 유연성과 타입 안전성은 매우 중요한 요소입니다.
- 다트(Dart) 언어는 이 두 가지를 모두 제공하는 강력한 기능인 제네릭을 지원합니다.
- 제네릭을 사용하면 코드의 재사용성을 높이고 타입 안전성을 유지하면서도 다양한 데이터 타입을 다룰 수 있습니다.
- 이번 포스팅에서는 다트의 제네릭에 대해 자세히 알아보고, 실제 사용 사례와 함께 그 장점을 살펴보겠습니다.
제네릭이란?
제네릭은 타입을 파라미터로 사용할 수 있게 해주는 프로그래밍 기법입니다. 이를 통해 함수나 클래스가 여러 타입의 데이터를 다룰 수 있게 되어, 코드의 재사용성과 유연성이 크게 향상됩니다. 다트에서 제네릭은 <T>
와 같은 형태로 표현되며, 여기서 T는 타입 파라미터를 나타냅니다.
다트에서 제네릭의 기본 사용법
제네릭 함수
- 다트에서 제네릭 함수를 정의하는 방법은 다음과 같습니다:
T identity<T>(T value) {
return value;
}
- 이 함수는 어떤 타입의 값이든 받아서 그대로 반환합니다. 사용 예는 다음과 같습니다:
var result1 = identity<int>(42); // int 타입
var result2 = identity<String>('Hello'); // String 타입
제네릭 클래스
- 제네릭은 클래스 정의에도 사용될 수 있습니다:
class Box<T> {
T value;
Box(this.value);
T getValue() {
return value;
}
}
- 이
Box
클래스는 어떤 타입의 값이든 저장할 수 있습니다:
var intBox = Box<int>(42);
var stringBox = Box<String>('Dart');
제네릭의 장점
- 코드 재사용성 : 동일한 로직을 여러 타입에 적용할 수 있어 코드 중복을 줄일 수 있습니다.
- 타입 안전성 : 컴파일 시점에 타입 체크가 이루어져 런타임 에러를 줄일 수 있습니다.
- 성능 향상 : 타입 캐스팅이 줄어들어 성능이 향상됩니다.
- 더 명확한 코드 : 의도한 타입을 명시적으로 표현할 수 있어 코드의 가독성이 높아집니다.
고급 제네릭 기능
다중 타입 파라미터
제네릭은 여러 개의 타입 파라미터를 가질 수 있습니다:
class Pair<T, U> {
T first;
U second;
Pair(this.first, this.second);
}
var pair = Pair<int, String>(1, 'one');
제네릭 제약 조건
특정 타입이나 그 서브타입만 허용하도록 제약을 걸 수 있습니다:
class NumberBox<T extends num> {
T value;
NumberBox(this.value);
void printValue() {
print(value);
}
}
var intBox = NumberBox<int>(42); // 가능
var doubleBox = NumberBox<double>(3.14); // 가능
// var stringBox = NumberBox<String>('Not a number'); // 컴파일 에러
제네릭 메서드
클래스 내부의 메서드도 자체적인 타입 파라미터를 가질 수 있습니다:
class Utilities {
static T max<T extends Comparable>(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
}
var maxInt = Utilities.max(5, 3); // 5
var maxString = Utilities.max('apple', 'banana'); // 'banana'
실제 사용 사례
1. 리스트 처리
제네릭을 사용하면 다양한 타입의 리스트를 처리하는 함수를 쉽게 만들 수 있습니다:
void printList<T>(List<T> list) {
for (var item in list) {
print(item);
}
}
var intList = [1, 2, 3];
var stringList = ['a', 'b', 'c'];
printList(intList);
printList(stringList);
2. 캐시 시스템
제네릭을 사용하여 다양한 타입의 데이터를 저장할 수 있는 범용 캐시 시스템을 구현할 수 있습니다:
class Cache<K, V> {
final Map<K, V> _storage = {};
void set(K key, V value) {
_storage[key] = value;
}
V? get(K key) {
return _storage[key];
}
}
var stringCache = Cache<String, String>();
stringCache.set('name', 'Alice');
var intCache = Cache<int, double>();
intCache.set(1, 3.14);
3. 상태 관리
Flutter 앱에서 제네릭을 사용하여 범용적인 상태 관리 클래스를 만들 수 있습니다:
class StateNotifier<T> {
T _state;
final List<Function(T)> _listeners = [];
StateNotifier(this._state);
T get state => _state;
void setState(T newState) {
_state = newState;
_notifyListeners();
}
void addListener(Function(T) listener) {
_listeners.add(listener);
}
void _notifyListeners() {
for (var listener in _listeners) {
listener(_state);
}
}
}
// 사용 예
var counterState = StateNotifier<int>(0);
counterState.addListener((state) {
print('Counter changed: $state');
});
counterState.setState(1); // 출력: Counter changed: 1
주의사항 및 팁
- 타입 추론 : 다트는 대부분의 경우 타입을 추론할 수 있지만, 명시적으로 타입을 지정하면 코드의 의도를 더 명확히 할 수 있습니다.
- 동적 타입과의 상호작용 :
dynamic
타입과 제네릭을 함께 사용할 때는 주의가 필요합니다. 타입 안전성이 손상될 수 있기 때문입니다. - 제네릭과
null
안전성 : 다트의 null 안전성 기능과 제네릭을 함께 사용할 때는 nullable 타입과 non-nullable 타입을 적절히 사용해야 합니다. - 성능 고려 : 제네릭은 대부분의 경우 성능에 큰 영향을 미치지 않지만, 매우 성능에 민감한 코드에서는 제네릭 사용을 신중히 고려해야 합니다.
결론
- 다트의 제네릭은 강력하고 유연한 프로그래밍을 가능하게 하는 핵심 기능입니다. 코드의 재사용성을 높이고, 타입 안전성을 유지하면서도 다양한 데이터 타입을 효과적으로 다룰 수 있게 해줍니다.
- 제네릭을 적절히 활용하면 더 견고하고 유지보수가 쉬운 코드를 작성할 수 있습니다.
- 다트와 Flutter 개발에서 제네릭의 장점을 충분히 활용하여 더 나은 소프트웨어를 만들어보세요.
맺음말
- 이번 포스팅에서는 다트의 제네릭에 대해 알아보았습니다.
- 다음 포스팅에서는 다트의 비동기 프로그래밍에 대해 알아보고자 합니다.
반응형
'다트(Dart) 언어 강좌' 카테고리의 다른 글
스트림과 Iterable 을 알아보자 (0) | 2024.07.29 |
---|---|
비동기 프로그래밍을 알아보자 (0) | 2024.07.28 |
컬렉션 List, Set, Map 을 알아보자 (0) | 2024.07.26 |
예외 처리를 알아보자 (0) | 2024.07.26 |
Getter와 Setter 를 알아보자 (0) | 2024.07.26 |