Programování

Používejte konstantní typy pro bezpečnější a čistší kód

V tomto tutoriálu se bude rozšiřovat myšlenka vyjmenované konstanty jak je popsáno v článku Erica Armstronga: „Vytvářejte výčet konstant v Javě.“ Důrazně doporučuji přečíst si tento článek, než se do tohoto článku ponoříte, protože předpokládám, že jste obeznámeni s pojmy souvisejícími s vyčíslenými konstantami.

Koncept konstant

Při řešení vyjmenovaných konstant budu diskutovat o vyjmenovaný část konceptu na konci článku. Prozatím se zaměříme pouze na konstantní aspekt. Konstanty jsou v podstatě proměnné, jejichž hodnota se nemůže změnit. V C / C ++ klíčové slovo konst se používá k deklaraci těchto konstantních proměnných. V Javě používáte klíčové slovo finále. Zde představený nástroj však není jen primitivní proměnná; je to skutečná instance objektu. Instance objektu jsou neměnné a neměnné - jejich vnitřní stav nemusí být změněn. Je to podobné jako u singletonového vzoru, kde třída může mít pouze jednu jedinou instanci; v tomto případě však třída může mít pouze omezenou a předdefinovanou sadu instancí.

Hlavními důvody pro použití konstant jsou jasnost a bezpečnost. Například následující část kódu není samozřejmá:

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

Z tohoto kódu můžeme zjistit, že se nastavuje barva. Ale jakou barvu představuje 5? Pokud by tento kód napsal jeden z těch vzácných programátorů, kteří komentují jeho práci, mohli bychom najít odpověď v horní části souboru. Pravděpodobněji však budeme muset hledat nějaké staré dokumenty o návrhu (pokud vůbec existují), abychom je mohli vysvětlit.

Jasnějším řešením je přiřadit hodnotu 5 proměnné se smysluplným názvem. Například:

 public static final int RED = 5; public void someMethod () {setColor (RED); } 

Nyní můžeme okamžitě zjistit, co se s kódem děje. Barva se nastavuje na červenou. To je mnohem čistší, ale je to bezpečnější? Co když se jiný kodér zaměňuje a deklaruje různé hodnoty, jako je tento:

public static final int RED = 3; public static final int ZELENÁ = 5; 

Nyní máme dva problémy. Předně, ČERVENÉ již není nastavena na správnou hodnotu. Za druhé, hodnota pro červenou je reprezentována pojmenovanou proměnnou ZELENÁ. Snad nejděsivější částí je, že se tento kód zkompiluje dobře a chyba nemusí být detekována, dokud nebude produkt odeslán.

Tento problém můžeme vyřešit vytvořením definitivní barevné třídy:

public class Color {public static final int RED = 5; public static final int ZELENÁ = 7; } 

Poté prostřednictvím dokumentace a kontroly kódu doporučujeme programátorům, aby to používali takto:

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

Říkám povzbudit, protože design v tomto seznamu kódů nám neumožňuje donutit kodéra, aby vyhověl; kód se bude i nadále kompilovat, i když není vše v pořádku. I když je to trochu bezpečnější, není to úplně bezpečné. Ačkoli programátoři by měl použijte Barva třídy, nejsou povinni. Programátoři mohli velmi snadno napsat a zkompilovat následující kód:

 setColor (3498910); 

setColor metoda rozpoznat toto velké číslo jako barvu? Asi ne. Jak se tedy můžeme chránit před těmito nepoctivými programátory? To je místo, kde typy konstant přicházejí na záchranu.

Začneme předefinováním podpisu metody:

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

Nyní programátoři nemohou předat libovolnou celočíselnou hodnotu. Jsou nuceni poskytnout platné Barva objekt. Příklad implementace tohoto může vypadat takto:

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

Stále pracujeme s čistým a čitelným kódem a jsme mnohem blíže k dosažení absolutní bezpečnosti. Ale ještě nejsme úplně tam. Programátor má stále nějaký prostor, aby způsobil zmatek, a může libovolně vytvářet nové barvy, například takto:

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

Této situaci předcházíme vytvořením Barva třída neměnná a skrytí instance před programátorem. Každý jiný typ barvy (červená, zelená, modrá) vytváříme ojediněle. Toho je dosaženo provedením konstruktoru jako soukromého a poté vystavením veřejných popisovačů omezenému a dobře definovanému seznamu instancí:

public class Color {private Color () {} public static final Color RED = new Color (); public static final Barva ZELENÁ = nová Barva (); public static final Barva MODRÁ = nová Barva (); } 

V tomto kódu jsme konečně dosáhli absolutní bezpečnosti. Programátor nemůže vyrobit falešné barvy. Lze použít pouze definované barvy; jinak se program nebude kompilovat. Takto nyní vypadá naše implementace:

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

Vytrvalost

Dobře, teď máme čistý a bezpečný způsob řešení neustálých typů. Můžeme vytvořit objekt s atributem color a mít jistotu, že hodnota barvy bude vždy platná. Ale co když chceme tento objekt uložit do databáze nebo jej zapsat do souboru? Jak uložíme hodnotu barvy? Tyto typy musíme mapovat na hodnoty.

V JavaWorld výše uvedený článek, Eric Armstrong použil řetězcové hodnoty. Používání řetězců poskytuje další bonus, protože vám dává něco smysluplného, ​​abyste se mohli vrátit do toString () metoda, díky níž je výstup ladění velmi jasný.

Struny však mohou být drahé. Celé číslo vyžaduje k uložení své hodnoty 32 bitů, zatímco řetězec vyžaduje 16 bitů na znak (kvůli podpoře Unicode). Například číslo 49858712 lze uložit do 32 bitů, ale řetězec TYRKYSOVÝ by vyžadovalo 144 bitů. Pokud ukládáte tisíce objektů s barevnými atributy, může se tento relativně malý rozdíl v bitech (v tomto případě mezi 32 a 144) rychle sčítat. Pojďme tedy použít celočíselné hodnoty. Jaké je řešení tohoto problému? Hodnoty řetězců zachováme, protože jsou důležité pro prezentaci, ale nebudeme je ukládat.

Verze Java od 1.1 jsou schopny serializovat objekty automaticky, pokud implementují Serializovatelné rozhraní. Aby Java nemohla ukládat cizí data, musíte tyto proměnné deklarovat pomocí přechodný klíčové slovo. Takže za účelem uložení celočíselných hodnot bez uložení řetězcové reprezentace deklarujeme atribut řetězce jako přechodný. Tady je nová třída spolu s přístupovými právy k atributům integer a string:

veřejná třída Color implementuje java.io.Serializable {private int value; soukromý přechodný název řetězce; veřejná statická konečná barva ČERVENÁ = nová barva (0, "červená"); public static final Barva MODRÁ = nová Barva (1, "Modrá"); veřejná statická finální Barva ZELENÁ = nová Barva (2, "Zelená"); soukromá barva (int hodnota, název řetězce) {this.value = hodnota; this.name = name; } public int getValue () {návratová hodnota; } public String toString () {návratové jméno; }} 

Nyní můžeme efektivně ukládat instance konstantního typu Barva. Ale co jejich obnova? Bude to trochu složité. Než půjdeme dále, pojďme to rozšířit do rámce, který pro nás zvládne všechny výše uvedené úskalí, což nám umožní zaměřit se na jednoduchou záležitost definování typů.

Rámec konstantního typu

Díky našemu pevnému pochopení konstantních typů teď můžu skočit do nástroje tohoto měsíce. Nástroj se nazývá Typ a je to jednoduchá abstraktní třída. Musíte pouze vytvořit velmi jednoduchá podtřída a máte plnohodnotnou knihovnu konstantního typu. Tady je naše Barva třída bude vypadat teď:

public class Color extends Type {protected Color (int value, String desc) {super (value, desc); } veřejná statická konečná barva ČERVENÁ = nová barva (0, "červená"); public static final Barva MODRÁ = nová Barva (1, "Modrá"); public static final Barva ZELENÁ = nová Barva (2, "Zelená"); } 

The Barva třída se skládá pouze z konstruktoru a několika veřejně přístupných instancí. Všechna logika diskutovaná k tomuto bodu bude definována a implementována v nadtřídě Typ; jak budeme postupovat, budeme přidávat další. Tady je co Typ zatím vypadá:

public class Type implementuje java.io.Serializable {private int value; soukromý přechodný název řetězce; chráněný typ (int hodnota, název řetězce) {this.value = value; this.name = name; } public int getValue () {návratová hodnota; } public String toString () {návratové jméno; }} 

Zpět k vytrvalosti

S naším novým rámcem v ruce můžeme pokračovat v diskusích o vytrvalosti tam, kde jsme přestali. Nezapomeňte, že můžeme uložit naše typy uložením jejich celočíselných hodnot, ale nyní je chceme obnovit. To bude vyžadovat a vzhlédnout - zpětný výpočet k vyhledání instance objektu na základě jeho hodnoty. Abychom mohli provést vyhledávání, potřebujeme způsob, jak vyčíslit všechny možné typy.

V Ericově článku implementoval vlastní výčet implementací konstant jako uzlů v propojeném seznamu. Chystám se této složitosti vzdát a místo toho použít jednoduchý hashtable. Klíčem pro hash budou celočíselné hodnoty typu (zabalené v Celé číslo object) a hodnota hash bude odkazem na instanci typu. Například ZELENÁ instance Barva bude uložen takto:

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

Samozřejmě to nechceme psát pro každý možný typ. Mohly by existovat stovky různých hodnot, což by vytvořilo typickou noční můru a otevřelo dveře nějakým ošklivým problémům - můžete zapomenout vložit jednu z hodnot do hashtable a pak ji například nebudete moci vyhledat později. Takže uvnitř deklarujeme globální hashtable Typ a upravit konstruktor tak, aby ukládal mapování po vytvoření:

 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 to vytváří problém. Pokud máme podtřídu nazvanou Barva, který má typ (tj. Zelená) s hodnotou 5 a poté vytvoříme další podtřídu nazvanou Odstín, který má také typ (tj Temný) s hodnotou 5, do hashtable bude uložen pouze jeden z nich - poslední, který bude vytvořen.

Aby se tomu zabránilo, musíme uložit popisovač typu nejen na základě jeho hodnoty, ale také jeho třída. Vytvořme novou metodu pro ukládání odkazů na typy. Použijeme hashtable hashtables. Vnitřní hashtable bude mapování hodnot na typy pro každou konkrétní podtřídu (Barva, Odstín, a tak dále). Vnější hashtable bude mapování podtříd na vnitřní tabulky.

Tato rutina se nejprve pokusí získat vnitřní tabulku z vnější tabulky. Pokud obdrží null, vnitřní tabulka ještě neexistuje. Takže vytvoříme novou vnitřní tabulku a vložíme ji do vnější tabulky. Dále přidáme mapování hodnoty / typu do vnitřní tabulky a máme hotovo. Tady je kód:

 private void storeType (Type type) {String className = type.getClass (). getName (); Hodnoty hashtable; synchronized (types) // vyhnout se podmínkám pro vytvoření vnitřní tabulky {values ​​= (Hashtable) types.get (className); if (values ​​== null) {values ​​= new Hashtable (); types.put (název_třídy, hodnoty); }} values.put (new Integer (type.getValue ()), type); } 

A tady je nová verze konstruktoru:

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

Nyní, když ukládáme cestovní mapu typů a hodnot, můžeme provádět vyhledávání a tak obnovit instanci na základě hodnoty. Hledání vyžaduje dvě věci: identitu cílové podtřídy a celočíselnou hodnotu. Pomocí těchto informací můžeme extrahovat vnitřní tabulku a najít popisovač instance instance odpovídající. Tady je kód:

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

Obnovení hodnoty je tedy tak jednoduché (všimněte si, že je nutné vracet návratovou hodnotu):

 int hodnota = // čtení ze souboru, databáze atd. Barevné pozadí = (ColorType) Type.findByValue (ColorType.class, hodnota); 

Výčet typů

Díky naší organizaci hashtable-of-hashtables je neuvěřitelně jednoduché odhalit funkce výčtu nabízené implementací Erica. Jedinou výhradou je, že třídění, které Ericův design nabízí, není zaručeno. Pokud používáte Javu 2, můžete nahradit tříděnou mapu za vnitřní hashtables. Ale jak jsem uvedl na začátku tohoto sloupce, jde mi právě o verzi JDK 1.1.

Jedinou logikou požadovanou k výčtu typů je načíst vnitřní tabulku a vrátit její seznam prvků. Pokud vnitřní tabulka neexistuje, jednoduše vrátíme null. Tady je celá metoda: