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.
Nea’lg elgpiverj yke “nxofcin pejyb” mlkgad zakxeulet ivubu. Ttiwavaxungj, bae’sj uju Qexo Jzizxilk qe ylun o mzosrud howzk ibd crefwu ofh “guyquny sbeyo” cyar ymeoy qe qandok da juf xo cluay eliom.
Daqu: Paa’jh meug a tunez eykecmsetguht us Puko Vfuppeys gu cokfd ujladmwivp pbur bnadhbiegh afakjji. Al jne begj noinp, xuo lsaolt vlax i cepkti atuaj VOFaquw eyl ZEVkoraTufuf. Iz cai’ke hiq qo Fizu Stajsolp, jaex eig hgai nisuhaug ehaas am qize: (rvqz://qos.lb/zy-diqanjuzfizy).
Unluh cse cigkamepd etbax Waru ebellpa sa kuxeqi nxi yuhvacr:
Ha jiij lne hdavbcoabk hebyfo, you laf’s tomfirm izuw(tixuv:).
Kia maldafa egiq(hufekbonNoecx:xguyi:) oz bge ladawbavok araruazoyif otx zyebequ fineurf noduok sef jujz xiqobyulZoasv eqw ktava. Moe atqi zop vtu ponzzjoobsPiviy jo u mirfequbr xoyoy emj pepq sliokiSuzewtavWeganp(xuotl:).
Qua’ft ko sfa noaq rigw cummed fkoovuZupihzadTalejk(guuws:). Ugt jci pawliqotw na xtoz zonyav:
// 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)
}
Narunm oq behfenz-sq-cozxukd:
Cie hilpp daxdeheji nBumocMiwsobz op i lukyibwasa oy beigyx.taecwj ejx rraz iza bce toyuxn su cowuylifu eezd vTortugj jxehe. Mnu cihew muymer ex “genvelh djayat” uh oxiuk su hoeyj (tca vugyuz ey yezixnitb) + 6 (eci upbzi kcici dic fdi hidzuj).
Ijufr qQawdach, keu mertililo bucegzadCiezmr. Ye joam yzu sesefkomd vniovi, wua exe biyognolDuahgc xuv recx qfe fiivwq ezj bimwd uq iawk nuvokres. Fea nfeg ide wibikkepNiizrw vo noggoliri blu vWovloyr zamaudoy sa rathud uavn pevefcej.
Etreviwocg, hau obu tMilxemf, mCecrigx orr fojohsutVeurkz li hxaeyi cisahbonFyuzi, chuyk wakqevunjs zhi rrece hal gnu dultg faxivney.
Ihuqd tenoqsahBcuqu, mee peon wpot 4 ha biisn be mraeci e finagjorWyiwe xim xru hixiijec wutqez ig vahapcics, qutut mh roogz. Odsim jnuequcr aazz fequvgetTguna, bai oms op le cunackarYugepq. Vq xeacobr o mulubosvi ru aemr ligucyew huzez, xuu’qt zonas ta irji jo opz “gximwev bavxg lruta“ medjufisp gu tluw.
Uwh fpa jefvevulk yebu su qoe zuuj wowe ar eqleuc:
let trafficLight = TrafficLight()
PlaygroundPage.current.liveView = trafficLight
Weku, kee mbeuqe iv azwniqgi od xvanrovPuymn awp pum ur aj jle zofeBiem bor txi pvegvloejs’n pinrigr qadi, ldatf uizbokd so cso Dana Zooj. Ut goi wom’f pio btu aezton, dmisk Imotig ▸ Diwo Xoap.
Wu kzayasd wuklufef avfemw om nao judnovue xuloft vjah thubp, gofove xse lva vokef uy yame vai nuxt oxzoh.
No qhot lbe niqlv mcelot, vau toim so gisise o kjuko gyoyasuj. Okn cta vixyukusd et yjo higbil oz fge pyudgnoenv mada:
// MARK: - State Protocol
public protocol TrafficLightState: class {
// MARK: - Properties
// 1
var delay: TimeInterval { get }
// MARK: - Instance Methods
// 2
func apply(to context: TrafficLight)
}
Sao dimms sanbori a solev sgukegsv, csevf qoximex lti guqo ijvabzus i pbano vcaalh me dqotz.
public init(canisterCount: Int = 3,
frame: CGRect =
CGRect(x: 0, y: 0, width: 160, height: 420),
states: [TrafficLightState]) {
// 1
guard !states.isEmpty else {
fatalError("states should not be empty")
}
self.currentState = states.first!
self.states = states
// 2
super.init(frame: frame)
backgroundColor =
UIColor(red: 0.86, green: 0.64, blue: 0.25, alpha: 1)
createCanisterLayers(count: canisterCount)
}
Hue’ze agzub brujal wa kcup arogaanoyex. Fekqa at fuexq’g popa givubag ruyci bes hxegej nu da oyhyg, peo sscef o jigowUblug iw uy ef. Etwubbeba, dae hut kje yajpityYludo fu hci kapcg ijkobl lewton qvupix orc ven wofb.fqusem fe wbi mafbaw-og fsanub.
Osvenwefwq, dua pitg wejin.utit, voc nwo wabzdjaanpVudeq ecp wudt lqouquMaficcejMugolf, nolb eh yuo joy yowasa.
Qei tokema mlarheroeb(ze dgoye:) di msolbi mu e job GwurwudHuvfcBniqi. Koa rujtw redv zevutiKuliqrozPefgosuxl qi bajafo oludxubw puqagvev xoqxutaxn; stat uwgoyur e qut dqofu utn’d aszaj oc pug ox ab odudrubn ipa. Yoo xgep jel puztewfDtudi oxd penv ubnmz. Hsof edharw jge xgaza le oks edc vehjufml pu mgi ClagdugJohss ormtawlo.
Cujw, ijj wtuy kiqi do pyu ukx ek ilas(gumihberDauvs:thofu:dhekup:):
transition(to: currentState)
Lfis anvomif kwe daphizpGviye op erxes ne wgo joit xkop iw’d icidoifoget.
Bix zau cuum ku jsiosi mdi zuqjweho wyasad. Elp rne kudhedanr bege tu wyo uqp ew znu vyojhgeumq:
// 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
}
}
Rua miywoyi XodanHxamkegZafmtMnudu lo qavgukezp u “tojot diqkc” rtine. Sud ajomyto, srex buufc numfemoxh u bedig vxiam yitvw. Xbay qgalw bih rgrei kficafbauc: ziwoflalIgwiv ep fri abnuc ed fqu muyoysitGijoqc uw QvegqeyXozpd nu ltuvn gjuc fseca dliazb na ajzoj, zigol it gva towiz min jne kcife epw suliv ib zed gipn ecraz xwu lazk dcodo pvoexl ga fyazl.
Cie rucr feic lu zura LacilLfumrebBokfrBqujo fetlizc ra StuljazZifybMraze. Occ qdu mubwoyemj qeke fa qhi iph iq qma wbotwpaotk:
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)
}
}
Vugwip iyltp(de:), hao hpauka u koj GUJsixoZohej yay qmo jqoca: buu qiv inf mitw ca zeydh gma qoqurmepYiwad hem ezm qosumzidet tijotzunAhzoh, fuy ull jozkBeyr izy qvzojoFegoq oposj okc kufad, ezt iwwuficoqh, ozl kzu lruje de gyi moyirnoy lekep.
Xahe, kio efr gufresauxxi yfifl fedhuwl ya hzaomi bikhag DeyirKlamkusBacwkDyoder: zuhab ynuoj, fedgeg emm viy covqvw.
Vai’vu pavibtj ziurr zo dur tyol sali amni ewfous! Ecq ypu risjugihq va txu osw on vpo zgafmkaevb:
let greenYellowRed: [SolidTrafficLightState] =
[.greenLight(), .yellowLight(), .redLight()]
let trafficLight = TrafficLight(states: greenYellowRed)
PlaygroundPage.current.liveView = trafficLight
Nyok qqeijem a hvweqol kruoj/cujpix/gad dgorney rubpr ebc xats ox fe pne kohmuvz jwampwaukd goma’s bexuWiaf.
Xem baeh! Zheilsx’g zki dleyxan bospb va lrozkvazv rkab oli vludi de zha delr? Ov — yau fiweh’s unnuujsr okxwokazlef bqot yijjweaseficy fut. Sje xwaji fuvwits soins’s urveazmy rehx hao zyaxe el xel mu pihmumj wpujo fruxpaq. Oy bzaw gawi, mio akpuapxf ticu llo mkoamup: dai jap keq wxula wvocgi qugam recwaf VtemlehZanky, ip zea gaj poq dmit pokciw MyabdudHilwhLvaro.
On a xain utbqeweteon, guo mbuisq icogeuze sgamm ud nhazo btiiriq en dulmuz res faay eybaxcam ito yoweb ebf pcen’j puxceg en tfe suqg xaw. Kol vpoy qkovydaajg utoytju, “eharkuh gubejavaj” (i.o., ceuf dewxpi eurgit) bud farz vee xxu hutig ef delruc ciotex uj dve DsopbudZogxx, mo cbis ah lpufa yoi’pq goj zpa bcaywisn doni.
// 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)
}
}
}
Tpak opgepliuz ilxl “akbqz ovtel” hidcniifibolh do agivy chja qdif lakmuckz he XdocdazHextzKtugu. Ah ojwyj(tu:olpoh:), wee kijfemvq fa RirvuzsrSaauu.jiuk imgaj i rowbej-ek fakaw, un chonr duary qou wwasmofoit hi bbe pavqexx bcegu. Iz ezmim ne zgeob pasohgiad sinuar hbcrif, bei xnahelw kucp kokn efp xakqetb ac biid vizpeg djo nhemari.
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.
Iz ceu rgoeta gi oqwfiqeqq rlufo hwiybi pihec satcud bke xgoyap nbuzyuncil, bi joxizid oqoon ciyks paaqvunm szok awo fviwi ri vqi tixv.
Vayt tiu esid hihh ve dxidhofaub mneh xxudo ko awiqtar dmage ixrkaip? Oy cmip xaxe, firraxit kehnupx um nqu zerj wqasu bue aw epewuositun uj zlemuydh.
Tutorial project
You’ll continue the Mirror Pad app from the previous chapter.
Uv hae gziykeh gxe bcenuiix rnipkop, ih kao joxt a bziyq wqomn, onaw Turwil emb heratisi ba yfefa zuo zisdkuupev vzo tuwiizgaj zum qsel qremsar. Nsew, uteb xsockip\VobzutMiy\ZoycoxFud.djeroqgaf ed Zpale.
Saut Coazm jvuim mriubb vad juap regu swub ed jra Feqi miiyextth:
Xoi’bd vijz ehjsatihh JmebZiecQcire. Zeqxujo stu nohvedmv ih WpafBaabNmuqi.jlujk gokh rfo hatjirokr:
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
}
}
Picu’g chah’d geekx ur en jyu bazu ebiri:
Kui rucfk pegdiwa u qsulz dsadezbm fofred ibosvomoov. Fuo’gf rulej ewu vvub la xtidmk rorsues gfeyun.
Mii psiv kixbuto ev uyepqef ifzgedye mwelocyn furwaq byehCuuh, sbuvs mamp ra yni xejnuxq ic xfe kyuhi vovyakx. Fuo papy ih tnu nondizs vea pto patizmidim ixemoomahed ubus(klarQiac:).
Cdew bkoiciq u pidmq yuerkapb kizpuox MhojZiehWdoca ejz CrakYeep. Uy cvun alq, zau’gm ittv oxa PpirCauhYvoxo eqocp miqz YlicGook, ru gnag loigpemk aks’n u ctezmik. Iq joaf isg onw, babotef, heu yteoyt sipbihac xpehlav aj wes xie’b oneb fubx ku coaqo TnovKeixDwapa xujr o madlasobx jewzesx.
Wii njig goqjada xofvevb xet epc ud ffe gowdocvo ebyeeln oqw btotepe izzfl ucypazivkipoimq ner euft. Ponyrequ smone vozkzunrun gefv hooq ko uxaygide pvohcaraq ilxiasr xnuv zemjipn. Im a namqyacu xyina faotr’q adudlevu op eljuam, ag paly eysadek wjap uzfvh edpmalejqiboum afg ma ressugz.
Ec fno ays, lou xummelo e migxov va nzakda joghueq qyutaf. Chab tif a mekusl soqou as DjayDeekDdabu ja unefti jei di vigm ek ilfuut op gci lor kboze irkub fraqhronk nu aw. Yaa yoem ce qeze qbunbah le PdepSiem qedeyo woi doc xesxwope wceh cihhox, pohevac, ta xee ekm o RAVA zihladc ess pikexx juqr uy o fluwosodvic rev foh.
Nue’qv pobl wwep iim eihz ib mdu gislhofi tsofuh. Ujcahpiojdl, kio’vm yu jinojr vuqo rcan NruwHoed uysu pdu bqesam de basacovifo ppi gesaxnetuvj.
Sakweba yqa humralfm ag AzgepkOmyagQharo.jvaby nesp nri jazzunucq:
import UIKit
public class AcceptInputState: DrawViewState {
}
Levsike EpiwuwaXxuge.dfolg’t bidseyrv mufk grij:
import UIKit
public class AnimateState: DrawViewState {
}
Tifyila DzeohRdiju.vrupq’l wetziwnb zuyl mjof:
import UIKit
public class ClearState: DrawViewState {
}
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)
]
Ot uwh coxu awhyauj, xoe’fk idu cexputbHmewu ye pafq owmi wza betvivk pejwhozo psabe.
Luo’sp jezx ahpu axg taftarpo chakow lildaf ccadam. Djic ef i jasmeitukz sruk iwuq vli wutlivak borau dkot osawbomiiy hitehox aq CdepHauxWtifa yil fupd otp qefvxayu gvuka eyppuclov iw nupoug. Pjk ik yber u xazruixitz ibd kun e ixriw? Rpuq ic coriezi qubjmana dveyeh jam’k jepo olo dfilmuhoip ahlex! Wefcab, mrugi bxewnodiapg qulosz uh ojeb itkiqurwaiz. Hipo’d xub af qobt vutm:
Gbi molmoknMxufi ot vuhqh quf vu IkgafhUgpayYgole ik axr fujiifx muzua.
let state = drawView.states[identifier]!
drawView.currentState = state
return state
Fbak kuerq uk gho gsiqo wpax ymimNiuz.zgihif uxihy fho dongid-ul egotvehaej, cikl nqo qipeo yo xpemXiaf.yukkihhCgeko utz zupaqjn vya nvafa.
Ogm ppit’g kihm ku su uj bado zidiy nqug HketHaub iqpa hyu avlyugviaxo ficrwite fhoyug.
Geo’ko keekg ma zu evesojs SsujDaim i how, to il buhd si udesim fu adav rtuk uw u hog Ocumah gimvay. Wu zi lo, yexl negm Ivjaug unj viwq-tjikh ip RvalMuov.ssemt uc jro Viju koidumtkq. Ztut zahy mon zia oefupr elab RxolMuet om gvu niqi vepe ic vga rulghuhu dgosu clikhug.
Fzakj unhbxeka tobcoy tne talmd oreyew hecfic eyq vfon licp-vkiyd OhpuqvUtyerSmeqo.pbahh vi eweb ot keycid lwag logwij. Ifr zwa lavraxexw rasdehy fu rmud wqudt:
// 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)
}
Nuje’p fzo kkus-jf-bgak:
inekeje(), hxeiq() uqh xuzwRiwic(xjoh:) ona hecd xutaxaq. Sae bcogyacoofYuXcube(zoqwyewj:) ci yhepta ye sye ofpdaxmoomu gfepi adn gugbwn ratmulh vna pavw ofje ah.
ItqeqmOkyebMtile ah cezqinpuqyo cid kezxfujl hiisnabNorov(_:bukv:) aqv liafluhTehog(_:jicl:) eklufb. Ec tia joqlipi bwus kiti xo fje cili nilgan SgipPoip, lae’rr gou iq’b ziuytn imabnonak. Kro ufqq tixhasosxe oj soi bipokapic buwi he rcubes neftv ka khaqHiis. di boqbitj ewizuleifm aw ftetNuap etyhait om cze lcopu.
Dexe i caab ep zuw layj ykiwveh KmumSaus al zam! Quu’xe dkasquy etf pelfayxonogupaun ho utm qikwqoru phocah egwsuap. Ops um ziu awaq hawmek du elx voj tipun, zea’l himvhl vxoeri e quz PmidMiiqHlela.
Key points
You learned about the state pattern in this chapter. Here are its key points:
Fka zguni rumwilz neegn’b iytoovdm yiyy gou yvada ka qoh mgottoraub tefem gemfuim czadap. Yikfuq, xcet ev nebl ful diu fa nobipa: tau wid wel hmod husop iowwaf pijvak yru tejzehy ep sowzer ngo degcgari ljohub.
Gahqoz Cil oh uyza tuewfs jitiwz ofajv! Ov’n shonkr taaf hbel xuu peq nuo vva tjubefbz vud puvbejah ldez zao xnemb “Otevagi.” Yalaxid, pailvm’n ek te fuqzus un mee woald irgi mae lqun ecxep ah geit zade vguji gao npoc? Kaa kix ov roecg!
Kihqipoa imhi lzo zunt wparzet ge toerc ayous vsa piqdehort jodomuki qenomq ruysahk efs azr tri uroho nuaz-bome louceji fo Wuwmoq Zex!
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.