Programovanie

Používajte stále typy pre bezpečnejší a čistejší kód

V tomto návode sa rozšíri myšlienka vymenované konštanty ako je uvedené v dokumente Erica Armstronga „Vytvorte vymenované konštanty v Jave“. Dôrazne odporúčam prečítať si tento článok skôr, ako sa ponoríte do tohto, pretože predpokladám, že ste oboznámení s pojmami súvisiacimi s vymenovanými konštantami, a rozšírim niektoré z príkladov kódu, ktorý predstavil Eric.

Koncept konštánt

Pri práci s vymenovanými konštantami budem diskutovať o vymenované časť konceptu na konci článku. Zatiaľ sa zameriame iba na konštantný aspekt. Konštanty sú v podstate premenné, ktorých hodnota sa nemôže meniť. V C / C ++ kľúčové slovo konšt sa používa na deklarovanie týchto konštantných premenných. V prostredí Java používate kľúčové slovo konečné. Tu uvedený nástroj však nie je iba primitívnou premennou; je to skutočná inštancia objektu. Inštancie objektov sú nemenné a nemenné - ich vnútorný stav nemusí byť možné zmeniť. Je to podobné ako v prípade singletonového vzoru, keď trieda môže mať iba jednu inštanciu; v tomto prípade však môže mať trieda iba obmedzený a preddefinovaný súbor inštancií.

Hlavnými dôvodmi na použitie konštánt sú prehľadnosť a bezpečnosť. Napríklad nasledujúca časť kódu nie je samozrejmá:

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

Z tohto kódu môžeme zistiť, že sa nastavuje farba. Akú farbu však predstavuje 5? Ak tento kód napísal jeden z tých vzácnych programátorov, ktorí komentujú jeho prácu, odpoveď by sme mohli nájsť v hornej časti súboru. Pravdepodobnejšie však bude, že budeme musieť hľadať vysvetlenie niektorých starých dokumentov o dizajne (ak vôbec existujú).

Jasnejším riešením je priradiť hodnote 5 premennej so zmysluplným názvom. Napríklad:

 verejný statický konečný int ČERVENÉ = 5; public void someMethod () {setColor (RED); } 

Teraz môžeme okamžite povedať, čo sa deje s kódom. Farba sa nastavuje na červenú. Je to oveľa čistejšie, ale je to bezpečnejšie? Čo ak sa iný kódovač zamení a deklaruje rôzne hodnoty, napríklad takto:

verejný statický konečný int ČERVENÉ = 3; public static final int ZELENÁ = 5; 

Teraz máme dva problémy. Po prvé, ČERVENÁ už nie je nastavená na správnu hodnotu. Po druhé, hodnotu červenej predstavuje premenná s názvom ZELENÁ. Najdesivejšou časťou je možno to, že sa tento kód skompiluje v poriadku a chyba sa nemusí zistiť, kým nebude produkt dodaný.

Tento problém môžeme vyriešiť vytvorením definitívnej farebnej triedy:

verejná trieda Farba {verejný statický konečný int ČERVENÉ = 5; verejny staticky final int ZELENY = 7; } 

Potom prostredníctvom dokumentácie a kontroly kódu odporúčame programátorom, aby ich používali takto:

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

Hovorím povzbudenie, pretože dizajn v tomto zozname kódov nám neumožňuje prinútiť kódera, aby vyhovel; kód sa bude stále kompilovať, aj keď nie je všetko v poriadku. Aj keď je to teda trochu bezpečnejšie, nie je to úplne bezpečné. Aj keď programátori by mal Použi Farba triedy, nie sú povinní. Programátori mohli veľmi ľahko napísať a zostaviť nasledujúci kód:

 setColor (3498910); 

setColor metóda rozpoznať toto veľké číslo ako farbu? Pravdepodobne nie. Ako sa môžeme chrániť pred týmito nečestnými programátormi? To je miesto, kde prichádzajú na pomoc konštantné typy.

Začneme predefinovaním podpisu metódy:

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

Teraz nemôžu programátori zadať ľubovoľnú celočíselnú hodnotu. Sú nútení poskytnúť platné Farba objekt. Príklad implementácie tohto môže vyzerať takto:

 public void someMethod () {setColor (nová farba ("červená")); } 

Stále pracujeme s čistým a čitateľným kódom a sme oveľa bližšie k dosiahnutiu absolútnej bezpečnosti. Ale ešte tam nie sme celkom. Programátor má stále nejaký priestor na to, aby spôsobil zmätok, a môže tak ľubovoľne vytvárať nové farby, napríklad takto:

 public void someMethod () {setColor (new Color ("Hi, my name is Ted.")); } 

Tejto situácii predchádzame tým, že urobíme Farba trieda nemenná a skrytie inštancie pred programátorom. Každý iný typ farby (červená, zelená, modrá) vyrábame ako jednu. To sa dá dosiahnuť tak, že konštruktor sa stane súkromným a potom sa verejné úchyty vystavia obmedzenému a presne definovanému zoznamu inštancií:

public class Color {private Color () {} public static final Color RED = new Color (); verejná statická konečná Farba ZELENÁ = nová Farba (); verejná statická konečná Farba MODRÁ = nová Farba (); } 

V tomto kóde sme konečne dosiahli absolútnu bezpečnosť. Programátor nedokáže vyrobiť falošné farby. Môžu sa použiť iba definované farby; inak sa program nezkompiluje. Takto vyzerá naša implementácia teraz:

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

Vytrvalosť

Dobre, teraz máme čistý a bezpečný spôsob riešenia stálych typov. Môžeme vytvoriť objekt s atribútom color a mať istotu, že hodnota farby bude vždy platná. Čo však v prípade, ak chceme tento objekt uložiť do databázy alebo zapísať do súboru? Ako uložíme hodnotu farby? Tieto typy musíme mapovať na hodnoty.

V JavaWorld vyššie uvedený článok, Eric Armstrong použil reťazcové hodnoty. Používanie reťazcov poskytuje ďalší bonus, pretože vám dáva niečo zmysluplné, aby ste sa mohli vrátiť v natiahnuť() metóda, vďaka ktorej je výstup ladenia veľmi jasný.

Struny sa však môžu skladovať draho. Celé číslo vyžaduje na uloženie svojej hodnoty 32 bitov, zatiaľ čo reťazec vyžaduje 16 bitov na znak (kvôli podpore Unicode). Napríklad číslo 49858712 je možné uložiť do 32 bitov, ale do reťazca TYRKYSOVÁ by vyžadovalo 144 bitov. Ak ukladáte tisíce objektov s atribútmi farieb, tento relatívne malý rozdiel v bitoch (v tomto prípade medzi 32 a 144) sa môže rýchlo sčítať. Namiesto toho teda použijeme celočíselné hodnoty. Aké je riešenie tohto problému? Hodnoty reťazcov si ponecháme, pretože sú dôležité pre prezentáciu, ale nebudeme ich ukladať.

Verzie Java od 1.1 sú schopné serializovať objekty automaticky, pokiaľ implementujú Serializovateľné rozhranie. Aby ste zabránili tomu, aby Java ukladala cudzie údaje, musíte tieto premenné deklarovať pomocou prechodný kľúčové slovo. Takže, aby sme mohli uložiť celočíselné hodnoty bez uloženia reťazcovej reprezentácie, vyhlásime atribút reťazca za prechodný. Tu je nová trieda spolu s prístupovými právami k atribútom integer a string:

verejná trieda Color implementuje java.io.Serializable {private int value; súkromné ​​prechodné meno reťazca; verejná statická konečná Farba ČERVENÁ = nová Farba (0, „Červená“); verejná statická konečná Farba MODRÁ = nová Farba (1, „Modrá“); verejná statická konečná Farba ZELENÁ = nová Farba (2, „Zelená“); private Color (int hodnota, názov reťazca) {this.value = value; this.name = meno; } public int getValue () {návratová hodnota; } public String toString () {návratové meno; }} 

Teraz môžeme efektívne ukladať inštancie konštantného typu Farba. Čo však s ich obnovou? Bude to trochu zložité. Než pôjdeme ďalej, rozšírme to do rámca, ktorý pre nás zvládne všetky vyššie uvedené úskalia a umožní nám zamerať sa na jednoduchú záležitosť definovania typov.

Rámec konštantného typu

S našim pevným pochopením konštantných typov teraz môžem skočiť do nástroja tohto mesiaca. Nástroj sa volá Typ a je to jednoduchá abstraktná trieda. Musíte len vytvoriť veľmi jednoduchá podtrieda a máte plne funkčnú knižnicu konštantného typu. Tu je to, čo je naše Farba trieda bude vyzerať teraz:

verejná trieda Color extends Type {protected Color (int value, String desc) {super (value, desc); } verejná statická konečná Farba ČERVENÁ = nová Farba (0, "Červená"); verejná statická konečná Farba MODRÁ = nová Farba (1, „Modrá“); verejná statická konečná Farba ZELENÁ = nová Farba (2, „Zelená“); } 

The Farba trieda pozostáva iba z konštruktora a niekoľkých verejne prístupných inštancií. Celá logika diskutovaná k tomuto bodu bude definovaná a implementovaná v nadtriede Typ; ako budeme postupovať, budeme pridávať ďalšie. Tu je čo Typ vyzerá zatiaľ:

public class Type implementuje java.io.Serializable {private int value; súkromné ​​prechodné meno reťazca; chránený typ (hodnota int, názov reťazca) {this.value = hodnota; this.name = meno; } public int getValue () {návratová hodnota; } public String toString () {návratové meno; }} 

Späť k vytrvalosti

S našim novým rámcom v ruke môžeme pokračovať v diskusii o vytrvalosti tam, kde sme skončili. Pamätajte, že naše typy môžeme uložiť uložením ich celočíselných hodnôt, ale teraz ich chceme obnoviť. Bude to vyžadovať a vyhľadať - spätný výpočet na vyhľadanie inštancie objektu na základe jeho hodnoty. Na vykonanie vyhľadávania potrebujeme spôsob, ako vymenovať všetky možné typy.

V Ericovom článku implementoval svoj vlastný výčet implementáciou konštánt ako uzlov v prepojenom zozname. Ja sa tejto zložitosti vzdám a použijem namiesto toho jednoduchý hashtable. Kľúčom pre hash budú celočíselné hodnoty typu (zabalené do Celé číslo objekt) a hodnota hash bude odkazom na inštanciu typu. Napríklad ZELENÁ inštancia z Farba bude uložený takto:

 hashtable.put (new Integer (GREEN.getValue ()), GREEN); 

Samozrejme, nechceme to vypisovať pre každý možný typ. Môžu existovať stovky rôznych hodnôt, čo vytvára typickú nočnú moru a otvára dvere nepríjemným problémom - môžete zabudnúť vložiť jednu z hodnôt do hashtable a potom ju napríklad nebudete môcť vyhľadať. Vo vnútri teda vyhlásime globálny hashtable Typ a upraviť konštruktor tak, aby ukladal mapovanie po vytvorení:

 private static final Hashtable types = new Hashtable (); Protected Type (int value, String desc) {this.value = value; this.desc = desc; types.put (nové celé číslo (hodnota), toto); } 

Ale toto vytvára problém. Ak máme podtriedu tzv Farba, ktorý má typ (tj. zelená) s hodnotou 5, a potom vytvoríme ďalšiu podtriedu s názvom Tieň, ktorý má tiež typ (tj Tma) s hodnotou 5, do hashtable bude uložený iba jeden z nich - posledný, ktorý má byť inštancovaný.

Aby sa tomu zabránilo, musíme uložiť popisovač typu nielen na základe jeho hodnoty, ale aj jeho hodnoty trieda. Vytvorme novú metódu na ukladanie odkazov na typy. Použijeme hashtable hashtables. Vnútorná hashtable bude mapovanie hodnôt na typy pre každú konkrétnu podtriedu (Farba, Tieň, a tak ďalej). Vonkajší hashtable bude mapovanie podtried na vnútorné tabuľky.

Táto rutina sa najskôr pokúsi získať vnútornú tabuľku z vonkajšej tabuľky. Ak dostane hodnotu null, vnútorná tabuľka ešte neexistuje. Takže vytvoríme nový vnútorný stôl a vložíme ho do vonkajšieho stola. Ďalej pridáme mapovanie hodnoty / typu do vnútornej tabuľky a máme hotovo. Tu je kód:

 private void storeType (Type type) {String className = type.getClass (). getName (); Hashtable hodnoty; synchronized (types) // vyhnúť sa podmienkam rasy pre vytvorenie vnútornej tabuľky {values ​​= (Hashtable) types.get (className); if (values ​​== null) {values ​​= new Hashtable (); types.put (className, hodnoty); }} values.put (new Integer (type.getValue ()), type); } 

A tu je nová verzia konštruktora:

 Protected Type (int value, String desc) {this.value = value; this.desc = desc; storeType (toto); } 

Teraz, keď ukladáme cestovnú mapu typov a hodnôt, môžeme vykonávať vyhľadávania a tým obnoviť inštanciu na základe hodnoty. Vyhľadanie vyžaduje dve veci: identitu cieľovej podtriedy a celočíselnú hodnotu. Pomocou týchto informácií môžeme extrahovať vnútornú tabuľku a nájsť popisovač k inštancii zhodného typu. Tu je kód:

 public static Type getByValue (Class classRef, int value) {Type type = null; Reťazec className = classRef.getName (); Hodnoty Hashtable = (Hashtable) types.get (className); if (values! = null) {type = (Type) values.get (new Integer (value)); } návrat (typ); } 

Obnovenie hodnoty je teda také jednoduché (všimnite si, že návratovú hodnotu je potrebné odovzdať):

 int hodnota = // načítanie zo súboru, databázy atď. Farebné pozadie = (ColorType) Type.findByValue (ColorType.class, hodnota); 

Vymenovanie typov

Vďaka našej organizácii hashtable-of-hashtables je neuveriteľne jednoduché odhaliť funkcie výčtu, ktoré ponúka implementácia Erica. Jedinou výhradou je, že triedenie, ktoré ponúka Ericov dizajn, nie je zaručené. Ak používate Java 2, môžete nahradiť zoradenú mapu za vnútorné hashtables. Ale ako som uviedol na začiatku tohto stĺpca, momentálne sa zaoberám iba verziou JDK verzie 1.1.

Jedinou logikou požadovanou na vymenovanie typov je načítanie vnútornej tabuľky a vrátenie zoznamu jej prvkov. Ak vnútorná tabuľka neexistuje, jednoducho vrátime hodnotu null. Tu je celá metóda:

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