Ohjelmointi

Käytä vakiotyyppejä turvallisempaan ja puhtaampaan koodiin

Tässä opetusohjelmassa laajennetaan ajatusta luetellut vakiot kuten on kuvattu Eric Armstrongin julkaisussa "Luo lueteltuja vakioita Javaan". Suosittelen voimakkaasti, että luet tämän artikkelin ennen kuin upotat itsesi tähän artikkeliin, koska oletan, että olet perehtynyt lueteltuihin vakioihin liittyviin käsitteisiin, ja aion laajentaa eräitä esimerkkejä koodista, jonka Eric esitti.

Vakioiden käsite

Käsitellessäni lueteltuja vakioita aion keskustella lueteltu osa käsitteestä artikkelin lopussa. Toistaiseksi keskitymme vain vakio näkökohta. Vakiot ovat periaatteessa muuttujia, joiden arvo ei voi muuttua. Avainsana C / C ++: ssa vakio käytetään näiden vakiomuuttujien ilmoittamiseen. Java-sovelluksessa käytät avainsanaa lopullinen. Tässä esitelty työkalu ei kuitenkaan ole vain primitiivinen muuttuja; se on varsinainen objektin esiintymä. Kohde-esiintymät ovat muuttumattomia ja muuttumattomia - niiden sisäistä tilaa ei voida muuttaa. Tämä on samanlainen kuin yksittäinen malli, jossa luokassa voi olla vain yksi yksittäinen esiintymä; tässä tapauksessa luokassa voi kuitenkin olla vain rajoitettu ja ennalta määrätty joukko instansseja.

Tärkeimmät syyt vakioiden käyttämiseen ovat selkeys ja turvallisuus. Esimerkiksi seuraava koodikappale ei ole itsestään selvä:

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

Tämän koodin avulla voimme varmistaa, että väriä asetetaan. Mutta mitä väriä 5 edustaa? Jos tämän koodin kirjoitti yksi niistä harvoista ohjelmoijista, jotka kommentoivat hänen työstään, saatamme löytää vastauksen tiedoston yläosasta. Mutta todennäköisemmin meidän on kaivettava joitain vanhoja suunnitteluasiakirjoja (jos niitä on edes olemassa) selityksen saamiseksi.

Selkeämpi ratkaisu on määrittää arvon 5 muuttujalle, jolla on merkityksellinen nimi. Esimerkiksi:

 julkinen staattinen lopullinen int RED = 5; public void someMethod () {setColor (RED); } 

Nyt voimme kertoa välittömästi, mitä koodilla tapahtuu. Väri asetetaan punaiseksi. Tämä on paljon puhtaampaa, mutta onko se turvallisempaa? Entä jos toinen kooderi hämmentyy ja ilmoittaa erilaiset arvot näin:

julkinen staattinen lopullinen int RED = 3; julkinen staattinen lopullinen int VIHREÄ = 5; 

Nyt meillä on kaksi ongelmaa. Ensinnäkin, PUNAINEN ei ole enää asetettu oikeaan arvoon. Toiseksi punaisen arvon edustaa muuttuja nimeltä VIHREÄ. Ehkä kaikkein pelottavin osa on, että tämä koodi kääntyy hienosti, ja virhettä ei välttämättä havaita ennen tuotteen lähettämistä.

Voimme korjata tämän ongelman luomalla lopullisen väriluokan:

julkinen luokka Väri {julkinen staattinen loppu int PUNAINEN = 5; julkinen staattinen lopullinen int VIHREÄ = 7; } 

Sitten kannustamme ohjelmoijia dokumentaation ja koodin tarkistuksen avulla käyttämään sitä näin:

 public void someMethod () {setColor (Väri.PUNAINEN); } 

Sanon rohkaisevaa, koska koodiluettelon muotoilu ei salli meidän pakottaa kooderia noudattamaan; koodi kääntyy edelleen, vaikka kaikki ei olisikaan kunnossa. Vaikka tämä onkin hieman turvallisempaa, se ei ole täysin turvallista. Vaikka ohjelmoijat pitäisi Käytä Väri luokassa, heitä ei vaadita. Ohjelmoijat voivat helposti kirjoittaa ja koota seuraavan koodin:

 setColor (3498910); 

Onko setColor Menetelmä tunnistaa tämän suuren määrän väriksi? Luultavasti ei. Joten miten voimme suojautua näiltä kelmiohjelmoijilta? Siellä vakiotyypit tulevat auttamaan.

Aloitamme määrittelemällä menetelmän allekirjoitus uudelleen:

 public void setColor (väri x) {...} 

Nyt ohjelmoijat eivät voi syöttää mielivaltaista kokonaislukua. Heidän on pakko antaa voimassa oleva Väri esine. Esimerkki tämän toteuttamisesta saattaa näyttää tältä:

 public void someMethod () {setColor (uusi väri ("punainen")); } 

Työskentelemme edelleen puhtaalla, luettavalla koodilla, ja olemme paljon lähempänä absoluuttisen turvallisuuden saavuttamista. Mutta emme ole vielä siellä. Ohjelmoijalla on vielä tilaa tuhota ja hän voi mielivaltaisesti luoda uusia värejä, kuten:

 public void someMethod () {setColor (uusi Väri ("Hei, nimeni on Ted.")); } 

Estämme tämän tilanteen tekemällä Väri luokka on muuttumaton ja piilottaa ennakoinnin ohjelmoijalta. Teemme jokaisesta erityyppisestä väristä (punainen, vihreä, sininen) yhden. Tämä saavutetaan tekemällä rakentaja yksityiseksi ja altistamalla sitten julkiset kahvat rajoitetulle ja hyvin määritetylle ilmentymäluettelolle:

julkinen luokka Väri {yksityinen väri () {} julkinen staattinen lopullinen väri PUNAINEN = uusi väri (); julkinen staattinen lopullinen väri VIHREÄ = uusi väri (); julkinen staattinen lopullinen väri SININEN = uusi väri (); } 

Tässä koodissa olemme vihdoin saavuttaneet absoluuttisen turvallisuuden. Ohjelmoija ei voi valmistaa valheellisia värejä. Vain määriteltyjä värejä saa käyttää; muuten ohjelmaa ei käännetä. Näin toteutuksemme näyttää nyt:

 public void someMethod () {setColor (Väri.PUNAINEN); } 

Sitkeys

Okei, nyt meillä on puhdas ja turvallinen tapa käsitellä vakiotyyppejä. Voimme luoda objektin väriominaisuudella ja olla varmoja siitä, että väriarvo on aina voimassa. Mutta entä jos haluamme tallentaa tämän objektin tietokantaan tai kirjoittaa sen tiedostoon? Kuinka tallennamme väri-arvon? Meidän on kartoitettava nämä tyypit arvoihin.

vuonna JavaWorld Edellä mainitussa artikkelissa Eric Armstrong käytti merkkijonoarvoja. Merkkijonojen käyttäminen tarjoaa lisäbonuksen, joka antaa sinulle jotain mielekästä palata toString () menetelmä, joka tekee tuotannon virheenkorjauksesta erittäin selkeän.

Jouset voivat kuitenkin olla kalliita tallentaa. Kokonaisluku vaatii 32 bittiä arvon tallentamiseksi, kun taas merkkijono vaatii 16 bittiä merkkiä kohden (Unicode-tuen vuoksi). Esimerkiksi numero 49858712 voidaan tallentaa 32 bittiä, mutta merkkijono TURKOOSI vaatisi 144 bittiä. Jos tallennat tuhansia esineitä, joilla on väriominaisuuksia, tämä suhteellisen pieni ero biteissä (tässä tapauksessa välillä 32 ja 144) voi kasvaa nopeasti. Joten käytetään sen sijaan kokonaislukuarvoja. Mikä on ratkaisu tähän ongelmaan? Säilytämme merkkijonon arvot, koska ne ovat tärkeitä esityksen kannalta, mutta emme aio tallentaa niitä.

Java-versiot 1.1: stä lähtien pystyvät sarjaamaan objektit automaattisesti, kunhan ne toteuttavat Sarjattavissa käyttöliittymä. Jotta Java ei tallentaisi ylimääräisiä tietoja, sinun on ilmoitettava tällaiset muuttujat ohimenevä avainsana. Joten, jotta kokonaislukuarvot voidaan tallentaa tallentamatta merkkijonon esitystä, julistamme merkkijonomääritteen olevan ohimenevä. Tässä on uusi luokka sekä kokonaisluku- ja merkkijonomääritteiden käyttöoikeudet:

julkinen luokka Väri toteuttaa java.io.Serializable {private int value; yksityinen ohimenevä merkkijono; julkinen staattinen lopullinen väri RED = uusi väri (0, "punainen"); julkinen staattinen lopullinen väri SININEN = uusi väri (1, "sininen"); julkinen staattinen lopullinen väri VIHREÄ = uusi väri (2, "vihreä"); yksityinen väri (int-arvo, merkkijonon nimi) {this.value = arvo; tämä.nimi = nimi; } public int getValue () {return value; } public String toString () {return name; }} 

Nyt voimme tehokkaasti tallentaa vakiotyyppisiä esiintymiä Väri. Mutta entä niiden palauttaminen? Se tulee olemaan hieman hankalaa. Ennen kuin menemme pidemmälle, laajennetaan tämä kehykseksi, joka käsittelee kaikki edellä mainitut meille karvat ja antaa meille mahdollisuuden keskittyä yksinkertaiseen tyypin määrittelyyn.

Vakiotyyppinen kehys

Kun ymmärrämme vakaasti vakiotyypit, voin nyt siirtyä tämän kuukauden työkaluun. Työkalua kutsutaan Tyyppi ja se on yksinkertainen abstrakti luokka. Sinun tarvitsee vain luoda erittäin yksinkertainen alaluokka ja sinulla on monipuolinen vakiotyyppinen kirjasto. Tässä on meidän Väri luokka näyttää nyt:

julkinen luokka Väri laajentaa tyyppiä {suojattu väri (int-arvo, merkkijono desc) {super (arvo, desc); } julkinen staattinen lopullinen väri PUNAINEN = uusi väri (0, "punainen"); julkinen staattinen lopullinen väri SININEN = uusi väri (1, "sininen"); julkinen staattinen lopullinen väri VIHREÄ = uusi väri (2, "vihreä"); } 

Väri luokka koostuu vain rakentajasta ja muutamasta julkisesti saatavilla olevasta instanssista. Kaikki tähän asti keskusteltu logiikka määritellään ja toteutetaan yliluokassa Tyyppi; lisäämme lisää mennessämme. Tässä mitä Tyyppi näyttää toistaiseksi:

public class Type toteuttaa java.io.Serializable {private int value; yksityinen ohimenevä merkkijono; suojattu tyyppi (int-arvo, merkkijonon nimi) {this.value = arvo; tämä.nimi = nimi; } public int getValue () {return value; } public String toString () {return name; }} 

Takaisin pysyvyyteen

Uuden kehyksemme ollessa käsillä voimme jatkaa siitä, mihin jäimme keskustelemalla itsepintaisuudesta. Muista, että voimme tallentaa tyypit tallentamalla niiden kokonaislukuarvot, mutta nyt haluamme palauttaa ne. Tämä edellyttää a Katso ylös - käänteinen laskelma objektin esiintymän paikantamiseksi sen arvon perusteella. Haun suorittamiseksi tarvitsemme tavan luetella kaikki mahdolliset tyypit.

Ericin artikkelissa hän toteutti oman luettelonsa toteuttamalla vakiot solmuina linkitetyssä luettelossa. Aion hylätä tämän monimutkaisuuden ja käyttää sen sijaan yksinkertaista hashtabelia. Hajautusavaimen avain on tyypin kokonaislukuarvot (kääritty Kokonaisluku objekti), ja tiivisteen arvo viittaa tyypin esiintymään. Esimerkiksi VIHREÄ esiintymä Väri tallennettaisiin näin:

 hashtable.put (uusi kokonaisluku (GREEN.getValue ()), VIHREÄ); 

Emme tietenkään halua kirjoittaa tätä jokaiselle mahdolliselle tyypille. Arvoja voi olla satoja, mikä luo konekirjoituksen painajaisen ja avaa oven joillekin ikäville ongelmille - saatat unohtaa laittaa yhden arvoista hashtableen, etkä voi esimerkiksi etsiä sitä myöhemmin. Joten julistamme globaalin hashtabelin sisällä Tyyppi ja muokkaa konstruktoria tallentamaan kartoitus luomisen yhteydessä:

 yksityinen staattinen lopullinen Hashtable-tyyppi = uusi Hashtable (); suojattu tyyppi (int-arvo, merkkijono desc) {this.arvo = arvo; tämä.desc = desc; type.put (uusi kokonaisluku (arvo), tämä); } 

Mutta tämä luo ongelman. Jos meillä on alaluokka nimeltä Väri, jolla on tyyppi (eli Vihreä), jonka arvo on 5, ja sitten luodaan toinen alaluokka nimeltä Sävy, jolla on myös tyyppi (eli Tumma), jonka arvo on 5, vain yksi niistä tallennetaan hashtableen - viimeinen instantioitavana.

Tämän välttämiseksi meidän on tallennettava kahvaan tyyppiä sen arvon lisäksi myös sen perusteella luokassa. Luodaan uusi menetelmä tyyppiviittausten tallentamiseksi. Käytämme hashtable hashtableja. Sisäinen hashtable on arvojen yhdistäminen tyyppeihin kullekin alaluokalle (Väri, Sävy, ja niin edelleen). Ulompi hashtable on aliluokkien kartoitus sisempiin pöytiin.

Tämä rutiini yrittää ensin hankkia sisemmän pöydän ulkopöydästä. Jos se saa nollan, sisäistä taulukkoa ei ole vielä olemassa. Joten, luomme uuden sisäpöydän ja laitamme sen ulkopöytään. Seuraavaksi lisätään arvon / tyypin kartoitus sisempään taulukkoon ja olemme valmiit. Tässä koodi:

 private void storeType (Type type) {String className = type.getClass (). getName (); Hashtable-arvot; synkronoitu (tyypit) // vältä kilpailuolosuhteita sisäisen taulukon luomisessa {arvot = (Hashtable) tyypit.get (luokanNimi); if (arvot == null) {arvot = uusi Hashtable (); types.put (className, arvot); }} values.put (uusi kokonaisluku (type.getValue ()), type); } 

Ja tässä on rakentajan uusi versio:

 suojattu tyyppi (int-arvo, merkkijono desc) {tämä.arvo = arvo; tämä.desc = desc; storeType (tämä); } 

Nyt kun tallennamme tyyppien ja arvojen tiekartan, voimme tehdä hakuja ja palauttaa siten ilmentymän arvon perusteella. Haku vaatii kahta asiaa: kohde-alaluokan identiteetin ja kokonaisluvun arvon. Näiden tietojen avulla voimme purkaa sisäisen taulukon ja löytää kahvan vastaavan tyyppiseen ilmentymään. Tässä koodi:

 public staattinen tyyppi getByValue (Class classRef, int arvo) {Type type = null; Merkkijono className = classRef.getName (); Hashtable-arvot = (Hashtable) tyypit.get (className); if (arvot! = null) {type = (Tyyppi) values.get (uusi kokonaisluku (arvo)); } return (tyyppi); } 

Arvon palauttaminen on näin yksinkertaista (huomaa, että palautusarvo on valettava):

 int arvo = // luettu tiedostosta, tietokannasta jne. Väritausta = (ColorType) Type.findByValue (ColorType.class, arvo); 

Tyyppien luettelointi

Hashtable-of-hashtables-organisaation ansiosta on erittäin helppoa paljastaa Ericin toteutuksen tarjoama luettelointitoiminto. Ainoa huomautus on, että Ericin suunnittelun mukaista lajittelua ei taata. Jos käytät Java 2: ta, voit korvata lajitellun kartan sisäisillä hashtabeleilla. Mutta kuten totesin tämän sarakkeen alussa, olen nyt huolissani vain JDK: n 1.1-versiosta.

Ainoa logiikka, jota tarvitaan tyyppien luettelointiin, on sisäisen taulukon noutaminen ja sen elementtiluettelon palauttaminen. Jos sisempää taulukkoa ei ole, palautamme yksinkertaisesti nollan. Tässä on koko menetelmä: