Programmering

Bruk konstante typer for tryggere og renere kode

I denne opplæringen vil vi utvide ideen om oppregnede konstanter som dekket i Eric Armstrongs, "Create enumerated konstants in Java." Jeg anbefaler på det sterkeste å lese den artikkelen før du fordyper deg i denne, da jeg antar at du er kjent med begrepene relatert til oppregnede konstanter, og jeg vil utvide noe av eksempelkoden som Eric presenterte.

Konseptet med konstanter

Når jeg håndterer oppregnede konstanter, skal jeg diskutere oppregnet en del av konseptet på slutten av artikkelen. For nå vil vi bare fokusere på konstant aspekt. Konstanter er i utgangspunktet variabler hvis verdi ikke kan endres. I C / C ++, nøkkelordet konst brukes til å erklære disse konstante variablene. I Java bruker du nøkkelordet endelig. Imidlertid er verktøyet introdusert her ikke bare en primitiv variabel; det er en faktisk objektforekomst. Objektforekomstene er uforanderlige og uforanderlige - deres interne tilstand kan ikke endres. Dette ligner på singleton-mønsteret, der en klasse bare kan ha en enkelt forekomst; i dette tilfellet kan imidlertid en klasse bare ha et begrenset og forhåndsdefinert sett med forekomster.

Hovedårsakene til å bruke konstanter er klarhet og sikkerhet. For eksempel er følgende kode ikke selvforklarende:

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

Fra denne koden kan vi fastslå at det settes en farge. Men hvilken farge representerer 5? Hvis denne koden ble skrevet av en av de sjeldne programmørene som kommenterer hans eller hennes arbeid, kan vi kanskje finne svaret øverst i filen. Men mer sannsynlig må vi grave etter noen gamle designdokumenter (hvis de til og med eksisterer) for en forklaring.

En mer klar løsning er å tilordne en verdi på 5 til en variabel med et meningsfylt navn. For eksempel:

 offentlig statisk sluttint RED = 5; offentlig ugyldig someMethod () {setColor (RED); } 

Nå kan vi fortelle umiddelbart hva som skjer med koden. Fargen blir satt til rødt. Dette er mye renere, men er det tryggere? Hva om en annen koder blir forvirret og erklærer forskjellige verdier slik:

offentlig statisk slutt int RED = 3; offentlig statisk finale int GRØNN = 5; 

Nå har vi to problemer. Først av alt, RØD er ikke lenger satt til riktig verdi. For det andre representeres verdien for rødt av variabelen som heter GRØNN. Det skumleste er kanskje at denne koden vil kompilere helt fint, og feilen blir kanskje ikke oppdaget før produktet har blitt sendt.

Vi kan løse dette problemet ved å lage en endelig fargeklasse:

offentlig klasse Farge {offentlig statisk slutt int RED = 5; offentlig statisk finale int GRØNN = 7; } 

Deretter, via dokumentasjon og kodegjennomgang, oppfordrer vi programmerere til å bruke den slik:

 offentlig ugyldig someMethod () {setColor (Color.RED); } 

Jeg sier oppmuntre fordi utformingen i kodelisten ikke tillater oss å tvinge koderen til å overholde; koden vil fortsatt kompilere selv om alt ikke er i orden. Dermed, selv om dette er litt tryggere, er det ikke helt trygt. Selv om programmerere bør bruke Farge klasse, de er ikke pålagt å. Programmerere kan veldig enkelt skrive og kompilere følgende kode:

 setColor (3498910); 

Gjør den setColor metoden gjenkjenne dette store tallet for å være en farge? Sannsynligvis ikke. Så hvordan kan vi beskytte oss mot disse useriøse programmererne? Det er der konstanttyper kommer til unnsetning.

Vi starter med å omdefinere signaturen til metoden:

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

Nå kan ikke programmerere sende inn en vilkårlig heltallverdi. De er tvunget til å gi en gyldig Farge gjenstand. Et eksempel på implementering av dette kan se slik ut:

 offentlig ugyldig someMethod () {setColor (ny farge ("rød")); } 

Vi jobber fortsatt med ren, lesbar kode, og vi er mye nærmere å oppnå absolutt sikkerhet. Men vi er ikke helt der ennå. Programmereren har fremdeles litt plass til å skape kaos og kan vilkårlig lage nye farger slik:

 offentlig ugyldig someMethod () {setColor (ny farge ("Hei, mitt navn er Ted.")); } 

Vi forhindrer denne situasjonen ved å lage Farge klasse uforanderlig og skjuler instantiering fra programmereren. Vi lager hver type farge (rød, grønn, blå) til en singleton. Dette oppnås ved å gjøre konstruktøren privat og deretter utsette offentlige håndtak for en begrenset og veldefinert liste over forekomster:

offentlig klasse Farge {privat Farge () {} offentlig statisk slutt Farge RØD = ny Farge (); offentlig statisk slutt Farge GRØNN = ny farge (); offentlig statisk slutt Farge BLÅ = ny farge (); } 

I denne koden har vi endelig oppnådd absolutt sikkerhet. Programmereren kan ikke lage falske farger. Bare de definerte fargene kan brukes; Ellers kompileres ikke programmet. Slik ser implementeringen vår ut nå:

 offentlig ugyldig someMethod () {setColor (Color.RED); } 

Standhaftighet

OK, nå har vi en ren og trygg måte å håndtere konstante typer på. Vi kan opprette et objekt med en fargeattributt og være sikker på at fargeverdien alltid vil være gyldig. Men hva om vi vil lagre dette objektet i en database eller skrive det til en fil? Hvordan lagrer vi fargeverdien? Vi må kartlegge disse typene til verdier.

I JavaWorld artikkel nevnt ovenfor, brukte Eric Armstrong strengverdier. Bruk av strenger gir den ekstra bonusen å gi deg noe meningsfylt å returnere i toString () metode, som gjør feilsøking utdata veldig tydelig.

Strenger kan imidlertid være dyre å oppbevare. Et heltall krever 32 bits for å lagre verdien mens en streng krever 16 bits per karakter (på grunn av Unicode-støtte). For eksempel kan tallet 49858712 lagres i 32 bits, men strengen TURKUS ville kreve 144 biter. Hvis du lagrer tusenvis av objekter med fargeregenskaper, kan denne relativt lille forskjellen i biter (mellom 32 og 144 i dette tilfellet) legge seg raskt opp. Så la oss bruke heltallverdier i stedet. Hva er løsningen på dette problemet? Vi beholder strengverdiene, fordi de er viktige for presentasjon, men vi skal ikke lagre dem.

Versjoner av Java fra og med 1.1 er i stand til å serieisere objekter automatisk, så lenge de implementerer Serialiserbar grensesnitt. For å forhindre at Java lagrer fremmede data, må du erklære slike variabler med flyktig nøkkelord. Så for å lagre heltallverdiene uten å lagre strengrepresentasjonen, erklærer vi strengattributtet for å være forbigående. Her er den nye klassen, sammen med tilgang til heltall- og strengattributtene:

offentlig klasse Color implementerer java.io.Serializable {private int-verdi; privat forbigående navn på streng; offentlig statisk sluttfarge RØD = ny farge (0, "rød"); offentlig statisk slutt Farge BLÅ = ny Farge (1, "Blå"); offentlig statisk endelig Farge GRØNN = ny farge (2, "grønn"); privat farge (int-verdi, strengnavn) {this.value = verdi; this.name = navn; } public int getValue () {returverdi; } public String toString () {return name; }} 

Nå kan vi effektivt lagre forekomster av den konstante typen Farge. Men hva med å gjenopprette dem? Det blir litt vanskelig. Før vi går videre, la oss utvide dette til et rammeverk som vil håndtere alle de nevnte fallgruvene for oss, slik at vi kan fokusere på det enkle å definere typer.

Den konstante typen rammeverk

Med vår faste forståelse av konstante typer, kan jeg nå hoppe inn i denne månedens verktøy. Verktøyet kalles Type og det er en enkel abstrakt klasse. Alt du trenger å gjøre er å lage en veldig enkel underklasse, og du har et fullverdig bibliotek med konstant type. Her er hva vår Farge klassen vil se ut nå:

offentlig klasse Color utvides Type {beskyttet farge (int-verdi, streng-desc) {super (verdi, desc); } offentlig statisk sluttfarge RØD = ny farge (0, "rød"); offentlig statisk slutt Farge BLÅ = ny Farge (1, "Blå"); offentlig statisk endelig Farge GRØNN = ny farge (2, "grønn"); } 

De Farge klasse består av ingenting annet enn en konstruktør og noen få offentlig tilgjengelige forekomster. All logikken som er diskutert til dette punktet vil bli definert og implementert i superklassen Type; vi vil legge til flere når vi går videre. Her er hva Type ser så langt ut:

offentlig klasse Type implementerer java.io.Serializable {private int-verdi; private transient String name; beskyttet Type (int-verdi, strengnavn) {this.value = verdi; this.name = navn; } public int getValue () {returverdi; } public String toString () {return name; }} 

Tilbake til utholdenhet

Med vårt nye rammeverk i hånden kan vi fortsette der vi slapp i diskusjonen om utholdenhet. Husk at vi kan lagre typene våre ved å lagre heltallverdiene, men nå vil vi gjenopprette dem. Dette kommer til å kreve en se opp - en omvendt beregning for å finne objektforekomsten basert på verdien. For å utføre et oppslag trenger vi en måte å telle opp alle mulige typer.

I Erics artikkel implementerte han sin egen oppregning ved å implementere konstantene som noder i en koblet liste. Jeg kommer til å gi avkall på denne kompleksiteten og bruke en enkel hashtable i stedet. Nøkkelen til hash vil være heltallverdiene av typen (pakket inn i en Heltall objekt), og verdien av hash vil være en referanse til typen forekomst. For eksempel GRØNN tilfelle av Farge vil bli lagret slik:

 hashtable.put (nytt heltal (GRØNN.getValue ()), GRØNN); 

Selvfølgelig ønsker vi ikke å skrive ut dette for hver mulig type. Det kan være hundrevis av forskjellige verdier, og dermed skape et typiske mareritt og åpne dørene for noen stygge problemer - du kan glemme å sette en av verdiene i hashtabellen og for eksempel ikke kunne slå opp det senere. Så vi vil erklære en global hashtable innen Type og endre konstruktøren for å lagre kartleggingen ved opprettelsen:

 private statiske endelige Hashtable-typer = nye Hashtable (); beskyttet Type (int-verdi, strengbeskrivelse) {this.value = verdi; this.desc = desc; types.put (nytt heltall (verdi), dette); } 

Men dette skaper et problem. Hvis vi har en underklasse kalt Farge, som har en type (det vil si Grønn) med verdien 5, og så lager vi en annen underklasse kalt Skygge, som også har en type (altså Mørk) med verdien 5, vil bare en av dem lagres i hashtabellen - den siste som blir instantiert.

For å unngå dette, må vi lagre et håndtak av typen basert på ikke bare verdien, men også dens klasse. La oss lage en ny metode for å lagre typereferansene. Vi bruker en hashtable av hashtables. Den indre hashtabellen vil være en kartlegging av verdier til typer for hver spesifikke underklasse (Farge, Skygge, og så videre). Den ytre hashtabellen vil være en kartlegging av underklasser til indre tabeller.

Denne rutinen vil først forsøke å skaffe det indre bordet fra det ytre bordet. Hvis den mottar null, eksisterer ikke den indre tabellen ennå. Så vi lager et nytt indre bord og legger det inn i det ytre bordet. Deretter legger vi til verdi / type-kartleggingen til den indre tabellen, og vi er ferdige. Her er koden:

 privat tomrom storeType (Type type) {String className = type.getClass (). getName (); Hashtable-verdier; synkronisert (typer) // unngå løpetilstand for å lage indre tabell {verdier = (Hashtable) types.get (className); hvis (verdier == null) {verdier = ny Hashtable (); types.put (className, verdier); }} values.put (nytt heltall (type.getValue ()), type); } 

Og her er den nye versjonen av konstruktøren:

 beskyttet Type (int-verdi, strengbeskrivelse) {this.value = verdi; this.desc = desc; storeType (dette); } 

Nå som vi lagrer et veikart av typer og verdier, kan vi utføre oppslag og dermed gjenopprette en forekomst basert på en verdi. Oppslaget krever to ting: målunderklasseidentiteten og heltallverdien. Ved hjelp av denne informasjonen kan vi trekke ut den indre tabellen og finne håndtaket til den matchende typen forekomsten. Her er koden:

 offentlig statisk Type getByValue (Class classRef, int-verdi) {Type type = null; String className = classRef.getName (); Hashtable-verdier = (Hashtable) types.get (className); hvis (verdier! = null) {type = (Type) values.get (nytt heltall (verdi)); } returner (type); } 

Å gjenopprette en verdi er altså så enkelt som dette (merk at returverdien må kastes):

 int-verdi = // lest fra fil, database osv. Fargebakgrunn = (ColorType) Type.findByValue (ColorType.class, verdi); 

Oppregning av typene

Takket være vår hashtable-of-hashtables-organisasjon er det utrolig enkelt å avsløre oppregningsfunksjonaliteten som Erics implementering tilbyr. Den eneste advarselen er at sortering, som Erics design tilbyr, ikke er garantert. Hvis du bruker Java 2, kan du erstatte det sorterte kartet med de indre hashtabellene. Men, som jeg sa i begynnelsen av denne kolonnen, er jeg bare opptatt av 1.1-versjonen av JDK akkurat nå.

Den eneste logikken som kreves for å oppregne typene, er å hente den indre tabellen og returnere elementlisten. Hvis det indre bordet ikke eksisterer, returnerer vi ganske enkelt null. Her er hele metoden:

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