Traditsioonilised vaated juhtimise inversioonil (IoC) näib tõmbavat karmi joone kahe erineva lähenemisviisi vahel: teenuse lokaatori ja sõltuvuse süstimise (DI) mustrid.
Praktiliselt iga projekt, mida tean, sisaldab DI-raamistikku. Inimesed tõmbuvad nende poole, kuna nad soodustavad klientide ja nende sõltuvuste vaba sidumist (tavaliselt konstruktori sissepritsimise kaudu) minimaalse katlakoodi koodiga või üldse mitte. Ehkki see sobib suurepäraseks kiireks arenguks, leiavad mõned inimesed, et see võib muuta koodi jälgimise ja silumise raskeks. „Maagiline telgitagused“ saavutatakse tavaliselt refleksiooni kaudu, mis võib tuua terve hulga uusi probleeme.
Selles artiklis uurime alternatiivset mustrit, mis sobib hästi Java 8+ ja Kotlini koodibaasidele. See säilitab suurema osa DI-raamistiku eelistest, olles samas sama otsene kui teenuseotsija, ilma et oleks vaja väliseid tööriistu.
Järgmises näites modelleerime teleri rakendust, kus sisu saamiseks võib kasutada erinevaid allikaid. Peame ehitama seadme, mis suudaks vastu võtta signaale erinevatest allikatest (nt maapealsed, kaabel-, satelliit- jms). Ehitame järgmise klasside hierarhia:
Alustame nüüd traditsioonilisest DI-rakendusest, kus selline raamistik nagu Spring on meie jaoks kõik juhtmestik:
public class TV { private final TvSource source; public TV(TvSource source) { this.source = source; } public void turnOn() { System.out.println('Turning on the TV'); this.source.tuneChannel(42); } } public interface TvSource { void tuneChannel(int channel); } public class Terrestrial implements TvSource { @Override public void tuneChannel(int channel) { System.out.printf('Adjusting dish frequency to channel %d
', channel); } } public class Cable implements TvSource { @Override public void tuneChannel(int channel) { System.out.printf('Changing digital signal to channel %d
', channel); } }
Me märkame mõningaid asju:
Meil on hea algus, kuid mõistame, et selleks võib DI-raamistiku toomine olla veidi üle jõu. Mõned arendajad on teatanud probleemidest, mis on seotud ehitusprobleemide silumisega (pikad korstnajäljed, jälgimatud sõltuvused). Samuti on meie klient väljendanud, et tootmise ajad on oodatust veidi pikemad ning meie profiiliprofiil näitab peegeldavate kõnede aeglustumist.
Alternatiiviks oleks teenusepakkuja mustri rakendamine. See on lihtne, ei kasuta peegeldust ja võib olla meie väikese koodibaasi jaoks piisav. Teine võimalus on jätta klassid rahule ja kirjutada nende ümber sõltuvuse asukohakood.
Pärast paljude alternatiivide hindamist otsustame selle rakendada pakkujate liideste hierarhiana. Igal sõltuvusel on seotud pakkuja, kes vastutab ainuisikuliselt klassi sõltuvuste leidmise ja süstitud eksemplari loomise eest. Samuti muudame teenuseosutaja kasutamise hõlbustamiseks sisemise liidese. Nimetame seda Mixini süstimiseks, kuna iga pakkuja on oma sõltuvuste leidmiseks segatud teiste pakkujatega.
Üksikasjad selle kohta, miks ma selle struktuuriga otsustasin, on täpsustatud üksikasjades ja põhjendustes, kuid siin on lühike versioon:
Järgmine diagramm näitab, kuidas sõltuvused ja teenusepakkujad suhtlevad, ja rakendamist on illustreeritud allpool. Lisame ka peamise meetodi, kuidas näidata oma sõltuvuste komponeerimist ja teleobjekti konstrueerimist. Selle näite leiate ka selle näite pikema versiooni GitHub .
public interface TvSource { void tuneChannel(int channel); interface Provider { TvSource tvSource(); } } public class TV { private final TvSource source; public TV(TvSource source) { this.source = source; } public void turnOn() { System.out.println('Turning on the TV'); this.source.tuneChannel(42); } interface Provider extends TvSource.Provider { default TV tv() { return new TV(tvSource()); } } } public class Terrestrial implements TvSource { @Override public void tuneChannel(int channel) { System.out.printf('Adjusting dish frequency to channel %d
', channel); } interface Provider extends TvSource.Provider { @Override default TvSource tvSource() { return new Terrestrial(); } } } public class Cable implements TvSource { @Override public void tuneChannel(int channel) { System.out.printf('Changing digital signal to channel %d
', channel); } interface Provider extends TvSource.Provider { @Override default TvSource tvSource() { return new Cable(); } } } // Here compose the code above to instantiate a TV with a Cable TvSource public class Main { public static void main(String[] args) { new MainContext().tv().turnOn(); } static class MainContext implements TV.Provider, Cable.Provider { } }
Mõned näited selle näite kohta:
Oleme näinud mustrit toimimas ja mõningaid selle taga olevaid põhjendusi. Te ei pruugi olla kindel, et peaksite seda juba praegu kasutama, ja teil oleks õigus; see pole just hõbekuul. Isiklikult usun, et see on enamikus aspektides parem teenuseotsija mustrist. Kuid võrreldes DI-raamistikega tuleb hinnata, kas eelised kaaluvad üles katlakoodi lisamise üldkulud.
Kui pakkuja laiendab teist, on sõltuvused seotud. See loob staatilise valideerimise põhialuse, mis takistab kehtetute kontekstide loomist.
Teenuse asukoha määramise mustri üks peamisi valupunkte on see, et peate helistama üldisele GetService()
meetod, mis kuidagi teie sõltuvuse lahendab. Kompileerimise ajal ei ole teil mingeid garantiisid, et sõltuvus registreeritakse kunagi lokaatoris ja teie programm võib töötamise ajal ebaõnnestuda.
Ka DI muster ei lahenda seda. Sõltuvuse lahendamine toimub tavaliselt peegelduse kaudu välise tööriista abil, mis on enamasti kasutaja eest varjatud. See ebaõnnestub ka käitamise ajal, kui sõltuvused pole täidetud. Tööriistad nagu IntelliJ CDI (saadaval ainult tasulises versioonis) pakub mingil tasemel staatilist kinnitust, kuid ainult Pistoda näib, et eeltöötleja oma märkustega tegeleb selle probleemiga kujunduse järgi.
Arendajate kogukond seda ei nõua, kuid kindlasti soovib. Ühelt poolt saate lihtsalt vaadata konstruktorit ja kohe näha klassi sõltuvusi. Teiselt poolt võimaldab üksuse testimise liik millest paljud inimesed kinni peavad, st konstrueerides testitava subjekti tema sõltuvuste mõnitustega.
See ei tähenda, et muid mustreid ei toetata. Tegelikult võib isegi leida, et Mixin Injection lihtsustab testimiseks keerukate sõltuvusgraafikute koostamist, kuna peate juurutama ainult kontekstiklassi, mis laiendab teie subjekti pakkujat. MainContext
ülaltoodud on suurepärane näide, kus kõigil liidestel on vaikerakendused, nii et sellel võib olla tühi rakendus. Sõltuvuse asendamine nõuab ainult selle pakkuja meetodi tühistamist.
Vaatame järgmist teleklasside testi. See peab teleri kiirendama, kuid klassi konstruktorile helistamise asemel kasutab telerit. Provideri liides. TvSource.Provideril pole vaikerakendust, seega peame selle ise kirjutama.
public class TVTest { @Test public void testWithProvider() { TvSource source = Mockito.mock(TvSource.class); TV.Provider provider = () -> source; // lambdas FTW provider.tv().turnOn(); Mockito.verify(source, times(1)).tuneChannel(42); } }
Lisame nüüd teleklassile veel ühe sõltuvuse. CathodeRayTube sõltuvus töötab võluväel, et pilt teleriekraanile ilmuks. See on teleri rakendusest lahti ühendatud, kuna võiksime tulevikus minna üle LCD-le või LED-ile.
public class TV { public TV(TvSource source, CathodeRayTube cathodeRayTube) { ... } public interface Provider extends TvSource.Provider, CathodeRayTube.Provider { default TV tv() { return new TV(tvSource(), cathodeRayTube()); } } } public class CathodeRayTube { public void beam() { System.out.println('Beaming electrons to produce the TV image'); } public interface Provider { default CathodeRayTube cathodeRayTube() { return new CathodeRayTube(); } } }
Kui teete seda, märkate, et äsja kirjutatud test koostab ja läbib ootuspäraselt. Lisasime telerisse uue sõltuvuse, kuid pakkusime ka vaikimisi rakendust. See tähendab, et me ei pea seda mõnitama, kui tahame lihtsalt kasutada reaalset teostust, ja meie testid võivad luua keerukaid objekte mis tahes soovitud visuaalse detailsusega.
See on kasulik, kui soovite keerulises klassihierarhias mõnd konkreetset pilgata (nt ainult andmebaasi juurdepääsukiht). Muster võimaldab sellist tüüpi hõlpsalt seadistada seltskondlikud testid mida mõnikord eelistatakse üksikkatsetele.
Sõltumata eelistusest võite olla kindel, et võite pöörduda mis tahes vormis testimise poole, mis sobib igas olukorras paremini teie vajadustele.
Nagu näete, puuduvad viited ega mainimised välistele komponentidele. See on paljude projektide jaoks võtmetähtsusega, millel on suurust või isegi turvanõuded. See aitab ka koostalitlusvõimet, sest raamistikud ei pea pühenduma konkreetsele DI-raamistikule. Java-s on tehtud jõupingutusi nagu JSR-330 sõltuvuse süstimine Java-standardile mis leevendavad ühilduvusprobleeme.
Teenuse lokaliseerimise rakendused ei tugine tavaliselt peegeldusele, kuid DI rakendused siiski tuginevad (välja arvatud märkimisväärne Dagger 2). Sellel on rakenduse käivitamise aeglustamise peamised puudused, kuna raamistik peab teie moodulid skannima, sõltuvusgraafiku lahendama, objektid peegeldavalt üles ehitama jne.
Mixin Injection nõuab, et kirjutaksite oma teenuste kiirendamiseks koodi, mis on sarnane teenuse asukoha mustris registreerimise etapiga. See väike lisatöö eemaldab peegeldavad kõned täielikult, muutes teie koodi kiiremaks ja arusaadavaks.
Kaks projekti, mis hiljuti minu tähelepanu pälvisid ja millest on kasu peegeldumise vältimisest, on Graali substraat VM ja Kotlin / Pärismaalane . Mõlemad kompileerivad algse baitkoodini ja see nõuab, et kompilaator teaks eelnevalt teie peegeldavatest kõnedest. Graali puhul on see täpsustatud punktis a JSON-fail, mida on raske kirjutada , ei saa staatiliselt kontrollida, seda ei saa oma lemmiktööriistade abil hõlpsasti taastada. Mixin Injectioni kasutamine peegeldumise vältimiseks on suurepärane võimalus saada omakeelse kompileerimise eeliseid.
Nõutavate liideste juurutamise ja laiendamise abil koostate sõltuvusgraafiku üks tükk korraga. Iga pakkuja istub konkreetse rakenduse kõrval, mis toob teie programmi juurde korra ja loogika. Selline kiht on tuttav, kui olete varem kasutanud mustri Mixin või kooki.
Siinkohal tasub ehk rääkida klassist MainContext. See on sõltuvusgraafiku juur ja teab suurt pilti. See klass hõlmab kõiki teenusepakkujate liideseid ja on staatilise kontrolli lubamise võti. Kui naaseme näite juurde ja eemaldame Cable.Provideri rakenduste loendist, näeme seda selgelt:
magento 2 juhendaja arendajale
static class MainContext implements TV.Provider { } // ^^^ // MainContext is not abstract and does not override abstract method tvSource() in TvSource.Provider
Siin juhtus see, et rakendus ei määranud konkreetset kasutatavat TvSource'i ja kompilaator tabas vea. Teenuseotsija ja peegelpõhise DI-ga oleks see viga võinud jääda tähelepanuta, kuni programm tööajal kokku jooksis - isegi kui kõik üksuse testid olid läbitud! Usun, et need ja muud meie näidatud eelised kaaluvad üles mustri toimimiseks vajaliku katlakivi kirjutamise negatiivse külje.
Naaseme CathodeRayTube'i näite juurde ja lisame ringikujulise sõltuvuse. Oletame, et me tahame, et talle süstitakse telerieksemplari, seega laiendame telerit. Pakkuja:
public class CathodeRayTube { public interface Provider extends TV.Provider { // ^^^ // cyclic inheritance involving CathodeRayTube.Provider default CathodeRayTube cathodeRayTube() { return new CathodeRayTube(); } } }
Koostaja ei luba tsüklilist pärimist ja me ei saa sellist suhet määratleda. Enamik raamistikke ebaõnnestub käitamise ajal, kui see juhtub, ja arendajad kipuvad selle ümber töötama, et ainult programm käivitada. Kuigi seda anti-mustrit võib leida reaalses maailmas, on see tavaliselt märk halbast kujundusest. Kui koodi ei õnnestu koostada, peaksime julgustama otsima paremaid lahendusi, enne kui muutmiseks on liiga hilja.
Üks argument SL-i kasuks DI asemel on see, et seda on lihtne ja silumine lihtsam. Näidetest on selge, et sõltuvuse tuvastamine on lihtsalt pakkuja meetodi kõnede ahel. Sõltuvuse allika jälitamine on sama lihtne kui meetodikõnesse astumine ja vaatamine, kuhu jõuate. Silumine on mõlemast alternatiivist lihtsam, sest saate otse teenusepakkujalt navigeerida täpselt seal, kus sõltuvused on tuvastatud.
Tähelepanelik lugeja võib olla märganud, et see rakendus ei lahenda teenuse eluea probleemi. Kõik pakkujate pakutavad meetodid kutsuvad esile uued objektid, muutes selle sarnaseks Kevade prototüübi ulatus .
See ja muud kaalutlused jäävad selle artikli reguleerimisalast veidi välja, kuna tahtsin lihtsalt tutvustada mustri olemust üksikasju häirimata. Toote täielikul kasutamisel ja rakendamisel tuleks siiski arvestada täislahendusega koos eluaegse toega.
Sõltumata sellest, kas olete harjunud sõltuvuse süstimise raamistikega või kirjutate oma teenuse lokaatorid, võiksite seda alternatiivi uurida. Kaaluge mixin-mustri kasutamist, mida me just nägime, ja vaadake, kas saate oma koodi turvalisemaks ja mõistlikumaks muuta.
Seotud: JS-i parimad tavad: ehitage Discript Bot koos TypeScripti ja sõltuvuse süstimisegaRäägime juhtimise inversioonist, kui koodijupp on võimeline delegeerima käitumise teisele komponendile, mis pole selle koodi kirjutamise ajal teada (tavaliselt üldtuntud liideste ja laienduspunktide kaudu).
Sõltuvuse süstimine on muster, mille abil saame saavutada süsteemi komponentide tasemel juhtimise inversiooni. Klass deklareerib ainult eesmärgi saavutamiseks vajalikke komponente, mitte seda, kuidas neid komponente leida või luua.