[디자인 패턴] Strategy Pattern
defines a family of interchangeable objects that can be set or switched at runtime.
Object using a Strategy (전략을 사용하는 객체)
iOS 에서는 주로 뷰 컨트롤러가 이에 해당합니다.
Strategy Protocol (전략 프로토콜)
모든 전략들이 반드시 구현해야 하는 메소드를 정의하고 있습니다.
Concrete Strategies (구체적인 전략)
Strategy protocol
을 구현합니다.
언제 써야 하나?
- interchangeable 인 2개 이상의 서로 다른 동작들을 갖고 있을 때
2. delegation 패턴과 유사점: 둘 다 프로토콜에 의존 (유연성 향상). 결과적으로 모든 객체들은 strategy 프로토콜을 구현하여 런타임 동안 strategy로 사용 가능
3. delegation 패턴과 다른점: strategy 패턴은 객체들의 family를 사용.
- delegation 패턴은 런타임 동안 자주 delegate 들이 고정된다.
- 하지만, strategy 들은 런타임 동안 쉽게 interchangeable 하다.
코드 예시
Strategy 프로토콜 예시
import UIKittypealias MovieRatingResult = (_ rating: String, _ review: String) -> Voidprotocol MovieRatingStrategy {// 1: 별점 서비스 이름
var ratingServiceName: String { get }// 2: 비동기로 영화의 별점들을 가져옴.
func fetchRating(for movie: String, success: MovieRatingResult)
}
전략 구현1 예시
class RottenTomatoesClient: MovieRatingStrategy {
let ratingServiceName = "Rotten Tomatoes" func fetchRating(for movie: String, success: MovieRatingResult) { // 가상 response
let rating = "95%"
let review = "It rocked"
success(rating, review)
}
}
전략 구현2 예시
class IMDbClient: MovieRatingStrategy {
let ratingServiceName = "IMDb" func fetchRating(for movie: String, success: MovieRatingResult) {// 가상 response
let rating = "3 / 10"
let review = "It was terrible!"
success(rating, review)
}
}
뷰컨트롤러 예시
뷰컨트롤러가 인스턴스화 될 때마다 client를 값을 세팅해줘야 합니다. 여기서 중요한 점은 뷰컨트롤러는 MovieRatingStrategy
의 구체적인 구현에 대해서는 알 필요가 없다는 것입니다. 어떤 전략을 사용할지는 런타임까지 미뤄질 수 있기 때문에 사용자가 선택을 할 수도 있습니다(앱이 허용한다는 가정하에)
class ViewController: UIViewController { // MARK: - Properties
var client: MovieRatingStrategy! // MARK: - Outlets
@IBOutlet weak var movieTitleTextField: UITextField!
@IBOutlet weak var ratingServiceNameLabel: UILabel!
@IBOutlet weak var ratingLabel: UILabel!
@IBOutlet weak var reviewLabel: UILabel! // MARK: - 뷰 라이프 사이클
override func viewDidLoad() {
super.viewDidLoad()
ratingServiceLabel.text = client.ratingServiceName
} // MARK: - Actions
@IBAction func didTapSearch() {
guard let movieTitle = movieTitleTextField.text else { return }
self.client.fetchRating(for: movieTitle) {
[self] rating, review in
self.ratingLabel.text = rating
self.reviewLabel.text = review
}
}}
주의해야 할 점
이 패턴을 과하게 사용하지 않도록 주의해야합니다 (모든 패턴들마다 과사용을 주의하라는 말을 필수네요)
특히 동작이 절대 바뀔 일이 없다면 해당하는 뷰컨트롤러나 객체 컨텍스트 안에 직접 구현을 해도 괜찮습니다.
이 패턴의 핵심은 언제 동작들을 꺼낼지를 아는 것이고lazy
하게 어디에 필요할지 결정하면 그 때 수행해도 됩니다.
요약
- 전략 패턴은 내부적으로 변경가능한(런타임 때 세팅 되거나 바뀌는) 객체들의
family
를 정의 - 3가지 파트로 구성: 전략 프로토콜, 전략 구현부들, 전략을 사용하는 객체
- delegate 패턴과 비교: 유연성을 위해 프로토콜을 사용하는 점에서 유사하나 전략패턴은 런타임때 전략이 바뀌는 반면 delegate는 보통 고정되어 있다는 점이 다름