이번 글에서는 Dart에서 추상 클래스와 동적 바인딩을 활용한 객체 지향 설계를 예제로 설명합니다.
추상 클래스의 개념부터 동적 바인딩을 사용해 유연하고 확장 가능한 코드를 설계하는 방법까지 살펴보겠습니다.
1. 팀장의 요청: 동물의 행동 구현
1-1. 초기 설계
처음에는 각각의 동물 클래스에 독립적인 메서드를 정의했습니다. 하지만, 물고기(Fish) 클래스에 다른 메서드(hungry())를 사용하면서 일관성이 깨졌습니다.
class Dog {
void performAction() {
print('멍멍 배고파');
}
}
class Cat {
void performAction() {
print('야옹 배고파');
}
}
class Fish {
void hungry() {
print('뻐금뻐금 배고파');
}
}
void main() {
Dog d = Dog();
Cat c = Cat();
d.performAction();
c.performAction();
Fish f = Fish();
f.hungry(); // Fish 클래스는 메서드가 다르므로 혼란 발생
}
문제점
- 각 클래스에서 메서드 이름이 통일되지 않아 코드의 일관성이 부족합니다.
- 동적 바인딩이나 다형성을 사용할 수 없어 확장성이 떨어집니다.
1-2. 추상 클래스 사용으로 문제 해결
Animal 추상 클래스를 정의하여 모든 동물이 일관된 인터페이스를 따르도록 설계합니다.
abstract class Animal {
void performAction(); // 추상 메서드
}
class Dog implements Animal {
@override
void performAction() {
print('멍멍 배고파');
}
}
class Cat implements Animal {
@override
void performAction() {
print('야옹 배고파');
}
}
class Fish implements Animal {
@override
void performAction() {
print('뻐금뻐금 배고파');
}
}
// 동적 바인딩 활용
void start(Animal animal) {
animal.performAction();
}
void main() {
start(Dog());
start(Cat());
start(Fish());
}
출력 결과
멍멍 배고파
야옹 배고파
뻐금뻐금 배고파
동적 바인딩의 장점
- start() 함수는 Animal 타입의 참조를 받아, 실제 객체 타입에 따라 적절한 메서드를 실행합니다.
- 코드의 확장성과 유지보수성이 높아집니다.
2. 도형의 넓이 계산 프로그램
2-1. 초기 설계: 추상 클래스와 동적 바인딩
도형(원, 직사각형)의 넓이를 계산하는 프로그램을 설계합니다.
모든 도형은 Shape 추상 클래스를 구현하며, 넓이를 계산하는 공통된 메서드(getArea)와 출력 메서드(displayArea)를 제공합니다.
import 'dart:math';
// 추상 클래스
abstract class Shape {
void displayArea(); // 추상 메서드
double getArea(); // 추상 메서드
}
// 원 클래스
class Circle implements Shape {
final double radius;
Circle(this.radius);
@override
void displayArea() {
double area = getArea();
print('원의 넓이 : ${area}');
}
@override
double getArea() {
return radius * radius * pi;
}
}
// 직사각형 클래스
class Rectangle implements Shape {
final double width;
final double height;
Rectangle(this.width, this.height);
@override
void displayArea() {
double area = getArea();
print('직사각형의 넓이 : ${area}');
}
@override
double getArea() {
return width * height;
}
}
// 동적 바인딩을 활용한 전역 함수
void calculateArea(Shape s) {
print(s.getArea());
s.displayArea();
}
void main() {
Shape myCircle = Circle(5.0);
Shape myRectangle = Rectangle(4.0, 6.0);
calculateArea(myCircle); // 원의 넓이 계산 및 출력
calculateArea(myRectangle); // 직사각형의 넓이 계산 및 출력
}
78.53981633974483
원의 넓이 : 78.53981633974483
24.0
직사각형의 넓이 : 24.0
2-3. 동적 바인딩 활용
- calculateArea()는 Shape 타입을 받아, 런타임에 실제 객체 타입(Circle 또는 Rectangle)에 따라 동작합니다.
- 새로운 도형 클래스(Triangle, Square 등)를 추가해도 기존 코드를 수정하지 않고 확장 가능합니다.
3. 요약 및 결론
추상 클래스와 동적 바인딩의 장점
- 일관성:
추상 클래스는 공통된 인터페이스를 제공하여 코드의 일관성을 유지합니다. - 확장성:
동적 바인딩을 활용하면 새로운 클래스 추가 시 기존 코드를 수정할 필요 없이 확장할 수 있습니다. - 유지보수성:
공통 로직을 추상 클래스에서 정의하고, 세부 구현은 하위 클래스에서 처리하여 코드 재사용성을 높입니다.
코드 설계의 개선 과정
- 초기 설계에서는 메서드 이름과 구조가 일관되지 않아 코드 유지보수가 어려웠습니다.
- 추상 클래스와 동적 바인딩을 도입하여 일관성, 유연성, 확장성을 확보했습니다.
적용 가능한 상황
- 동물의 행동, 도형의 넓이 계산과 같은 다양한 객체 지향 설계 상황에서 활용할 수 있습니다.
- 실시간 데이터 처리, 이벤트 기반 시스템 등에서도 동적 바인딩은 강력한 도구입니다.
Dart의 추상 클래스와 동적 바인딩을 활용해 보다 유연하고 확장 가능한 프로그램을 설계해보세요!
Dart에서 **제너릭(Generic)**을 사용하여 추상 클래스의 리턴 타입을 지정하려면, 추상 클래스 정의 시 제너릭 타입 매개변수를 선언하고 이를 메서드의 리턴 타입으로 활용하면 됩니다. 이렇게 하면 리턴 타입이 호출 시점에 명확하게 지정되므로, 더욱 유연하고 타입 안정성이 높은 코드를 작성할 수 있습니다.
1. 제너릭 추상 클래스 정의
abstract class Animal<T> {
T performAction(); // 제너릭 타입을 리턴 타입으로 지정
}
- <T>: 제너릭 타입 매개변수를 선언합니다.
- performAction(): 제너릭 타입 T를 리턴 타입으로 사용합니다.
2. 제너릭 추상 클래스를 구현
구체적인 타입으로 추상 클래스를 구현하여 제너릭 메서드를 정의합니다.
class Dog implements Animal<String> {
@override
String performAction() {
return '멍멍 배고파';
}
}
class Cat implements Animal<String> {
@override
String performAction() {
return '야옹 배고파';
}
}
class Fish implements Animal<int> {
@override
int performAction() {
return 42; // 물고기 소리 대신 숫자를 반환
}
}
- Dog와 Cat 클래스는 String을 리턴 타입으로 지정합니다.
- Fish 클래스는 int를 리턴 타입으로 지정합니다.
3. 동적 바인딩과 제너릭 추상 클래스 활용
void start<T>(Animal<T> animal) {
T result = animal.performAction();
print(result);
}
void main() {
start(Dog()); // 출력: 멍멍 배고파
start(Cat()); // 출력: 야옹 배고파
start(Fish()); // 출력: 42
}
start<T>():
- 제너릭 함수를 정의하여 다양한 Animal 타입의 객체를 받아 처리합니다.
- performAction() 호출 후 결과를 출력합니다.
4. 제너릭 리턴 타입을 활용한 추가 예제
다양한 타입을 처리할 수 있는 프로그램을 설계할 때, 제너릭 리턴 타입을 활용하면 재사용성을 극대화할 수 있습니다.
추상 클래스
abstract class Repository<T> {
T fetchById(int id); // 특정 ID로 데이터를 가져옴
}
구체적인 구현
class UserRepository implements Repository<String> {
@override
String fetchById(int id) {
return 'User_$id'; // 사용자 이름 반환
}
}
class ProductRepository implements Repository<Map<String, dynamic>> {
@override
Map<String, dynamic> fetchById(int id) {
return {'id': id, 'name': 'Product_$id'}; // 제품 정보 반환
}
}
제너릭 활용
void main() {
Repository<String> userRepo = UserRepository();
Repository<Map<String, dynamic>> productRepo = ProductRepository();
print(userRepo.fetchById(1)); // 출력: User_1
print(productRepo.fetchById(2)); // 출력: {id: 2, name: Product_2}
}
5. 실생활 적용: API 응답 처리
제너릭 추상 클래스를 사용하여 다양한 데이터 타입을 처리할 수 있는 API 클라이언트를 설계할 수 있습니다.
abstract class ApiResponse<T> {
T processResponse(Map<String, dynamic> response);
}
class UserResponse implements ApiResponse<String> {
@override
String processResponse(Map<String, dynamic> response) {
return 'User: ${response['name']}';
}
}
class ProductResponse implements ApiResponse<Map<String, dynamic>> {
@override
Map<String, dynamic> processResponse(Map<String, dynamic> response) {
return {'id': response['id'], 'name': response['name']};
}
}
void main() {
ApiResponse<String> userResponse = UserResponse();
ApiResponse<Map<String, dynamic>> productResponse = ProductResponse();
Map<String, dynamic> userApiData = {'id': 1, 'name': 'Alice'};
Map<String, dynamic> productApiData = {'id': 101, 'name': 'Laptop'};
print(userResponse.processResponse(userApiData)); // 출력: User: Alice
print(productResponse.processResponse(productApiData)); // 출력: {id: 101, name: Laptop}
}
6. 요약
제너릭 추상 클래스의 장점
- 유연성: 다양한 데이터 타입에 대해 동작할 수 있습니다.
- 타입 안전성: 컴파일 타임에 타입 검사를 통해 안전성을 보장합니다.
- 코드 재사용: 여러 클래스에서 동일한 로직을 재사용하면서 타입에 따라 유연하게 동작합니다.
제너릭 추상 클래스 활용 패턴
- 공통 인터페이스 제공: 다양한 타입을 처리하는 로직을 통합합니다.
- 다형성과 결합: 제너릭과 동적 바인딩을 결합하여 확장 가능한 구조를 설계합니다.
- 실생활 적용: 데이터베이스, API 클라이언트, 컬렉션 처리 등 다양한 상황에서 활용 가능합니다.
Dart에서 제너릭과 추상 클래스를 활용하여 더 유연하고 강력한 코드를 작성해보세요! 😊
'Flutter' 카테고리의 다른 글
[Flutter00] 플러터의 기본개념 다지기1 (0) | 2025.01.08 |
---|---|
[Flutter 17] Dart Mixin과 Java 관계 개념 정리 (1) | 2025.01.07 |
[Flutter 15] storeApp 만들기1 (0) | 2025.01.06 |
댓글