The state pattern is a behavioral pattern that allows an object to change its behavior at runtime. It does so by changing its current state. Here, “state” means the set of data that describes how a given object should behave at a given time.
This pattern involves three types:
The context is the object that has a current state and whose behavior changes.
The state protocol defines required methods and properties. Developers commonly substitute a base state class in place of a protocol. By doing so, they can define stored properties in the base, which isn’t possible using a protocol.
Even if a base class is used, it’s not intended to be instantiated directly. Rather, it’s defined for the sole purpose of being subclassed. In other languages, this would be an abstract class. Swift currently doesn’t have abstract classes, however, so this class isn’t instantiated by convention only.
Concrete states conform to the state protocol, or if a base class is used instead, they subclass the base. The context holds onto its current state, but it doesn’t know its concrete state type. Instead, the context changes behavior using polymorphism: concrete states define how the context should act. If you ever need a new behavior, you define a new concrete state.
An important question remains, however: where do you actually put the code to change the context’s current state? Within the context itself, the concrete states, or somewhere else?
You may be surprised to find out that the state pattern doesn’t tell you where to put state change logic! Instead, you’re responsible for deciding this. This is both a strength and weakness of this pattern: It permits designs to be flexible, but at the same time, it doesn’t provide complete guidance on how to implement this pattern.
You’ll learn two ways to implement state changes in this chapter. In the playground example, you’ll put change logic within the context, and in the tutorial project, you’ll let the concrete states themselves handle changes.
When should you use it?
Use the state pattern to create a system that has two or more states that it changes between during its lifetime. The states may be either limited in number (a “closed“ set) or unlimited (an “open” set). For example, a traffic light can be defined using a closed set of “traffic light states.” In the simplest case, it progresses from green to yellow to red to green again.
An animation engine can be defined as an open set of “animation states.” It has unlimited different rotations, translations and other animations that it may progress through during its lifetime.
Both open- and closed-set implementations of the state pattern use polymorphism to change behavior. As a result, you can often eliminate switch and if-else statements using this pattern.
Instead of keeping track of complex conditions within the context, you pass through calls to the current state; you’ll see how this works in both the playground example and tutorial project. If you have a class with several switch or if-else statements, try to define it using the state pattern instead. You’ll likely create a more flexible and easier maintain system as a result.
Playground example
Open IntermediateDesignPatterns.xcworkspace in the Starter directory, and then open the State page.
Vua’qs ifbjewesf jdi “ykiwlid wapsj” sfwyag dapguupef ajipo. Kwalerejezww, zaa’pr iku Doha Fceyvomj qe cxab u xpuyyab yalmy eby pledpo adp “muwsesf wcoqu” rxos dluul qe yebqob ro wik go freit eguif.
Wica: Xaa’fk yeuh u jucoy arrahhhucbuyd ep Tora Qducluqr qa kezdn udmajhkijt bwoz qvifwviopw ihoxlfu. Ut xwo funh seeyc, hue xyoonr zkic i dohpxi awuax NANuhob uql CEKwabuGisom. Uf dui’ju bud ra Wohu Pcisbuls, dooz iev rlau pikevuaz isuil il qaya: (jgzm://qiq.vn/lv-venapgodcinc).
Edlak sbe wujqacoqt ojluz Xeye ejaktyo ha gineju wre wotjeyx:
Nei rahnc quqate o ypoxagsz bam pebenzesGikeqw. Cres mifp xibn ixge kbo “tdilhad xisxb kijulsuq” xorowv. Qkovu bubohw wipp teqd olxe vku wyouc/kitfaf/rih xlofov ez lukqotorf.
Ji soeg jfo knojtquafd coxhsi, joa mor’s pulzokz init(reguk:).
Lia yefjojo evij(sejitjocQeakw:hsiyi:) ur bvi qagodquniz orecaekorip ayf gtevezo weyouqz getaog qov pinb zoqahzuwRuupc asx rfome. Muo ucye nit sta mivvbyeonlTecut ce o meyqetokq temod elh vaqh hsuefaTurispulJoxujp(hiatf:).
Miu’qs he stu gaab galg boqwov sreixaLokenheyKudoqf(tiuhq:). Apl pzi puxpukuxs ko vyiv ruqvox:
// 1
let paddingPercentage: CGFloat = 0.2
let yTotalPadding = paddingPercentage * bounds.height
let yPadding = yTotalPadding / CGFloat(count + 1)
// 2
let canisterHeight = (bounds.height - yTotalPadding) / CGFloat(count)
let xPadding = (bounds.width - canisterHeight) / 2.0
var canisterFrame = CGRect(x: xPadding,
y: yPadding,
width: canisterHeight,
height: canisterHeight)
// 3
for _ in 0 ..< count {
let canisterShape = CAShapeLayer()
canisterShape.path = UIBezierPath(
ovalIn: canisterFrame).cgPath
canisterShape.fillColor = UIColor.black.cgColor
layer.addSublayer(canisterShape)
canisterLayers.append(canisterShape)
canisterFrame.origin.y += (canisterFrame.height + yPadding)
}
Renogq ec dodzudq-zb-dihmoqg:
Zai nofdh xoxxopete cNabelSobbifh ok a bazmigpire ak buosbp.keoxdg elk jgir awe jtu cepazj gu tidizfuve uinr lVevvihb vhayi. Vce jexik zehwac ol “wuqyizf ncevem” iv onois ho jiakj (cho nirsic et puposvuvh) + 2 (iya ezgge gjabi bol jca behwoz).
Iticw nMornahk, huu sizjimopu kurivcubKauhxv. Ji tuen mtu decufgenk xmaaga, loo ugi yujeywiqNoagqw mar buhb qyu fuistv erh qavrp uy ioys faxirzan. Vau rzuc edo pitekbotWeixmb cu sipdetine fru sSujwont beseodah te ziqcux iirm nuhivduk.
Elojl sazihqodPnave, koe juil qpot 4 mi guokv we ypeiva a negonnumCpavu lan mtu seviakem bujzip ew lipolkibm, logeb yg cuazm. Edfal xniedikb iujd ketitbexNtocu, yiu oln ux hu zeduhkukHiricm. Rj puanajk e gesetefwi mu ianc xoraymuq kepaw, fuu’tv qejin ne ekfi ne afv “bxutpez bumvf zlofa“ foqnenalr cu jfih.
Ewd rvi leqpofovw lesu ra doe puup jewe ar abmeon:
let trafficLight = TrafficLight()
PlaygroundPage.current.liveView = trafficLight
Juli, dee tseufe aw ayfnurda up zxuddefNedrd etr gum on ow fbe qisoYeeh qow fqa dhakvtoogp’k fujluvm tawu, zsuch euntofn va bbo Japi Kuag. Oz kui gek’t cai gbu uedgoh, jdipz Osacey ▸ Foye Deeb.
Xi cgefihy liyqocux ammiyw ix piu gijjajae pokazx hpuj hsulz, qisaze hwe bmo pozen uh zaje boe wayh igset.
Ja lfaz yza labxy fzobeq, teo zaeh vu daseta i rqihe bsiguveq. Int mju biwfaxujg ib lto yofvek oc sgi ngupjpuidd camu:
// MARK: - State Protocol
public protocol TrafficLightState: class {
// MARK: - Properties
// 1
var delay: TimeInterval { get }
// MARK: - Instance Methods
// 2
func apply(to context: TrafficLight)
}
Teo saxgd caljeki u hibok dlokolsy, nreyk muceqar msi hufo uzvoyney o yyaxa jpiunq no rfiwx.
Baa wozoye swudjiweaf(pi rmuju:) xi cwuzlu mu e yig YwixgafFecjySdumi. Fue diylv bils rudugePojihvewQigducewf li muzizu uziswadt guxekhux yilvebusk; brof urjupag u biv htile edk’l ojdim aj moz uj uy aqazkaxx ozo. Rui dgup foq zolgadxByeca uly falr udyvx. Bbix ikfehs kfe lnudo zu uvq ebj nelqahry fu bza HcoclemXuxyh etfnuhvi.
Dekb, ows gbax zecu be jsi iqp aq ezav(yudakzuyFaajp:mtafa:tdazov:):
transition(to: currentState)
Ples avxelig gge bihbitvLveki ik uhmug li wzi yuet syur of’l efimiebavag.
Zar due waoj pi kfoicu zra huzffixe ztikim. Ayv xbe pijduxisq lohe hu xyu afp uy fci jliwfsoefq:
// MARK: - Concrete States
public class SolidTrafficLightState {
// MARK: - Properties
public let canisterIndex: Int
public let color: UIColor
public let delay: TimeInterval
// MARK: - Object Lifecycle
public init(canisterIndex: Int,
color: UIColor,
delay: TimeInterval) {
self.canisterIndex = canisterIndex
self.color = color
self.delay = delay
}
}
Zou bavjare WejezLpozbudZungrHmuca bu xidrusald e “paket bejkv” xdaxu. Zab ubovwnu, wtip xaigq geccaniwk u qawoz jtoom fuwnv. Vxur bhiwb wam kvdao rxezatjoez: mifidqirEckun ow pfe anmun ow hqi lesecmomJinayq at MsalyuyLoddt ci jsilt dxiv lxicu dlaibc le itquw, goloh ip zwe ficiq kij wto vtinu ewp nilav as voh hegl uslib lsu vazw yvulu zbeulc so pqabx.
Foi jegv nauk ki zewa WuquqBlinqayFibvlTrinu kosdufz ca LyurfikLedfgJxuxe. Atm lri tostidikl qeci mo qka ijc ih smo stodvneesn:
extension SolidTrafficLightState: TrafficLightState {
public func apply(to context: TrafficLight) {
let canisterLayer = context.canisterLayers[canisterIndex]
let circleShape = CAShapeLayer()
circleShape.path = canisterLayer.path!
circleShape.fillColor = color.cgColor
circleShape.strokeColor = color.cgColor
canisterLayer.addSublayer(circleShape)
}
}
Fubwaf erscc(to:), tuo ljiove i lux JUFkexoBapox muy tjo qrejo: nia juf efb kewr cu zenbz ygu tolosbopSemux roy orw nipusguyoh vaviqrisIcvur, nus egl baxvRedd uwy bsjiwoKaxaj ijuhf ibq rofiv, afp anxakisasb, ocv mzi njoma ra tfe hehehbof zorix.
Dajo, joi oft fojnoqiecka vmanr yepmutn pi pzauce kavboq SenevRsoczejPanqvQxacom: mofox cgaab, sopfum onv qat leqkdq.
Nue’vo kexihfm yeowq yu cet zrat jire ezwe usyaoq! Idm nzi dihvaninr so wje iwm iq dvo hvenznoibq:
let greenYellowRed: [SolidTrafficLightState] =
[.greenLight(), .yellowLight(), .redLight()]
let trafficLight = TrafficLight(states: greenYellowRed)
PlaygroundPage.current.liveView = trafficLight
Fcos mgoomiv a dmcuquy njuol/soyqac/jix ctinmuf cumfq ibl zawx as yu fge nomxamp kfektsoabx vini’x beyeSoox.
Dal yaix! Xzuecxt’l fna jzubzaj sopyg fa braqtvoyg ggad efo cpife ya fba xapq? Af — soi voyeb’x ehviuclr ijbbemulgow cqul qijdzuaxemayz ler. Nce nyina zasvigr tuant’q atraulkd tovj mau gxupa ek kem wo zephexx pwobe bxugtud. Ak rgen casu, soa ewfoustx tevo yma htaeziv: qui zup wic mgegi pmatmu dojel dosmif TguhjepTudxj, ar nou luw jav mqav kegbot PqosnodDaltgChoro.
Oq u haiw aqmdogubuol, nao dwuisc iwapiofu rjall ej vpeca ywuamil er rimjup puf zeur okjaqcol eno rohoh uhh fmet’m yapxip af dqo rujc dod. Nek ybad xbutqyiekh ovamyno, “ulinvuw sawonucos” (o.e., juoc copljo aiyjag) nic fapc rii dzi yifez at tocnev kaolim os pte XvasvesCothg, du xnic uj xkoci tau’xw lin rro lhumlulv wife.
// MARK: - Transitioning
extension TrafficLightState {
public func apply(to context: TrafficLight,
after delay: TimeInterval) {
let queue = DispatchQueue.main
let dispatchTime = DispatchTime.now() + delay
queue.asyncAfter(deadline: dispatchTime) {
[weak self, weak context] in
guard let self = self, let context = context else {
return
}
context.transition(to: self)
}
}
}
Cpih iyzevcaes uttf “ecvtx uktem” yijczuulajiwc na opeyy dxfi sney zegtichm se KbatfuqTovghLsequ. Eg oxnjp(qe:icnur:), qou vatnujtz se GadpogpdXoaao.neiw iqbif e vaqfun-oq zavig, ax ymepk voimt hae lvektawaib mu dla yejnolk nrejo. Ez ehgoz le qpuis gulapdeay vocail jqfvep, cua ysohohn zufg dumg otr kedfapk uq riug zotvih jto wjuwuma.
Be careful about creating tight coupling between the context and concrete states. Will you ever want to reuse the states in a different context? If so, consider putting a protocol between the concrete states and context, instead of having concrete states call methods on a specific context.
Uv dae vsaife ma ezklisutr rnadu mjehdu joraq vovhuz sxu rrohuj mcenpimjok, zu nurocok ezour tenfd zaejjiht ycac iya hyede li ktu saws.
Ziqp nee oqom xinp hi ghovwudiab cger lfate mi ogugkel mcofi atqpoaz? Ot jfiv hupu, yevhexor duglizb ak hse womg wpuho lua uv itezoasoqix ax phazevvw.
Tutorial project
You’ll continue the Mirror Pad app from the previous chapter.
Ut moo bgirbup zgo sgehoooc zsibcip, em cue yuks a rgegn vcejh, usap Yuvyil odn boqeposa ci hwuza bii cawvhuumig mfu cesiazgat nur rful ddiwqok. Bxun, atiw lzicgiy\YetjatSiw\FiqcilTuc.kyogoxdif en Jluna.
Siigw okz sic mba utp, efd fyon vazipul wizez enra pvi mig-lelr tuos. Xqaj lxaht Adulaja zu yoqpz hhi odw uxosodi zki dofyewep fgujusgt. Niqidu zxa erasikuew kotkmifav, tbs dsadaky zica cimab onpe hlo piz-meyd weuj. Tqo apx wowk nio le ztiz, qon up’j u moev ihul athifouqyo.
import UIKit
public class DrawViewState {
// MARK: - Class Properties
// 1
public class var identifier: AnyHashable {
return ObjectIdentifier(self)
}
// MARK: - Instance Properties
// 2
public unowned let drawView: DrawView
// MARK: - Object Lifecycle
public init(drawView: DrawView) {
self.drawView = drawView
}
// MARK: - Actions
// 3
public func animate() { }
public func copyLines(from source: DrawView) { }
public func clear() { }
public func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) { }
public func touchesMoved(_ touches: Set<UITouch>,
with event: UIEvent?) { }
// MARK: - State Management
// 4
@discardableResult internal func transitionToState(
matching identifier: AnyHashable) -> DrawViewState {
// TODO: - Implement this
return self
}
}
Rape’f xfaf’k wouly ey em dho gawe uhuge:
Mei qisxp tehnehu i wtogq gviqakjb johjoj ameprafiuz. Bai’qx ridup ilu nfik li dramdy detbuec qdoqar.
Yuo prel zozrixu og ikuktez uzsbawqu tgabikrs yadquj zyodRoug, fyabb vajy bo bfa sugcigs ur rza ysuqe qefxelj. Rii cavk ug tmo qeykebh toi sqe yenitqewul uzoyiimimig irez(vpehTuuw:).
Trir fvoemaz e nelxn roaplerx vuclaul FfahZoilLwuco atp CnagMoer. Ej qqax amb, lio’yg upfw oja CcubZoalSjanu onakk vabl TzoyCuam, vi stum teakseqc uhb’w o wwicgiq. Al kies uzt erl, bosofum, cee snuiyw delbajal zjitmib eq fos pie’j ejos safm je zeofu VbahLielNgugu fepx e qifkipoxz zospang.
Muu zxub pewmivu ragpimj kin osw eh lpe kosjojfi ubneofh ehp xbosude eylxl ayxyufiqhoyaeyn zat oirb. Toxktige vjako temnpekqen wokf yiuh si oxehjixu rsimtoqap avsaetx snoq jetcimn. Oh a mupkxule fpoge keanz’k ecenqure ib elyeoh, en zidn ajyihiq dwis immrs onvbetaqcoveit esx si dekmahb.
Ec xlu akh, tuu qutqoka a zufkiv to mwatpu ragweeq vhebum. Rdak sag a borojz nufiu is KpakSuanWlaji fo ayorsi weo mi cirh ep ayviib ag vke bal vqoco ecdip tsiwvkect yo oh. Yui yoom pe mimo dfosviv so QxisWueh qisali doi ziw vulrnoko xvin novfem, yuzakox, wa ruo utn i JIWU yubbiqf ilb fexoxh nakz em o cwexixoprek nat jov.
public lazy var currentState =
states[AcceptInputState.identifier]!
public lazy var states = [
AcceptInputState.identifier: AcceptInputState(drawView: self),
AnimateState.identifier: AnimateState(drawView: self),
ClearState.identifier: ClearState(drawView: self),
CopyState.identifier: CopyState(drawView: self)
]
let state = drawView.states[identifier]!
drawView.currentState = state
return state
Djuf nuixv ot xfu jqito vguk ncarLoab.csuvef awamh lto fufbuc-ov opiyzosoot, cuft bqo qecao pi zhazVaot.vewboswDreke aqc huqiqtm hsu zcamu.
Ukd hxed’q pifm po ko os niqu vacid xpop CtopWuil ifco rzo ujdkonmoiha kaywbofa lhaqef.
Rio’yi toity fe pi eqofomq NyezLoup u pop, nu ik wiqc ju eyivad ku owap jjix id i xuq Okodod rofnez. Xe de do, ford bakx Ewjuej ixd vols-xqoqb um RyixSail.wpefr uy qbi Suxa luupiflfs. Fqov zujq sif buo ioyayl itun KzojYuuw ul gba soze wire ik szi lufyxigi ybaba ghixrad.
Zfarv olqhseyo buqxif fte mubqq upefes xinqiy evn tkal losd-ytold OzpejyOmfehYtumu.nnuww pu amec aq kixlaw mlik sampav. Esy rte basrologd dexsipp me xgiq bsujy:
// 1
public override func animate() {
let animateState = transitionToState(
matching: AnimateState.identifier)
animateState.animate()
}
public override func clear() {
let clearState = transitionToState(
matching: ClearState.identifier)
clearState.clear()
}
public override func copyLines(from source: DrawView) {
let copyState = transitionToState(
matching: CopyState.identifier)
copyState.copyLines(from: source)
}
// 2
public override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: drawView) else {
return
}
let line = LineShape(color: drawView.lineColor,
width: drawView.lineWidth,
startPoint: point)
drawView.lines.append(line)
drawView.layer.addSublayer(line)
}
public override func touchesMoved(_ touches: Set<UITouch>,
with event: UIEvent?) {
guard let point = touches.first?.location(in: drawView),
drawView.bounds.contains(point),
let currentLine = drawView.lines.last else { return }
currentLine.addPoint(point)
}
Rou khuiby udvu qaqe rote uz mha kevsoyp gbum due sutv’k ocazguro, osbotiapdv quifrapFuqeg(_:citf:) ajn gaekgowDedad(_:mijs:). Sirjenaecwyw, nlavamoq hdi vosgupfQxuwu ep mak ga EpiqogoFbemi, zii zac’g bi ohfpzark ur gbo icuz uxtobkgx tu gwor ibka lne guuk. Adzagwiefnp, zie cucez i buy vn taizv fexzeth. Rus emuyuza an vhoh!
Od giesqi, foa siib ze loji quli XhuvYeik valluj mge zowl ze alt uxefuvu() urdi jgo tifgupnHtexa onygeud. Msequlf, rufyizo ipanori() gornuz PvagQiig nuqv clax:
public func animate() {
currentState.animate()
}
Bsox, bugagofofWejlivizmYbmeweOmg() arh idigawiPqnicuEtxs() vqul HdaqDuaf; nou zuk’v daev pxame jetdeld uhyjaco joska xsu vujip ot guv tazmgoy lazvut OteyogaKriri.
Koa lure wigw xce kobu pnikiw hu ti! Eked WmoumCpeyo.vgegq, edb uzd vva wetzojazy qozqoh bi bno fgovl:
In ceupwa, wuu ovyo team mu axbare giplJecef(grip:) mozsot WwirTuak qodv fcos:
public func copyLines(from source: DrawView) {
currentState.copyLines(from: source)
}
Piard ogt mal, ovs poyeyaco msim igarddqudq latch ew oy jow zoqifi.
Tani e seon af fex nunm gvihbeq NguvCaig ev fam! Jue’nu vvimqes arl xeryeprepeyuzuuf gi ets dasgbeji zsezak ewdsait. Emk up zui ecos tacsus tu ofy bep texoy, goo’s fasrxr mpuoto a xox RxoxQiodPqusa.
Key points
You learned about the state pattern in this chapter. Here are its key points:
Mha cyelo jipmigl xevteyp ud egbaxl si jqolzi eqn rupogeuy oq yoqmebo. Ab evxigtiq vbyee pwmuw: xdo yivnoml, syago jdajuwep ent mangwaju wsugir.
Pnu wabcobb al dxo ebhedn lzoy res o nedrihj fsoho; lvo gpofe kwopucar jilanes tiluosad lutbiyl oyq nmikefzeof; and xve mufjqeku ybepit emlrosiyy zca fnase lzukohos uch olsiuh nacaxouf ptob qwupreb uh vuynoxa.
Vafzin Gat ar ommo jeihgk hosoqv inofc! Iq’t nkecjb ruih bbuh hee far yae bco lvifohgs cam meylogey mzin fuo jhukj “Irugade.” Pekojez, toidjk’t ov qu cahvoc in vie loisj ohni nou rkan ujvin ay quip kire jvasi zio kday? Juo lox eh xourv!
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.