SOLID 원칙은 소프트웨어 개발에서 객체 지향 프로그래밍(OOP)의 설계 원칙으로, 유지보수성과 확장성을 높이며 변경에 따른 부작용을 줄이는 데 도움을 준다.
이 원칙은 2000년 Robert C. Martin(일명 Uncle Bob)에 의해 소개되었으며, 이후 Michael Feathers가 SOLID라는 약어를 만들면서 사용되기 시작했다. 각 원칙과 이를 Flutter 프로젝트에 적용하는 방법을 정리해 보았다.
1. S - 단일 책임 원칙 (Single Responsibility Principle, SRP)
정의: 클래스나 모듈은 하나의 책임만 가져야 하며, 변경의 이유도 하나여야 한다. 책임이란 특정 작업이나 기능을 의미하며, 단일 책임 원칙은 클래스가 너무 많은 기능을 담당하지 않도록 설계하는 것이다. 이를 통해 코드의 명확성과 변경 용이성을 확보할 수 있다.
Flutter 적용 예시:
E-commerce 앱에서 제품 목록을 표시하는 기능을 개발한다고 가정했을 때,
- Product 클래스: 제품의 속성을 정의하는 데이터 클래스이며, 데이터를 표현만 하며, 로직을 포함하지 않는다.
class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
}
- ProductRepository 클래스: 제품 데이터를 관리하고 네트워크 또는 로컬 저장소에서 데이터를 가져오며, 데이터 소스와의 통신 및 데이터를 제공한다.
class ProductRepository {
Future<List<Product>> fetchProducts() async {
// 예: 서버에서 데이터를 가져오는 코드
return [
Product(id: '1', name: 'Laptop', price: 1200.0),
Product(id: '2', name: 'Smartphone', price: 800.0),
];
}
}
- ProductListScreen 위젯: UI를 담당하며, ProductRepository에서 데이터를 받아 화면에 표시한다. 즉, 데이터를 UI로 변환하는 역할을 담당한다.
class ProductListScreen extends StatelessWidget {
final ProductRepository repository;
ProductListScreen({required this.repository});
@override
Widget build(BuildContext context) {
return FutureBuilder<List<Product>>(
future: repository.fetchProducts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error loading products'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No products available'));
}
final products = snapshot.data!;
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text('\$${product.price}'),
);
},
);
},
);
}
}
위의 예시처럼 각 클래스가 고유한 역할을 가지도록 분리하면 코드가 더 모듈화되고 유지보수하기 쉬워진다.
2. O - 개방-폐쇄 원칙 (Open/Closed Principle, OCP)
정의: 클래스는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.
Flutter 적용 예시:
제품 목록에 필터 기능을 추가한다고 가정했을 때 기존 코드를 수정하지 않고 새로운 필터 클래스를 추가하여 기능을 확장할 수 있다.
- ProductFilter 추상 클래스: 필터의 기본 구조 정의
- CategoryFilter, AvailabilityFilter: 구체적인 필터 구현
- ProductListScreen: 필터를 매개변수로 받아 동적으로 처리
이 접근법은 기존 코드를 변경하지 않고 새로운 필터를 추가할 수 있도록 하여 OCP를 준수한다.
3. L - 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
정의: 상위 클래스 객체는 하위 클래스 객체로 대체 가능해야 하며, 프로그램의 동작이 올바르게 유지되어야 한다.
Flutter 적용 예시:
DiscountedProduct라는 하위 클래스를 만들어 할인 정보를 추가한다고 가정해보자. 이 클래스는 Product 클래스를 확장하며, 기존 코드에서 문제없이 사용될 수 있다.
- DiscountedProduct: 할인 속성 추가
- ProductListScreen: 제품이 DiscountedProduct인지에 따라 UI를 다르게 표시
이를 통해 상위 클래스(Product)와 하위 클래스(DiscountedProduct)가 서로 교환 가능함을 보장한다.
4. I - 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
정의: 클라이언트는 사용하지 않는 메서드에 의존하지 않아야 한다. 즉, 인터페이스는 작고 특정한 목적에 맞게 설계되어야 한다.
Flutter 적용 예시:
음악 플레이어 앱에서 온라인과 오프라인 재생 기능을 각각 구현한다고 가정해보자.
- MusicPlayer 인터페이스: 공통 동작(playMusic, stopMusic) 정의
- OnlineMusicPlayer, OfflineMusicPlayer: 각각 인터넷과 로컬 저장소에서 음악 재생 구현
- MusicPlayerWidget: 특정 구현체에 의존하지 않고 인터페이스를 통해 동작
이 접근법은 클라이언트가 필요하지 않은 메서드에 의존하지 않도록 하여 ISP를 준수한다.
5. D - 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
정의: 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.
Flutter 적용 예시:
데이터베이스 서비스의 로컬 및 원격 구현을 분리한다고 가정해보자.
- DatabaseService 추상 클래스: 데이터 가져오기 메서드 정의
- LocalDatabaseService, RemoteDatabaseService: 각각 로컬 및 원격 데이터베이스 구현
- DataManager: 구체적인 구현체 대신 추상화된 인터페이스(DatabaseService)에 의존
이를 통해 데이터베이스 구현을 쉽게 교체할 수 있으며, 고수준 모듈(DataManager)이 저수준 구현체에 직접 의존하지 않도록 한다.
마무리
SOLID 원칙은 Flutter 앱 개발에서 코드를 더 유지보수 가능하고 확장 가능하게 만들어준다.
SRP: 각 클래스는 하나의 책임만 가진다.
OCP: 기존 코드를 수정하지 않고 새로운 기능 추가 가능
LSP: 상위 클래스와 하위 클래스가 교환 가능해야 한다.
ISP: 클라이언트는 필요 없는 메서드에 의존하지 않아야 한다.
DIP: 고수준 모듈과 저수준 모듈 모두 추상화에 의존해야 한다.
이 원칙들을 준수하면 Flutter 프로젝트뿐만 아니라 다른 OOP 기반 프로젝트에서도 더 깨끗하고 효율적인 코드를 작성하는데 도움이 될 것이라 생각한다.
아래 링크의 포스팅을 참고하여 정리한 내용입니다. 원문을 확인하고 싶으시면 아래 링크를 확인해주세요.
출처 - https://levelup.gitconnected.com/mastering-solid-principles-in-flutter-30cdaaa5475b
'Personal Posting > Flutter' 카테고리의 다른 글
Dart/Flutter 프로토타입 디자인 패턴 (0) | 2025.01.04 |
---|---|
Flutter 기반 함수형 에러핸들링 (0) | 2025.01.03 |
Secure API key 사용 (1) | 2025.01.03 |
Flutter에서 Android MacAddress 에러 (0) | 2024.04.23 |
Dart 내용 정리 (0) | 2023.12.20 |