Flutter/Dart로 개발하다 보면 클래스의 속성에 접근하는 방법을 선택해야 할 때가 있습니다. 메서드로 구현할지, getter로 구현할지 고민되는 순간들이 있죠. 오늘은 이 둘의 차이와 올바른 선택 기준을 명확하게 정리해보겠습니다.
1. 기본적인 차이점
메서드 방식
class Rectangle {
double width;
double height;
// 메서드로 구현
double area() {
return width * height;
}
double perimeter() {
return 2 * (width + height);
}
Rectangle(this.width, this.height);
}
// 사용
void main() {
final rect = Rectangle(10, 5);
print(rect.area()); // ← 괄호 필요
print(rect.perimeter()); // ← 괄호 필요
}
Getter 방식
class Rectangle {
double width;
double height;
// Getter로 구현
double get area => width * height;
double get perimeter => 2 * (width + height);
Rectangle(this.width, this.height);
}
// 사용
void main() {
final rect = Rectangle(10, 5);
print(rect.area); // ← 괄호 없음 (속성처럼)
print(rect.perimeter); // ← 괄호 없음
}
핵심 차이: 메서드는 () 괄호를 붙여 호출하고, getter는 속성처럼 접근합니다.
2. 왜 Getter를 사용해야 할까?
이유 1: Dart 스타일 가이드 권장
Dart 공식 문서는 "계산 비용이 낮고, 부수 효과가 없으며, 상태를 나타내는 것"은 getter로 구현하도록 권장합니다.
// ❌ Java/C# 스타일 (Dart에서는 비권장)
class User {
String _name;
String getName() {
return _name;
}
void setName(String value) {
_name = value;
}
}
// ✅ Dart 스타일
class User {
String _name;
String get name => _name;
set name(String value) {
_name = value;
}
}
이유 2: Dart 표준 라이브러리와 일관성
Dart의 모든 기본 타입들이 getter를 사용합니다:
final list = [1, 2, 3];
list.length; // getter
list.isEmpty; // getter
list.first; // getter
list.last; // getter
final text = "Hello";
text.length; // getter
text.isEmpty; // getter
final now = DateTime.now();
now.year; // getter
now.month; // getter
만약 이것들이 메서드였다면?
// 이상한 코드가 됨
list.length(); // 어색함
text.isEmpty(); // 뭔가 실행하는 느낌
now.year(); // 부자연스러움
이유 3: 가독성과 의도 표현
class BankAccount {
double _balance;
List<Transaction> _transactions;
// "상태"를 나타내는 것 → Getter
double get balance => _balance;
bool get hasTransactions => _transactions.isNotEmpty;
int get transactionCount => _transactions.length;
// "행동/액션"을 나타내는 것 → 메서드
void deposit(double amount) {
_balance += amount;
}
void withdraw(double amount) {
_balance -= amount;
}
List<Transaction> searchTransactions(String query) {
return _transactions.where((t) => t.contains(query)).toList();
}
}
void main() {
final account = BankAccount();
// 자연스러운 읽기
print(account.balance); // 잔액 확인
print(account.hasTransactions); // 거래 내역 있는지 확인
// 액션 수행
account.deposit(1000); // 입금 실행
account.withdraw(500); // 출금 실행
account.searchTransactions('cafe'); // 검색 실행
}
3. 언제 Getter를 사용할까?
✅ Case 1: 속성처럼 보이는 것
class Person {
String firstName;
String lastName;
DateTime birthDate;
// 파생된 속성들 → Getter
String get fullName => '$firstName $lastName';
int get age {
final now = DateTime.now();
int age = now.year - birthDate.year;
if (now.month < birthDate.month ||
(now.month == birthDate.month && now.day < birthDate.day)) {
age--;
}
return age;
}
bool get isAdult => age >= 18;
Person(this.firstName, this.lastName, this.birthDate);
}
void main() {
final person = Person('김', '철수', DateTime(1990, 5, 15));
print(person.fullName); // 속성처럼 자연스럽게
print(person.age); // 나이 확인
print(person.isAdult); // 성인 여부 확인
}
✅ Case 2: 간단한 계산
class ShoppingCart {
List<Product> items;
// 간단한 계산 → Getter
int get itemCount => items.length;
double get subtotal => items.fold(0, (sum, item) => sum + item.price);
double get tax => subtotal * 0.1;
double get total => subtotal + tax;
ShoppingCart(this.items);
}
void main() {
final cart = ShoppingCart([
Product('사과', 1000),
Product('바나나', 2000),
]);
print('상품 수: ${cart.itemCount}');
print('소계: ${cart.subtotal}');
print('세금: ${cart.tax}');
print('총액: ${cart.total}');
}
✅ Case 3: 상태 체크
class FileUpload {
String? fileName;
int bytesUploaded;
int totalBytes;
// 상태 체크 → Getter
bool get hasFile => fileName != null;
double get progress => bytesUploaded / totalBytes;
bool get isComplete => progress >= 1.0;
bool get isInProgress => progress > 0 && progress < 1.0;
String get status {
if (isComplete) return '완료';
if (isInProgress) return '업로드 중';
return '대기 중';
}
FileUpload(this.fileName, this.bytesUploaded, this.totalBytes);
}
4. 언제 메서드를 사용할까?
✅ Case 1: 계산 비용이 큰 작업
class ImageProcessor {
List<int> imageData;
// ❌ Getter로 하면 안 됨 (계산 비용이 큼)
// List<int> get processed { ... }
// ✅ 메서드로 구현
List<int> processImage() {
// 복잡한 이미지 처리 알고리즘
// 수십만 번의 연산...
return transformedData;
}
Future<List<int>> processImageAsync() async {
// 비동기 처리
return await heavyComputation();
}
ImageProcessor(this.imageData);
}
✅ Case 2: 부수 효과가 있는 경우
class Counter {
int _count = 0;
List<int> _history = [];
int get currentCount => _count; // ✅ 단순 조회
// ❌ Getter로 하면 안 됨 (상태 변경)
// int get nextCount {
// _count++;
// return _count;
// }
// ✅ 메서드로 구현
int increment() {
_count++;
_history.add(_count);
return _count;
}
void reset() {
_count = 0;
_history.clear();
}
}
✅ Case 3: 매개변수가 필요한 경우
class Calculator {
// 매개변수 필요 → 메서드
double add(double a, double b) => a + b;
double multiply(double a, double b) => a * b;
// 이전 계산 결과 기반 → Getter 가능
double? _lastResult;
double? get lastResult => _lastResult;
double calculate(double a, double b, String operation) {
switch (operation) {
case '+': _lastResult = add(a, b); break;
case '*': _lastResult = multiply(a, b); break;
default: throw Exception('Unknown operation');
}
return _lastResult!;
}
}
✅ Case 4: 외부 시스템과 상호작용
class DatabaseConnection {
// ❌ Getter로 하면 안 됨
// List<User> get users { ... } // DB 쿼리!
// ✅ 메서드로 구현
Future<List<User>> fetchUsers() async {
// 데이터베이스 쿼리
return await database.query('users');
}
Future<void> saveUser(User user) async {
await database.insert('users', user.toJson());
}
}
class ApiClient {
// ✅ 메서드로 구현
Future<Response> get(String endpoint) async {
return await http.get(endpoint);
}
Future<Response> post(String endpoint, Map data) async {
return await http.post(endpoint, body: data);
}
}
5. 실전 예제
예제 1: 전자상거래 상품
class Product {
String name;
double price;
int stock;
double discount; // 0.0 ~ 1.0
// Getter: 파생된 속성들
double get discountedPrice => price * (1 - discount);
bool get isOnSale => discount > 0;
bool get isInStock => stock > 0;
bool get isLowStock => stock > 0 && stock < 10;
String get stockStatus {
if (stock == 0) return '품절';
if (stock < 10) return '재고 부족';
return '재고 있음';
}
// 메서드: 액션들
bool canPurchase(int quantity) {
return stock >= quantity;
}
void purchase(int quantity) {
if (!canPurchase(quantity)) {
throw Exception('재고가 부족합니다');
}
stock -= quantity;
}
void restock(int quantity) {
stock += quantity;
}
Product(this.name, this.price, this.stock, {this.discount = 0.0});
}
void main() {
final product = Product('노트북', 1000000, 5, discount: 0.1);
// Getter 사용 (속성처럼)
print(product.discountedPrice);
print(product.isOnSale);
print(product.stockStatus);
// 메서드 사용 (액션)
if (product.canPurchase(2)) {
product.purchase(2);
}
product.restock(10);
}
예제 2: 사용자 프로필
class UserProfile {
String username;
String email;
String? phoneNumber;
DateTime? lastLoginAt;
List<String> roles;
// Getter: 상태 체크
bool get hasPhone => phoneNumber != null;
bool get isVerified => email.endsWith('@verified.com');
bool get isAdmin => roles.contains('admin');
bool get hasRecentActivity {
if (lastLoginAt == null) return false;
final diff = DateTime.now().difference(lastLoginAt!);
return diff.inDays < 7;
}
String get displayName => username.toUpperCase();
// 메서드: 액션
void login() {
lastLoginAt = DateTime.now();
}
void logout() {
// 로그아웃 로직
}
void addRole(String role) {
if (!roles.contains(role)) {
roles.add(role);
}
}
bool hasPermission(String permission) {
// 권한 체크 로직
return isAdmin || roles.contains(permission);
}
UserProfile(this.username, this.email, this.roles);
}
6. 베스트 프랙티스
✅ DO: Getter 사용
// 상태/속성을 나타내는 것
bool get isValid { ... }
bool get hasData { ... }
String get displayText { ... }
int get count { ... }
double get total { ... }
// O(1) 계산
String get fullName => '$firstName $lastName';
double get area => width * height;
✅ DO: 메서드 사용
// 액션/행동을 나타내는 것
void save() { ... }
void update() { ... }
void delete() { ... }
// 매개변수가 필요한 것
bool contains(String value) { ... }
double calculate(int x, int y) { ... }
// 계산 비용이 큰 것
List<Item> filterItems() { ... }
Future<Data> fetchData() async { ... }
// 부수 효과가 있는 것
int increment() { _count++; return _count; }
void reset() { _count = 0; }
❌ DON'T: 잘못된 사용
// ❌ 부수 효과가 있는 getter
int get nextId {
_counter++; // 상태 변경!
return _counter;
}
// ❌ 계산 비용이 큰 getter
List<int> get sortedList {
return items.sort(); // O(n log n) 연산!
}
// ❌ 비동기 getter (불가능)
// Future<String> get data async { ... } // 컴파일 에러!
// ❌ 파라미터 있는 getter (불가능)
// String get format(String pattern) { ... } // 컴파일 에러!
7. 판단 기준 요약
Getter를 사용하세요:
- ✅ 상태를 조회하는 것
- ✅ 간단한 계산 (O(1) ~ O(n), 빠름)
- ✅ 파생된 속성
- ✅ 부수 효과 없음
- ✅ 매개변수 불필요
메서드를 사용하세요:
- ✅ 액션/행동을 수행하는 것
- ✅ 복잡한 계산 (느림)
- ✅ 부수 효과가 있는 것
- ✅ 매개변수가 필요한 것
- ✅ 외부 시스템 호출
8. 마무리
구분 메서드 Getter
| 문법 | obj.method() | obj.property |
| 용도 | 액션/행동 | 상태/속성 |
| 매개변수 | 가능 | 불가능 |
| 부수효과 | 가능 | 피해야 함 |
| 비동기 | 가능 | 불가능 |
| Dart 스타일 | 행동에 사용 | 상태에 사용 ✅ |
기억할 것:
- Getter는 "속성처럼" 보이고 동작해야 합니다
- 메서드는 "무언가를 하는" 액션을 나타냅니다
- 의심스러울 때는 Dart 표준 라이브러리를 참고하세요
반응형
'개발 & IT > 프론트엔드' 카테고리의 다른 글
| JavaScript 개발자가 Flutter의 copyWith를 이해하기까지 (1) | 2026.01.06 |
|---|---|
| TypeScript 인터페이스 확장: Declaration Merging과 Module Augmentation (0) | 2025.10.27 |
| 웹 빌드 도구 비교: Webpack vs Vite vs Rollup 그리고 etc (0) | 2025.10.26 |
| Vite: 차세대 프론트엔드 개발 도구 (0) | 2025.10.20 |
| Vue3 Carousel에서 화면 줄어들 때 이전 슬라이드가 겹쳐 보이는 문제 해결하기 (2) | 2025.08.18 |