[Swift/디자인패턴] Strategy Pattern

frogKing·2023년 3월 12일
0

디자인패턴

목록 보기
2/6
post-thumbnail

Strategy Pattern

알고리즘이나 행위를 캡슐화하여 동적으로 교환하는 패턴.

알고리즘을 사용하는 클래스(컨텍스트)와 알고리즘을 정의하는 인터페이스(전략)로 구성된다. 이 패턴을 사용하면 컨텍스트에서는 전략 인터페이스를 호출하여 알고리즘을 실행할 수 있으며, 이전에 정의된 여러 가지 전략 클래스 중 하나를 선택하여 사용할 수 있다.

전략 패턴의 장점은 알고리즘을 실행하는 코드와 알고리즘 자체를 분리할 수 있다는 것이다. 또한, 새로운 알고리즘이나 전략을 추가하기 위해서는 전략 인터페이스를 구현하는 클래스를 작성하기만 하면 되므로, 코드 변경이 최소화된다.

따라서, Strategy 패턴은 유지보수성과 확장성을 높이는 데 유용한 패턴 중 하나이다.

예제

Example

다소 비약이 있는 예시지만 전략 패턴이 무엇인지 정도는 이야기하기에 적합할 것 같다.

이 게임은 플레이어에게 일주일에 한 번, 플레이어의 랭크가 어느 정도의 수준인지를 표시해준다고 한다. 플래티넘일 경우 빵빠레와 함께 플레티넘임을 축하해주고, 실버일 경우 다소 조약한 bgm과 함께 실버임을 알려준다. 하지만 귀찮아서 이를 출력문으로 대체하였다.

protocol RankDisplay {
    func show()
}

final class SilverRankDisplay: RankDisplay {
    func show() {
        print("상위 80% 정도의 실력입니다. 분발하세요!")
    }
}

final class GoldRankDisplay: RankDisplay {
    func show() {
        print("상위 50% 정도의 실력입니다. so so~")
    }
}

final class PlatinumRankDisplay: RankDisplay {
    func show() {
        print("상위 30% 정도의 실력입니다. good!")
    }
}

final class JustGame {
    func showRankResult(with rankDisplay: RankDisplay) {
        rankDisplay.show()
    }
}

Usage

let game = JustGame()

game.showRankResult(with: SilverRankDisplay()) // 상위 80% 정도의 실력입니다. 분발하세요!

showRankResult에 인자로 RankDisplay를 채택하는 클래스 타입만 넣어주면 해당 클래스에 각각 정의된 show 메서드를 실행하게 된다. 이를 전략 패턴이라 하고 클래스마다 사용할 알고리즘을 런타임에 정해준다는 특징을 갖고 있다.

하지만 위 예시만으로는 조금 아쉽다. 어떤 상황에서 사용할 수 있을지는 어렴풋이 예상이 되지만 예시도 조약하고 좀 더 활용할 수 있는 방법은 없을까 고민도 될 것이다.

그래서 Builder Pattern과 Strategy pattern을 함께 응용한 예시를 준비했다.

Example

플레이어 빌더 클래스를 정의하고 랭크에 따라 플레이어 객체를 생성한다.

enum Rank {
    case silver
    case gold
    case platinum
    
    var display: RankDisplay {
        switch self {
        case .silver:
            return SilverRankDisplay()
        case .gold:
            return GoldRankDisplay()
        case .platinum:
            return PlatinumRankDisplay()
        }
    }
}

protocol RankDisplay {
    func show()
}

final class SilverRankDisplay: RankDisplay {
    func show() {
        print("상위 80% 정도의 실력입니다. 분발하세요!")
    }
}

final class GoldRankDisplay: RankDisplay {
    func show() {
        print("상위 50% 정도의 실력입니다. so so~")
    }
}

final class PlatinumRankDisplay: RankDisplay {
    func show() {
        print("상위 30% 정도의 실력입니다. good!")
    }
}

final class PlayerBuilder {
    func build(with rank: Rank) -> Player {
        switch rank {
        case .silver:
            return Player(rank: .silver)
        case .gold:
            return Player(rank: .gold)
        case .platinum:
            return Player(rank: .platinum)
        }
    }
}

final class Player {
    private var rank: Rank?
    
    init() {}
    init(rank: Rank) {
        self.rank = rank
    }
    
    func show() {
        rank?.display.show()
    }
}

Usage

let silverPlayer = PlayerBuilder().build(with: .silver)
silverPlayer.show() // 상위 80% 정도의 실력입니다. 분발하세요!

예시에서는 빌더 패턴과 전략 패턴을 함께 적용하였다. 랭크에 따라 플레이어를 생성(빌더 패턴)하였고, Rank 열거형에 정의한 display 프로퍼티를 통해 rank에 따라 다른 RankDisplay(전략 패턴)를 반환하도록 하였다.

100% 만족스러운 예시는 아니지만 전략 패턴이라는 단순하다면 단순할 패턴을 어떻게 응용할지에 대해 조금이라도 도움이 되었으면 좋겠다.

결론

  • 런타임에 Context에 맞는 전략(알고리즘)을 선택할 수 있는 패턴이다.
  • 빌더 패턴과 함께 적용한다면 더 깔끔하고 유지보수에 좋은 코드를 짤 수 있다.
  • 전략 패턴을 어떤 상황에 적용하면 좋을지 고민해보고 이를 적절한 상황에 사용해보자.
profile
내가 이걸 알고 있다고 말할 수 있을까

0개의 댓글