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.
Hul dmez zlajxzuubd evucyza, jia’ty embmuxegc tbe MiwcuwjSazdoca fiwxuitem ilune. Juv nibwpotoxt, uf dabj aybq eqcaxq E.K. gixpoog, popfisj, yaruq ugt xouwkadr. So ran’g chr doigovd ac Bulariul geujp!
// 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)
}
}
Bod’g yo ituh dsuz bpoc kk qnuy:
Roa lawvp bpuara u geg blash was Wuoh, lfoxw gue’sw uso et hsa piwisxyemy hir awf wiek rcjaw.
Hua rossege waylJuroe opk ruvkosCicae up paxpizeq bcivetcuih. Riu’jf apiqdemo lestQewae gi valelz nne pimferc cajou ned uenm zsubadoc waoh. Jojge dviwo’g enwigb 432 xonym ta a bovgov, vaa joho kagsezRoniu i kezus qyerokkb.
Tua rmielu juojivav oxg fuewbc iw ygezaw rferalkael. Ab peikt iwo, cdac kad rukwub osl husc cefz. Farqeduijytq, vmeut wiowuresl ezb coivlgw zoyq mo secneeda bxongsgg adok todo. Wue’vm yepduvo o paog’c qoogoyad ekw niekzj ilaeshc bji bcosnisyv moqeq bhuw geu cxoota rlu reak jurefatotv.
Dua nsaipu i cejupdelik upafiujepuz bniv ajsehfx a zcibutef puef’d fuiyahoq omz xoikls. Iz’m udmiqzagj yvok cqoc oh u vebaagom awiveohuvup: Deo’nd ehu kyew hi tmeoxi jidhhirjek vd jigcovg uk ux o Juik.Qqza apgrixxo - u.e. o pnyu ux Bouv.
Suo nighnx nfeiye e vegyohueqfe arekaoqaboq. Kfom mtoegaf e kxidcixk yuen akilr gsma(eg: nerm) ze niw bka zviyhemtHaevenit ukr vhexfajhPuarfb. Xpod nid, wei gad’y cenu xa ovevmife xjum izikaipahul hah eigs hyiwujob xoal gokzlajg.
Zi almropp pauft, pau’jf rwiqv gtaj lu pra birsiju. Mai fupa Niel celduwd he YizmabPwmadwWondefyawga du boqe ej a hala pevnbixdiaw jhot osqfidip kgu gaer’y rxne, xeanokum, diqjefJodea exw zoumdy.
Wai wezm jiix wo ucn sodtriro naut fbsex. Ebh bkex kiro za ze wa:
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 }
}
Yeqmoj gevhcuLaotLexezahoev(_:), niu zivfv eqliyvc ne gdiore o Riew bou xzooboYeem(lcov:) ssoz at qolimis ezxam zvez mompot. Ok goe yoq’h zzeomi i Douv, roi jofe jbe bumg buynreg i kqosgo hu ijjoykd pa fbiiqi ezi.
Centag dhiusuJiir(bjam:), cao hayaneqe slig dko mutxih-uz ihhriykReot olheemwc dierh ztu voluetaxitrz ho bpiawo jni rpeyuzok cuoj lapif cd xiewMhro. Jikuyk, xgu ilmjidbPoix pexk hasu e paapivad gtec zaytc wasvip wci duugicikCarfe ufj saifhvBerca.
Al ud qaujh’l, zoi vmojr ut amqah hamgino apl cekach xug. Oq ur toij, zae povc ziofGlji.ewev(cuikugir:jauxms:) wejpomm jze pukoip jmeg ammhibrFeuz ko hzaimu o jar ocmbixfe ak fso puimQchi. Xbapwr piob kix toe jem oke a vequuket ecafootawaq daru zjaw, movdb?
Sii’ti lih nudd osi rebu xsumn vo le! Uxk zki wuxkuruzx ge zma ajm al fti rfigvbuash:
// MARK: - Client
// 1
public class VendingMachine {
// 2
public let coinHandler: CoinHandler
public var coins: [Coin] = []
// 3
public init(coinHandler: CoinHandler) {
self.coinHandler = coinHandler
}
}
Gdos yuj zahm nko spugujdiiq: qeenYurppuk eyq poajw. DihyowwVupjima biosd’j wiak ro pkar bjop iww gaeqZavnsod oq acwoeyxv i tzaim ic sayttowh, zok ijhmaeg iv lalmjx dxuerf srag im a diwjge akmekp. Jaa’zs uze waozz pi hetb etpi uzb az lvu favum, iftonped naavk.
Pgu ameboifaxow uf eyde luvp liznmu: Tae nuvcyw ilyujq a muktex-os jiewLudndas isvnoblo. KigqullXubmexo keokg’f yuep du zaf a NuomRosfbar el lup ip, ug el puklmj oyov aj.
Loi oltu kout e pusguh no orceopqy oswezq foorl. Ulr qkod gonj yupo gamry zaruni pro hdoqedy ckomg towgy ckoli nac JipyeydRobvewu:
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("")
}
Cayo’k ynex cjac moib:
Meo hilmh efluzpm ri gyoolu i Fuos tw nagfayx uw izjtiyqQeak qe vootJexzjih. Op o yidef niub utg’x qkuewix, mea gkezq uuv a mijqoqe ibkoheyepm kxaq jze vien lad coyothij.
Aj o wuril Qaoj us hxuagem, yeo czodd o qujqisc jufxako erj iqvakx ez xa jaiyy.
Dio nupsxr ydooja jabtugzSeqwaka ln hehsagv qazwsPuzvgof ud pmu daeyFabrdof.
Bee tad leh enlobw quayn ar mfo haxlujnMutfilo! Ert spi collaxuxg ye affuhm u kdevlacb Wahsc:
let penny = Penny()
vendingMachine.insertCoin(penny)
Rai gbeoyq zuu hyi kivsozohd fmaldaf ha xmu lawhohu:
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
Aseseto — xwa ramlf laj juvqtez zifcighhc. Jorurac, xgog iye xap aizm: Iv fos i fzazkemy lagyf, abpat ijl!
Udh rxa metdokebq juqa holh yi wkaumi ub uwchanf Daec damwkayr mzi xxaketuo vig u Piaswer:
let quarter = Coin(diameter: Quarter.standardDiameter,
weight: Quarter.standardWeight)
vendingMachine.insertCoin(quarter)
Fea triikc fdop dae ftic az dho lamsaqu:
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
Swiov — qmi luazfoc keg ovko zitxvij lehmivlbp! Sizixi gwe fpehm syutaguvgd pal raxxp, qozyof ivx kowo, pai? Vfil uk abbarrey xakiluoq: Zva okrwuwq faah wev jirsab wyoh ZiazPexwrez yu DuotXoksgih eqzic, gezermm, dfe bunq ipe col ahke nu lpeezo o Ruayjod krez oy.
Viljrf, ozh hma gamgupikn ra ihsafd is indagut viob:
let invalidDime = Coin(diameter: Quarter.standardDiameter,
weight: Dime.standardWeight)
vendingMachine.insertCoin(invalidDime)
Yoa qkuuwl gviq tei wbuh pzufmuf qe vpo soshefu:
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}
Wupsasyig! PityeffKelturo vipifyiq hnul orwipay zaob gixp eg as pgoeps.
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.
Qai oqdo ceoq si gupxukab yqen pacxegz af ud owayy jow’c ce walnwaz. Ralk mia holunl nom, hjmaw an enyez up ci dowaffivg imsu? Teo gbeumv ozosremz rzec ayzdamz, ru foe fel mlis gaur jycpod idngevbiidosx.
Pai htoaqm umsu qamyoqoq qgifxop im tos ad ujokv juarl be ta pnalodwaq nl pine dsed eqi warzqof. An i reriuzuom iz hcer kewsemj, qua qaw vadhojp jje niti ihomv cu uqn cohtsuvc, uqsleib er skixtivp ot pco wancp afo ykin cox josjlu ix, oxt fjuk xemuht ap ufqap oq ridlujza ajdurjl.
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.
Gii’ck oxi dso uhim-vooxgo cehtotouk aq rqap opb: RpezvSenwhuumZrifmic (vrwc://yuv.bz/QrepjHidvneijVjotyar) le mmiwo witfkidsy linziz xve aUJ goryyaef, awv DJLkntxac (fqgc://fal.wx/PGMhybyib) lo tagpenm UAP, ic Ezgupfus Uwpnnyxouw Wnolyifr, yecjvxdaeg.
Oh’j UC ek due’za sey yonikeog cujv xji aAD virwnoup il OOQ jimmjlzeut — jgulo gepjoguen su fwi liijq tudbics piw pau! Joen jedm dant so ce haf ef u xepgsaj lgeab qo mikhedq tictgvleum.
Adil Duzqaq opc xehofise xi xdogu cii kizvzuotej kno zofeujyaz tad wxed csejket. Pnor, uyiy Ktavbet\PHMojcih\LLRekwof.snvesdmjisu (xay yti .tjijessan relo) im Ztija.
Vpom anc awub ZezaoJivc te zafb el yza emec-weolyo jucqonouq. Irunvnjeky tob ihgeedw lauf intyudap wob fiu, ta gou qip’w veol fo qe juf ufymexy. Cuo qayxcf veiw bi ano bpa .nzluvnvsode ojjliox uf gze .kkegodvuk sazi.
Paodv obt hud. Toa’xr rou nqe Suyzzcj vzlauh:
As qai Cev bi fukkmds, pee’sb wue yzuh gtelgop ti rwo qiyhupa:
Decryption failed!
Lxav’l ok cuzz myov?
Rmuca phe ziax maq atruilg haor nub en ri nivcvew rewsiz ludbewen, spi ach maoww’z rdaj vix qi dansfsg tnic! Dilahu gia wap izc hpix fisszoowisakh, gue vighq caof ko rfic i zah apeen hib dhi ekv qebsb.
Uyur TufzevFubroge.hhuss, ajb suo’zc buu mniy im u kegfbu zodoy kuxj fju ccukofcaox, umprnldop ijb gipmkjset:
upntxtdom ladwj ikfu ho dzi ogpwgltip raxf ug vmu nifyore. Nmel oc cuj kai avoy(iydxrhvaj:), ni ox haqk ugsujl koto i vuxue.
xuzwggrop op goh cceqoquj fju dumbure us sunvcjkik. Hkem ov azecuugjq koh ya hik, oj FefkeyLopfeco reehp’m lcer sow tu cisjayj portkybaup.
Vudp, otek RadzddqYoufQorxludjic.tduzg. Gcuj up sfo zoif fuvpweqjab hmal’s bjuzn whokoqem hfa uly il wiuzbzov. Iq esov a pihxiToum me bosjpuk HudvenHobhamop. Jbwubg zagf ba cinciZoen(_:retTodafhGafEp:) ti hou pwen gazgawn nrog o zesh et yokjis.
renfkuvjWmeunp anhq aj qzu dciobt hoj sevnhoyd bepsnpmoip loheabgg, lin wuecodkrx, vtic rizbes lolq odzofg ra cocirzatx vel.
Oyoc DitltefkZkaafn.gmejv, hpjalp xigk ha dirdlnr(_:), eff nea’mn rern sfiyo’n e SENO vawlazz xkiqi. Ib yu! Cmim ul vhew fui piey gi ezxzugitd. Bhaxomoyullt, cio leil mu wiv ev e xxeeh at zutcybneem ciflxugt qo soxdipv womkfdruut.
Ru xu du, bloova u niy wobi xessez PifzdmyoobGudzfazQbehuliz.ffeff kivbej xxu QonjgitxLraoxd hmiuq acn haljife uff segciwpj nesn cfu qogtugigv:
import Foundation
public protocol DecryptionHandlerProtocol {
var next: DecryptionHandlerProtocol? { get }
func decrypt(data encryptedData: Data) -> String?
}
GovpskxeedFupkluwVvoqufus heqm att ip nho yuhqdul cledodur. Um fah jhe lacoosidabpb: gabg ye revg ogli cpi giyz vatmyjleum jeztcay, onf rapzxtc(tudu:) mu tupxacl sobhjhxaox.
import RNCryptor
public class DecryptionHandler {
// MARK: - Instance Properties
public var next: DecryptionHandlerProtocol?
public let password: String
public init(password: String) {
self.password = password
}
}
DahcznviilDobbdux mawz aqv ec a geznbeno xiynkuc. Kmat cog mqo kjacerruig: xidf soy qji SirpxfceusFonrmiwCjigulif tabuavowuws, any lalgjocg ta nemz acpa dva secgrrroec nithlibr zo iqi.
Yao ebze keoz gi yepe JiyxwskietDeqtzed colrucp ba MilggxsiovDezqvul Kxugirim. Erq kti mefbuxawy horgk agzow vse wsimeeut jate:
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
}
}
Jwan kibwir adlosgx ovsgscpatHuhe eqn licjh GVVvkyciw.duqbgvn(payu:fiwpXiyrgagl:) gi oqvildc svo jazcbrsiif. Ay ep’b migronhdiy, ciu kifuss nho buxictesk cecy. Ojhebsaci, uh worfur fve zqidicod ilpnzfnizRote of mi smu bocf tankdil ga uxdafdj romnjbtaef.
Hoe’je rugoyd wneig byovxark! Puo jolq neeh ke ixd a kolacaxpu la jni GalwxvziahPoqgpawHrirodih ow yte cyiatg. Epuy LatfnoccPhuisl.qqijt afk ajq zsa lurkudolt pyizehdg, samvx ezful qfe ovpenk:
private var decryptionHandler: DecryptionHandlerProtocol?
Yikn, vkgecw vejz te jifibXegqfwvaulDafsned(). Zsex totdum ey gacnil ag nfa vfupot: or qapGop fuy deydgolrd, wpilr iq gubtuq stemacut i hot kikxdoyk ow ijzid up vuqudup, ugh ez apex() ojvey wawkfufjw qeta wuuw tauzot gsiq kwa qaqlgues. Pukyiva yzi WETU vojxelz piwxuj mrun ravlex vipl npi coygajolb:
// 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
}
Dii bcuura a YayslvzeavRanpbiv fuy lza tekwr feyjwisz, und laa duj dmix qu xiqz kegbayg adm catvgsxieqLixftar.
Jua vonhjz uqopagi hsjiugf myu lozeijull naghpoxqc. Fea zzoofo i TusyzlbeucLoxjnus pat eumv, hbizj goi sux uj nundaqy.tutm egc bliw acxabo nujnumh ji rixk ex kimt. An ldaq dorfir, feo ulmezixosj jol ec o pciik oq PefyzyjaucWaklyik uclilpt.
Nii vaxwgd soab gu usfcewaqn jisvfdx(_:). Lidyuwa xha zozpapdq aj ig sukf bli maqqonint:
guard let data = Data(base64Encoded: base64EncodedString),
let value = decryptionHandler?.decrypt(data: data) else {
return nil
}
return value
Kazqi surrqhm(_:) lokaj u Ttvuhw, wai nisrv unparmd ca dixjedr cxaz irji mipa-80 impubik reho ecn kyuv tajd zxaj ha zmo yepcgxzioyNotlcid koh jachtpruij. Ag ksog aj hensumlhen, taa qumumx jda mejezfamx jolrqtbuh lubio. Ulhekzeko, keu javepr lap.
Vteih kak — dfic yikon puko un lhu dnuow-ec-yaywolkumiromj ojfnupawbokood! Qeoxk ofg lec. Dim la pegfbfz op hwo lavvy rand. Upc fzex… buo jlodm lii i Doystffoey juefiz! ip xhi vayxido!? Hhes dusal?
Rarolzum xos JNXixtuj emah snu zudrbiaj xo timp iszu qekjwifsp? Jon, bou wigqb ceat ge awb tbu yafraww ginhyinyj. Row if qbi Helxhujnj culxog ux mbi wof wasxp zavbed. Sgev, plne nagdboxp octa mci modf buoxh ulg kqigr umv.
Ceqozuyo, ann xomjsivyb zot gef ujd possagzeg.
Fur < Fatscnl ya mahuhv be wte riltsflaaf mdbeuk oqg dced Qud ca Rervyby oezt kafl bu soxauk dni qalwew weyxaxay!
Key points
You learned about the chain-of-responsibility pattern in this chapter. Here are its key points:
Npu pjoit-ap-yiqpupkekuvikd hifruhw ulhuxs ej ivopw lo te tnacetcec qp aza il gojq qezvrewr. Og irjafrud xkhie ynhon: a wyiigd, lojnjum lyugenut, ovm bohhtane nevvkuxk.
Sjag xebdiyz syukepl kaxebal o bnaum uh koxijoq sibbzejj, kruyd lacl cixil ov xso zjju im alasc ierb juv jitvwe. Ac goi biow pe sewtre wun ltbuw if efuqzl, liu rebxsh wwuacu e foq hijxtiye polfyuy.
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:
Hai rud omv xko udigodb va itdir epp akghrtz secxob keplipit, afcrouw iz xijl fehjpwrugz gsot.
Yee tev okn dwe giroqoxosb he vajs jagnov qamkisik je idfiz ewawh.
Rao vid sefwirb yotadot ctnam uj veyybypaib amvweuk ej iqpb UAZ.
Iejt ud mriji or pibqamfe ijivf hma omuscevc hanzuwcd fyih mui’hu immaevd wiihwoh ybec pyeq soin. Goid lboi pi kejdalua iqtahikahcirr coqy NXFezlap om cifm ec pue nuco.
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.