Swift/Design_Pattern

[Swift 디자인패턴] State Pattern (상태) - 디자인 패턴 공부 21

Dev_Pingu 2021. 6. 29. 00:59
반응형

안녕하세요 Pingu입니다!🐧

 

지난 글에서는 행동 패턴 중 하나인 Observer Pattern(옵저버)에 대해 알아봤었는데요, 이번 글에서는 또 다른 행동 패턴 중 하나인 State Pattern에 대해 알아보도록 하겠습니다.

상태 패턴이란?

State Pattern은 런타임에서 내부의 상태가 변경 될 때 객체가 동작을 변경할 수 있도록 하여 마치 객체가 클래스를 변경하는 것처럼 보이게 하는 패턴입니다.

  • Context
    • Context는 Concrete State 객체 중 하나에 대한 참조를 저장하고 모든 State의 작업을 위임합니다.
    • Context는 State Interface를 통해 State 객체와 통신합니다.
  • State
    • State의 메서드를 제공합니다.
  • Concrete State
    • State Interface에서 정의된 메서드들을 구체화합니다.
    • Concrete State는 Context 객체를 역으로 참조 할 수도 있습니다. 이러한 참조를 통해 State가 Context에서 필요한 정보를 가지고 오고 State를 변화할 수 있습니다.

상태 패턴은 언제 쓰나요?

TCP/IP 통신을 아시나요? TCP 통신은 커넥션이 만들어져야 할 수 있는데요, 즉 커넥션이 만들어진 상태, 그렇지 않은 상태로 나뉘게 됩니다. 두 상태에 따라 네트워크가 연결 될 수도, 연결 실패할 수도 있는 거죠. 즉 네트워크 연결 상태에 따라 사용할 수 있는 기능들이 다릅니다. 이렇게 상태에 따라 객체의 기능이 달라져야 할 때 State Pattern을 사용할 수 있습니다.

 

실생활에서 예를 찾아보면, 예전 iPhone에는 Home 버튼이 있죠? 이 버튼을 잠금 상태에서 누를 때, 앱을 사용중에 누를 때와 같이 스마트폰의 상태에 따라 동일한 버튼의 기능이 달라지는 경우가 있습니다. 이런 경우도 State Pattern이라고 할 수 있겠죠?

상태 패턴의 결과

장점

  • Single Responsibility Principle(단일 책임 원칙)을 준수합니다.
  • 기존의 State, Context 클래스를 변경하지 않고 새로운 State를 도입할 수 있기 때문에 Open / Closed Principle(개방 / 폐쇄 원칙)을 준수합니다.
  • State 객체에 인스턴스 변수가 없는 경우 Context는 State 객체를 공유할 수 있기 때문에 Flyweight 패턴처럼 Context의 코드를 단순화 할 수 있습니다.

단점

  • State가 몇 개 없거나 변경될 이유가 거의 없을 땐 패턴을 도입하는 것이 비효율적일 수 있습니다.

예제

그럼 이제 State Pattern을 Swift로 구현해보겠습니다.

 

State Pattern으로 만들어 볼 상황은 유튜브 프리미엄을 결제한 상태와 그렇지 않은 상태를 만들어보겠습니다.

유튜브 프리미엄을 결제하면, 백그라운드에서도 유튜브 영상을 재생 할 수 있는데요, 이를 간단하게 한 번 구현해보겠습니다.

 

일단 먼저 Context 역할을 할 YoutubeApp 클래스를 하나 만들어줍니다.

// Context
class YoutubeApp {
    var youtubePremiumState: State
    
    init(subscribeState: State) {
        self.youtubePremiumState = subscribeState
    }
    
    func subscribe() {
        print("\n유튜브 프리미엄 구독 시작\n")
        self.youtubePremiumState = SubscribeState()
    }
    
    func unSubscribe() {
        print("유튜브 프리미엄 구독 해지\n")
        self.youtubePremiumState = UnSubscribeState()
    }
    
    func clickHomeButton() {
        self.youtubePremiumState.playBackground()
    }
    
    func clickAppIcon() {
        self.youtubePremiumState.playForeground()
    }
    
    func clickDownload() {
        self.youtubePremiumState.videoDownload()
    }
}

유튜브 앱에서는 다운로드, 앱 아이콘 클릭, 홈버튼 클릭과 같은 메서드가 존재하고 구독, 구독해지 메서드도 존재합니다.

 

그런 뒤엔 State 프로토콜을 하나 정의합니다.

// State Interface
protocol State {
    func playBackground()
    func playForeground()
    func videoDownload()
}

 

 

이번엔 State 프로토콜을 채택한 Concrete State인 유튜브 프리미엄 구독 상태, 구독 해지 상태를 만들면 될 것 같아요

// Concrete State
class SubscribeState : State {
    func playBackground() {
        print("결제를 해서 백그라운드에서도 재생중")
    }
    func playForeground() {
        print("영상 재생 중")
    }
    func videoDownload() {
        print("결제를 해서 비디오 다운로드 가능")
    }
}
// Concrete State
class UnSubscribeState: State {
    func playBackground() {
        print("결제를 안하면 백그라운드에서는 재생 할 수 없어요.")
    }
    func playForeground() {
        print("영상 재생 중")
    }
    func videoDownload() {
        print("결제를 안하면 비디오 다운로드가 불가능해요.")
    }
}

 

이렇게 만든 뒤 실행을 해보겠습니다.

위의 결과를 보면 처음에는 구독 해제 상태로 앱이 시작됩니다. 그러다 subscribe() 메서드를 호출해서 유튜브 프리미엄 구독을 시작하게 되는 순간부터 백그라운드 재생, 비디오 다운로드가 가능해지게 되는 것을 볼 수 있습니다.

 

이렇게 State Pattern을 알아보고 간단하게 구현도 해봤습니다.

예제를 구현 할 때 꽤나 재밌게 구현했던 거 같아요.

 

전체 코드는 여기에서 볼 수 있습니다.

 

감사합니다.

반응형