Programmering

Brug konstante typer til mere sikker og renere kode

I denne vejledning udvides ideen om opregnede konstanter som dækket af Eric Armstrongs "Opret opregnede konstanter i Java." Jeg anbefaler på det kraftigste at læse den artikel, før du fordyber dig i denne, da jeg antager, at du er bekendt med begreberne relateret til opregnede konstanter, og jeg vil udvide nogle af de eksempler på kode, som Eric præsenterede.

Begrebet konstanter

Når jeg beskæftiger mig med opregnede konstanter, vil jeg diskutere opregnet en del af konceptet i slutningen af ​​artiklen. For nu vil vi bare fokusere på konstant aspekt. Konstanter er grundlæggende variabler, hvis værdi ikke kan ændres. I C / C ++, nøgleordet konst bruges til at erklære disse konstante variabler. I Java bruger du nøgleordet endelig. Imidlertid er det værktøj, der er introduceret her, ikke blot en primitiv variabel; det er en faktisk objektinstans. Objektforekomsterne er uforanderlige og uforanderlige - deres interne tilstand kan muligvis ikke ændres. Dette svarer til singleton-mønsteret, hvor en klasse måske kun kan have en enkelt forekomst; i dette tilfælde kan en klasse dog kun have et begrænset og foruddefineret sæt forekomster.

De vigtigste grunde til at bruge konstanter er klarhed og sikkerhed. For eksempel er følgende stykke kode ikke selvforklarende:

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

Fra denne kode kan vi fastslå, at der indstilles en farve. Men hvilken farve repræsenterer 5? Hvis denne kode blev skrevet af en af ​​de sjældne programmører, der kommenterer hans eller hendes arbejde, finder vi muligvis svaret øverst i filen. Men mere sandsynligt bliver vi nødt til at grave rundt efter nogle gamle designdokumenter (hvis de overhovedet findes) for en forklaring.

En mere klar løsning er at tildele en værdi på 5 til en variabel med et meningsfuldt navn. For eksempel:

 offentlig statisk endelig int RED = 5; offentlig ugyldighed someMethod () {setColor (RED); } 

Nu kan vi straks fortælle, hvad der sker med koden. Farven indstilles til rød. Dette er meget renere, men er det mere sikkert? Hvad hvis en anden koder bliver forvirret og erklærer forskellige værdier sådan:

offentlig statisk endelig int RED = 3; offentlig statisk endelig int GRØNN = 5; 

Nu har vi to problemer. Først og fremmest, RØD er ikke længere indstillet til den korrekte værdi. For det andet er værdien for rødt repræsenteret af den navngivne variabel GRØN. Måske er den mest skræmmende del, at denne kode vil kompilere helt fint, og fejlen opdages muligvis ikke, før produktet er afsendt.

Vi kan løse dette problem ved at oprette en endelig farveklasse:

offentlig klasse Farve {offentlig statisk endelig int RED = 5; offentlig statisk endelig int GRØNN = 7; } 

Derefter opfordrer vi programmører til at bruge det sådan via dokumentation og kodegennemgang:

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

Jeg siger opmuntre, fordi designet i kodelisten ikke tillader os at tvinge koderen til at overholde dem; koden kompileres stadig, selvom alt ikke er helt i orden. Selvom dette er lidt mere sikkert, er det således ikke helt sikkert. Selvom programmører skulle gerne brug Farve klasse, er de ikke forpligtet til det. Programmører kunne meget let skrive og kompilere følgende kode:

 setColor (3498910); 

Gør det sæt Farve metode genkende dette store antal for at være en farve? Sikkert ikke. Så hvordan kan vi beskytte os mod disse useriøse programmører? Det er her konstanttyper kommer til undsætning.

Vi starter med at omdefinere signaturen til metoden:

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

Nu kan programmører ikke give en vilkårlig heltal værdi. De er tvunget til at give en gyldig Farve objekt. Et eksempel på implementering af dette kan se sådan ud:

 offentlig ugyldighed someMethod () {setColor (ny farve ("rød")); } 

Vi arbejder stadig med ren, læsbar kode, og vi er meget tættere på at opnå absolut sikkerhed. Men vi er ikke helt der endnu. Programmøren har stadig lidt plads til at skabe kaos og kan vilkårligt skabe nye farver som sådan:

 offentlig ugyldighed someMethod () {setColor (ny farve ("Hej, mit navn er Ted.")); } 

Vi forhindrer denne situation ved at gøre Farve klasse uforanderlig og skjuler instantiering fra programmøren. Vi gør hver anden type farve (rød, grøn, blå) til en singleton. Dette opnås ved at gøre konstruktøren privat og derefter udsætte offentlige håndtag for en begrænset og veldefineret liste over forekomster:

offentlig klasse Farve {privat Farve () {} offentlig statisk endelig Farve RØD = ny farve (); offentlig statisk endelig Farve GRØN = ny farve (); offentlig statisk endelig Farve BLÅ = ny farve (); } 

I denne kode har vi endelig opnået absolut sikkerhed. Programmøren kan ikke fremstille falske farver. Kun de definerede farver kan bruges; Ellers kompileres programmet ikke. Sådan ser vores implementering ud nu:

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

Udholdenhed

Okay, nu har vi en ren og sikker måde at håndtere konstante typer på. Vi kan oprette et objekt med en farveattribut og være sikre på, at farveværdien altid vil være gyldig. Men hvad hvis vi vil gemme dette objekt i en database eller skrive det til en fil? Hvordan gemmer vi farveværdien? Vi er nødt til at kortlægge disse typer til værdier.

I JavaWorld artikel nævnt ovenfor brugte Eric Armstrong strengværdier. Brug af strenge giver den ekstra bonus at give dig noget meningsfuldt at vende tilbage i toString () metode, hvilket gør debugging output meget klart.

Strenge kan dog være dyre at opbevare. Et heltal kræver 32 bits for at gemme dens værdi, mens en streng kræver 16 bits pr. karakter (på grund af Unicode-support). F.eks. Kan tallet 49858712 lagres i 32 bit, men strengen TURKUISE ville kræve 144 bits. Hvis du gemmer tusindvis af objekter med farveattributter, kan denne relativt lille forskel i bits (mellem 32 og 144 i dette tilfælde) tilføjes hurtigt. Så lad os bruge heltal værdier i stedet. Hvad er løsningen på dette problem? Vi bevarer strengværdierne, fordi de er vigtige til præsentation, men vi lagrer dem ikke.

Versioner af Java fra 1.1 er i stand til at serieisere objekter automatisk, så længe de implementerer Serialiserbar interface. For at forhindre Java i at gemme fremmede data skal du erklære sådanne variabler med forbigående nøgleord. Så for at gemme heltalets værdier uden at lagre strengrepræsentationen erklærer vi strengattributten for forbigående. Her er den nye klasse sammen med accessorer til heltal og strengattributter:

offentlig klasse Color implementerer java.io.Serializable {private int værdi; privat forbigående Strengnavn; offentlig statisk endelig farve RØD = ny farve (0, "rød"); offentlig statisk endelig Farve BLÅ = ny farve (1, "Blå"); offentlig statisk endelig Farve GRØN = ny farve (2, "grøn"); privat farve (int-værdi, strengnavn) {this.value = værdi; dette.navn = navn; } public int getValue () {returværdi; } public String toString () {return name; }} 

Nu kan vi effektivt gemme forekomster af den konstante type Farve. Men hvad med at gendanne dem? Det bliver lidt vanskeligt. Inden vi går videre, lad os udvide dette til en ramme, der håndterer alle de ovennævnte faldgruber for os, så vi kan fokusere på det enkle spørgsmål om at definere typer.

Den konstante type ramme

Med vores faste forståelse af konstante typer kan jeg nu springe ind i denne måneds værktøj. Værktøjet kaldes Type og det er en simpel abstrakt klasse. Alt du skal gøre er at oprette en meget enkel underklasse, og du har et komplet bibliotek med konstant type. Her er hvad vores Farve klasse vil se ud nu:

offentlig klasse Farve udvides Type {beskyttet farve (int værdi, streng desc) {super (værdi, desc); } offentlig statisk endelig farve RØD = ny farve (0, "rød"); offentlig statisk endelig Farve BLÅ = ny farve (1, "Blå"); offentlig statisk endelig Farve GRØN = ny farve (2, "grøn"); } 

Det Farve klasse består af intet andet end en konstruktør og et par offentligt tilgængelige forekomster. Al den logik, der er diskuteret til dette punkt, vil blive defineret og implementeret i superklassen Type; vi tilføjer mere, når vi går videre. Her er hvad Type ligner indtil videre:

offentlig klasse Type implementerer java.io.Serializable {privat int-værdi; privat forbigående Strengnavn; beskyttet type (int-værdi, strengnavn) {this.value = værdi; dette.navn = navn; } public int getValue () {returværdi; } public String toString () {return name; }} 

Tilbage til udholdenhed

Med vores nye ramme i hånden kan vi fortsætte, hvor vi slap i diskussionen om vedholdenhed. Husk, vi kan gemme vores typer ved at gemme deres heltal, men nu vil vi gendanne dem. Dette vil kræve en kig op - en omvendt beregning for at lokalisere objektforekomsten baseret på dens værdi. For at udføre et opslag har vi brug for en måde at opregne alle mulige typer på.

I Erics artikel implementerede han sin egen optælling ved at implementere konstanterne som noder på en sammenkædet liste. Jeg vil give afkald på denne kompleksitet og bruge en simpel hashtable i stedet. Nøglen til hash vil være heltalets værdier af typen (indpakket i en Heltal objekt), og værdien af ​​hashen vil være en reference til typeinstansen. F.eks GRØN forekomst af Farve ville blive gemt som sådan:

 hashtable.put (nyt heltal (GRØN.getValue ()), GRØN); 

Selvfølgelig ønsker vi ikke at skrive dette ud for hver mulig type. Der kan være hundreder af forskellige værdier, hvilket skaber et typiske mareridt og åbner dørene for nogle ubehagelige problemer - du glemmer måske at sætte en af ​​værdierne i hashtabellen og derefter ikke være i stand til at slå det op senere, for eksempel. Så vi erklærer en global hashtable indeni Type og ændre konstruktøren til at gemme kortlægningen ved oprettelsen:

 private statiske endelige Hashtable-typer = nye Hashtable (); beskyttet type (int-værdi, streng-desc) {this.value = værdi; this.desc = beskrivelse; types.put (nyt heltal (værdi), dette); } 

Men dette skaber et problem. Hvis vi har en underklasse kaldet Farve, som har en type (dvs. Grøn) med en værdi på 5, og derefter opretter vi en anden underklasse kaldet Skygge, som også har en type (dvs. Mørk) med en værdi på 5, gemmes kun en af ​​dem i hashtabellen - den sidste der skal instantieres.

For at undgå dette er vi nødt til at gemme et håndtag af typen baseret på ikke kun dets værdi, men også dens klasse. Lad os oprette en ny metode til at gemme typereferencer. Vi bruger en hashtable af hashtables. Den indre hashtabel vil være en kortlægning af værdier til typer for hver specifik underklasse (Farve, Skygge, og så videre). Den ydre hashtable vil være en kortlægning af underklasser til de indre tabeller.

Denne rutine forsøger først at erhverve det indre bord fra det ydre bord. Hvis den modtager en null, findes den indre tabel endnu ikke. Så vi opretter et nyt indre bord og lægger det i det ydre bord. Dernæst tilføjer vi værdi / type-kortlægning til den indre tabel, og vi er færdige. Her er koden:

 privat tomrum storeType (Type type) {String className = type.getClass (). getName (); Hashtable-værdier; synkroniseret (typer) // undgå race-tilstand for oprettelse af den indre tabel {værdier = (Hashtable) types.get (className); hvis (værdier == null) {værdier = ny Hashtable (); types.put (className, værdier); }} values.put (nyt heltal (type.getValue ()), type); } 

Og her er den nye version af konstruktøren:

 beskyttet type (int-værdi, streng-desc) {this.value = værdi; this.desc = beskrivelse; storeType (dette); } 

Nu hvor vi gemmer et kørekort med typer og værdier, kan vi udføre opslag og dermed gendanne en instans baseret på en værdi. Opslaget kræver to ting: målunderklassens identitet og heltalets værdi. Ved hjælp af disse oplysninger kan vi udtrække den indre tabel og finde håndtaget til den matchende type forekomst. Her er koden:

 offentlig statisk Type getByValue (Class classRef, int-værdi) {Type type = null; String className = classRef.getName (); Hashtable-værdier = (Hashtable) types.get (className); hvis (værdier! = null) {type = (Type) values.get (nyt heltal (værdi)); } returner (type); } 

Således er gendannelse af en værdi så enkel som denne (bemærk, at returværdien skal kastes):

 int værdi = // læses fra fil, database osv. Farvebaggrund = (ColorType) Type.findByValue (ColorType.class, værdi); 

Opregning af typerne

Takket være vores hashtable-of-hashtables-organisation er det utroligt nemt at eksponere den optællingsfunktionalitet, som Erics implementering tilbyder. Den eneste advarsel er, at sortering, som Erics design tilbyder, ikke er garanteret. Hvis du bruger Java 2, kan du erstatte det sorterede kort med de indre hashtables. Men som jeg sagde i begyndelsen af ​​denne kolonne, er jeg kun bekymret for 1.1-versionen af ​​JDK lige nu.

Den eneste logik, der kræves for at opregne typerne, er at hente den indre tabel og returnere dens elementliste. Hvis den indre tabel ikke findes, returnerer vi simpelthen null. Her er hele metoden:

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