학습
Scala 스칼라의 시스템 설계 원칙
Scala 스칼라의 시스템 설계 원칙 — 고급
스칼라의 시스템 설계 원칙
1. 도입 (왜 배워야 하는가)
스칼라는 함수형 프로그래밍과 객체지향 프로그래밍의 장점을 결합한 언어로, 대규모 시스템을 설계하고 구현하는 데 매우 유용합니다. 실무에서는 복잡한 비즈니스 로직을 처리하기 위해 고도의 구조화와 설계 원칙이 필요합니다. 이러한 원칙을 이해하지 못하면 코드의 유지보수성, 확장성, 재사용성이 떨어져 결과적으로 시스템의 품질이 저하될 수 있습니다. 또한, 잘못 설계된 시스템은 버그를 유발하거나 성능 저하를 초래할 수 있습니다. 따라서 스칼라의 시스템 설계 원칙은 효율적이고 안정적인 소프트웨어 개발을 위한 필수 요소입니다.
2. 핵심 개념 설명
2.1. SOLID 원칙
SOLID는 다섯 가지 객체지향 설계 원칙의 약어로, 좋은 소프트웨어 설계의 기초를 제공합니다.
| 원칙 | 설명 |
|---|---|
| S - Single Responsibility Principle | 클래스는 단 하나의 책임만 가져야 한다. |
| O - Open/Closed Principle | 소프트웨어 개체는 확장에 대해 열려 있어야 하고, 수정에 대해 닫혀 있어야 한다. |
| L - Liskov Substitution Principle | 자식 클래스는 부모 클래스의 대체가 가능해야 한다. |
| I - Interface Segregation Principle | 특정 클라이언트를 위한 인터페이스가 범용 인터페이스보다 낫다. |
| D - Dependency Inversion Principle | 고수준 모듈은 저수준 모듈에 의존해서는 안 된다. |
이러한 원칙들은 마치 건축의 기초와 같습니다. 기초가 튼튼해야 건물이 오래가고 안전하게 유지될 수 있듯이, 소프트웨어의 기초도 튼튼해야 합니다.
3. 기본 코드 예제
다음은 SOLID 원칙 중 하나인 단일 책임 원칙(SRP)을 적용한 간단한 예제입니다.
// 단일 책임 원칙을 적용한 클래스
class User(val name: String, val email: String) {
def save(): Unit = {
// 사용자 정보를 저장하는 로직
println(s"User $name saved.")
}
}
class UserNotification {
def notify(user: User): Unit = {
// 사용자에게 알림을 보내는 로직
println(s"Notification sent to ${user.email}.")
}
}
// 사용 예
val user = new User("Alice", "[email protected]")
user.save()
val notifier = new UserNotification()
notifier.notify(user)
실행 결과
User Alice saved.
Notification sent to [email protected].
코드 한 줄씩 동작 설명
class User(...): 사용자 정보를 담는 클래스 정의.def save(): Unit: 사용자를 저장하는 메서드 정의.println(...): 사용자 저장 메시지를 출력.class UserNotification {...}: 사용자에게 알림을 발송하는 클래스 정의.def notify(user: User): Unit: 특정 사용자를 위한 알림 메서드 정의.println(...): 알림 발송 메시지를 출력.val user = new User(...): 사용자 객체 생성.user.save(): 사용자 정보 저장 메서드 호출.val notifier = new UserNotification(): 알림 객체 생성.notifier.notify(user): 사용자에게 알림을 발송.
4. 실전 활용 예제
4.1. 쇼핑몰 장바구니 시스템
쇼핑몰에서 장바구니를 구현할 때, 각 상품과 장바구니의 책임을 분리하여 설계하는 것이 중요합니다. 아래의 예제는 장바구니와 상품 클래스를 정의한 것입니다.
class Product(val name: String, val price: Double)
class Cart {
private var items: List[Product] = List()
def addItem(product: Product): Unit = {
items = product :: items
println(s"Added ${product.name} to the cart.")
}
def totalPrice(): Double = {
items.map(_.price).sum
}
}
// 사용 예
val cart = new Cart()
cart.addItem(new Product("Laptop", 1000.0))
cart.addItem(new Product("Phone", 500.0))
println(s"Total price: ${cart.totalPrice()}")
실행 결과
Added Laptop to the cart.
Added Phone to the cart.
Total price: 1500.0
4.2. API 호출 로직
외부 API와 통신할 때, 각각의 API 호출을 담당하는 클래스를 만드는 것이 좋습니다. 아래는 API 호출을 위한 클래스입니다.
class ApiService {
def fetchData(url: String): String = {
// API 호출 로직 (가정)
s"Data from $url"
}
}
// 사용 예
val apiService = new ApiService()
val data = apiService.fetchData("http://api.example.com/data")
println(data)
실행 결과
Data from http://api.example.com/data
5. 흔한 실수와 주의사항
5.1. 책임의 중복
잘못된 코드:
class User {
def save(): Unit = { / ... / }
def notify(): Unit = { / ... / } // 사용자 알림 로직이 User 클래스에 포함됨
}
올바른 코드:
User 클래스와 UserNotification 클래스를 사용.5.2. 인터페이스의 과도한 사용
잘못된 코드:
trait NotificationService {
def notifyUser(): Unit
def logEvent(): Unit // 불필요한 메소드
}
올바른 코드: