Programavimas

Jei norite saugesnio ir švaresnio kodo, naudokite pastovius tipus

Šioje pamokoje bus išplėsta idėja išvardytos konstantos kaip aprašyta Erico Armstrongo „Sukurti išvardytas konstantas„ Java “. Primygtinai rekomenduoju perskaityti tą straipsnį prieš įsigilinant į šį straipsnį, nes manysiu, kad esate susipažinę su sąvokomis, susijusiomis su išvardintomis konstantomis, ir aš išplėsiu keletą pavyzdžių kodo, kurį pateikė Erikas.

Konstantų samprata

Kalbėdamas apie išvardytas konstantas, aptarsiu išvardyti straipsnio pabaigoje pateikiama koncepcijos dalis. Kol kas sutelksime dėmesį tik į pastovus aspektas. Konstantai iš esmės yra kintamieji, kurių vertė negali pasikeisti. C / C ++ - raktinis žodis konst yra naudojamas šiems pastoviems kintamiesiems deklaruoti. „Java“ naudojate raktinį žodį galutinis. Tačiau čia įvestas įrankis nėra tik primityvus kintamasis; tai tikrasis objekto egzempliorius. Objekto egzemplioriai yra nekintami ir nekeičiami - jų vidinė būsena negali būti modifikuota. Tai panašu į pavienį modelį, kai klasėje gali būti tik vienas egzempliorius; tačiau šiuo atveju klasėje gali būti tik ribotas ir iš anksto nustatytas egzempliorių rinkinys.

Pagrindinės konstantų naudojimo priežastys yra aiškumas ir saugumas. Pvz., Šis kodo elementas nėra savaime suprantamas:

 public void setColor (int x) {...} public void someMethod () {setColor (5); } 

Iš šio kodo galime įsitikinti, kad nustatoma spalva. Bet kokią spalvą reiškia 5? Jei šį kodą parašė vienas iš tų retų programuotojų, kurie komentuoja jo ar jos darbą, atsakymą galėtume rasti failo viršuje. Bet labiau tikėtina, kad paaiškinimui turėsime pasidomėti kai kuriais senais projektiniais dokumentais (jei tokių yra).

Aiškesnis sprendimas yra priskirti 5 reikšmę kintamajam su prasmingu pavadinimu. Pavyzdžiui:

 viešasis statinis galutinis int RED = 5; public void someMethod () {setColor (RED); } 

Dabar mes galime iš karto pasakyti, kas vyksta su kodu. Spalva nustatoma į raudoną. Tai daug švariau, bet ar saugiau? Ką daryti, jei kitas programuotojas susipainioja ir deklaruoja skirtingas vertes:

viešasis statinis galutinis int RED = 3; public static final int ŽALIOJI = 5; 

Dabar turime dvi problemas. Pirmiausia, RAUDONA nebėra nustatyta teisinga vertė. Antra, raudonos spalvos reikšmę nurodo kintamasis, pavadintas ŽALIAS. Bene baisiausia yra tai, kad šis kodas bus sukompiliuotas puikiai, o klaida gali būti aptikta tik tada, kai produktas bus išsiųstas.

Šią problemą galime išspręsti sukurdami galutinę spalvų klasę:

viešoji klasė Spalva {public static final int RED = 5; public static final int ŽALIOJI = 7; } 

Tada, naudodamiesi dokumentais ir kodų peržiūra, raginame programuotojus tai naudoti taip:

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

Aš sakau skatinti, nes to kodo sąrašas neleidžia mums priversti koduotojo laikytis; kodas vis tiek bus sudarytas, net jei viskas bus ne taip tvarkingai. Taigi, nors tai yra šiek tiek saugiau, tai nėra visiškai saugu. Nors programuotojai turėtų naudoti Spalva klasės, jie neprivalo. Programuotojai labai lengvai galėjo parašyti ir sudaryti šį kodą:

 setColor (3498910); 

Ar setColor metodas atpažinti šį didelį skaičių kaip spalvą? Tikriausiai ne. Taigi, kaip mes galime apsisaugoti nuo šių nesąžiningų programuotojų? Čia gelbsti konstantų tipai.

Pradedame iš naujo apibrėždami metodo parašą:

 public void setColor (spalva x) {...} 

Dabar programuotojai negali perduoti savavališkos sveikojo skaičiaus vertės. Jie priversti pateikti galiojantį dokumentą Spalva objektas. To įgyvendinimo pavyzdys gali atrodyti taip:

 public void someMethod () {setColor (nauja Spalva ("Raudona")); } 

Mes vis dar dirbame su švariu, lengvai įskaitomu kodu ir esame daug arčiau absoliučio saugumo. Bet mes dar ne visai ten. Programuotojas vis dar turi šiek tiek vietos sunaikinti ir gali savavališkai sukurti tokias naujas spalvas:

 public void someMethod () {setColor (nauja Spalva ("Sveiki, mano vardas Tedas.")); } 

Mes išvengiame šios situacijos, atlikdami Spalva klasė nekintama ir akimirksnį slepianti nuo programuotojo. Kiekvieną skirtingų spalvų tipą (raudoną, žalią, mėlyną) padarome po vieną. Tai pasiekiama paverčiant konstruktorių privačiu, o tada viešosioms rankenoms pateikiant ribotą ir tiksliai apibrėžtą egzempliorių sąrašą:

public class Spalva {private Color () {} public static final Spalva RED = nauja Spalva (); public static final Spalva ŽALIA = nauja Spalva (); public static final Spalva MĖLYNA = nauja spalva (); } 

Pagal šį kodą mes pagaliau pasiekėme absoliučią saugą. Programuotojas negali pagaminti fiktyvių spalvų. Gali būti naudojamos tik apibrėžtos spalvos; priešingu atveju programa nebus kompiliuojama. Štai kaip dabar atrodo mūsų diegimas:

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

Atkaklumas

Gerai, dabar mes turime švarų ir saugų būdą kovoti su nuolatiniais tipais. Mes galime sukurti objektą su spalvos atributu ir būti tikri, kad spalvos reikšmė visada galios. Bet ką daryti, jei norime išsaugoti šį objektą duomenų bazėje arba įrašyti jį į failą? Kaip išsaugome spalvos vertę? Šiuos tipus turime susieti su vertybėmis.

Viduje konors „JavaWorld“ aukščiau minėtame straipsnyje Ericas Armstrongas naudojo eilutės reikšmes. Stygų naudojimas suteikia papildomą premiją, suteikiant jums kažką prasmingo grįžti į toString () metodas, dėl kurio derinimo išvestis yra labai aiški.

Vis dėlto stygos gali būti brangios laikyti. Sveikasis skaičius reikalauja 32 bitų, kad išsaugotų jo vertę, o eilutė reikalauja 16 bitų vienam simboliui (dėl „Unicode“ palaikymo). Pavyzdžiui, numerį 49858712 galima įrašyti 32 bitais, bet eilute TURKIS reikėtų 144 bitų. Jei saugote tūkstančius objektų su spalvų atributais, šis palyginti nedidelis bitų skirtumas (šiuo atveju tarp 32 ir 144) gali greitai padidėti. Taigi naudokime sveikųjų skaičių reikšmes. Koks šios problemos sprendimas? Mes išsaugosime eilučių reikšmes, nes jos yra svarbios pateikimui, tačiau jų nesaugosime.

"Java" versijos nuo 1.1 versijos gali automatizuoti objektų seriją, jei tik jos įdiegia Serijinis sąsaja. Norėdami neleisti „Java“ saugoti pašalinių duomenų, tokius kintamuosius turite deklaruoti naudodami trumpalaikis raktinis žodis. Taigi, norėdami išsaugoti sveikųjų skaičių reikšmes neišsaugodami eilutės atvaizdavimo, mes skelbiame, kad eilutės atributas yra laikinas. Čia yra nauja klasė kartu su sveikojo ir eilutės atributų prieigomis:

public class Color įgyvendina java.io.Serializable {private int value; privati ​​trumpalaikė eilutės pavadinimas; public static final Spalva RED = nauja spalva (0, "Raudona"); public static final Spalva MĖLYNA = nauja spalva (1, "Mėlyna"); public static final Spalva ŽALIA = nauja spalva (2, "Žalia"); privati ​​spalva (int reikšmė, eilutės pavadinimas) {this.value = value; this.name = vardas; } public int getValue () {return value; } public String toString () {grąžinimo vardas; }} 

Dabar galime efektyviai saugoti pastovaus tipo egzempliorius Spalva. Bet kaip su jų atstatymu? Tai bus šiek tiek keblu. Prieš eidami toliau, išplėskime tai į sistemą, kuri padės išspręsti visas anksčiau minėtas spąstus, leisdamas mums sutelkti dėmesį į paprastą tipų apibrėžimo klausimą.

Pastovaus tipo karkasas

Mūsų tvirtas supratimas apie pastovius tipus dabar galiu pereiti prie šio mėnesio įrankio. Įrankis vadinamas Tipas ir tai yra paprasta abstrakti klasė. Viskas, ką jums reikia padaryti, tai sukurti labai paprastas poklasis ir jūs turite pilnavertę pastovaus tipo biblioteką. Štai ką mes Spalva klasė atrodys kaip dabar:

viešoji klasė Spalva tęsiasi Tipas {apsaugota spalva (int reikšmė, String desc) {super (reikšmė, desc); } public static final Spalva RED = nauja spalva (0, "Raudona"); public static final Spalva MĖLYNA = nauja spalva (1, "Mėlyna"); public static final Spalva ŽALIA = nauja spalva (2, "Žalia"); } 

Spalva klasė susideda tik iš konstruktoriaus ir kelių viešai prieinamų egzempliorių. Visa iki šiol aptarta logika bus apibrėžta ir įgyvendinta superklase Tipas; mes pridėsime daugiau, kai eisime kartu. Štai ką Tipas atrodo iki šiol:

public class Type įgyvendina java.io.Serializable {private int value; privati ​​trumpalaikė eilutės pavadinimas; apsaugotas tipas (int reikšmė, eilutės pavadinimas) {this.value = value; this.name = vardas; } public int getValue () {return value; } public String toString () {grąžinimo vardas; }} 

Grįžtame prie atkaklumo

Turėdami naują sistemą, galime tęsti ten, kur baigėme diskusijoje apie atkaklumą. Atminkite, kad tipus galime išsaugoti saugodami jų sveikąsias skaičius, tačiau dabar norime juos atkurti. Tam reikės a peržiūra - atvirkštinis skaičiavimas objekto egzemplioriui surasti pagal jo vertę. Norėdami atlikti paiešką, turime išvardyti visus galimus tipus.

Eriko straipsnyje jis įgyvendino savo paties išvardijimą, įgyvendindamas konstantas kaip mazgus susietame sąraše. Aš atsisakysiu šio sudėtingumo ir vietoj to naudosiu paprastą hashtable. Maišos raktas bus viso tipo vertės (suvyniotos į Sveikasis skaičius objektas), o maišos vertė bus nuoroda į tipo egzempliorių. Pavyzdžiui, ŽALIAS pavyzdys Spalva būtų saugomi taip:

 hashtable.put (naujas sveikasis skaičius (GREEN.getValue ()), GREEN); 

Žinoma, nenorime to įvesti kiekvienam galimam tipui. Gali būti šimtai skirtingų vertybių, tokiu būdu sukuriant mašininį košmarą ir atveriant duris kai kurioms nemalonioms problemoms - galite pamiršti įdėti vieną iš vertybių į „hashtable“ ir tada nebegalėsite jos ieškoti, pavyzdžiui, vėliau. Taigi mes paskelbsime pasaulinį hashtable Tipas ir modifikuoti konstruktorių, kad sukūrimas išsaugotų žemėlapį:

 privatūs statiniai galutiniai „Hashtable“ tipai = naujas „Hashtable“ (); apsaugotas tipas (int reikšmė, String desc) {this.value = reikšmė; tai.desc = desc; types.put (naujas sveikasis skaičius (reikšmė), tai); } 

Bet tai sukuria problemą. Jei turime poklasį, vadinamą Spalva, kuris turi tipą (tai yra, Žalias), kurio vertė yra 5, ir tada mes sukuriame kitą poklasį, vadinamą Atspalvis, kuris taip pat turi tipą (tai yra Tamsu), kurio vertė yra 5, tik viena iš jų bus saugoma „hashtable“ - paskutinė, kuri bus iš karto sukurta.

Norėdami to išvengti, turime laikyti tokio tipo rankeną, atsižvelgdami ne tik į jos vertę, bet ir į jos vertę klasė. Sukurkime naują metodą, kaip išsaugoti tipų nuorodas. Mes naudosime hashtable hashtable. Vidinis šablonas bus kiekvieno konkretaus poklasio verčių susiejimas su tipais (Spalva, Atspalvis, ir taip toliau). Išorinis hashtable bus poklasių susiejimas su vidinėmis lentelėmis.

Ši rutina pirmiausia bandys įgyti vidinį stalą iš išorinio stalo. Jei jis gauna nulį, vidinė lentelė dar neegzistuoja. Taigi, mes sukuriame naują vidinį stalą ir dedame jį į išorinį stalą. Tada prie vidinės lentelės pridedame vertės / tipo atvaizdavimą ir viskas. Štai kodas:

 private void storeType (Tipo tipas) {String className = type.getClass (). getName (); „Hashtable“ vertės; sinchronizuoti (tipai) // venkite lenktynių sąlygų kurdami vidinę lentelę {reikšmės = (Hashtable) tipai.get (className); if (reikšmės == null) {reikšmės = nauja „Hashtable“ (); types.put (className, reikšmės); }} values.put (naujas sveikas skaičius (type.getValue ()), type); } 

Ir štai nauja konstruktoriaus versija:

 apsaugotas tipas (int reikšmė, String desc) {this.value = reikšmė; tai.desc = desc; storeType (tai); } 

Dabar, kai saugome tipų ir verčių kelių žemėlapį, galime atlikti paiešką ir taip atkurti egzempliorių pagal vertę. Norint atlikti paiešką reikia dviejų dalykų: tikslinio poklasio tapatybės ir sveiko skaičiaus vertės. Naudodamiesi šia informacija, mes galime išskleisti vidinę lentelę ir rasti atitikimo tipo egzemplioriaus rankeną. Štai kodas:

 public static Tipas getByValue (Class classRef, int reikšmė) {Type type = null; Eilutė className = classRef.getName (); „Hashtable“ reikšmės = [Hashtable] tipai.get (className); if (reikšmės! = null) {type = (Tipas) reikšmės.get (naujas Sveikasis skaičius (reikšmė)); } return (tipas); } 

Taigi vertės atkūrimas yra toks paprastas (atkreipkite dėmesį, kad grąžinimo vertė turi būti išleista):

 int reikšmė = // skaityti iš failo, duomenų bazės ir kt. Spalvų fonas = (ColorType) Type.findByValue (ColorType.class, value); 

Išvardijant tipus

Mūsų „hashtable-of-hashtables“ organizavimo dėka yra nepaprastai paprasta atskleisti Erico siūlomą skaitomumo funkciją. Vienintelis įspėjimas yra tas, kad rūšiavimas, kurį siūlo Eriko dizainas, nėra garantuotas. Jei naudojate „Java 2“, galite surūšiuotą žemėlapį pakeisti vidinėmis grotelėmis. Bet, kaip sakiau šio stulpelio pradžioje, man dabar rūpi tik 1.1 versijos JDK.

Vienintelė logika, reikalinga tipams išvardyti, yra vidinės lentelės gavimas ir jos elementų sąrašo grąžinimas. Jei vidinė lentelė neegzistuoja, mes tiesiog grąžiname nulį. Čia yra visas metodas: