Szeretem az Enum-ot

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.

Share
This entry was posted in Programozás, Technológia and tagged , , , , , . Bookmark the permalink. Follow any comments here with the RSS feed for this post. Trackbacks are closed, but you can post a comment.

Comments

  • Tetszett. 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.

  • Marhefka István

    Oct 8th, 2010

    Igen, é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, 2010

    Cangaroo: 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.

  • @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, 2012

    hron84: Igen, egyetértek.

Leave a Comment