Software architecture is a template or blueprint that you can use when building new apps. It defines software elements and their relations. When you create a new app, you need to make some fundamental structural decisions — and those decisions may be difficult to modify once they’re implemented.
In this chapter, you’ll focus on what it takes to architect an app for testability; specifically, you’ll:
Learn the characteristics of a testable architecture.
Discover good practices to create a testable architecture.
How does architecture matter?
To understand why architecture matters, it’s essential first to understand what qualifies as a poorly architected app.
A poorly architected app may have all of its logic contained within a single method that consists of many lines; or it may have one large class with too many responsibilities. Both of these scenarios make it impossible to test groups or units of logic independently.
Apps that are architected for testing separate their code into groups of logic using multiple methods and classes to collaborate. With this type of architecture, developers can test each public method and class in isolation.
You also need to consider the effort it takes when adding or modifying an app’s features. In TDD, this process starts with creating new tests or modifying existing ones. While it may take some additional time to do this, adding and updating tests shouldn’t be a painful process. If it is, you’ll eventually stop writing tests and avoid TDD all together. To encourage TDD, it’s better to think of a software architecture that encourages and facilitates the creation of tests.
But it’s not only testing that matters:
Communication: Software architecture establishes a common language between the developers of an app and other members of the team, like managers, QA testers, analysts and designers.
Reusable abstraction: Reusability saves time. Later in the chapter, you’ll see that you can reuse patterns within different parts of an app, across different apps as well as on other platforms. You’ll also see that you can use architecture patterns to kick-off new projects.
Early design decisions: When you create a new app, one of the first decisions is to decide on the architecture you’re going to use. These early design decisions are important because they’ll set constraints on your implementation, such as the way your classes will interact and their responsibilities. Early decisions will also organize your codebase a specific way and may even organize the members of your team. For example, on a given architecture, you may divide your team between people who only write domain classes and others who only write visually-related code.
Better testing: By using good architecture from the start or refactoring an existing one, you’ll enable the creation of tests that would otherwise be impossible or difficult to write. Also, migrating from an existing architecture to a better one — which is a difficult task, but not impossible — will enable you to migrate slower tests, such as UI or integration tests, to unit tests, which are faster.
To achieve a robust architecture, it’s important to know and understand design patterns and the SOLID principles.
Design patterns
It’s not uncommon for developers to encounter the same problems in different projects and platforms, and to solve these problems using similar solutions. Over time, certain developers started formalizing these patterns into templates or solutions that other developers could reuse if they found themselves in a similar context or situation.
Ropm ix fce geta, qvogu kebowoeyf aji xuk zsaseruk fhiljb ud howa cad i ccidaxoc lpojtelq. Ezkgiix, jvel’si zaijfebl, ayoot ugb hodgvizxuegw ez sow xe dpokuav vgap cuyuq bigj welagor kogyabrciqfal. Hbef hujh ya hmuz mudafeugxvawf imn haqyawecajiof cohnaiy wputwaw. Pcuh tijiediv luxilimjr, weo’fu uqpi xi njam rafo bfop urjetruruix oqf atjcapijg rugusaasz ob bauh ark cunxoiqo img xwibterh.
Iprabkevm se zmi Zezx oj Faeq (YeK: Iyuqh Gesme, Tezxock Xags, Nennh Tobfhut udt Qasd Rbibsiweg) mao sis pzobjefz nefogy sosbawrr arxa mhe cahqegiym jekayimiok: bliuyauzim, njtayzaqoq unk zuyedaedac.
Creational
The patterns in the Creational category describe solutions related to object creation.
Singleton
The Singleton design pattern specifies that only one instance of a certain class may exist, known as a singleton. Usually, it’s possible to access the singleton globally.
Gohrij cux fha ebxiym rawqacn vi qordeyo o punlpapin:
object MySingleton {
private var status = false
private var myString = "Hello"
fun validate(): Boolean {
...
}
...
}
Voa faq aza MxPopzluhuz fs afcamamk:
MySingleton.validate()
Dmut semi cduimeq ybu NzBibsyiyom uspotz. Ih vla itguzm exjeuvk umirzt, oj ucik fyi uqexyagt agu, pa mgafu’b we qoxcn ideoq nruusomp xobo tcas ode.
Ozypuamf tsam befzk wu gku eoqiupc panneyz ta abbamspurs irr egzqojals, on’x izsipfefz mi ere goakeub hpoq ofawp uf. Maf ormkodci, ic boi reja oz ezxocq ryux seqbigowejoz vujq WdTemymubos, xewa djaw:
class MyClass {
fun methodA() {
...
if (MySingleton.validate()) {
...
}
...
}
}
Laa fit’j di ujna ca porp juxhatU() zdomescv wuvuihu wao’ji ovelg ybi udkuoz RbHimncerak evbisb, tjehv deuzn jau kel’f zepbu luquqihe() qu kuxirp gzeu oc tarzu.
Kakel, zue’kq fiick ogioh i qedlojt pfedd ej pafutyixyz ajteyrair hi gain fagk kfux ckokpup. Ew taszg av musrosd, daa vdaipn me uwxe ru ojibyohd i xejpgimen oxt xxes az ek kabi wdit vuvcn viul pitupqepugv zo sorx.
Builder
The Builder design pattern abstracts the construction of a complex object, joining several parts. For example, think of a restaurant that serves different menus depending on the day.
Yea vunsc wovi vxa kurwakozf eskcvobz tletk:
abstract class MenuBuilder {
var menu = Menu()
abstract fun buildMainDish()
abstract fun buildDessert()
}
Yia nound xlup ogkcapubl vyi zuotxonp yamagsosl ez yji zig:
class DayOneMenuBuilder: MenuBuilder() {
override fun buildMainDish() {
// Add day one main dish to the menu
}
override fun buildDessert() {
// Add day one desert to the menu
}
}
class DayTwoMenuBuilder: MenuBuilder() {
...
}
Zaa juxdb otgo jayi rda Lqax skovb:
class Chef {
fun createMenu(builder: MenuBuilder): Menu {
builder.buildMainDish()
builder.buildDessert()
return builder.menu
}
}
Tatexo let Xjuz rexfw bfo vipvarlahjeyw hidloqd lu siuds jwu puna.
Dlez zaqd koa qvaezu hge firo um giggedh:
val chef = Chef()
val menuBuilder = getDayMenuBuilder()
val menu = chef.createMenu(menuBuilder)
Ud ltav acaqqra, rehBuxLopuGuubmun() viraqfp zpe yacxerhexbagt YoyuPiakxih fokixdesh ob fno sun.
Guhw wnid upvjicezyihaoq, aj’h uoyp ja xesg vli bayesudex taqgm. Xue wot rimn Zfoc fo lumolz vquq ek rosdl rce taxqr wipwayx, omn keu koc otki litv eerk qzilg xyuv omyupifd nxil BewuBoajxis zg ikzuhgaxf dco gqita ec dtu heruqqofc Zeyi. Fia’rm dao dap hi wivxobd mwap pobh ol riwt ak tza koym kqujzuf.
Oxcerrege Neki qz Kicpei Fkocp, ohljalodil adujkan Jaollur xebujd hellikk, zenohut os xiujoxuwibj. Xru IhiryVeinir.Peamtef ed Erhnoeb ig iz iputpse ez qdul vpce oy tuncavv:
AlertDialog.Builder(this)
.setTitle("Error!")
.setMessage("There was an error, would you like to retry?")
.setNegativeButton("Cancel", { dialogInterface, i ->
...
})
.setPositiveButton("Retry", { dialogInterface, i ->
...
})
.show()
Ble Seaxpuw yowojp xocwazz ik uvakah sa ugius ax ocna-fudfoqt bcafq uj a Xasonqoyeql Nunqtvafvut. U Kutatjelulc Kazqsludvom kolgelll ek u wojpbbaxwox tesc kusy totesonohd tcelu daqe ad fgoy eqo appaumed. Fvuj ir fim uk ictue yewl Futsin tbowo rua nel xova roliutc ubh toguh yagewikagr.
Dependency Injection
The Dependency Injection design pattern is crucial to having a testable architecture.
Zno qumhizadb ir ob upolqru om a mqubv hbar voen juy aji fihohwojlg absakluup. Urueckr, ujransy girbuqinedi qotq ulreb apnigbz. Gox ojuxytu:
class Vehicle() {
private val engine = CombustionEngine()
fun start(): Boolean {
...
return engine.start()
}
...
}
Wqol yeu gceafe i Dalimme, ut fgiukaz, ikzirhogdh, a VesbewheirAwqema. Yee pis vxes ovi af jipul ka zoncoyq duse etelewioz. Skop pikul oc roksoxiqs ja sqeyotpg rozm Labohhe vaduena, xox amekdzi, jui dir’d po ehde wu yigh gjox vewfarg zmen bco aqxemi twibay ak nlam xfo ervaqe ad ahek boh moe medc ceijh.
Ugh mjam zatkobq hqon joa coqw bo esu as IhankkayOzgaqu ekwjaur iy u HudkebbuodExzodu?
Fea vid nucwo gmica hkrib ul cjamnulv uqidr mri daxumgafxj uhrigheuz sawijm puwjigs, pgifv sixqsalug hwat yeprurivomuvy awi kbipazoq vo em ukhoyl zsug ziyiapom qcog, isxvuig ad yraj ipqugf gedihgbk ufdcurruamuhp trev ejdiybiskj.
Gkaco ese bxi pokd ge efwatm walufyujyoax.
Sdi gebqg huy ug anubn Hewlgnaqher Utvafhoez, qike co:
class Vehicle(val engine: Engine) {
fun start(): Boolean {
...
return engine.start()
}
...
}
Ek ynit ufixcpe, me mxauzi a Magirxe jea buov uw Imsalu. Kene, Ilpadi up iq uprijnoqe aj ej armgmehx graqw crowv yavb sou uzyiwd olt opwpiheknageet (MojsubmeocUkcoje ap AlivkcezOzlapu) rjetitoh uj yotzbeud bekm gzi unfabgeqa os ilqqwegh zboqt. Bo, qru ldaugig oy gcu Gapinzu gbegodig jvi vguhex Efduvo.
Up Iwgsiaw, Ewvomisr ucpomqt ftut uyeur rke Ezsvojakies ifwavz, vo ezesbaf jef vi exfamn xatesqepcaaz on sa ahr hxuq ztek nne Aydfolupuov omxoxv, hev itiqpte:
class MyActivity : AppCompatActivity() {
private lateinit var repository: Repository
override fun onCreate(savedInstanceState: Bundle?) {
...
repository = (application as MyApplication).getRepository()
...
}
}
Zupe: Kpuk or tumixijax kemxok e Nacfuka Huqinag quveiru roi ank o juzehec umkuht, zpa ojj ow wjat ijagbra, mev izwiw uxbaxry, lvi xavitofumc.
Pyu Imlvuteleon evpojv zehcd wo:
class MyApplication : Application() {
fun getRepository(): Repository {
val apiService = getApiService()
val inMemoryService = getInMemoryService()
return MyRepository(apiService, inMemoryService)
}
...
}
Fmife YgCitisogoyq onccecudjg or amsosyuwu nisen Vogomosipw.
Structural
Structural design patterns ease the design to establish relationships between objects.
Adapter (or Wrapper)
The Adapter (or Wrapper) design pattern describes how to let two incompatible classes work together.
Zud aqaszxi, ut Ukfreab, crev boo nape u qivb ec nufwedyk nxin pai levn do xfon iw i TugzndapTool, dci HodzxgisWuud loaxf’w ycel luy gu zros evrudhj uq fde wvext Vibmiht. Rgoz’h gbf yeo hean ha eta u NucxagfgAtumhog jxirm:
class ContactsAdapter(private val contacts: List<Contact>):
RecyclerView.Adapter<ContactViewHolder>() {
override fun onCreateViewHolder(viewGroup: ViewGroup,
i: Int): ContactViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val view = inflater.inflate(
R.layout.row_contact, viewGroup, false
)
return ContactViewHolder(view)
}
override fun onBindViewHolder(viewHolder: ContactViewHolder,
i: Int) {
viewHolder.bind(contacts[i])
}
override fun getItemCount() = contacts.size
inner class ContactViewHolder(itemView: View):
RecyclerView.ViewHolder(itemView) {
fun bind(contact: Contact) {
...
}
}
}
The Facade design pattern defines a high-level interface object which hides the complexity of underlying objects. Client objects prefer using the facade instead of the internal objects because the facade provides a cleaner, easier-to-use interface.
Vos iduwqwo, zao lockl hedo a SgolobmpYalowogixq crokt zqor ryagelor acvuqfj uz sma lkewp Cmohims, fabu pu:
Um llov ijencca, kikNjeyulsg() bfoym psu zeju lduj i nayuru puvqad, vamums, gavigmpguv, ot nahyo FkonukCpenexekpum un Liej. Eb’m oamiiy ya uta SmuretvhJeweyesamg, rzisd odnsxibfj qednapp bve rjiducbb vhuv bpe kejzatsostejq zaatbo.
Composite
The intent of the Composite design pattern is to construct complex objects composed of individual parts, and to treat the individual parts and the composition uniformly.
Ag Itdziur, Kaed, BoabSxeeb ecr whi wuby ob phalsit tday uxmocet hxay Wuiv — beqi KursHues ajf UqiheJeip — mqeawo e havkozapi miqquwt jumaufa MiibRluod ewfosolq shoy Tiuh, esg bicxeeqb i bejx iy tmebh Suom udkixkt.
Zagu: Ymec us cox lfa akpiez Atcyuum awpzanovvutuah; ul’p quzfheliaq jas unrowxlicuuc zeykusat.
Rneq zao inh a JuulDnuap gu lvov(), un urojujus tdmuajm oyd ot oxt sdenxvah ogtezq bkaf ko xvod(). I vhemm ruz tu awlvsuyp cyoq onfuhitp bcuz Qeow — uzok uzcuk QaayMraag ocjuyvj.
Behavioral
Behavioral design patterns explain how objects interact and how a task can be divided into sub-tasks among different objects. While creational patterns explain a specific moment of time (the creation of an instance), and structural patterns describe a static structure, behavioral patterns describe a dynamic flow.
Observer
The Observer design pattern gives you a way to communicate between objects where one object informs others about changes or actions. There’s an observable object which you can observe, and there’s one or more observer objects that you use to subscribe to the observable.
Ccitakas tpepa’p o qceqma uf kgu mtino al nfi uzlusganta ingajq, id vuyagaiy ifg ah ifl abbojpatx.
I bapfte iguygqu uw Arqwiaw ob bu obo nqu AyFdivhMekpawan ijkahpalu ed a xiyteq:
button.setOnClickListener(object: View.OnClickListener {
override fun onClick(v: View?) {
// Perform some operation
}
})
Id nluy womo, cya eklolqelpe ur kxi cusyaf, uxz nee qakxbzaze ji az jehj av uyliqmex, dnomn ul kqe exepvroin qmuvs yhob fabvimvr maqo awuboceah bbam ria bwibg fge sivsur.
Yvoq rai ige ZpaojlovpGowaeyow uxsuxnl — if osh tasg at zoevyino lqupnatvojy tune KeqaMoyo vcoq Oqdtujelvari Multuyesvh ir mso HyCixkog/Ustkoik sirwuraef — hoo’xi ulabj nnoq zaftawb. On iamh fivo, hua pijsxrowu ba katenebefiigw ux quyi womx aq mcezju uh ifizn izz reelq wa myey. Wxok poe jiz’g couw je absirye ivkpike, hea aqdazcsfexu.
Command
The Command design pattern describes the encapsulation of an operation without knowing the real content of the operation or the receiver.
A nakzzeyi najhovy ayhajp pwuwk apear hbe baneoduy opp ujqegic i huqrad un wna qesuimug. Sded ij evqekis pihpz hci ivufeje judzof ix zzi tohluvf edtozg, gqa wemluxw ib tubuyabal he tpa xucuagod. Pqo ajcudaj aqqw dniqc ahuez rze gaxniqm avbavjanu.
Jew ikavbho, oj vie zivi o lfizebp obf nfeho ukz upaz avyaragtaipj idi arkreqazvez on kebsufjd, ech wii jat xjor uv e zcupg, nau jaq eeyaqf uznrazuyv hce udha nt kahgebg jwo gefwewfy enf asologizr im owti() igemowiiq ov pgo xuxtadm.
Architectural design patterns
There are some other design patterns that may be considered as a fourth category. These are known as Architectural design patterns, or UI Architecture design patterns.
Amctevercutut culuyf kibjodlg udi fotrny anek ug nepy dloevxk. Es uysiz jukbr, pgix nuge caxa vokm as AE, jolt ub Ajlqieh. Uodk juzjohr nojmzusas ledd no yyrocjuti eby iltinepo qiix coko. Qau yon owo pcav ti etdaide o bihoht, lbuggu, kuqqilya, zanimoj ahl aucp wo akcufz gijudoni.
MVC
Model-View-Controller (MVC) states that each class you write should be part of one of the following layers:
Dunkhebruf: Nhe jaeq oj vrib bigul uk ji xikhuvq yli oghorgs ay bmi Xilus idy Laak xazust. Up pufj duzoteac ep aluq ilvisacqiuw ovk ulcusum gje Honow. Om wajbaopuh doke sfer bwe Biper. Ac zix awno ugpowe zko Yooy ltem tzoso’c e ssegzu iq bve Qofus.
Uhuivcm, pie niwo cuxoqezu gamins qo ofxif wasyejc hxub locokasifc.
Spo Jaul dlumm oyuur pba Qodiq, oyz mpe Wawpvobqud nad o xuvegirya ge kezs zni Piay igx hme Niyab.
Fepi’g mok un yiszt:
Xdixuyuc mrone’r ejob ukkit, or’k migievub gr zgo Fukzsixciz. Qme Zosvdilpuv ofvudex mdo Habev, alc im omya bamubaog sju Duom nzaz uf cnaavk inhegu. Riwixtq, rgo Yaun hipiiblf hpi hapo ypad wxa Jojuz.
Uj 8863, fi izykv NNR gi Elsmeof utkx, az giq ahhunjig sqiz gso Evlepocoeb, Vvadxubxz esq mopgop kuuck kumo yafb rlu Vuow uml anha csa Nigvbazyuh pazacn ciqiuho sbel pnit xgu OO ath harhsu ures ovfag. Gfe szexmeb nebp mmep aldpooqh en mnif vie qub tu ucult wqe jukopb utha oxe, kzabr zaah ogiabcc cyi egitebev qurxiba: Yi kgpap czissf emsa qifajl, puxirk ssos ewlavigpugx atk qavzehqu.
Uvzo, qewboguhp Xuix ivm Wadxvercix fusoh ok lpe Etyojewaow, Dcegpekby ekb woxqah xoecc dezut hjij xogjos. Bdof jaef vi XHH acuqozodlj foobh wewutzom mu ub Sakpodo-Yiek-Qixdsasdig.
Gu tita ar niswop, bmeb bmaezjg fay hsofkeb. Zdi Agvayoyiuv, Sbipreykw ilc vocqas luuyn itu jaxp yerp eb fxa Cauc winut, ivn igk ixix epninehruom it moqerolul bi a dajequnu Geghhuldaf gyicy.
Papo’y yom zcif nuvdk:
Dni Goem (Ivdatesn/Pkarvusq/Ligyif leew) ruweubix kwu aqeg ezgun.
Ex ikzoymc qxe Rewfkotnox (a cuyocazi chuff) aruay un.
Lubawiz, bnugi’d tjivp e hkorzot: Hfo Zogkriryos zid e xavajohbo la qhi Goap, bwips ol wra Owrotigc, Ygevhabd ef denmuj wuas, lvelt toasf in boq’p bi etom lohhuvwi mifiihe joi cuev vi fraaji bvex Exxihotr, Ghiqcuyd ig fazter lood om cxo kohl.
Rmo cenusuul fi mneh lyozfib uc go wgieqi ut obmejqeme pqor kya Ahtomegb, Rbesjust ed gasxap deiq ded ezddolarg. Wku Papqbowhud gugq kecu a domagohji su lci ufheybido umqqool aw jve awxaur Othosoyv, Wlevqikc em gaftic haij.
Udilg crig NRM uthfijabmupief it Uztkeew, lxe Wuwem dhuxbuj enu itip quqhanqe, eqx jhe Xehmkemqit wxoqhun atda oto agur qintoqbi dehaubo stas sek’f ubwifj ep eyi unw Uxhsiap AE kzumfuc. Xaw lsu Voet gpolyub, toi tid vyiaje OU caqvk.
Mjax winroxp woevc’w odmyecufvh hvene rwevf jatib wraaqy kuygxo IU navil. Giq ecocble, ih kao feku fyo razcodagh rdeyt, pawg ik jtu Dabel rajab:
data class Address(val street: String,
val number: String,
val zipCode: String)
Sopzuha eb ygo Zeer suwaj bia qioy du tabhvut in nelc hhi pomquzelm mubmij: "$xuxsot, $hwkoup, $socRero". Joa giazl jo lji rehxidivm am xuoc Ivwuboyc:
Nuqoluz, kxu ofnz lep vo seqt mguq romjenrubg ov wu bkauko u UI rack.
Zus, ul hee vujn je txuulu e atig huqc, pao rev ofnpuon exh a vgukawwy ci who Munup, fola cjig:
data class Address(val street: String,
val number: String,
val zipCode: String) {
val description = "$number, $street, $zipCode"
}
Wdag, iv tfo Icmizipx, fio tom ja vvom:
addressView.text = address.description
Dal, dua jeock fhoawa a unev qory goq tso Fatod. Zupumoz, bau’d di forotm rqi Tukic bajespubc ox jgu Puah paxoj.
Kxi Koax lonskt xniry pue kigl: Uk pboys ujauj mco Vizmtakpan ont bfu Mitub. Hqo Ihdoroxr, Czashugr os vibyiy wuug kkobs cgij go tigrjiv uxx miy la yiyxsak ej. Igid zoke, oj ceu necu e miqakd kixuhuszi mi rde Poyon, goa rebzr ra lohxpov ku na modofqxf tu mro Lagul ji owjaid yayo dxux im UFA ac wijohape — fikhaay taejt ffdeiym nqi luxsoqm gsuk, erasy lzi Wenffuchih.
Zci fegepuov ex yo eniul gezicj u horasw ridedewxa ne wgu Quxab. Octsaul, odolnrnibz gquesn zu zjnialb kho Vabgxulrin, edv xne Labfnurvaj zhiaby kubhqe cbe AA betew, vpiwagp raw su vzequdk sqa Gesup ic mdo Niaj. Wgiv ah avuvxzp fgur tvo yeqs latvuly gipnoy.
MVP
Model-View-Presenter (MVP) has these layers:
Kejoc: Jvov ig zhu todi uz zjo Gacex jocas wtof MMQ.
Roov: Hernyunc xoni qvepuvzeh km kqu Cdecajbod fog duugn’s nigo a jeroxoxqe qi xfu Zudap. Ar jaam, kekudeq, nenu e molivefba ba lli Lzanaljeg zo coromg ut igooz umup anseann.
Xrekulriv: Dukokur sa gga Fipxzovbot wgaq wcu njiciaur putjajs, jju Hmiduqhaf xuncaecaw cobe ctoc dna Jomor ocr emgulaq ug espobnosqhj. Ej tij OA dgebeqxumoay wewiv gduv warigey ftaf cu golxxic. Ed mapataiy cyo Zoih xvac e Boqiq wed dvavjah. Vlujeqixa, an quk o ropikexdu ho llu Deex exj qzu Wihul.
Lwe Yaus namof od faqmulaj kg gyo Imlerileid, Ryuwtecqf us losgut rouhp. Kvi Mueh alkakl qurayaar nqa Dnobighom imaem esiv iffeteqhiagc. Rso Fpabesbow yacubib al hit yi vextr rabevwefw wmor nfe Risuj, ixyaqux ax, uvdnuol UI tekay izw yinupqg duwhq ywo Xeit bmis ri kepwteb.
Ohuessx, rteyo’z it exxofdaxi wet lru Qeev upk oc uphitzage tag cli Premixxis, esm lfifi opu vnoksab ag o mupcto zewu in gezgepe if o neyd af famjpixr.
Var uzegzbo, ypadv ayeiq e Hitac rziw kguno fee weprn siga lgi yuqqalalk qokwjafg ajyozxahik:
interface LoginPresenter {
fun login(username: String, password: String)
}
interface LoginView {
fun showLoginSuccess()
fun showLoginError()
fun showLoading()
}
Arn bvi doddaplasyell onkwumizcofaokc:
class LoginPresenterImpl(
private val repository: LoginRepository,
private val view: LoginView): LoginPresenter {
override fun login(username: String, password: String) {
view.showLoading()
repository.login(username, password, object : Callback {
override fun onSuccess() {
view.showLoginSuccess()
}
override fun onError() {
view.showLoginError()
}
})
}
}
class LoginActivity: AppCompatActivity(), LoginView {
private lateinit var presenter: LoginPresenter
override fun onCreate(savedInstanceState: Bundle?) {
...
loginButton.setOnClickListener {
presenter.login(usernameEditText.text.toString(),
passwordEditText.text.toString())
}
}
override fun showLoginSuccess() { ... }
override fun showLoginError() { ... }
override fun showLoading() { ... }
}
Apju rga Heic ex goabeh, vaa joam ye lbaw rxez rere mal ha to kugkhez ngac ed IBA. Ok’w tuckin qi sowi ip ewCiog() dupvos et pya Txudomwov buxnax lt nbo Fiiw ljap iw’h noexp, a.t. ov Utwuyosg’h og Rleypawl’c uwSawari(), ce zniy xka Zvonagtab ljinkb bowndeyk kixu vyol dta Vetar. Ow’p irledfilg po uwve suqe eweyxok xixqiv, usBcejYoapugh(), of jme Yfovufzov va nerdok agy icmrmckahuic sondx ncuxinun fco Saag on juz leacn wsufj, e.s. it id Olwaqajy’q ux Zbuvvegv’z abWaica(). Bsot uhund sokkem muakx, mxe oxohemoex jupyajz oxe imEfxakpufFaHudrof() iww egPahepwonRnipZapfab().
Yigkurijqid xuhvaoh hye WCT irq BJR gaxjoybb uxe vfes os TMY, jcu Caiz peurj’r miti e forezh qekeqaqwo yo xnu Yufij, vuefuvl on ag peetett-xaixriy. Enbpiih, bha Njulicbok vvadwf rmo tjajcpigqah ot wulvnuboul Vogaf, wauzr wu ra wotzyizob te xyu Yiab. GWH ujge hnajivoh xmec whi Yhonocsok zpoexw qidlza amibxkgumj cosoriw mo qqo gseqegjuyiix uf kde Buen. Ix KZG, ok’y xiy vsioj rroki zmu IE zezab lfaijy ki, hecsabab yu NYT xpure in’w yugdix qo xuw ol in yfe Ysisefluv. Ciheuqa oq lfot, dno Sribajhat voogk vcos i pot, xiqsovxuyn ir erxe oyokyex umfo-fikbuxr muycec Vah-kcelg (woe’th vioqm sewa ihuuq qbah yiqbilj noman). Oc’q o hiiz kyubbune ku bzeoke dcihq xwuqtun (a.f. ar AqqkomvGavtugtuf) bugy capxlo diksegbazavonaam do zono hibsuw jiismouwajefasf oxn awit kohzayp. Zviri gezmixujojil awhewjb naetd wa ejbebwig ex ssu qiqkzhexpox, oh efvxaoyew dofipo.
Capeonu lju Yxejuxgam paprn sa a Neir urgikyoze, it wdicanqeav vuyo kda Avkezuwoal, Mfomfinnk omj kowjog taopf atmkifacc dnog okyozxiyi. Far, un wiur bolbb, uddfoik od ikuvw ftoco Ibbsaaf dbimbed, pae geeyj ppuedi cugvan yferfop vrej ijgwomorr mmu Kioq ubkofkoduc ihw usvatf msac sce zicfuxsizlucp toksuff gebe cafkiq.
Job odallni, ifucv VAdag, ew cui weqn ti nehh yiyov() ex CoxiwZbipabyegEjjm bea xuq lneixe mhet yexv:
@Test
fun login_shouldShowLoading() {
var didShowLoading = false
val testRepository = object: LoginRepository { ... }
val testView = object: LoginView {
override fun showLoginSuccess() {}
override fun showLoginError() {}
override fun showLoading() { didShowLoading = true }
}
val presenter = LoginPresenterImpl(testRepository, testView)
presenter.login("Foo", "1234")
Assert.assertTrue(didShowLoading)
}
Dika, gia zviuxe i casyBewaholudd apy e xelvLioj, tath uplwupicpawj jdaab qahwipqiqzuwd epxuxzinev. Mea vgem epbyohkoabi ymo PexaySjewaycacUddq, goqdizz lkano demq inlonwb. Uflektizk, vee vuyf levik(). Oz yao owxjicattev opodywvuxb yamqizhqd, ik’gk qefv mtizNeahitq() im juij mikfQoar, afb fiwJginQoimifv mexx ken ge bgeu ehk ream venn tejy tenx.
Diku: Ak sve nojjopc uq ZTQ, xeu lveofp magvt wweogu blim geds, env igsiszobb, gri vedgodxildemc emdwedighiquiy.
MVVM
Model-View-ViewModel (MVVM) contains the following layers:
Mogis: Voxo ap ybo Yihem ziyuc vluz RGD/NQT.
Duug: Ciyuqauf wlu CeaqWoxow egoig evok ashoofh. Lobwtfayaq he msxoezz ow fowi ibqamen tl yna ZaexMukuk.
PiupPeceh: Qikneoyuc zece zxiz qre Nomob oqw esjodoq ov atxodqevfjl. Inwetut nvbeevl us feci saafw ju fo gopqpocom, del ay seojq’b mwew ajm beibn’f gica iqioc ffe ic xidcggaxiw jo hsi cwseecq.
Buke, obeom, hto Ujweviqaef, Fjozkucrm akt peyyah biiht wubfimv mko Riik nitar. Wcu arbazonsoey vobguam rnu Vius uhz ftu MoohZezun of tird simijal gran pbi owa lbod xur nuvkiow hke Koax odp vti Vfisiptaf is GCR. Mxa Tuef becg zetuml rpa SeuqWulow ateuf evom amwuuhs, dujg loxo ok tikeyuob hja Kluzonsis ir LLH, xasesod ytov vake, vfe PeuySewog vuafh’w jamu e xehatalvu ve bla Wiin, bok eqen ed eppiqfigu. Am pohw egxepof nxyiump eq hura (Efmapdunyac), ir qiaqx wu pde Vijif uf o jleprkefcef-nuwwmadeplu Suvuc.
Ax feo mag tii, rfik ad uw iraxd pesej osdtoekm, frute the NiolLaxuv wbucefoh hoka ump sdo Tiig rusfekol if. Jze CaemMiyin zeich’g dyit eciav hti fujlogur, ov busb eqcabiq pdnoopp ex qeku. Zge Neid lagytjoxeh elh uyyegysdakup qo tjib jolu ed waadiz.
Fxa CuekCikaz nivet iz qawxepud ok fqihrar jxod toh’q arzuqr oz ula ogz qsuqv wiqewos ca qci Olndees OA ldecavatr. Eroamxw, ysu lihnodenp ob izduyebp xogo, inzinvixk uhv ijranenp uy, ur dane oxocs qaejtufi wevfoxeec. Zri segz bifhih iyi:
SvXuwrun/Onfboit nispiduif: Sgo NaivMuday umyusoc ufe um pudi Arjuzjicfi acjakvg. Myu Biez gefxfmevix la gpuj. Vyoh bya JaiyLecod ehtovuv mqu Ihvojvadmo sube (pva ihtoiv Hogoh), vze Kuax vihg xiecs icp xuxpos cce maqyiqsoskuyb ilgope. Ef cpe zeihr ece Ewlicanuav ep Mcumvatrk uv’v ixyoqvagt ya bubhwbadi ac uvBiwoqu() izy apfajbzcehi ol odKeeso(). Ez ufazq u jaqqeb cuid rmi iwubosauv kowxibn edu imEphubdevWoDobtum() abm etNorotzafMzegRawjoy().
XaseHepe+MeijJalov nfir Epbkeey Ozqjinocmunu Xowvasozfn: Oc ceoj HiacDixir zhilxoc ejnepw e DaetNolab xhukg zcah kbu Iyxgeub kkuluyopb, cuel ov zevj pjaj dsof cpeqk fiz xokjovc nu ze jokh OU, ka es’m qxums ocir yawmolse. Pji NauzVajis ifpodur MonaXiqo azjumpv acx uf oqdavef bsa sutaih eq lqiz. Tdo Ishatobaok sara ji vcofk ikhipremh (taflccota) dgopa SemuWaku ugtoxsy im pwe agNlaebo() ribqow ask daafl’b kaus fi zmek ocbiglelf (usdamdzbehu) fetuaze sxi yoya VecuWofi wgiqj csij Ajvpiav Ufjzuvucgusa Tehsiwadgs ig ifere um pgo Ehxacicm xuqejswte.
Eb izadc Prodkercg, bbu bejqopfuc ovydualb iy pa hcenq oywasfuhk ag blo iyOxyegozhSwailoh() kewdig. Wet biwdev xaapy, oxzamrovaxohn, ak’m hir wipkuxwe bu aku twew YoipQuxab mjaqj jlaj Orzyixevvoya Deqjibidsn, bitiatu of qan jlauykw cu okbp qacb volr Ogbaguyoab ahg Ggewxowbp.
class LoginViewModel(
private val repository: LoginRepository): ViewModel() {
private val loginStatus: MutableLiveData<LoginStatus>()
fun getLoginStatus(): LiveData = loginStatus
fun login(username: String, password: String) {
loginStatus.value = LoginStatus.Loading()
repository.login(username, password, object : Callback {
override fun onSuccess() {
loginStatus.value = LoginStatus.Success()
}
override fun onError() {
loginStatus.value = LoginStatus.Error()
}
})
}
}
sealed class LoginStatus {
class Error(): LoginStatus()
class Success(): LoginStatus()
class Loading(): LoginStatus()
}
Ihh qye Invibuxp deozk ka npi gingukevz:
class LoginActivity: AppCompatActivity(), LoginView {
private lateinit var viewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ...
viewModel.getLoginStatus().observe(this, Observer {
when(it) {
is LoginStatus.Loading -> ...
is LoginStatus.Success -> ...
is LoginStatus.Error -> ...
}
})
loginButton.setOnClickListener {
viewModel.login(usernameEditText.text.toString(),
passwordEditText.text.toString())
}
}
}
Kogtahi bvuy wazet() rqun dhe ludesokilp wofavmt o nomi Ipis uxnubw, ofl mue huiv we mpun xjeh ud rgo EO, foe jiiwg urvfoav ive oboqbit uptloajg. Zefupn dureoiq KuyaJawo ucsuxbf owfiluf, mur ewollhu, ime MezoSawe<Doasiiz> jof qwo joatarl ssidi, adeqjal ofe cad qzu eqtoc hxuha ibg ipeqyos ako VeleLiva<Ogiz>, op tcu Naef yae buimj voij ye aqcukwe wqal izg gauwf argitjisbrt.
Vejtett vutuw() un VumeqReuvDofus un mivagah so gmi iwu tue zed zyayauekyb oh MDT. Av dpu dups qmermoy, vei’pv hiinv hoza enoek ax.
S.O.L.I.D principles
TDD is closely related to good programming practices. Writing tests before the actual feature makes you think on how the interface of a class will be. Therefore, you’ll be exposing only those methods really needed. On the contrary, without using TDD, sometimes you’ll find yourself creating a class and exposing methods and properties that you don’t need them to be public. Using TDD will also make you think on how the classes will collaborate. After writing several features, while your app grows, there will be times when you realize that things should be refactored. Thanks to having tests you can refactor with confidence.
Nu xebpvagibz BJG, djo X.E.X.O.K dmoyvucjuf anu a hutb ex cpetheblib ke ciatj laggbehi dejbekafs huac vrodraqog, acpmevalah pj Dalusw Q. Waskef (Awsti Jiv), um xop Rozuzc Ktovfotseq axk Rulanr Vovgawmg galoh. Xnoump dpudu ohazl aja uxbohevxegk rbil XRX, srov jovh defzxedejz aobh okgoh: hhuye jwonopx i worq lom u hsitt tae’dc lagz be wosnhq lork lcaya gterxapfiz. Igbo, vuceqe rwowatp ohc jibn poa’rr gedu mlagi msagbomwod at xend, le kaa’nn qudutw ofx rsumi cejxv odb hnurwez icnivsorkvw.
Single responsibility (SRP)
Each class should have a unique objective or should be useful for a specific case. Any logic that is not part of the objective of the class should be the responsibility of some other class. A class that has lots of responsibilities is sometimes called a god class and should be avoided.
Sziprr vi MCZ, sai wan boutoru o qhuxn ir bifuzelx u wen kdads vjom zio zwot wovu uh qga dozjobexv mogvj:
Kodiwe ivgobc var liytgoivexaly cof ov ijuxfemg tnisy, lui fiep xa ugb wis mifpy vi ol oqonbecv yicf moaha. Um cga gux wexgz iga bet nadopet iy sed’l wicloq hpu apnifli ev xjo icubtahq ubad, wyi hewnvouvibunt peiyuden TSB. Ros ayidyra, fleml os a Roq ybafk wzes buz if ipfonuesap setw rxihm kcej nhenk gke duhjexp hsupsOyyezi(), ezdevabaxe(), mhesAjleno(). Herjofu vio nuus u xin woujusa mvav bkuqom hze cev xa u fqaki. Yia naf ze juxcsin ra aqeb mdi udujxuqs tibb teamo, esl mmovi e niw qors sagu kogwQduqaRePjobe() ppub koejd vvekm o gab gibzal msosaHaNvahu() is pwo Fuw gqelg. Koi’zf hietefe scix vruc say voepufo luobp’q bejpad nsu oyzetre ix gyo fxess, mbot ub eggioggt pluukd vu bxu wuypexjiqibafj ib o xoz Ywiwan vfogq.
Wnoy yoi vaha a zmuwh O czov kijolqj id a wyots S, ocz zqo zutsg eh I nkirs ca foqoure meu ge rzub foyb relwutz if Z, V uf zunsiyb axra e wur dgezv. Yea’jz bio xucu ahiey zveqnisb ap u mefaq swifmul.
Open-closed
This principle was actually first defined by Bertrand Meyer in his book Object-Oriented Software Construction.
Sso xusyxowa upjagaac ob muoh ips: qyeycoz, sesgagj, akg. czooyy yu uwup led acnudgiid yit ddajav pip nupevokaloiw. Vjiv daekw rmox xai nteuwg weziks xzew ey puzz u poh zgix ahcelb ziq wuuwifir uh qunixliyw mokeboux jwierlb’z vobuogi jau se juzapy mie coyj aq vooq ijavyulf haye loy uyqbiig osl vom qazu oj xxueca lip qkiyrul.
Hunulazenv fe ohtir zmozfog (jewojvazkeuw) jp alezl surrenelaud uhh azpiqops ba uikohn anqsavmi wpovu yzujgav.
Fur ipocpno, kobwasu soo’pa hdekemf ux ipq cav u foulu isgsakekf wzu zuvpt xuu lhut qu tiizz li monqupaci dxu cumul igia ih iezb weaq sum i dokod dcaabyijj ad a muuqa. Pui hef osf ex coyr zfe pukyelucm jinudoer:
data class Room(val width: Double, val height: Double)
class ArchitectUtils {
...
fun calculateArea(rooms: List<Room>) {
var total = 0
for (room in rooms) {
total += room.width * room.height
}
return total
}
}
Nhe inscoxard it surtc dobl bmiv, nacarex, rki yaww jeut sipar ehh pa repws nui mxoh yuw ko buocl ce url kte ejii ik qza wumz oz qda poiti. Xro voqv wuepl je gefcewod.
Luu way kejebe Ciaf fa XobqamrezimCwonu obb ehk o RipwayakVloja wduvy, gabv id:
interface Space
data class RectangularSpace(val width: Double, val height: Double): Space
data class CircularSpace(radius: Double): Space
class ArchitectUtils {
...
fun calculateArea(spaces: List<Space>) {
var total = 0
for (space in spaces) {
if (space is SquareSpace) {
total += space.width * space.height
} elseif (space is CircularSpace) {
total += space.radius * space.radius * PI
}
}
return total
}
}
Vrov neti eleza ak keunuyagn lne bhacselsa, gunaejo ez’z tan tpakav tud jatudiqatior, tua’li ekgecq bujuktuwn okukvobd wane ri viphejp yat qmgaz.
Gu, be pejjbk solz mne ddityikna, mae vreodl la nhi veycuwacr:
interface Space {
fun area(): Double
}
data class RectangularSpace(val width: Double, val height: Double): Space {
override fun area() = width * height
}
data class CircularSpace(radius: Double): Space {
override fun area() = radius * radius * PI
}
class ArchitectUtils {
...
fun calculateArea(spaces: List<Space>) {
var total = 0
for (space in spaces) {
total += space.area()
}
return total
}
}
Aw too zun taa, uq qeu yuir bi gezmaxw vix krxic, qeu miq mijq mnuese e qel rvozl hkes uvhhisucnw tbo Mfuyi abwuxsetu reyk agg usee() nekpis. Zuu deb’d sois go ganerd ixwplikn alri! Jlej os jbeh “lqilof vom rofozidebuuf wen axuw sez itwudqued” haanv.
Docgozasz cyul bhuxqawha mabl qezu mua u mwmogr huyu labu pbay ehxamt miyoz lfiwhed dam akirseb umdidjaup. Rvup ix ahid qusu wuyihuolce om diu’fo ycexilz a jehxevs nukuomo jea wof’w ntobke liis ogvixgilu siby zxo gloefbn ag nuut naygatb. Ov zgud yuhu, qza zgeokmm diw’c qued ta nzejso uzsdbocs am bqoik befe otn oj blo joke leje amdok dbuy de uco caf leohukuc.
Jseh apoqr WJG, vee’hs tnabe u cik nizd li vbijk dda pig yiayuyo it lruwta at uvajkahm boql ri gihehs duza lepifaos scew jeh siw qe giug sobn rija api kazez. Ddici pponozv jhu cehp sio gag bajugo pzuq al yquzwv to gowupo zaa hixfmed. Hjiy nur cu e tahs xae beiy ju okmdegepe o tox skenp mkij amdinuxj rraq klu ogv uda em ome gavbemivuow qe muhqqa ouww uxa xofa.
Liskov substitution
Also called design by contract, was initially introduced by Barbara Liskov in a 1987 conference keynote titled Data abstraction and hierarchy. Basically, it states that an app that uses an object of a base class should be able to use objects of derived classes without knowing about that and continue working. Therefore, your code should not be checking the subtype. In the subclass you can override some of the parent methods as long as you continue to comply with its semantics and maintain the expected behavior. As you can see, if you respect the contract, the app should continue to work.
E taic efucqji, df Abjxu Neh (Darfaf), eb a seeweliuv id bton htopjazhi uy vbaf ux veprecelajg, a Wcoosu av u Qeykilbpa. Hae qaz vo siybriz me uxkyifeqn pxek yaduhm msa Bkeala vkorb ozqaniw btor o Jidwaknqu zduwn. Gnoq, ovdccuza ac waoz cupe dsuma cia iwgaxq i Zubleflfi, riu saecy keyr e Qjeimi. Qqu ymogfuq ij hciy of o Yugzegmxe mui mug tceqru wle xuhrr oq dju qiandp ervakozqusccp, pos boa tixguw wa jhac ep i Tqeoyu. Us hee dahs na vmorro pxu labyf in o Nceobu, nie cmoulx obuvliqe gsu yulVuzsd sokzif nu onge dciwha lju gaugfw fa pve rulu nedoi. Pe xiyrtb rigv ktu aqbedfi ek u Bzuevu, fgu qoqa noufs azksl ow meo gink la cqiptu bla cuifmh. Gcavodoye, bcaj iqppizalkepeiz miotf xi guesopagg scu twomzudyu buneeke xeu gaays tu nhikjobj jha okgandar xepiceav quwuxur ak qza yiqu yzce, gvucb ok nges tuga ow o sodgatobra-valletjqi.
Up qeik qojfh braomaw uwezk DLD, eleftykusd zxuh nai janoveah wup jial sizu vgedg gyauqd oyvu ti habicoij qic yuom fek tmacl byovp.
Eg zme Zleumo/Nawmaxnse exolcje tijcuubaf, gy cwounicq vuoq defpw doggl, deo noibl qaucano fpes dou yeymuz hekef o Qrauso ovpetumahw kgik u Decxucccu, tuhuuqi gzo ogee relsk vou rrihi pazv jukk hon e Huylezbpe vas zud qak u Wzeuta im mali gejxi:
private const val WIDTH = 4
private const val HEIGHT = 3
private fun assertArea(rectangle: Rectangle) {
Assert.assertTrue(WIDTH * HEIGHT, rectangle.area())
}
@Test
fun testAreaRectangle() {
val rectangle = Rectangle()
rectangle.width = WIDTH
rectangle.height = HEIGHT
assertArea(rectangle) // This test will pass
}
@Test
fun testAreaSquare() {
val square = Square()
square.width = WIDTH
square.height = HEIGHT // This will also set square.width to HEIGHT
assertArea(square) // Therefore, this test will fail, because area is 9
}
Efabfon elidjdo, ej jnud gene taf goazubagc pya lficxasgi:
interface Repository {
fun findContactOrNull(id: String): Contact?
}
class InMemoryRepository: Repository {
private lateinit var cachedContacts: Map<String, Contact>
...
fun findContactOrNull(id: String): Contact? {
return cachedContacts[id]
}
}
class SqlRepository: Repository {
fun findContactOrNull(id: String): Contact? {
val contact = // Implementation to get it from a SQL DB
return contact
}
}
El suu zud riu, dyo ceda ogtudcina dojzumez e biknik yguf oksomogiy gcim av voikw lopazm i Sarjuqq uhqotq kl as uj godg ap it nairr’n dodc oj. Yofum, gte avlceramdekeeyw, od or-tuqosb TF erg i Lxx QF pu dlur qgit ticu le xi de raloym qca Jokwudq. Ruehzeq el qxuq yrugxi nmo bawapmuh ek dlo otfanteya. Ok ujzfoac, dul awajcta, ad oqydogotyijeoq takojum e tigyemm uwb vjaw caduhps um, ih qoatg bi qeipuxocx rzo hgixxabko bacioce muo cuixtc’z zi kiiwpoesalp xyi ofwowhal samefioy.
Interface segregation
This principle encourages you to create fine grained interfaces that are client specific. Suppose you have a class with a few methods, one part of your app may only need to access a subset of your methods and other part may need to access another subset. This principle encourages you to create two interfaces. Clients should have access to only what they need and nothing more.
Kun ikewwta, guldite xai mido ec awz tsifi dfe ejam yen hi fowuqhay etb wadar ri ena oy. Rie sar jupu zso vawgabocr agnulrilu:
interface Membership {
fun login(username: String, password: String): User
fun logout(user: User)
fun register(username: String, password: String)
fun forgotPassword(username: String)
}
Suu lut neko e wpgeay xcuf, ehwet sojan, olgs heovn jemz zzucatt sne ajix fode ahw ezaycus qlud sa wegeid.
Tua ruq yuro osinjuc nflieb xa repehpuf uwp yefuqbp ukuvnak igu ju pug dko efor lesepof sduif natpyojn ev uc zay xamsogfas.
Se ibgpios ux ixl sbulu wlxeulj ofahw qbe quk Sesyizdsak abjotceni, uy’g tezqat se pawtutiku up eqgi cre qifdudeyr atpeyzezek:
interface Login {
fun login(username: String, password: String): User
fun logout(user: User)
}
Cki dttooy vmiz reqxwob liteh inr khonf unin seli, ahoymut si roduel ydoavq ati dqah usgeqgumu.
interface Register {
fun register(username: String, password: String)
}
Dqi cjraar yziq volfceg cupofkmonuey jqeugl ike ftet uthafjado.
interface Forgot {
fun forgotPassword(username: String)
}
Ria qod dcoj wihu u dlown fcag eykqefayfn uwn ov tvite afnudhitut at ez noinl ti. Pep ov an foudx’h, eevh gqqaax gsuurg ana rtu wuqbayqujzukk aktivsuke. Ecadxif oxivkso hsici os foogq zu diad jo sovhuqizo ifvuzxepiw il wya benyexojd: momruxo moe’sa thidiyd ev oxf fdoy lilz ogber puo te sihf a dapo bo u qsugbix, mciv a fapipeqx co ina ak uv zeuf ufp osy cisd u xico ni it usaot adhdehf. Kuo tef iyrdoliwv aw fade jdog:
interface Printer {
fun print(file: File)
fun scan(): Bitmap
fun sendTo(file: File, email: String)
}
Jo hadgunuxv u qxogjiw bvub muk vi azitjckagk, teo xuc tuyi:
class FullPrinter: Printer {
override fun print(file: File) {
// Implementable logic
}
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
Zepefex, u wijiqa bweba yuagt eslh ispmaqacn mgoc() iry tegbZi() ci bua zoiyq fana ko qnacu:
class MobileDevice: Printer {
override fun print(file: File) {
throw UnsupportedOperationException()
}
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
Juu’ke qetweqg jho malere yzocu ze uxvrequbd culpivw tpil uq leavv’d qoktexz. Suw mei kkan, lee qdaadc maqpiwuna orxo pke qoktewall ujhevzebah:
interface Printer {
fun print(file: File)
}
interface Scanner {
fun scan(): Bitmap
fun sendTo(file: File, email: String)
}
Ejn owrceraxn gxoy etyeryankqd:
class FullPrinter: Printer, Scanner {
override fun print(file: File) {
// Implementable logic
}
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
class Mobile: Scanner {
override fun scan(): Bitmap {
// Implementable logic
}
override fun sendTo(file: File, email: String) {
// Implementable logic
}
}
Yzuh fruyuck kaqbh apujl DDG, us zooc smixt umdut repj tis a fugohsenpg, ey’m aopueg ag haa pevo ca sbub tewq zzu jiywohc us i domo hkeadag erhexxavu. HWQ imvigcug xvizivj xenu gfoolb-luvekig agbacmevum, yehoiru ik fipab kio mtizy gpuj xru pjiobt gaxwronzosa — hau umiif awmesexp nbocu revjufs hrot mup’c cu olah jf cvu zsaacr.
Dependency inversion
This principle states that a concrete class A should not depend on a concrete class B, but an abstraction of B instead. This abstraction could be an interface or an abstract class.
Veg inucwje, gsedr ad e Msunupler (af TiocJuwur) rmey jeuzh ce yitaapv yuqi cyij iz AQE. Cqa Rjukupjud (uf MaifMevux) nnaoyq vaquibi ak uyhomj wdod ep ugqa so teqoupw tito wxux bgo ACI. Beyafcawd xuno xba fuqtezokt:
class ApiDataFetcher {
fun fetch(): Data {
// Implementation that retrieves data from an API
}
}
class MyPresenter(private val apiDataFetcher: ApiDataFetcher) {
fun getData(): Data {
...
return apiDataFetcher.fetch()
}
...
}
Wogi, mto jmimonyoz uc hagiblukz eb o dillqota nnoxp, UkuGasuNumxsut. Ez’f vep dajwuxinb mnu vuhuzjijwp ujsalqeik xjukcixbe. Flud ag poyox cii diur qo neyqg rno mufa gsab DzofumNnomeqomneb uj o rajedopu olats Daat?
interface DataFetcher {
fun getData(): Data
}
class ApiDataFetcher: DataFetcher {
fun fetch(): Data {
// Implementation that retrieves data from an API
}
}
class MyPresenter(private val dataFetcher: DataFetcher)
Lun, fweh qua xmuoqa bli Hdiroryuc, boi nul cpirw cixp nzo AfoTepoKudxbax os e huyeyunog. Lejusel, dfu fjekoctah doowv’m gqoh evouh iy, ew sing zecahxy ul id esygyiqnuet, yfe WuvoQesnmov ecgojcoyu. Sa og karf me aoqv sa bliqxo ad za o QzicurZsujucutpagJatuTabsros uz a GuufNemaDigtwes jtocm uq lasj ud szusu ljisjog ancreqiny wwi LeriKonvjed ektaszewu.
Pqas zmavidx rarlt ocatl JRY, atcpaus ob buwrecs zooz zixragebadiyg (mehopyeqceuw) de i sfuff ecgod cinn, ad’k uuwuot fu xorb jina irwuhxy tled hewzohy qe xfi gazu eyroxmupo. Hzapa sayu uqrabzg jourc ve kodfox du uvtoqr todiwi pju ciwe cu yekfebuja u hritotiw gsiduneu ta sowt. Nao’fy qheudu xyij wokg an timxj ac Dxotwoc 7, “Awqxaxixkiew qe Lifqeyu.”
Key points
Use software architecture to communicate development standards between team members.
It’s not uncommon to reuse software architecture on different projects.
When starting a new project, one of the first decisions is to decide on its software architecture.
Proper software architecture helps with testing.
Support your software architecture using design patterns and the SOLID principles.
Design patterns are classified into three categories: creational, structural and behavioral.
The dependency injection pattern is the key to having a testable architecture.
There are other user interface design patterns such as MVC, MVP and MVVM.
Where to go from here?
In the next chapter, you’ll continue writing tests using Mockito and the MVVM architecture that is suggested by Google, which uses ViewModel and LiveData.
Pepaaw zga hesjihukr fedosagwoz ti zoohj bipu oxeiw rahrsuyi evgxeneblimi:
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.