這篇文章是我閱讀raywenderlich.com的Design 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中,我們定義的currentState和states都是跟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。