Strategy Pattern
Strategy Pattern 은 런타임 동안 계속해서 변경될 수 있는 객체들의 인터페이스를 정의하는 디자인 패턴이다. Strategy Pattern 의 클래스 다이어그램을 살펴보자
- Object using a strategy - iOS 개발을 진행할 때면 주로 View Controller 가 된다. Strategy Protoocl, 즉 전략을 직접 사용하는 객체가 된다.
- Strategy Protocol - 프로토콜을 준수하는 모든 객체가 구현해야 할 메소드, 혹은 프로퍼티를 정의한다.
- Strategies - Strategy Protocol 을 준수한 모든 Concrete object (구현체)
언제 Strategy Pattern 을 사용해야 할까?
두 개 혹은 그 이상의 같은 문제를 해결하지만 다른 알고리즘을 필요로 하는 객체가 필요할 때 Strategy Pattern 을 유용하게 사용할 수 있다. 다음과 같은 상황을 생각해보자.
영화에 대한 정보를 보여주는 애플리케이션이 있고, 영화를 평가하고 점수를 매기는 로튼 토마토나, IMDb, 메타 크리틱 같은 다양한 평가사들이 있다.
이 애플리케이션은 사용자가 평가사를 선택할 때마다 각 평가사들이 매긴 점수와 리뷰를 보여줘야 한다. 즉 각 평가사마다 공통적으로 점수를 보여줘야하고, 리뷰도 보여줘야한다.
위와 같은 공통사항을 Protocol 로 추상화할 수 있다.
Protocol 로 추상화하기
평가사의 이름과 네트워크 통신을 통해 리뷰와 평가점수를 가져오는 프로토콜을 정의해보자.
protocol MovieRatingStrategy {
var ratingServiceName: String { get }
func fetchRating(for movieTitle: String, success: (_ rating: String, _ review: String) -> ())
}
그리고 메타크리틱, IMDb, 로튼 토마토와 같은 평가사들이 위 프로토콜을 구현하도록 정의해보자.
// 로튼 토마토 평가사
final class RottenTomato: MovieRatingStrategy {
let ratingServiceName: String = "RottenTomato"
func fetchRating(for movieTitle: String, success: (_ rating: String, _ review: String) -> ()) {
success("Score is 9.5", "This movie is awesome")
}
}
// IMDb 평가사
final class IMDb: MovieRatingStrategy {
let ratingServiceName: String = "IMDb"
func fetchRating(for movieTitle: String, success: (String, String) -> ()) {
success("Score is 87", "This movie is good...")
}
}
// Metacritic 평가사
final class Metacritic: MovieRatingStrategy {
let ratingServiceName: String = "Metacritic"
func fetchRating(for movieTitle: String, success: (String, String) -> ()) {
success("Score is 2/5" , "This movie is trash")
}
}
이제 사용자가 평가사를 변경할 때마다, 해당 평가사가 남긴 리뷰와 평점을 보여주는 SwiftUI View 를 구현해보자.
struct ContentView: View {
@State private var currentMovieStrategy: MovieRatingStrategy = RottenTomato() {
didSet {
currentMovieStrategy.fetchRating(for: "아이언맨") { rating, review in
self.review = review
self.rating = rating
}
}
}
@State private var review: String = ""
@State private var rating: String = ""
var body: some View {
VStack {
Text("평가사 이름: \(currentMovieStrategy.ratingServiceName)")
.font(.largeTitle)
Text("평가 점수: \(rating)")
.padding()
Text("리뷰: \(review)")
.padding()
HStack {
Button("로튼 토마토") {
currentMovieStrategy = RottenTomato()
}
Button("IMDb") {
currentMovieStrategy = IMDb()
}
Button("Metacritic") {
currentMovieStrategy = Metacritic()
}
}
}
.onAppear {
currentMovieStrategy = RottenTomato()
}
}
}
- 위 SwiftUI View 에서 중요한 점은, currentMovieStrategy 프로퍼티가 MovieStrategy 프로토콜 타입을 가지고 있다는 점이다.
- 따라서 currentMovieStrategy 는 MovieStrategy 를 구현하는 concrete type 을 모두 가질 수 있다.
- 추후 평가사를 추가할 때, MovieStrategy 프로토콜을 구현한 뒤 프로퍼티에 대입해주기만 하면 쉽게 해당 평가사의 리뷰와 점수를 보여줄 수 있다.
- 즉 기존 코드의 수정 (MovieStrategy 를 사용하는 View) 의 수정 없이 새로운 클래스 (추가할 평가사) 를 추가함으로써 객체지향의 원리인 OCP 를 달성할 수 있다.
'디자인 패턴' 카테고리의 다른 글
Delegate pattern - iOS (0) | 2022.07.23 |
---|---|
Adapter Pattern (swift) (0) | 2022.07.09 |
Comment