【設(shè)計模式】14 - 狀態(tài)模式 (State Pattern)

這篇文章是我閱讀raywenderlich.comDesign Patterns by Tutorials的總結(jié),文中的代碼是我閱讀書本之后根據(jù)自己的想法修改的。如果想看原版書籍,請點擊鏈接購買。


狀態(tài)模式屬于行為模式,可以讓一個對象在運行中改變他自己的行為,而行為的改變時通過改變狀態(tài)來實現(xiàn)的。State指定是描述一個對象如何表現(xiàn)的數(shù)據(jù),跟Status的意思是不同的;Status可以理解為你正在做一件事,這件事要多個步驟才能完成,你可以用Status來描述現(xiàn)在進行到哪個步驟了。

這個模式涉及到三個部分:

  • Context:持有當(dāng)前的狀態(tài)
  • State Protocol:狀態(tài)協(xié)議,規(guī)定需要的方法和屬性。也可以是一個基類。
  • Concrete State:具體的實現(xiàn)類,遵循狀態(tài)協(xié)議(或者繼承于基類),描述Context如何表現(xiàn)自己的行為。Context持有當(dāng)前的狀態(tài),但是他不知道具體的狀態(tài)類型,通過多態(tài)特性來改變他的行為。

什么時候使用

通常我們使用這個模式來創(chuàng)建有多個狀態(tài)系統(tǒng),并且狀態(tài)會改變這個系統(tǒng)的行為。如果你的類里面大量使用了swtich或者if-else,可以考慮是否可以使用這個模式。

簡單demo

下面以模擬紅綠燈作為例子,最終我們做成這個樣子,紅黃綠三種顏色的燈自動切換:

根據(jù)上面提到的,這個模式涉及三個部分,所以在這個例子中:1)底部的整個View應(yīng)該是Context,他持有當(dāng)前的狀態(tài)和所有狀態(tài),定義為TrafficLightView,繼承自UIView;2)每一種顏色的燈屬于一種狀態(tài),所以把State Protocol定義為TrafficLightState;3)把Concrete State定義為SolidTrafficLightState

TrafficLightView

TrafficLightView中,我們默認(rèn)用三個light container layers來分別放置不同顏色的light layer:

final class TrafficLightView: UIView {

    // light container layers數(shù)組
    var lightContainerLayers: [CAShapeLayer] = []
    
    // 當(dāng)前的狀態(tài)
    private var currentState: TrafficLightState
    
    // 所有狀態(tài)
    private var states: [TrafficLightState]

    // MARK: - Initializers

    init(frame: CGRect,
         lightsCount: Int = 3,
         states: [TrafficLightState]) {

        guard !states.isEmpty else { fatalError("states不能為空")}

        self.currentState = states.first!
        self.states = states
        super.init(frame: frame)

        backgroundColor = UIColor(red: 0.8, green: 0.6, blue: 0.3, alpha: 1)
        createLightContainerLayers(count: lightsCount)
        transition(to: currentState)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // MARK: - Public

    // 下一個狀態(tài)
    var nextState: TrafficLightState {
        guard let currentIndex = states.index(where: { $0 === currentState }),
            currentIndex + 1 < states.count else {
                return states.first!
        }
        return states[currentIndex + 1]
    }

    // 切換到指定的狀態(tài)
    func transition(to state: TrafficLightState) {
        removeLightLayers()
        currentState = state
        currentState.apply(to: self)
        nextState.apply(to: self, after: currentState.delay)
    }

    // MARK: - Private

    // 創(chuàng)建light containers
    private func createLightContainerLayers(count: Int) {
        let yTotalPadding = 0.2 * frame.height
        let containerHeight = (frame.height - yTotalPadding) / CGFloat(count)

        let yPadding = yTotalPadding / CGFloat(count + 1)
        let xPadding = (frame.width - containerHeight) / 2.0

        var containerFrame = CGRect(x: xPadding,
                                   y: yPadding,
                                   width: containerHeight,
                                   height: containerHeight)

        for _ in 0 ..< count {
            let containerShape = CAShapeLayer()
            containerShape.path = UIBezierPath(ovalIn: containerFrame).cgPath
            containerShape.fillColor = UIColor.black.cgColor
            layer.addSublayer(containerShape)
            lightContainerLayers.append(containerShape)
            containerFrame.origin.y += (containerHeight + yPadding)
        }
    }
  
    // 移除所有l(wèi)ight containers里面的light layers
    private func removeLightLayers() {
        lightContainerLayers.forEach {
            $0.sublayers?.forEach {
                $0.removeFromSuperlayer()
            }
        }
    }
}

有些方法目前還沒有創(chuàng)建,可以先忽略,下面會講到。

TrafficLightState

我們都知道,每個顏色的燈都會持續(xù)一段時間,所以規(guī)定需要一個delay屬性;然后是把當(dāng)前燈應(yīng)用到context,所以規(guī)定apply(to context: TrafficLightView)方法。另外還通過擴展添加了一個方法,指定多少秒之后把當(dāng)前狀態(tài)應(yīng)用到context,這樣我們可以實現(xiàn)自動切換到下一個顏色的燈。

protocol TrafficLightState: class {
    var delay: TimeInterval { get }
    func apply(to context: TrafficLightView)
}

extension TrafficLightState {
    func apply(to context: TrafficLightView, after delay: TimeInterval) {
        DispatchQueue.main
            .asyncAfter(deadline: .now() + delay) { [weak self, weak context] in
                guard let strongSelf = self, let context = context else { return }
                context.transition(to: strongSelf)
        }
    }
}

SolidTrafficLightState

Solid在這里的意思指的是純色,index表示在紅綠燈中所在的位置。在apply(to context: TrafficLightView)的實現(xiàn)中: 先找到對應(yīng)的container,然后創(chuàng)建light layer并設(shè)置顏色,最后把light layer加到對應(yīng)的container中。

另外還定義了默認(rèn)的紅燈、黃燈和綠燈。

final class SolidTrafficLightState: TrafficLightState {
    let index: Int
    let color: UIColor
    let delay: TimeInterval

    init(index: Int, color: UIColor, delay: TimeInterval) {
        self.index = index
        self.color = color
        self.delay = delay
    }

    func apply(to context: TrafficLightView) {
        let containerLayer = context.lightContainerLayers[index]
        let lightShape = CAShapeLayer()
        lightShape.path = containerLayer.path
        lightShape.fillColor = color.cgColor
        lightShape.strokeColor = color.cgColor
        containerLayer.addSublayer(lightShape)
    }
}

// MARK: - Lights
extension SolidTrafficLightState {
    class func redLight(index: Int = 0,
                        color: UIColor = .red,
                        delay: TimeInterval = 10) -> SolidTrafficLightState {
        return SolidTrafficLightState(index: index, color: color, delay: delay)
    }

    class func yellowLight(index: Int = 1,
                           color: UIColor = .yellow,
                           delay: TimeInterval = 3) -> SolidTrafficLightState {
        return SolidTrafficLightState(index: index, color: color, delay: delay)
    }

    class func greenLight(index: Int = 2,
                          color: UIColor = .green,
                          delay: TimeInterval = 10) -> SolidTrafficLightState {
        return SolidTrafficLightState(index: index, color: color, delay: delay)
    }
}

使用

把上面三個類結(jié)合起來理解后,我們把TrafficLightView添加到一個View Controller的root view上,就能看到效果。

let frame = CGRect(x: 100, y: 100, width: 150, height: 400)
let lights: [SolidTrafficLightState] =
    [.greenLight(), .yellowLight(), .redLight()]
let trafficLight = TrafficLightView(frame: frame, states: lights)
view.addSubview(trafficLight)

總結(jié)

在作為Context的TrafficLightView中,我們定義的currentStatestates都是跟TrafficLightState協(xié)議相關(guān)的,而不是跟具體的State相關(guān)聯(lián),這樣能重復(fù)使用這個Context。另外如果我們還想把State應(yīng)用到其他Context中,State類中的Context就不應(yīng)該直接使用TrafficLightView,而是創(chuàng)建一個Context Protocol,然后State類中使用Context Protocol。

歡迎加入我管理的Swift開發(fā)群:536353151。

下一篇文章:【設(shè)計模式】15 - 多播委托模式 (Multicast Delegate Pattern)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容