본문 바로가기
iOS/Design Pattern

Design Pattern - Strategy Pattern

by HaningYa 2020. 8. 7.
728x90

Strategy Pattern

Strategy Pattern 은 런타임에 set 되거나 switch 될 수 있는 interchangeable 한 객체의 집합을 정의한다.

  • Who (Object Using a Strategy): interchangeable 해야 하는 객체가 담당할 수 있다. iOS 앱에서 이 부분은 view controller 가 담당한다. 
  • What (strategy protocol): 모든 strategy 가 반드시 구현해야할 메소드를 정의한다.
  • How (Strategies): strategy protocol 을 준수하는 객체들이다.

언제 사용해야 할까

  • 두개 이상의 다른 behavior이 interchangeable 해야 할 때 사용한다.
  • 이 패턴은 delegation pattern 과 비슷한데 두 패턴 모두 객체위주가 아닌 protocol 에 의존하면서 flexibility 를 높인 패턴이다.
  • 결과적으로 strategy protocol 을 준수한 어느 객체라도 런타임에 strategy로 사용될 수 있다.
  • Delegation Pattern 과의 차이점은 Strategy Pattern 은 객체들의 집합을 사용한다는 것 이다. (family of objects)
  • Delegates들은 런타임에 고정되어 있다. 예를들어 UITableView의 datasource 와 delegate같은 경우 Interface Builder 에서 설정되어 앱이 실행되는 도안 바뀌지 않는다.
  • 하지만 Strategies 의 경우 앱이 실행되는 동안 쉽게 바뀌도록 의도되어 있다.

Strategy Pattern 은 Behavior Pattern 에 포함된다.

Delegation Pattern 과 마찬가지로 한 객체가 다른객체한테 뭘 하라고 시키는 거니까 Behavior Pattern 에 속한다.


예제 - 영화 평점 서비스

앱이 IMDb 나 로튼 토마토와 같은 영화 평점 서비스를 사용한다고 생각해 보자

각각의 서비스에 대해 복잡한 if-else 문으로 일일이 view controller 에 직접 구현해야 될 상황이다.

이때 Strategy 패턴을 통해 각각의 평점 서비스에 대한 공통의 API 를 정의하는 protocol 을 만들어 문제를 해결 할 수 있다.

Protocol

//protocol example

public protocol MovieRatingStrategy {
	var ratingServiceName : String{ get }
    var fetchRating(for movieTitle: String, success:(_ rating:String, _ review:String) -> ())
    }

ratingServiceName 에는 로튼 토마토와 같은 별점을 제공해 주는 서비스의 이름이 들어가고 fetchRating은 비동기적으로 영화의 평점을 가지고 오는 역할을 할 것이다.

Client

// #1 로튼 토마토
public class RottenTomatoesClient : MovieRatingStrategy {
	public let ratingServiceName = "로튼토마토"
    
    public func fetchRating(for movieTitle: String, sucess: (_ rating:String, _ review: String) -> ()) {
    		//request network to fetch rating
            let rating = "85%"
            let review = "영화 구리다"
            sucess(rating,reivew)
        }
    }

// #2 IMBD
public class IMDBClient : MovieRatingStrategy {
	public let ratingServiceName = "IMDB"
    
    public func fetchRating(for movieTitle: String, sucess: (_ rating:String, _ review : String) -> ()) {
    	//request network to fetch rating
        let rating = "3/10"
        let review = "sucks"
        success(rating, review)
    }
 
}

두개의 Client 모두 MovieRatingStrategy를 차용하고 있기 때문에 소비하는 객체입장에서는 둘다 직접적으로 알고 있을 필요가 없다.

ViewController

public class MovieRatingViewController: UIViewController {

	public var movieRatingClient: MovieRatingStrategy!
    
	@IBOutlet public var movieTitleTextField: UITextField!
	@IBOutlet public var ratingServiceNameLabel: UILabel!
	@IBOutlet public var ratingLabel: UILabel!
	@IBOutlet public var reviewLabel: UILabel!

	public override func viewDidLoad() {
		super.viewDidLoad()
		ratingServiceNameLabel.text =
		movieRatingClient.ratingServiceName
	}
    
	@IBAction public func searchButtonPressed(sender: Any) {
	guard let movieTitle = movieTitleTextField.text
	else { return }
	movieRatingClient.fetchRating(for: movieTitle) {
			(rating, review) in
			self.ratingLabel.text = rating
			self.reviewLabel.text = review
		}
	}
}

위 코드에서 moviewRatingClient 를 로튼 토마토 또는IMDB로 설정 해줄 수 있다. (interchangble)

view controller가 instantiate 될때 movieRatingClient 를 설정해줘야 한다. (!) 

예제 코드를 보면 view controller 는 MovieRatingStrategy가 어떻게 생겨먹었는지 1도 모른다.

어떤 MovieRatingStrategy를 선택할 것인가는 실행시간에 정해질 수 있으며 허용한다면 사용자가 선택하게도 할 수 있다.


Strategy Pattern 사용시 주의해야 할 점

  • 과다 사용 금지 - 만약 behavior 가 바뀌지 않는다면 소비하는 view controller 나 객체에 직접 구현하는 것이 좋다.
  • 이 패턴의 어려운 점은 언제 behaviors를 pull out 하느냐 인데 나중에 필요할 때 lazily 하게 판단해도 괜찮다.

 

728x90

'iOS > Design Pattern' 카테고리의 다른 글

Design Pattern - Memento Pattern  (0) 2020.08.09
Design Pattern - Singleton Pattern  (0) 2020.08.09
Design Pattern - Delegation Pattern  (0) 2020.08.06
Design Pattern - MVC  (0) 2020.07.14
Design Pattern - Class Diagram  (0) 2020.07.14

댓글