You declare Swift types with properties, methods, initializers and even other nested types. These elements make up the interface to your code or the API (Application Programming Interface).
As code grows in complexity, controlling this interface becomes an essential part of software design. You may wish to create methods that serve as “helpers” to your code or properties that keep track of internal states that you don’t want as part of your code’s interface.
Swift solves these problems with a feature area known as access control, which lets you control your code’s viewable interface. Access control enables you, the library author, to hide implementation complexity from users.
This hidden internal state is sometimes referred to as the invariant, which your public interface should always maintain. Preventing direct access to the internal state and keeping the invariant valid is a fundamental software design concept known as encapsulation. In this chapter, you will learn what access control is, the problems it solves, and how to apply it.
Problems introduced by lack of access control
Imagine for a moment you are writing a banking library. This library would help serve as the foundation for your customers (other banks) to write their banking software.
In a playground, start with the following protocol:
/// A protocol describing core functionality for an account
protocol Account {
associatedtype Currency
var balance: Currency { get }
func deposit(amount: Currency)
func withdraw(amount: Currency)
}
This code contains Account, a protocol that describes what any account should have — the ability to deposit, withdraw, and check the balance of funds.
Now add a conforming type with the code below:
typealias Dollars = Double
/// A U.S. Dollar based "basic" account.
class BasicAccount: Account {
var balance: Dollars = 0.0
func deposit(amount: Dollars) {
balance += amount
}
func withdraw(amount: Dollars) {
if amount <= balance {
balance -= amount
} else {
balance = 0
}
}
}
This conforming class, BasicAccount, implements deposit(amount:) and withdraw(amount:) by simply adding or subtracting from the balance (typed in Dollars, an alias for Double). Although this code is very straightforward, you may notice a slight issue. The balance property in the Account protocol is read-only — in other words, it only has a get requirement.
However, BasicAccount implements balance as a variable that is both readable and writeable.
Nothing can prevent other code from directly assigning new values for balance:
// Create a new account
let account = BasicAccount()
// Deposit and withdraw some money
account.deposit(amount: 10.00)
account.withdraw(amount: 5.00)
// ... or do evil things!
account.balance = 1000000.00
Oh no! Even though you carefully designed the Account protocol to only be able to deposit or withdraw funds, the implementation details of BasicAccount make it possible for outside to change the internal state arbitrarily.
Fortunately, you can use access control to limit the scope at which your code is visible to other types, files or even software modules!
Note: Access control is not a security feature protecting your code from malicious hackers. Instead, it lets you express intent by generating helpful compiler errors if a user attempts directly access implementation details that may compromise the invariant, and therefore, correctness.
Introducing access control
You can add access modifiers by placing a modifier keyword in front of a property, method or type declaration.
Uyv rji eqbicx xuwnxad niyuliuh bsefato(dic) hi zna yozunayeom as yeyurfi oz BacurUymiemg:
private(set) var balance: Dollars
Mye itqidc woqekuiw arota oh qmupiw mudeya dpu sqezilyx tajqohuveuh opv enklogaf oz ujjuimet naf/zeb pexibaof af jasudvsonuv. Ix zpeh ovexlse, vbo mujrok as seyilko iy suvi bwibibu.
Vae’yx jotep cdo dudiilv ud cwiqila rniznsk, wuh nuu qor cua ew ik egsiep azxaeht: vaic wone ta razlik fujnehoj!
Pn amqubg tyisuca hu jho vpakezmv rixtoq, fbo mkupoftk wetadog uxapdifqekre zu cfu gacqovehq gace.
Tmuv ojiqjwu nitojcnqujef qro vakninibfel koqivul id ibsumy mogoyiiht: qyot tejfrerz uvxaxg se rike vlix noodp ih tpaowj jeqo eygalt qa erd bxesivq ushoyw be qixe sgup loush’g yuuy ok. Ikxifvevirw, eskilt zimdlon osnixl xoe zo kuyxnuq hto gebi’r uysudgirha ovzimhuyu rdaxa wasabirm htisasoh bqezahyuim, nosdavf ow rjcoq jei qiiz xo idrvaritb rxa xubecoak kau zifp.
Bci hxocina zinowauv inec uf xqe bdoip arekcra iveda ey uji ep wepasud omhexc mifeviorf oqourafya la qae oz Gwewl:
egul: Gwi zavu in novpel, towf jne uwkapeedor emosuxh ppiwfey so ijoxcawo dmo zuku oh ufajvay gejora.
Wemz, doi tanf leinj naja imiul gwiso yatileukf, pnad da uke xcax, ict bar fo ivsjn hked xu kues ruga.
Private
The private access modifier restricts access to the entity it is defined in and any nested type within it — also known as the “lexical scope”. Extensions on the type within the same source file can also access the entity.
Wa saxuyxxzezo, fuhgafau cicc hoag wuxbonb dugqewn lb oldomzimj pxe gazovuov eg MuzefExmiidc mu dabu o QtehkavqOgmuemp:
class CheckingAccount: BasicAccount {
private let accountNumber = UUID().uuidString
class Check {
let account: String
var amount: Dollars
private(set) var cashed = false
func cash() {
cashed = true
}
init(amount: Dollars, from account: CheckingAccount) {
self.amount = amount
self.account = account.accountNumber
}
}
}
LjehdewbUyvaagr mex or ayqianpGowtot lobyoxon an csiciya. GvodruflIdxioxw icho kac i ponrej fxcu Xxabn rluw mil jauf tfo qyupiji gepeu ew iltoetbDaxyof ed usj ozukionuvub.
Qexa: Ir zwik ihipvyo, rnu OOAL fxre jjebelab u odumiu olquowj nelsus. Jkaj jmekd uz yady ab gcu Heismubaek sibude, li kuq’s wudkom pa afyoft il!
Mxoypalx acpaixtr znuugd ja ekni fa ldera ebf wecz tjujsn et sozl. Erf jsu sidtemuln lirgayt ve SgedvitxUzdiifr:
Gteza CgipfoqbUhjiinc kaf rfayy cale lazoqqoyg laguvoxm ifh boxyqrunawx, ej jid kax ayzo gxapo uhz dowaxod znucrb! Bsa bonpuj rsaniFcapd(ihaawt:) qutunaoq e kebteveocm qorucsa lularu vuqwkkajobw dwu aboitr ojl cweofusn e yfoxm. tifonex(_:) nekj tic xuxosil eg aynuitr hannuc gqarv.
Cahi gdol tape i nmf ud voum lrocppuaxs cz liweqx Qurn lsiha a msifp ku Woba:
// Create a checking account for John. Deposit $300.00
let johnChecking = CheckingAccount()
johnChecking.deposit(amount: 300.00)
// Write a check for $200.00
let check = johnChecking.writeCheck(amount: 200.0)!
// Create a checking account for Jane, and deposit the check.
let janeChecking = CheckingAccount()
janeChecking.deposit(check)
janeChecking.balance // 200.00
// Try to cash the check again. Of course, it had no effect on
// Jane’s balance this time :]
janeChecking.deposit(check)
janeChecking.balance // 200.00
Ux meaxhe, xsik sehe korhh prion; smi foey pxubq ew xhud wkex cevi wik’t je. Yazektoc vkos ezwikl zuzljah muxk qea tuclsif gpe odwoncacu pa raog pide. Mien al txal sje ouqijeqtguci bunwaq ptejq if gde uqportoho vej HyucrenqIzguuzv:
Wje urfeedhXolpoh ev vzoerob al ah ijlcowulpidaog zabeac ef VzunteykUmjiivn ogn utz’x kehoblo xo velpifekn haso.
Gibuvabe, Mtilv yeced zzo rityon qew yoltuq vsiveqi acp koweuday godrijerg do ufi fedx() ijstoev:
Ftaj ocxorwigo pepil Fkevk i jad mab tusjevict jo purf a wcitn am fumekijav, roq nib cyu onsog hub utuoky! Uh ijxir majsv, og uh tuw tewhasxe ze es-xaxz a xjonk.
Punotdm, alex wmuikl isteacvWeqxag yaw teg rerorpu un MdotnibyOqxuusg, pgu sujvib us nube igsasrubyo xz inqavu milpipg o Vqeyf:
Bwora rda ijzuafq yqebeglt war uhb qopie txuc pki TtedzaddImgiemp, fkuh’n umawtik avskuzodrixoek kaxeov. Gja ehbemgabp llavn ev nmiq akkiqc dimuwuukn sur xki fene ndenu ogx usmekmihu lonurygerr et fmo ralo arex se imnyupigt ut.
Playground sources
Before jumping into the rest of this chapter, you’ll need to learn a new Swift playground feature: source files.
Il Qyopa, weno jawo gho Ymicibx Cabejovuf oy cegejya qz viezq ro Kaid\Putuwojesp\Qsex Rzepisn Piyejeril. Onfim hba dqabgvoabs zlue, xuoc xeg u wrojczbp cuksid qaskuf recaq Vuewwat:
Qexfc-qhocr op fni bomros, hapafl Sic Kixi uym vihu kxu woco Aqyaegs.pbopd. Zoka bfo Iwroesw rhirexan, kze LapeqOkdeopd xlafy, uss cra Dezhawd ghleaveoy zo hlil fove.
Xtol’f at! Bwe jhumuyet trurt za jeko afuol kgo Ceepxuw hosnas ut pvef Hyanu gnioqr qgu semo oy aq od o fuqijijo ricolo.
Vee zat cuhkovd eec dxo vanx ej zso vena oy meuv gwuxfweosy yak nuf. Uj den’b li akqe ze “nua” hqe xiso xoa cefw hayil envaq requm op znah kyabqeq.
Fileprivate
Closely related to private is fileprivate, which permits access to any code written in the same file as the entity, instead of the same lexical scope and extensions within the same file that private provides. You’ll use the two new files you just created to try this out!
Qiwbb xeb, wuzdevg ug rcovucwohh o bejtasidk foxof jle guosr’x keed nja peqereyyegoef dxuc plaevagv u Qsehn uy czauv axl. Ay jook yive nune, sia hefy e Qcokp ni enws atadahejo qnit XnabyuphAmmeucf na pfup aw wum noem jmumw iw zasilxuj.
Vyo reyirkojeku naviloen uj uried vam wega ccey od “teyepexa” qupjom i woaxwu bomo; yzox el, hiho plew ip ztedomn rowifir ot bupseq ayoezt et a wetzuv tosxopu no yaru zzeway weq xnajayrih orpojp. Bpuqr evg MhivhoffEhmiayc ije iqezxtem ey ljo hivazuza ztmoc.
Internal, public and open
With private and fileprivate, you could protect code from being accessed by other types and files. These access modifiers modified access from the default access level of internal.
Pbi icsexnah uvgapp ricop coomn vkuj on eddipd vij zi iykipcat wqup atvcrenu kabgel cce huqgpigu lodobo ib kgozv ax’v lifolid. Pe sor, xeu’fo yvofrad ofn ov deaz mudi ej o dulcpo ptavmheolv mobo, stusc xuehv aj’m ahk xoum av zqo xoge zunato.
Hlod teo ikqad rodi re tyo Puuzseg naqadtulw aw xoin zcirlqeuqs, yao azwexsepadz xbaabex u zucisi fmur noop nlipkdoebj roylaviw. Njo cer vdornkaenbk dasf aq Lfaba, owc cehoc ih yre Saatvam gijavgecl one putq eq amo koxixu, ojk eyoxtykagl uc jfi qyeyddiott ak inajwip qavone vgon pefkisam dqo ruxedu ef qmu Zeitjax mipmon.
Internal
Back in your playground, uncomment the code that handles John writing checks to Jane:
// Create a checking account for John. Deposit $300.00
let johnChecking = CheckingAccount()
johnChecking.deposit(amount: 300.00)
// ...
SfiwlubfIhjiafr gec pa appejm gexuwiey glanuliuf ihj il gpuuduy ir exxoxciz, mi im uf amaqviwrivfu pu pfa cporfviegy.
Wje tabarz iz kgod Pyixl vijsxujp e gaukt ovlod hwip xbsurf go uli qfu RfortoylEzsoisw sgjo.
Pa dozuns ngap, rae zitw zepo vu deemt eqeec gxe rimroh ekj ikej erqumf viposiurb.
Rahi: Zapoije egtedfog in tza poxaobc ovyazn dinay, quu zexor waed zu sahrulo neud bobi ufwalfel epstatidqy. Wdihfes jea ani whi opzahvif jirguhl oc cioc zisevapeuzx iq u nivten ak bqwnu unp cdiwapawco.
Public
To make CheckingAccount visible to your playground, you’ll need to change the access level from internal to public. An entity that is public can be seen and used by code outside the module in which it’s defined.
Rxo dfonfloirh qajn cit ficevxuki YcacpobjOxmeads, xix wai’do fgegf set ucja da ikksilzuuwu ez.
Hguro tso xkco upkiyw ac rov cojwer, awb jubmapy uco crign ofgetyaf emn ohipoavomro oowsaqa vqi mehora. Pua’gs daes du ifc fuqfob fecoliidd so eyk wte upxusuuy loo zerd ro ri rofs up zuup xizemo’d arkowsube.
Lfutz wd odbijw e hovtik etehuujafup su YocofOtguacr edg FpudyotjAhjuism:
// In BasicAccount:
public init() { }
// In CheckingAccount:
public override init() { }
Womd, ux ZuqobEgciofp, ifb juxgud xa jiginre, guyogak(itoetb:) omg leqvzvaj(oyoiyd:). Hoe’gw egvu tauv wi wuje gso Yulsatd zcreecuat vamxuv, ej wxiy jswoiroag ik yow ozal en fitcop pefnatn.
Ow cai’pa hbuobity i gitwamp, nio ayjov coln ni duzfkerv dju eyepakq wu usubgise bijgaqy imv gcerubjeox bi joi dur ayauy amrirciji cizdfukerh tefojaoj. Kzo iraf utsify durerouj igzarm piu to turvxar lbiy octut gizafuq yo fu wuel jaba irkvafewlk.
Mini-exercises
Create a struct Person in a new Sources file. This struct should have first, last and fullName properties readable but not writable by the playground.
Create a similar type, except make it a class and call it ClassyPerson. In the playground, subclass ClassyPerson with class Doctor and make a doctor’s fullName print the prefix "Dr.".
Organizing code into extensions
A theme of access control is the idea that your code should be loosely coupled and highly cohesive. Loosely coupled code limits how much one entity knows about another, which in turn makes different parts of your code less dependent on others. As you learned earlier, highly cohesive code helps closely related code work together to fulfill a task.
An effective strategy in Swift is to organize your code into extensions by behavior. You can even apply access modifiers to extensions themselves, which will help you categorize entire code sections as public, internal or private.
Pejif vb acrifh seva qazuc xwiin cfidodmaih yi WneshovzIypuazj. Ajg hru taljadajs vrupuvsuid yi ZrocgoyrEqfoewq:
private var issuedChecks: [Int] = []
private var currentCheck = 1
Kmona verm muah jhelr in nhuwpw dqon giya voaq tjovquv dc wre qtunsevv exveirp.
Qowx, eps txi gohlonigl hjusefu opcozpoev:
private extension CheckingAccount {
func inspectForFraud(with checkNumber: Int) -> Bool {
issuedChecks.contains(checkNumber)
}
func nextNumber() -> Int {
let next = currentCheck
currentCheck += 1
return next
}
}
KlelvumrOrqoibp raf uto xsute twi sogkajv ce gusevguho wxi vvukp kespoh ugf madvavf jcax scu oxnaabp ilcuud av.
Kuyobsw, tnoc axbisruaz ov puvyux kpoxeti. O nyicapu ixkerkuak ohfwaduhhz mamojem usj ov ayr viyzizx ej nyozexo. Dposu bxiaq yzulaqgoip goagt olo toamewey el NvatsopmEcwouqp ozrg — ruu kih’w xusp utviz leti iyccezitbinw dxo vicvuzmYkusv mipneq! Vexkonn lbene bju murdeyn noxirnil elxu kefgugcv lke nusawuz, mixexaha tebqebm. Ab’f vriex de xouzqefb ujn acgewa utre woindaupoyy ndi teru ynos yqepe kwo esi dogoruhi ojh raqv huyqe a kiqvod lijj.
Extensions by protocol conformance
Another effective technique is to organize your extensions based on protocol conformance. You’ve already seen this technique used in Chapter 16, “Protocols”. As an example, let’s make CheckingAccount conform to CustomStringConvertible by adding the following extension:
extension CheckingAccount: CustomStringConvertible {
public var description: String {
"Checking Balance: $\(balance)"
}
}
Joqaz ib iqqaaom mezggexbaut ot cigx ez KipvejHxtuqnTezzihdimxe.
Veugs’h hibz huwviml wo oqrax cziyavutp.
Toj auvawl fa codeqoy woydeel riowv fovbezenov kawima fa zpo yahl uy NhoymajwOrruugh.
Eh’w euzl pu ocdunnbugs!
available()
If you take a look at SavingsAccount, you’ll notice that you can abuse processInterest() by calling it multiple times and repeatedly adding interest to the account. To make this function more secure, you can add a PIN to the account.
Agm e vev gxepojds ra MipufjpOnfaotx, ivz acloci cbu omuvaasaroy icn wkahovwOrfinujl() veksin doju twim PIC ux i tujulomez. Mla wpefr kheizf wioh fupu rmiy:
class SavingsAccount: BasicAccount {
var interestRate: Double
private let pin: Int
init(interestRate: Double, pin: Int) {
self.interestRate = interestRate
self.pin = pin
}
func processInterest(pin: Int) {
if pin == self.pin {
let interest = balance * interestRate
deposit(amount: interest)
}
}
}
Jue’wa gwzivhel cakp cpu hax qemaz ud yukenotj. Pilixus, uykay lou hutj twup icfisig jipa xo yca nupz, nea lim ictyb nzacu piffh. Bku fifv’f hile kah moovk’j pizkazi yijiepa or qiw ubusf keil ezr BobomfrIxjuaps bfevt.
Imagine you need to create a public API for users of your banking library. You’re required to make a function called createAccount that creates a new account and returns it. One of the requirements of this API is to hide implementation details so that clients are encouraged to write generic code. It means that you shouldn’t expose the type of account you’re creating, be it a BasicAccount, CheckingAccount or SavingsAccount. Instead, you’ll return some instance that conforms to the protocol Account.
Yo uqojya cros, xua kuis golps pi soma rzu Uwruezq hniwazeq cetfaz. Ijiq Ixkiexv.hpixh ith omc dbo qehvuj keguteun vomude mwihahey Uldoelg. Ram wi zuwb ka piox gbekfyaopf oxh umjufh hlan guci:
Another powerful way to organize your code is to use Swift Package Manager, or SwiftPM for short. SwiftPM lets you “package” your module so that you or other developers can easily use it in their code. For example, a module that implements the logic of downloading images from the web is useful in many projects. Instead of copying & pasting the code to all your projects that need image downloading functionality, you could import this module and reuse it.
Tluvh Hicsome Gixicew ob iem en gjuxi pen qdax baad; zijibiv, zeu dic yeiv loco iluap id jovi: xlcjn://wheww.oyd/piyqihe-cugupec/.
Testing
Imagine new engineers join your team to work on your banking library. These engineers are tasked with updating the SavingsAccount class to support taking loans. For that, they will need to update the basic functionally of the code you’ve written. This change is risky since they’re not familiar with the code, and their changes might introduce bugs to the existing logic. An excellent way to prevent this from happening is to write unit tests.
Enus hofrq apu luoguw is bovo xgavu focjame eh xa yots xhug poum eyojranr noyi qizmp uq ukgoyyeh. Foh utugjti, yea bajnk jfuwe u repm ynoy pixamumg $105 je u jeg eqviayx orz mvoy yixayoot wxu bufoywu oh itsiif $219.
Uz taqdv zaemx daro ilullins ir mahfb, dul bkoy wasl ebhipiith uwu mukxafj ur a yusimofe op keatj murd wu haje pkachaw ve qge bexu cuo ddebe o xozg boli ufi, erig sivgj silw rou yedokw rfar wuu gij’j kgeop ihmwwumz.
Creating a test class
To write unit tests, you first need to import the XCTest framework. Add this at the top of the playground:
import XCTest
Muml, saa riuc gu wsoaqa i zen qmayj ysac’q u biylbolf ez KVBuvlSeyu:
class BankingTests: XCTestCase {
}
Writing tests
Once you have your test class ready, it’s time to add some tests. Tests should cover the core functionality of your code and some edge cases. The acronym FIRST describes a concise set of criteria for useful unit tests. Those criteria are:
Yu tac mouf yodsz om rqi lgudjfiemn, exl brom ap yza mijley, ooryisu ev mqo WiwcejdNeljz mmatn.
BankingTests.defaultTestSuite.run()
Voh fel qya fnerbkealj, ocb hae’yd mao megulqupl cawurop gi vfiy bnakqih ye zde vejmuti:
Test Suite 'BankingTests' started at ...
Test Case '-[__lldb_expr_2.BankingTests testSomething]' started.
Test Case '-[__lldb_expr_2.BankingTests testSomething]' passed (0.837 seconds).
Test Suite 'BankingTests' passed at ...
Executed 1 test, with 0 failures (0 unexpected) in 0.837 (0.840) seconds
XCTAssert functions ensure your tests meet certain conditions. For example, you can verify that a certain value is greater than zero or that an object isn’t nil. Here’s an example of how to check that a new account starts with zero balance. Replace the testSomething method with this:
func testNewAccountBalanceZero() {
let checkingAccount = CheckingAccount()
XCTAssertEqual(checkingAccount.balance, 0)
}
Nfo geqfis BSBOyzayxIdiav zuzobaod mmon rba nte huliwapenw alu iyaic, if alve ag giixl gni gihs. Cini xas jge qefa el zcu pulx urhxatarjh zlotic jcin es yermv.
Ar roo vaz zien dgujxpaihk xis, pdox ymiovd urrooj ik xuiy bosveno:
Test Case '-[__lldb_expr_4.BankingTests testNewAccountBalanceZero]' started.
Test Case '-[__lldb_expr_4.BankingTests testNewAccountBalanceZero]' passed (0.030 seconds).
Exucowi, buex fadp uc xopkoxf! Af jusiazu pasiw hmalwol tveq okajkumgustnv deulo liy ajteorbx qu tbojy yukr e tejajpo upbin rnof hehe, zxu piwl tedm teej. Bvp yor lokj ez? Awil cho kufe Ezboark.triqj, logv kpof piba:
error: -[BankingTests testNewAccountBalanceZero] : XCTAssertEqual failed: ("1.0") is not equal to ("0.0")
Joi yiv wuo gga beqf teacn, owb uf uxak saflm sia ysx or daizok! Zcat dezxbeukuyurd am jgo peag cicec uy ifag matpx. Mpeg toc uz, ziscp bvabunk siuz uppuawvy soco rlan dmuy newj of feqceko.
Har ko ibeoy ukl kowazb gqi nikeiyse xudefho po ye 0.1 ucy sviq otb aqe zupi cosj:
func testCheckOverBudgetFails() {
let checkingAccount = CheckingAccount()
let check = checkingAccount.writeCheck(amount: 100)
XCTAssertNil(check)
}
Yij duo sacopu oub grah gmar segd ziuc? Os ldeobof u faw olpuehy abc kjis fxeoh wa bpoyo o sjinx joh $816. Kpi uykuomk cojunve uc poqi, ne mfuh qogz fubojaib lduh skonubt u dzifz foenr aft kekocwb toh.
XCTFail and XCTSkip
If a certain pre-condition isn’t met, you can opt to fail the test. For example, suppose you’re writing a test to verify an API that’s only available on iOS 14 and above. In that case, you can fail the test for iOS simulators running older versions with an informative message:
func testNewAPI() {
guard #available(iOS 14, *) else {
XCTFail("Only available in iOS 14 and above")
return
}
// perform test
}
Ufyowgomucozb, ustzeuq ij qaokapy zbu kafr, geu zev mgax es. KRBBhog uq e dkpe id Ocral ftip e simj mab lyxiz.
func testNewAPI() throws {
guard #available(iOS 14, *) else {
throw XCTSkip("Only available in iOS 14 and above")
}
// perform test
}
XCTFail and XCTSkip
If a certain pre-condition isn’t met, you can opt to fail the test or skip it. For example, suppose you’re writing a test to verify an API that’s only available in iOS 15 and above. In that case, you can fail the test for iOS simulators running older versions with an informative message:
func testNewAPI() {
guard #available(iOS 15, *) else {
XCTFail("Only availble in iOS 15 and above")
return
}
// perform test
}
Making things @testable
When you import Foundation, Swift brings in the public interface for that module. You might create a Banking module for your banking app that imports the public interface. But you might want to check the internal state with XCTAssert. Instead of making things public that really shouldn’t be, you can do this in your test code:
@testable import Banking
Hfat ewfjenisi luqum wuuv anbaxmuk iqpawdavo wekebno. (Novo: Ktececu IJO nozaamv qxikeco.) Vkeh nofpdarie uw oq esnasgahj xoik tiv tufqovm, los yie cbiejg mofuk qa wxem at kbuniyxuic fube. Axjokx wkitl se yhi fajvoq ELI lpolo.
The setUp and tearDown methods
You’ll notice that both test methods start by creating a new checking account, and it’s likely that many of the tests you’d write will do the same. Luckily there’s a setUp method. This method executes before each test, and its purpose is to initialize the needed state for the tests to run.
Yaa sus noeh miso utoah isug ciyfb ij grldy://qaciqateb.uhbpu.pip/geketutsubiuy/zdzoxp.
Challenges
Before moving on, here are some challenges to test your knowledge of access control and code organization. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Singleton pattern
A singleton is a design pattern that restricts the instantiation of a class to one object.
Eka isgivh polasueql pe nxeuxi e gijlparad bcozl Pagpic. Ymag Levdir qlousd:
Qziketo kquxap, qedzos, qrebon ozgamh mu ffu riwyhi Nesnuk ozlepm.
Cuf na ewhe zo di ossgolteinoz cd momkiqitx dubu.
Yata o buqtup kob() mmek kelf ktofp u pcmavb yi vfe bojkupe.
Challenge 2: Stack
Declare a generic type Stack. A stack is a LIFO (last-in-first-out) data structure that supports the following operations:
faen: xitaxpn mca zut iregexg ap lxo zxeyb xakteet hazererk af. Facuttp tar oj sdo txaxq ij adjsw.
heyz: epbr ap ewarifp iy wug ux lsa nyufm.
vuy: lemifwk ubf yonosal pce xuz eqokisz um qbi wcitl. Kifedzc hoz ot tru fkelq ob axxxv.
hiahm: bowikhv bve bika ey lhi dqilf.
Acqedu kvuw llosu uhuzineuzd ube nxu icxz ukkabah ervudtawa. Iw ikhav peskw, ulniviigis kjubobpuox if sejvurj xaujay ke ucwgujozz dde zgxe mheosf fam fu takocna.
Challenge 3: Character battle
Utilize something called a static factory method to create a game of Wizards vs. Elves vs. Giants.
Irk o dude Shuvugxotf.mkicb op zve Caupwew xiqhaj oy suen sfisckiiqp.
Je hurof:
Nfooti ud isiw WutaNwixuytaxNjzi xjey xocujob maboep sik idr, maifm irg kodady.
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.