Programiranje

Za varnejšo in čistejšo kodo uporabite konstantne vrste

V tej vadnici bomo razširili idejo naštete konstante kot je zajeto v Ericu Armstrongu, "Ustvari naštete konstante v Javi." Močno priporočam, da preberete članek, preden se poglobite v ta, saj predvidevam, da poznate koncepte, povezane z naštetimi konstantami, in razširil bom nekaj primerov kode, ki jo je predstavil Eric.

Pojem konstant

Pri obravnavi naštetih konstant bom razpravljal o našteti del koncepta na koncu članka. Za zdaj se bomo osredotočili le na konstanten vidik. Konstante so v bistvu spremenljivke, katerih vrednost se ne more spremeniti. V C / C ++ ključna beseda const se uporablja za razglasitev teh konstantnih spremenljivk. V Javi uporabljate ključno besedo dokončno. Vendar tukaj predstavljeno orodje ni le primitivna spremenljivka; gre za dejanski primerek predmeta. Primerki predmeta so nespremenljivi in ​​nespremenljivi - njihovega notranjega stanja ni mogoče spreminjati. To je podobno enojnemu vzorcu, kjer ima lahko razred samo en primerek; v tem primeru pa ima lahko razred le omejen in vnaprej določen nabor primerov.

Glavna razloga za uporabo konstant sta jasnost in varnost. Na primer, naslednji del kode ni samoumeven:

 javna praznina setColor (int x) {...} javna praznina someMethod () {setColor (5); } 

Iz te kode lahko ugotovimo, da je nastavljena barva. Kakšno barvo pa predstavlja 5? Če bi to kodo napisal eden redkih programerjev, ki komentira njegovo delo, bi odgovor morda našli na vrhu datoteke. Verjetneje pa bomo za razlago morali poiskati nekaj starih projektnih dokumentov (če sploh obstajajo).

Jasnejša rešitev je dodelitev vrednosti 5 spremenljivki s pomenljivim imenom. Na primer:

 javni statični končni int RDEČA = 5; javna void someMethod () {setColor (RDEČA); } 

Zdaj lahko takoj povemo, kaj se dogaja s kodo. Barva je nastavljena na rdečo. To je veliko čistejše, a je varnejše? Kaj pa, če se še en kodirnik zmede in tako objavi različne vrednosti:

javni statični končni int RDEČA = 3; javni statični končni int GREEN = 5; 

Zdaj imamo dve težavi. Najprej, RDEČA ni več nastavljena na pravilno vrednost. Drugič, vrednost za rdečo predstavlja spremenljivka named ZELENA. Morda je najbolj grozljivo, da se bo ta koda zbrala v redu in napake morda ne bo mogoče zaznati, dokler izdelek ni poslan.

To težavo lahko odpravimo z ustvarjanjem dokončnega barvnega razreda:

javni razred Barva {javni statični končni int RDEČA = 5; javni statični končni int GREEN = 7; } 

Nato prek dokumentacije in pregleda kode spodbujamo programerje, da jo uporabljajo tako:

 javna void someMethod () {setColor (Color.RED); } 

Pravim spodbudo, ker nam zasnova v tem seznamu kod ne omogoča, da bi morali kodera prisiliti v skladnost; koda se bo še vedno prevajala, tudi če ni vse v redu. Čeprav je to nekoliko varneje, ni povsem varno. Čeprav programerji bi morali uporabi Barva razreda, od njih se ne zahteva. Programerji bi lahko zelo enostavno napisali in prevedli naslednjo kodo:

 setColor (3498910); 

Ali setColor metoda prepozna to veliko število kot barvo? Verjetno ne. Torej, kako se lahko zaščitimo pred prevarantskimi programerji? Tam na pomoč priskočijo vrste konstant.

Začnemo z novo opredelitvijo podpisa metode:

 javna void setColor (Barva x) {...} 

Zdaj programerji ne morejo predati poljubne celoštevilčne vrednosti. Prisiljeni so priskrbeti veljavno Barva predmet. Primer izvedbe tega bi lahko bil videti tako:

 javna void someMethod () {setColor (nova barva ("rdeča")); } 

Še vedno delamo s čisto, berljivo kodo in smo veliko bližje doseganju popolne varnosti. Ampak še nismo čisto tam. Programer ima še nekaj prostora za opustošenje in lahko samovoljno ustvari nove barve, kot je ta:

 public void someMethod () {setColor (new Color ("Živjo, moje ime je Ted.")); } 

Te razmere preprečujemo z izdelavo Barva razred nespremenljiv in skrivanje primerka pred programerjem. Vsako različno vrsto barve (rdečo, zeleno, modro) naredimo po eno. To dosežemo tako, da konstruktor postane zaseben in nato javne ročaje izpostavimo omejenemu in natančno določenemu seznamu primerov:

javni razred Color {private Color () {} public static final RDEČA = nova barva (); javna statična končna barva ZELENA = nova barva (); javna statična končna barva MODRA = nova barva (); } 

V tem kodeksu smo končno dosegli popolno varnost. Programer ne more izdelovati lažnih barv. Uporabljajo se lahko samo določene barve; v nasprotnem primeru se program ne bo prevedel. Tako izgleda naša izvedba zdaj:

 javna void someMethod () {setColor (Color.RED); } 

Vztrajnost

V redu, zdaj imamo čist in varen način za spopadanje s stalnimi vrstami. Ustvarimo lahko objekt z atributom barve in smo prepričani, da bo vrednost barve vedno veljavna. Kaj pa, če želimo ta predmet shraniti v bazo podatkov ali zapisati v datoteko? Kako shranimo barvno vrednost? Te vrste moramo preslikati v vrednosti.

V JavaWorld V zgoraj omenjenem članku je Eric Armstrong uporabil nizne vrednosti. Uporaba nizov zagotavlja dodaten bonus, ki vam daje nekaj pomembnega za vrnitev v toString () metoda, ki naredi izhod za odpravljanje napak zelo jasen.

Strune pa so lahko drage za shranjevanje. Celo število zahteva 32 bitov, da shrani svojo vrednost, niz pa 16 bitov na znak (zaradi podpore Unicode). Številko 49858712 lahko na primer shranite v 32 bitov, vendar niz TURKIZ bi potreboval 144 bitov. Če shranjujete na tisoče predmetov z barvnimi atributi, se lahko ta razmeroma majhna razlika v bitih (v tem primeru med 32 in 144) hitro sešteje. Torej raje uporabimo celoštevilske vrednosti. Kakšna je rešitev te težave? Vrednosti nizov bomo ohranili, ker so pomembne za predstavitev, vendar jih ne bomo shranili.

Različice Java od 1.1 naprej lahko samodejno serializirajo predmete, če le uporabljajo Serializabilno vmesnik. Da bi Javi preprečili shranjevanje tujih podatkov, morate take spremenljivke prijaviti z prehodno ključna beseda. Torej, če želimo shraniti celoštevilske vrednosti, ne da bi shranili predstavitev niza, razglasimo atribut niza kot prehoden. Tu je novi razred, skupaj z dostopniki do atributov celo število in niz:

javni razred Color izvaja java.io.Serializable {vrednost int int; zasebno prehodno ime niza; javna statična končna barva RDEČA = nova barva (0, "rdeča"); javna statična končna barva MODRA = nova barva (1, "modra"); javna statična končna barva ZELENA = nova barva (2, "zelena"); private Color (int vrednost, ime niza) {this.value = value; this.name = ime; } public int getValue () {vrnjena vrednost; } javni String toString () {return ime; }} 

Zdaj lahko učinkovito shranjujemo primerke konstantnega tipa Barva. Kaj pa njihovo obnovo? To bo malo zapleteno. Preden nadaljujemo, razširimo to na okvir, ki bo za nas obravnaval vse prej omenjene pasti in nam omogočil, da se osredotočimo na preprosto določitev vrst.

Stalni okvir tipa

Z našim trdnim razumevanjem konstantnih tipov lahko zdaj vstopim v orodje tega meseca. Orodje se imenuje Tip in to je preprost abstraktni razred. Vse, kar morate storiti, je ustvariti zelo preprost podrazred in imate popolno knjižnico konstantnega tipa. Tukaj je tisto, kar je naše Barva razred bo videti tako kot zdaj:

javni razred Color razširja Type {protected Color (int value, String desc) {super (value, desc); } javna statična končna barva RDEČA = nova barva (0, "rdeča"); javna statična končna barva MODRA = nova barva (1, "modra"); javna statična končna barva ZELENA = nova barva (2, "zelena"); } 

The Barva class je sestavljen iz nič drugega kot konstruktor in nekaj javno dostopnih primerkov. Vsa logika, obravnavana do te točke, bo opredeljena in izvedena v nadrazredu Tip; dodali jih bomo še naprej. Tukaj je kaj Tip izgleda do zdaj:

javni razred razreda izvaja java.io.Serializable {vrednost int int; zasebno prehodno ime niza; zaščiteni tip (vrednost int, ime niza) {this.value = vrednost; this.name = ime; } public int getValue () {vrnjena vrednost; } javni String toString () {return ime; }} 

Nazaj na vztrajnost

Z našim novim okvirom v rokah lahko nadaljujemo tam, kjer smo ustavili razpravo o vztrajnosti. Ne pozabite, da lahko svoje tipe shranimo s shranjevanjem njihovih celoštevilnih vrednosti, zdaj pa jih želimo obnoviti. Za to bo potreben a Poglej gor - povratni izračun za iskanje primerka predmeta na podlagi njegove vrednosti. Za izvedbo iskanja potrebujemo način za naštevanje vseh možnih vrst.

V Ericovem članku je izvedel lastno naštevanje z uporabo konstant kot vozlišč na povezanem seznamu. Odpovedal se bom tej zapletenosti in namesto tega uporabil preprosto razpršilno tablico. Ključ za zgoščevanje bodo celoštevilčne vrednosti tipa (ovite v Celo število object), vrednost zgoščene oznake pa bo referenca na primerek tipa. Na primer ZELENA primer Barva bi bili shranjeni tako:

 hashtable.put (novo celo število (GREEN.getValue ()), GREEN); 

Seveda tega ne želimo vnašati za vsako možno vrsto. Obstaja lahko na stotine različnih vrednot, kar ustvarja tipično nočno moro in odpira vrata nekaterim neprijetnim težavam - morda boste pozabili vstaviti eno od vrednosti v zgoščevalno tabelo in je na primer kasneje ne boste mogli iskati. Tako bomo znotraj razglasili globalno razpršilno tablico Tip in spremenite konstruktor, da shrani preslikavo ob ustvarjanju:

 zasebni statični končni tipi Hashtable = new Hashtable (); zaščiteni tip (int vrednost, niz desc) {this.value = vrednost; this.desc = desc; types.put (novo celo število (vrednost), to); } 

Toda to ustvarja problem. Če imamo imenovan podrazred Barva, ki ima tip (tj. Zelena) z vrednostjo 5, nato pa ustvarimo še en podrazred, imenovan Senca, ki ima tudi tip (tj Temno) z vrednostjo 5 bo v razpršilni tabeli shranjen le eden od njih - zadnji, ki bo instanciran.

Da bi se temu izognili, moramo tipu shraniti ročaj, ki temelji ne samo na njegovi vrednosti, temveč tudi na njeni razred. Ustvarimo novo metodo za shranjevanje referenc na tipe. Uporabili bomo zgoščevalno tablico hashtables. Notranja zgoščevalna tabela bo preslikava vrednosti na tipe za vsak določen podrazred (Barva, Senca, in tako naprej). Zunanja zgoščevalna tabela bo preslikava podrazredov v notranje tabele.

Ta rutina bo najprej poskušala pridobiti notranjo tabelo od zunanje tabele. Če prejme nič, notranja tabela še ne obstaja. Tako ustvarimo novo notranjo mizo in jo postavimo v zunanjo mizo. Nato v notranjo tabelo dodamo preslikavo vrednosti / tipa in končamo. Tu je koda:

 private void storeType (Type type) {StringName razreda = type.getClass (). getName (); Vrednosti razpršilne tabele; sinhronizirano (tipi) // izogibamo se dirkalnim pogojem za ustvarjanje notranje tabele {values ​​= (Hashtable) types.get (className); if (values ​​== null) {values ​​= new Hashtable (); types.put (className, vrednosti); }} values.put (novo celo število (type.getValue ()), vrsta); } 

In tu je nova različica konstruktorja:

 zaščiteni tip (int vrednost, niz desc) {this.value = vrednost; this.desc = desc; storeType (to); } 

Zdaj, ko shranjujemo časovni načrt vrst in vrednosti, lahko izvedemo iskanje in tako obnovimo primerek, ki temelji na vrednosti. Iskanje zahteva dve stvari: identiteto ciljnega podrazreda in celoštevilsko vrednost. S pomočjo teh informacij lahko izvlečemo notranjo tabelo in poiščemo ročico primerka ustreznega tipa. Tu je koda:

 public static Type getByValue (Class classRef, int value) {Type type = null; Niz className = classRef.getName (); Vrednosti Hashtable = (Hashtable) types.get (className); if (values! = null) {type = (Type) values.get (novo celo število (vrednost)); } vrnitev (vrsta); } 

Tako je obnovitev vrednosti tako preprosta (upoštevajte, da je treba vrniti vrnjeno vrednost):

 int value = // branje iz datoteke, baze podatkov itd. Barvno ozadje = (ColorType) Type.findByValue (ColorType.class, vrednost); 

Naštevanje vrst

Zahvaljujoč naši organizaciji hashtable-of-hashtables je neverjetno enostavno izpostaviti funkcijo naštevanja, ki jo ponuja Ericova izvedba. Edino opozorilo je, da sortiranje, ki ga ponuja Ericova zasnova, ni zagotovljeno. Če uporabljate Javo 2, lahko razvrščeni zemljevid zamenjate za notranje hashtables. Kot sem že navedel na začetku te kolumne, se zdaj ukvarjam le z različico 1.1 JDK.

Edina logika, ki je potrebna za naštevanje tipov, je pridobiti notranjo tabelo in vrniti njen seznam elementov. Če notranja tabela ne obstaja, preprosto vrnemo nič. Tu je celotna metoda:

$config[zx-auto] not found$config[zx-overlay] not found