Strategy Pattern

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