Most egy könnyed témát választottam. A Java-beli Enum
-okról lesz szó. Több aspektust is érintek a témával kapcsolatban:
- olvasható kód,
- egyszerűség, azaz hogyan ne bonyolítsuk túl a szoftvert,
- equals,
- GUI,
- adatbázis (O/R mapping és migráció kérdésköre)
Mi az Enum?
Az Enum
egy olyan adattípus, amely elnevezett értékek halmazából áll (Wikipedia). Például:
public enum Color { RED, GREEN, BLUE, YELLOW, BLACK, WHITE }
Ezzel a definícióval egy új típust hoztunk létre. Ha valahol deklarálunk egy mezőt, paramétert, változót, akkor típusként használhatjuk a fent definiált Color
-t. Például:
public class Painting { private Color backgroundColor = Color.BLACK; }
Olvashatóbb kód
Az Enum
-ok használatával olvashatóbbá tehető a kód. Példa:
Ehelyett a kód helyett:
public void registerUser(String name, String address, boolean female, …) {..}
Hívás:
registerUser("Józsi", false, …);
írhatjuk a következőt:
public void registerUser(String name, String address, Sex sex, …) {..}
Hívás:
registerUser("Józsi", Sex.MALE, …);
Tehát sokszor érdemes lehet már egyszerű, két elemszámú fogalmakat is Enum
-mal helyettesíteni. A boolean
-ok helyett elnevezett típusokat és értékeket használhatunk.
Túltervezés (over engineering)
Ha valakinek nem ismerős a túltervezett kód fogalma, olvassa el korábbi post-omat a témában.
Tipikus jelenség, amikor az ügyfél azt mondja (vagy ezt olvassuk a követelményspecifikációban): minden adattípus (lista) legyen bővíthető a felhasználói felületről. Egy webshop fejlesztése esetén ez azt jelentheti például, hogy a szállítás módja lista (futár, személyes átvétel, posta) is bővíthető legyen.
Az ilyen jellegű kérések hátterében az van, hogy az ügyfél nem tudja pontosan, hogy mit akar, és be akarja biztosítani magát azzal, hogy ha a későbbiekben bővíteni kell a listát, akkor legyen arra lehetősége, és ne kelljen pluszpénzt fizetnie. Több probléma is van a dologgal:
- Sokszor az egyes “buta” lista elemekre különféle üzleti logika is épül. Tehát nem elegendő a felhasználói felületen felvinni az újabb listaelemet, mert a rendszerhez amúgy is hozzá kell nyúlni;
- Bonyolultabb lesz a kód, mert mindent dinamikusan kell felolvasni, holott megtenné egy
Enum
is; - Többet dolgozunk azért is, mert még egy admin felületet is gyárthatunk, amin ezeket az értékeket szerkeszteni lehet.
Azt tudom javasolni, hogy ezen esetekben próbáljuk visszaverni az ügyfél ilyen jellegű kéréseit (prioritásokra és az idő rövidségére hivatkozva). Mi magunk pedig használjunk simán “beégetett” Enum
-okat erre a célra. Ha valamit tényleg bővíteni érdemes, az majd idővel úgyis kiderül. Törekedjünk inkább egy egyszerűbb, de működő szoftverre.
Az is előfordulhat, hogy az ügyfél nem kéri a listák bővíthetőségét. Ebben az esetben viszont mi magunk legyünk észnél, és ne akarjuk dinamikusra bonyolítani a kódot. Elégedjünk meg egy Enum
-mal, és foglalkozzunk inkább az értéket teremtő funkciókkal.
Equals
A jó hír az Enum
-okkal kapcsolatban, hogy nem kell equals
-t használni, hanem megteszi az ==
operátor használata is az összehasonlításnál. Ez nekem azért tetszik jobban, mert könnyebben olvasható a kód:
if(backgroundColor.equals(COLOR.BLACK)) { … }
helyett:
if(backgroundColor == COLOR.BLACK) { … }
vagy:
if(backgroundColor.isBlack()) { ... }
És az Enum
-ban pedig:
public enum Color { BLACK, WHITE, … public Boolean isBlack() { return this == Color.BLACK; } }
GUI
Sokszor előfordul, hogy az Enum
-ban lévő elemek közül választani kell a felhasználói felületen. Ilyenkor két problémával szembesülünk:
- Az egyes elemek nevét hogyan jeleníthetjük meg?
- Hogyan konvertálunk a GUI és az üzleti logika között?
Az Enum
-ok tovább dekorálhatóak.
public enum Color { BLACK("fekete"), WHITE("fehér"), … private String displayName; public Color(String displayName) { this.displayName = displayName; } public String getDisplayName() { return displayName; } }
Ezek után a Color.BLACK.getDisplayName()
kifejezés “fekete”-t fog visszaadni. Ezzel az egyszerű módszerrel minden elemhez visszaadható az a string, amit a felhasználói felületnek meg kell jelenítenie.
Mi alapján azonosíthatjuk be az egyes elemeket? Tehát ha a felhasználói felületen kiválasztják a “fekete”-t, hogyan lesz belőle az Enum
megfelelő eleme?
Mint minden kiválasztásnál, itt is valamifél ID-t kell használnunk. Nem célszerű számot alkalmaznunk (1, 2, 3…), mert ha a GUI-ban is van logika (pl. ha valamelyik listaelemet kiválasztjuk, akkor plusz adatot kell rögzíteni), akkor az ott lévő kódot nehezen áttekinthetővé teszik ezek a számok. Ráadásul, ha a későbbiekben beszúrúnk új elemet az Enum
-ba, a sorszámok teljesen megváltoznak.
Ezen megfontolások alapján ID-ként string-et érdemes használni, méghozzá magának az elemnek a nevét (BLACK
, WHITE
, …) Ez könnyedén lekérdezhető Java-ból, hiszen a Color.BLACK.name()
kifejezése értéke az a string, amely maga a "BLACK"
szó. A visszafelé konverzió esetén a Color.valueof("BLACK")
értéke Color.BLACK
lesz.
Az Enum
-ok O/R mapping-je
Egy Enum
típusú mező perzisztálása esetén (Hibernate, JPA) célszerű ellátni a mezőt a következő annotációval:
@Enumerated(EnumType.STRING) private Color color;
Ebben az esetben a color mező értéke az adatbázis táblában VARCHAR
-ra fog leképeződni, az oszlopban pedig az Enum
értékének a nevét találhatjuk majd (pl. "BLACK"
, "WHITE"
). Az alaphelyzet az, hogy map-peléskor az Enum
értékének a sorszáma kerül az oszlopba (“ordinal”). Ha natív SQL-eket írunk, sokkal olvashatóbb, ha string literálokat alkalmazunk a számkódok helyett. Ráadásul ha migrációt végzünk (az alkalmazásból újabb verziót telepítünk, és megváltozott az Enum
) nem lesz gondunk azzal sem, hogy elcsúsznak a sorszámok. Ha átnevezzük az Enum
típusban valamelyik értéket, akkor az O/R mapper futási idejű hibával elszáll, ha olyan értékkel találkozik az adatbázisban, amelyhez nem tartozik Enum
érték.
Janó
Apr 28th, 2010Tetszett. Szinte bármilyen komolyabb problémával találkoztam eddig, a megoldáshoz szükségem volt diszkrét értékek típusára. Az OOP és a Java széleskörű elterjedése előtt sok-sok évvel a C nyelv 3 eszközt adott arra, hogy típust definiáljunk {Enum, Struct, Union}. C++ -ban főleg az első kettővel találkoztam, de aztán arra is rájöttem, hogy struct nélkül is simán meg lehet élni és sokkal jobban járok, ha osztályokat használok C++ -ban is. Ha valaki STL-t használt C++ -ban, akkor az tudja, hogy ott már nem is a lehetett – emlékeim szerint – struct-tal operálni. A következő lépcsőfok pedig a Java, ami el is felejtette a struct-is, de az Enumot hozta magával a letisztult OO modellbe. Szóval az Enum egy túlélő, mert hasznos és jó dolog.
Még egy gondolat. A szkriptnyelvekkel dolgozó fejlesztők is különböző módon próbálják az enum viselkedését utánozni (nincs enum PHP-ban, Perl-ben, JS-ben): stringek tömbje vagy egy olyan objektum, amiben a kulcsok a Tipus.ERTEK-ek az értékek pedig a “Érték”-ek. Azaz valami ilyesmi:
var Szin = {KEK: “kék”, ZOLD: “zöld”, PIROS: “piros”};
if (ertek = “KEK”) {
say(Szin.KEK);
}
Nem olyan szép, mint a Java, de használható, ha jobb nincs.
LutischanF
Oct 8th, 2010Azért lehet ezt még ragozni :
http://alexradzin.blogspot.com/2010/10/hierarchical-structures-with-java-enums_05.html
Marhefka István
Oct 8th, 2010Igen, én is olvastam
Flying cangaroo
Oct 11th, 2010“Ezek után a Color.BLACK.getDisplayName() kifejezés “fekete”-t fog visszaadni. Ezzel az egyszerű módszerrel minden elemhez visszaadható az a string, amit a felhasználói felületnek meg kell jelenítenie.”
Egy láma kérdés: Hogy lesz mindez többnyelvü? Ha GUI-rol van szo, szerintem ez alapovetö kerdes (lehet).
Marhefka István
Oct 11th, 2010Cangaroo: Igazad van, ha a többnyelvűség is szempont, akkor ez a megoldás – finoman szólva – nem a legjobb. De még mindig felhasználható magának az enumnak a neve (.name()). Ezt egyfajta ID-ként használva könnyen lehet mappelni a GUI és a backend között anélkül, hogy valamiféle spéci azonosítókat (magic number) használnánk.
hron84
Mar 18th, 2012@infokukac vagy pedig nem azt mondom, hogy getDisplayName, hanem azt, hogy getBundleId. Es a visszakapott ertek valami olyasmi lesz, hogy “color.black”. Aztan ennyi, ezt mar be lehet csovezni egy lokalizalos tortenetbe. Persze ez csak akkor, ha valamiert a name() nem hasznalhato bundle id-kent.
Marhefka István
Mar 21st, 2012hron84: Igen, egyetértek.