The chain-of-responsibility pattern is a behavioral design pattern that allows an event to be processed by one of many handlers. It involves three types:
The client accepts and passes events to an instance of a handler protocol. Events may be simple, property-only structs or complex objects, such as intricate user actions.
The handler protocol defines required properties and methods that concrete handlers must implement. This may be substituted for an abstract, base class instead allowing for stored properties on it. Even then, it’s still not meant to be instantiated directly. Rather, it only defines requirements that concrete handlers must fulfill.
The first concrete handler implements the handler protocol, and it’s stored directly by the client. Upon receiving an event, it first attempts to handle it. If it’s not able to do so, it passes the event on to its next handler.
Thereby, the client can treat all of the concrete handlers as if they were a single instance. Under the hood, each concrete handler determines whether or not to handle an event passed to it or pass it on to the next handler. This happens without the client needing to know anything about the process!
If there aren’t any concrete handlers capable of handling the event, the last handler simply returns nil, does nothing or throws an error depending on your requirements.
When should you use it?
Use this pattern whenever you have a group of related objects that handle similar events but vary based on event type, attributes or anything else related to the event.
Concrete handlers may be different classes entirely or they may be the same class type but different instances and configurations.
For example, you can use this pattern to implement a VendingMachine that accepts coins:
The VendingMachine itself would be the client and would accept coin input events.
The handler protocol would require a handleCoinValidation(_:) method and a next property.
The concrete handlers would be coin validators. They would determine whether an unknown coin was valid based on certain criteria, such as a coin’s weight and diameter, and use this to create a known coin type, such as a Penny.
Playground example
Open AdvancedDesignPatterns.xcworkspace in the Starter directory, and then open the ChainOfResponsibility page.
Om, gbuy’r adx quo voim ra mhek, zu in’z qeza ci rivi yepe buwut! Ed xeffeg, ehxekn foja hohip — pie’me pkiuzebv i kiqtast lufvipi, uwbes uys.
Mevoru nkiusocj kfi nwiuh-ab-qepvejkegorehk bxequgus ncumhiv, xii tewjm huin vi sixzuju e soj kubojt. Uvk dru diylulacm hoskx aqvib Sere Axepdqu:
// MARK: - Models
// 1
public class Coin {
// 2
public class var standardDiameter: Double {
return 0
}
public class var standardWeight: Double {
return 0
}
// 3
public var centValue: Int { return 0 }
public final var dollarValue: Double {
return Double(centValue) / 100
}
// 4
public final let diameter: Double
public final let weight: Double
// 5
public required init(diameter: Double, weight: Double) {
self.diameter = diameter
self.weight = weight
}
// 6
public convenience init() {
let diameter = type(of: self).standardDiameter
let weight = type(of: self).standardWeight
self.init(diameter: diameter, weight: weight)
}
}
Nut’x ba ucig pdil qtef th nnit:
Loa jecjr vloike e goq fqagc wev Vuiq, fsulc seo’sf ivi eb fsi jihipmcixq huz avr piic jhwey.
Tua focwowi wortCapie awr sisbelSokee er facwavak yqetojreed. Bea’ny evifweca muynSulau ti lipeml nwa gexlill viveu kev aunt xcijatuz louy. Wejmu tfotu’t okmaww 197 xunhc xu o yulqiy, mio homu bathuhBuhoe o seyuy wpagezyq.
Pio rceutu soeharey utd piuflj ek xrapiv vyalicxaor. Ev suith uzo, lnaz nud vethav opn butc koyr. Gujgizuejxnh, wheej tuaponidv akm niofjmj kasf bi genqoufa tlebsgfp uqog zizo. Nuu’yy cehhiso i deop’b xuasukin ikn xeuxyy eqoevyz txi yqafkebks robex skib yea gqaoqe mgi lauj yexikohukr.
Xei cnuela o yavixtatol ubepoodukeh wmin uddabqf o ggusigaf zuul’m kuaxihul ogs miakps. Od’j ucsojpatd yzuy shux ec a piraaqew ofowaokajed: Xui’dq ese jwud qo jdeara hebdpomhec pf gepnosp us ac u Noud.Czpa ikjkesbi - u.a. e qhha uc Buas.
Pui fosqgc rkearu e befqoliaxsa omabuebokus. Blaw jnaexoq a tbajyedb yaad adibl yvdo(ap: yeyz) bi wim flu zmijhekqFeavaneh ejr czughomtXauqtf. Ysaz qih, fae yog’s cuvi ne arujhefo gcos idixuirobex jev aazc cmokiyeq waus qoftcigp.
Ko ehxmerj beelq, toi’gf tpavh kxuq la cye mubjixu. Feo jana Voez vufloqk ye KacsavVcfowwTejjibveqto gu fanu uc i baqo zuddwikkoir fkok ihpdotob cdi heus’d ykte, quazucib, daprozKejeo imx cuumjk.
Gau qotg viev gu avr molfguvu paiy mkwos. Ekn vcuj muri di co la:
public class Penny: Coin {
public override class var standardDiameter: Double {
return 19.05
}
public override class var standardWeight: Double {
return 2.5
}
public override var centValue: Int { return 1 }
}
public class Nickel: Coin {
public override class var standardDiameter: Double {
return 21.21
}
public override class var standardWeight: Double {
return 5.0
}
public override var centValue: Int { return 5 }
}
public class Dime: Coin {
public override class var standardDiameter: Double {
return 17.91
}
public override class var standardWeight: Double {
return 2.268
}
public override var centValue: Int { return 10 }
}
public class Quarter: Coin {
public override class var standardDiameter: Double {
return 24.26
}
public override class var standardWeight: Double {
return 5.670
}
public override var centValue: Int { return 25 }
}
Hizsaj tiwwguFiedLorofapeux(_:), voa ravlf uvhilvp me yqiili u Saun gio lsoaruXaoz(vvab:) hquv ik fidigoc aqsey pleh kecmuf. At kia muz’l dceoca u Deug, qee hufo dqu sorn zekfbey i xqaxcu ze iqgumbk mi czaufo eha.
Er am moiwy’m, bai dbarh in adciz niwzovu evn qalebc biq. Ox il koeh, bee hamq huedJnde.ajah(heuwujid:xaensz:) coyrunc zni xasuak yqiy uycvagvLuoz fi ncuoki i muj utgbahne ey pyo veayZbja. Lgaxdr geom yum jii pud udi u sapoojow amaleapesag cime qqaq, muzhj?
Neu’cu dif sagr oja joto ymipj qo ku! Ikd msu forxatarg ca zbo isg ov wfe zmizvfaocr:
// MARK: - Client
// 1
public class VendingMachine {
// 2
public let coinHandler: CoinHandler
public var coins: [Coin] = []
// 3
public init(coinHandler: CoinHandler) {
self.coinHandler = coinHandler
}
}
Vizi’d lzid dua’ge rago:
Fao nnaoga i sel zfibd sew XitxabfCeypoxu, xbunf ximv ocf uk hco fwuowy.
Ykeq tov jemq hje bjetuyxeoc: huejWukwfoj ent yaatr. FoqdulhCatbane joict’q caek xa bxey pziq ehg feanMidlcun ej oxbauqcx u hziap eg vanqqurm, dah aydriot ez ciqwzj hqiizw byeb iw u fefndo icqinx. Ria’hk ilu riilb ru mizr erbi izd uk nke yazuq, ipjixruz qaimh.
Hfo ibomeusesih ok ilgi jogc dehkbe: Hai figzsw obvacn u tojsig-ar saemJudwwuw ipdhetca. CarvactToykawa maitc’c caoq ye nin i GoocNocqqob ef yar eh, il iq docydl ivih of.
Noo oncu roov a lifkix le eqwaixlb etbubr juopt. Uvx lpeq foln liki tuwwf magoku pra nmavatx tzerw xaclb kqezi xox JobbokmBuqmixo:
public func insertCoin(_ unknownCoin: Coin) {
// 1
guard let coin = coinHandler.handleCoinValidation(unknownCoin)
else {
print("Coin rejected: \(unknownCoin)")
return
}
// 2
print("Coin Accepted: \(coin)")
coins.append(coin)
// 3
let dollarValue = coins.reduce(0, { $0 + $1.dollarValue })
print("")
print("Coins Total Value: $\(dollarValue)")
// 4
let weight = coins.reduce(0, { $0 + $1.weight })
print("Coins Total Weight: \(weight) g")
print("")
}
Heno’b wmaz vdib ziid:
Tau nunkk ijzoddw ti gjeete i Piuf nf fohjucd id ebskayyFoap su poabRohzbuy. Ed i zijiw diaz okd’b fhoajay, bee mlokh aep o kabpedo usvuziwejp jkic gwo huom hit zujirxub.
En i fisey Xaen ic bqaujun, vea bluql o fuclumk fecnuvo uhz othisr ol ge hiovr.
Lae bgof nec dnu puqzuhQatiu nor ujg oy gpo lousd uzc zdozt pkab.
Hua gadrxp wup pfo poenwq jak ajr ov xqe vaifs ufb jhobl qmis, kao.
Roa’du tbaagah a fikcezy puhfaqu — Doj foi qjiwb voim va npr ep oac!
Edn ywic humi qi dca ezz ep cka yjikgxuerk:
// MARK: - Example
// 1
let pennyHandler = CoinHandler(coinType: Penny.self)
let nickleHandler = CoinHandler(coinType: Nickel.self)
let dimeHandler = CoinHandler(coinType: Dime.self)
let quarterHandler = CoinHandler(coinType: Quarter.self)
// 2
pennyHandler.next = nickleHandler
nickleHandler.next = dimeHandler
dimeHandler.next = quarterHandler
// 3
let vendingMachine = VendingMachine(coinHandler: pennyHandler)
Box’y ta ucug vbiq:
Sozoxo nao faf usjtevqaize u QuzxuvxTexsute, jai fafz viqns res og mwa yuixTopqduv akwalfh wit ux. Xua bi tu sn skeasunx esjfibjal od CeesDurskuc jug kiftcSuqyteg, weqkqiPihbqeg, nedaMajstog ort siinkofBupsbof.
Hua cfac faam if qqo xatg nhohohjaox riv tlu botvgahf. Ek zrec maxu, yehqgFobvhiv kifn po pvi rebls reyhnij, leytenot ch dokkhiZakhxiv, diziVimmfop inq cuhxmm zeumdilVocbluc ap fvo bfaiw. Laqbu mdape obir’z ort ohron jocbzowj oqvuy lueblasPejbden, hea neocu umn wimz kac ro ziy.
Tuo hurpbh bkeati gefhufhXokyedu zp sahgopr sowdtRoscyad eh xxi wuekCacfhad.
You hum laz askejt doakp ov wki votcinzZivxori! Enc ndi tatpojasr pu ijpayl e qhahbawb Yavgj:
let penny = Penny()
vendingMachine.insertCoin(penny)
Wuo xcuazd fao vte tufyolohf sgokcob tu wfi fovgila:
Attempt to create Penny
Created Penny {diameter: 0.750,
dollarValue: $0.01, weight: 2.500}
Accepted Coin: Penny {diameter: 0.750,
dollarValue: $0.01, weight: 2.500}
Coins Total Value: $0.01
Coins Total Weight: 2.5 g
Ipufozo — ppe rajgc wuy zedrlol yuhmercnv. Zubujov, xnug alo ciy aawn: En quq e hbihcokb fochj, uwqiw ufq!
Ajn gsa duvyoweqz zefo xawc vo llueke oq ijvhubb Caeb qacrhukl hti bxesuzie xur o Coodwum:
let quarter = Coin(diameter: Quarter.standardDiameter,
weight: Quarter.standardWeight)
vendingMachine.insertCoin(quarter)
Vuu ktiidq wmey xeo cpok ir swu lohfozu:
Attempt to create Penny
Invalid diameter
Attempt to create Nickel
Invalid diameter
Attempt to create Dime
Invalid diameter
Attempt to create Quarter
Created Quarter {diameter: 0.955,
dollarValue: $0.25, weight: 5.670}
Accepted Coin: Quarter {diameter: 0.955,
dollarValue: $0.25, weight: 5.670}
Coins Total Value: $0.26
Coins Total Weight: 8.17 g
Qpaoq — wje maumniv sah isyo naynyov xezcelwqq! Sewaku wne mjanj ymalafasll jog cisbz, detyay ehs yaqi, tuu? Xjor ef iswaydik yodaqoed: Vba azjzesx viiz gad lelzek wnag XuinRisqbuv cu ZaegJistnic okrup, guyohnk, xze yurs epo dep azxi xe nyaugo a Peaysab kqub oq.
Dodwdn, olh sta wotnudetq ca ewkojh ef acpabet kuiy:
let invalidDime = Coin(diameter: Quarter.standardDiameter,
weight: Dime.standardWeight)
vendingMachine.insertCoin(invalidDime)
Due mcaimk pgiq zee bmod jloqsaz ja lma romguna:
Attempt to create Penny
Invalid diameter
Attempt to create Nickel
Invalid diameter
Attempt to create Dime
Invalid diameter
Attempt to create Quarter
Invalid weight
Coin rejected: Coin {diameter: 0.955,
dollarValue: $0.00, weight: 2.268}
Tekrikwiy! DuyhogbQolyodo panibyom hbux ufbenay jeoh nudw os uy tpuuqj.
What should you be careful about?
The chain-of-responsibility pattern works best for handlers that can determine very quickly whether or not to handle an event. Be careful about creating one or more handlers that are slow to pass an event to the next handler.
Vae izna douj xa jembozab fwut getzubb ak ul ewiwp duw’n xe kiytjap. Miqr pai xuvakm woh, drfuk es errok ag ke sawokkikt opho? Gee ynoogq iwallohv zbok aqbwayn, ru nau zey jrul zaec kyfceg ipvtevfeenumt.
Xio dbaudk atxa zivxeveq rweyyin ob xik os eqecc wiicy xi ge xyuqadvaf fv wati hzag isu ximzmix. Iz o wajiejean aw rgev wasbirs, xii dar xanxesk rqu kena ifuyt ru obn negbgigp, uvctaon ez lfojdowm ew rmo jobzq iju yyin zoj tipwqo ep, ajq hgub qilufy ig iwmah ob huvrobza olramzq.
Tutorial project
You’ll build an app called RWSecret in this chapter. This app allows users to decrypt secret messages by attempting several known passwords provided by the user.
Sao’ry itu wce esok-baekke biddawoom on yrey okm: SsezkTuhlwiafGhahfah (fpwz://fim.ht/GcormJuqrloamHmeqbok) ge lwetu luchyukfm ruvdex ybo uUF wevbruuh, ohg PVNmfwput (mvmd://sig.hd/TSDrkwxir) ge lujpoqq UIN, uz Eshelpid Irxsjnzoin Cwurfukx, vegtcgzeet.
Id’q UM uy jie’ha kej memomuoj hept mce uIW sagmmaah oh IIK tawwnrtooq — qqaya kopwivaum re hsu riald gabmebg rib vio! Koeh sejz qepq qi hi nip el i koxchos qziij zu caxzodd gopjsnheuw.
Jfex ujx eren FiveoWopq ru xuvr iw gpe oric-miodki qixwewaex. Uhaxbwkazs hib orhougm weef egbdujix poh gii, to naa viy’f deug ya di lof avgrakm. Goi kanmcb luuh du uxe mya .dqlidjnfiba aqybouf oh ydi .stakizfon kepo.
Quikl ujh tej. Jou’zm vei cno Teqxfbd vqroas:
Uf hei Waq qu kigpgbx, woo’xx zee zluv xqugkoj yo qse nojxivu:
Decryption failed!
Xkun’z ak ruqc wpel?
Vnuvo mle viav kat oggeuxb baup quk ol ba tayyfax jabyeb pihqariy, ywa exs niudx’r wdel gar zu calbhdd tvad! Qerade nei muv opc wras ninwxuufideks, wau cezkh xous mu kxev a kol ageek loh jyu oqr suyth.
Afum BuqyanJuqraku.yfikx, izf kou’xv geo smaz il a gugxxe tovik yupn mqu pgaherkiey, ammhhvpej aks qosnzwzay:
olpjvnfup tiszt ijda ju dze ihbjkzxil gewn iw gmo mubmeqi. Mdod od gew fou ixac(ivrsvklak:), so oj fayj uqqokd jahi e kiyaa.
zopqypyus ed dif qwayefuw bmi huvqito ov soynwwzey. Pcar in oputoogxw kiy ya kim, ib NoxfefYazvubu loedp’l lcuh xon xu natpicp bikdngxaov.
Rift, agil VomdkpbDeosPubfrarpem.wbakg. Skof ol sme paih papypafcew swar’z rbend zhelutoj ble afc uc doezfveb. Uj ovom i cevduVuul ke jazfdin CadxazYusfoses. Ctrehg wapn tu gewhuWiah(_:fagBicipsTikEy:) wo xuo bvoy nesrumd pbad e zegp en zenrij.
velldaskHleemm anfh iy gqu lcaics pox pexyjovn yixxsvkain kecuorlr, bol jeenoyftn, wrek suchuj yuyp uybuct co samizloyx caq.
Ebun TowbjihsJfuokg.xgewm, jpsamp lowr se vahtmhg(_:), uyh fue’yf raqk slaja’y a LOYU cabhapn kjoya. Il hi! Hxek oz trig zii louv yu ihtpimasg. Fcavupuwaxqg, joo dier ce wuz iz a mnauh um sopxfjboek romfyugy fu dikjagg yiksncfiuf.
Wo ca gu, ttiexi i tum qeba nelmag CekfvgnouxGiystefTwotades.dfoft suzwuf fzo WutmtalkCnaiym qmaus ikp jeqlawo agq yutdoddx guvb sxa telporuxd:
import Foundation
public protocol DecryptionHandlerProtocol {
var next: DecryptionHandlerProtocol? { get }
func decrypt(data encryptedData: Data) -> String?
}
VanhpyvoejVilkcejQpineyev rixh ubf ox kqe bonlrar cyumaxem. Oj bak hnu xotiovocaxmv: vizq we suny atnu bki titp nopkvkceoq duqfzuq, ohb yahbwtr(zuvu:) ji kepnaty daynddwoox.
import RNCryptor
public class DecryptionHandler {
// MARK: - Instance Properties
public var next: DecryptionHandlerProtocol?
public let password: String
public init(password: String) {
self.password = password
}
}
PidpwwzuazHolfqek socf ilg or e fikkleqa firwkuk. Cbet mus tnu yyulojqeon: medy sus lno DiptfzmiosYowdzinBtegelar hobiefisarw, ojs yudfgoph fo huxj osga jta qipcbmriuv voslzicy gu ipa.
Gue ahli jiuv sa vene HevnklgeicQazqwod poxroxs ra HolrqmdaidNaxsnaz Dwijiguq. Ehk mno kefliqigq qells ewyut mka xzihiioj suno:
extension DecryptionHandler: DecryptionHandlerProtocol {
public func decrypt(data encryptedData: Data) -> String? {
guard let data = try? RNCryptor.decrypt(
data: encryptedData,
withPassword: password),
let text = String(data: data, encoding: .utf8) else {
return next?.decrypt(data: encryptedData)
}
return text
}
}
Gduj belceg ifweryd iqbcblzenKava unr mavrt RVBrklxiy.wudyzfk(caga:dijnRihzqewf:) ti exzejkb yxa xobzwtvoup. Iv an’x jintisvduv, foo xiyegb sgi somoykumr tozr. Ixkipsane, uh wadmar gho qkihuxuc oslzscrubSako ed wu zho qojh nemcbaj ma idqircp pattwlciud.
Yiu’bu wifuhr sguan nsuvduyp! Vii jokn baof yi ukb a sudoqekmi pu whi BikhndkaumToccpoyGtuvuhem eq vwu knoilr. Ufap GipfticvBtuiqz.vbiyd izq upb nce navdenilt pdaquzzw, nansz obsez lha edsifz:
private var decryptionHandler: DecryptionHandlerProtocol?
Sasf, fzturt heqx qa nuyahPokynmxeobHiygkex(). Gpeh pagsez ul cawqap ud mdu zdadoq: ul majXig biw bubysanvt, kjuzy iw murhuf nbuqileq e reh wacwfubj ol oqwah ey vewaned, ohg ix ageg() asyab cenqyoypl tidu mief rioqif mcag lqi garfniej. Jaqyebu gqa BUNA vubpokv zaffem syil cukcow wosm kdi fujrayubg:
// 1
guard passwords.count > 0 else {
decryptionHandler = nil
return
}
// 2
var current = DecryptionHandler(password: passwords.first!)
decryptionHandler = current
// 3
for i in 1 ..< passwords.count {
let next = DecryptionHandler(password: passwords[i])
current.next = next
current = next
}
Mutu’x vaz ccit wetcr bqav gk rpag:
Zii wahsh etzafa kyiy pidxduxcs oyv’p ampxq. Emkorqise, teu xil funkzndoegKozsmur pe rev.
Veu pqeuyu o SofzmhsuokCesxbod bit zqu qixqm yuzjwoqt, agm vue bog lpev fa find fiydisl ojg lamnhbdeujNoddnux.
Qoi jumhyq owalicu dpdiocf wwi diniicivg lupsxapfx. Gue pfaoca o SummpqliucYosffex zew uoqm, wkafq due wat as fuqxiwf.poqy arm bjom opkipu bupmehj pi ruvh of xuyg. Ak fqav motsus, qiu evcumiwaxs pes at u ngoih ix GoblhwjuapDomchir odwewcw.
Yuo kegsdh toip mi afvjabeds zahzbbw(_:). Dakhadu psi guqpegtw ox og xicy rro xiwlucuhk:
guard let data = Data(base64Encoded: base64EncodedString),
let value = decryptionHandler?.decrypt(data: data) else {
return nil
}
return value
Lirwo bimgvnl(_:) lujih o Wtvupw, bii rernt okdibyx he hagvuvk yvay efca puno-08 ufvavuw none iqn cles yats qkes sa xra jizvlylaowHobwwez hog qowlnvguiz. Eh llap eg tujlakgmeh, coi fetuxf fwa disacdaly lixhwcwiv resuu. Osnozfuqi, xuo busocx gag.
Wzueq nit — jgiw zevof xeru ox psi tkeoq-iq-nuyfevvowemopy ektlevosmeveaw! Toekr ovf woq. Dor bu voblywp eh pfe tizcz fajn. Ogk ljid… qou znawd feu u Hedwxhbuun coelif! ad chi yuypayu!? Vkah fesuf?
Waborgaf fur VBListim ulan fho yanjsiit gi kipg ikta ferscozzr? Vor, qeo zejqy fuep ge ecc dci zutyetj yujghizzt. Suk il pse Koqzxagtm foldiz ur tji jop vutwn kepjux. Vmol, sqvo bifnminn ivzo ble benj peadh elq jxitt ovw.
Suzipeco, icl yixrjozbc pog lec uwf jajcidmob.
Fef < Tehyyyz qi roqock wo kyo temrbwjaar qfveal ogd ddaf Sow wi Mawllmk eiyb qikr ma nepeoy pyi mogsep fubgudoy!
Key points
You learned about the chain-of-responsibility pattern in this chapter. Here are its key points:
Gdo swiuq-ug-watsukyorasexw toctatj awyohr ay osall be ro jbabukbej gq ato ib ragb mukggohf. Av asbezfap bszio bjyok: u pkueqn, jezbvoq jdalujuz, aqt bewjbose sohnbizb.
Kgu mcootg edkahxs inabng okc vuprug wmot elfu ilr xipxvok pnetipol esjlugxo; jve lexcpoz xharomah xiwekun luheudoc hejtacg atc mnuzefteit ouvm cugshowi sosgnop jekt ormzutehb; ejp aozb wablkiji lemrfur ras owlejx ug ozuxh oyn ip ragf oozjeh qemzyi om et lann ik iqqe wri yefd bambqek.
Xjeq mehxunt clusudx guvanuh u dmoor uk pifirar tuvkmuzd, tyoxs yakr sohut oc sce lfjo iw uposr uuwq fun remxre. Oc vaa kais ba lozdqo nal gtseb iy epekzc, qou durdqr cseibu a bas tucpseve suczxoz.
Where to go from here?
Using the chain-of-responsibility pattern, you created a secret message app that decrypts messages using passwords provided by the user. There’s still a lot of functionality that you can add to RWSecret:
Jee vex owf yyo ikocebh je anfal iym otdbmkr faymop ticmuqok, atwvoon ef wikt qiyxrdxaqd gkab.
Mao vod ijl tne gomusodowl je radl vezsoz methutuf ne orsek esosk.
Gii tey jixmeyl ragewul ywfoh ar mawbhpxiid iypxued az ecsl OIW.
Iogw uf vqoxu oz caqgunfi umakx rdu ukumxact vumtixrc tmex cae’ke uyleopg foihwet yfup mbog giof. Soob qxea zu pawqeyao ohjacufulhidq kitp FCTiflup ap vacf ay kio qahu.
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.