Una costante di stringa dovrebbe essere definita se deve essere usata una sola volta?

22

Implementiamo un adattatore per Jaxen (una libreria XPath per Java) che ci consente di utilizzare XPath per accedere al modello di dati della nostra applicazione.

Questo viene fatto implementando le classi che mappano le stringhe (passate a noi da Jaxen) in elementi del nostro modello di dati. Stimiamo che avremo bisogno di circa 100 classi con oltre 1000 confronti di stringhe in totale.

Penso che il modo migliore per farlo sia semplice se le istruzioni / else con le stringhe scritte direttamente nel codice - piuttosto che definire ogni stringa come una costante. Ad esempio:

public Object getNode(String name) {
    if ("name".equals(name)) {
        return contact.getFullName();
    } else if ("title".equals(name)) {
        return contact.getTitle();
    } else if ("first_name".equals(name)) {
        return contact.getFirstName();
    } else if ("last_name".equals(name)) {
        return contact.getLastName();
    ...

Tuttavia mi è sempre stato insegnato che non dovremmo incorporare i valori stringa direttamente nel codice, ma piuttosto creare costanti di stringa. Sarebbe simile a questo:

private static final String NAME = "name";
private static final String TITLE = "title";
private static final String FIRST_NAME = "first_name";
private static final String LAST_NAME = "last_name";

public Object getNode(String name) {
    if (NAME.equals(name)) {
        return contact.getFullName();
    } else if (TITLE.equals(name)) {
        return contact.getTitle();
    } else if (FIRST_NAME.equals(name)) {
        return contact.getFirstName();
    } else if (LAST_NAME.equals(name)) {
        return contact.getLastName();
    ...

In questo caso penso che sia una cattiva idea. La costante verrà sempre utilizzata una sola volta, nel metodo getNode() . Usare direttamente le stringhe è altrettanto facile da leggere e capire come usare le costanti e ci consente di scrivere almeno un migliaio di righe di codice.

Quindi c'è qualche ragione per definire le costanti di stringa per un singolo uso? O è accettabile utilizzare direttamente le stringhe?

PS. Prima che qualcuno suggerisca di utilizzare l'enumerazione, invece, l'abbiamo prototipato, ma la conversione enum è 15 volte più lenta del semplice confronto tra stringhe, quindi non viene presa in considerazione.

Conclusione: Le risposte seguenti hanno ampliato lo scopo di questa domanda oltre le sole costanti di stringa, quindi ho due conclusioni:

  • Probabilmente è OK utilizzare le stringhe direttamente anziché le costanti stringa in questo scenario, ma
  • Ci sono modi per evitare di utilizzare le stringhe, il che potrebbe essere migliore.

Quindi vado a provare la tecnica wrapper che evita completamente le stringhe. Sfortunatamente non possiamo usare l'istruzione switch delle stringhe perché non siamo ancora su Java 7. In definitiva, penso che la migliore risposta per noi sia provare ogni tecnica e valutare le sue prestazioni. La realtà è che se una tecnica è chiaramente più veloce, probabilmente la sceglieremo indipendentemente dalla sua bellezza o aderenza alla convenzione.

    
posta gutch 24.04.2012 - 04:35
fonte

6 risposte

4

Prova questo. La riflessione iniziale è certamente costosa, ma se la userai molte volte, cosa che penso che farai, questa è sicuramente una soluzione migliore di ciò che stai proponendo. Non mi piace usare il riflesso, ma mi trovo a usarlo quando non mi piace l'alternativa alla riflessione. Penso che questo salverà la tua squadra un sacco di mal di testa, ma devi passare il nome del metodo (in lettere minuscole).

In altre parole, anziché passare il "nome", si passa "fullname" perché il nome del metodo get è "getFullName ()".

Map<String, Method> methodMapping = null;

public Object getNode(String name) {
    Map<String, Method> methods = getMethodMapping(contact.getClass());
    return methods.get(name).invoke(contact);
}

public Map<String, Method> getMethodMapping(Class<?> contact) {
    if(methodMapping == null) {
        Map<String, Method> mapping = new HashMap<String, Method>();
        Method[] methods = contact.getDeclaredMethods();
        for(Method method : methods) {
            if(method.getParameterTypes().length() == 0) {
                if(method.getName().startsWith("get")) {
                    mapping.put(method.getName().substring(3).toLower(), method);
                } else if (method.getName().startsWith("is"))) {
                    mapping.put(method.getName().substring(2).toLower(), method);
                }
            }
        }
        methodMapping = mapping;
    }
    return methodMapping;
}

Se è necessario accedere ai dati contenuti all'interno dei membri di contatto, si potrebbe prendere in considerazione la creazione di una classe wrapper per il contatto che ha tutti i metodi per accedere alle informazioni richieste. Ciò sarebbe anche utile per garantire che i nomi dei campi di accesso rimarranno sempre gli stessi (cioè se la classe wrapper ha getFullName () e si chiama con fullname, funzionerà sempre anche se il nome getFullName () del contatto è stato rinominato - it causerebbe errori di compilazione prima che ti permettesse di farlo).

public class ContactWrapper {
    private Contact contact;

    public ContactWrapper(Contact contact) {
        this.contact = contact;
    }

    public String getFullName() {
        return contact.getFullName();
    }
    ...
}

Questa soluzione mi ha salvato diverse volte, vale a dire quando volevo avere una singola rappresentazione di dati da usare nei data jsf e quando quei dati dovevano essere esportati in un report usando jasper (che non gestisce bene gli accessor degli oggetti complicati la mia esperienza).

    
risposta data 24.04.2012 - 09:44
fonte
5

Se possibile, utilizza Java 7 che ti consente di utilizzare le stringhe in switch statement.

Da link

public class StringSwitchDemo {

    public static int getMonthNumber(String month) {

        int monthNumber = 0;

        if (month == null) {
            return monthNumber;
        }

        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }

        return monthNumber;
    }

    public static void main(String[] args) {

        String month = "August";

        int returnedMonthNumber =
            StringSwitchDemo.getMonthNumber(month);

        if (returnedMonthNumber == 0) {
            System.out.println("Invalid month");
        } else {
            System.out.println(returnedMonthNumber);
        }
    }
}

Non ho misurato, ma credo che le istruzioni switch siano compilate su una tabella di salto invece di una lunga lista di confronti. Questo dovrebbe essere ancora più veloce.

Per quanto riguarda la tua domanda attuale: se la usi solo una volta non hai bisogno di trasformarla in una costante. Considera comunque che una costante può essere documentata e apparire in Javadoc. Questo può essere importante per valori di stringa non banali.

    
risposta data 24.04.2012 - 08:55
fonte
4

Se hai intenzione di mantenerlo (apportare qualsiasi tipo di modifiche non banali mai), potrei effettivamente considerare l'utilizzo di una sorta di generazione di codice basata su annotazione (forse tramite CGLib ) o anche solo uno script che scrive tutto il codice per te. Immagina il numero di errori di battitura e errori che potrebbero insinuarsi nell'approccio che stai considerando ...

    
risposta data 24.04.2012 - 06:22
fonte
2

Userei ancora le costanti definite nella parte superiore delle tue classi. Rende il tuo codice più gestibile dal momento che è più facile vedere cosa può essere modificato in un secondo momento (se necessario). Ad esempio, "first_name" potrebbe diventare "firstName" in un secondo momento.

    
risposta data 24.04.2012 - 04:57
fonte
1

Se la tua denominazione è coerente (ovvero "some_whatever" è sempre mappata a getSomeWhatever() ) puoi utilizzare il reflection per determinare ed eseguire il metodo get.

    
risposta data 24.04.2012 - 08:40
fonte
0

Suppongo che l'elaborazione delle annotazioni potrebbe essere la soluzione, anche senza annotazioni. È la cosa che può generare tutto il codice noioso per te. Lo svantaggio è che otterrai classi N generate per classi di modelli N. Non puoi aggiungere nulla a una classe esistente, ma scrivendo qualcosa come

public Object getNode(String name) {
    return SomeModelClassHelper.getNode(this, name);
}

una volta per classe non dovrebbe essere un problema. In alternativa, potresti scrivere qualcosa come

public Object getNode(String name) {
    return getHelper(getClass()).getNode(this, name);
}

in una superclasse comune.

È possibile utilizzare la riflessione anziché l'elaborazione delle annotazioni per la generazione del codice. Lo svantaggio è che è necessario compilare il codice prima di poter utilizzare la riflessione su di esso. Ciò significa che non puoi fare affidamento sul codice generato nelle classi del modello, a meno che non generi alcuni stub.

Prenderò in considerazione anche l'uso diretto della riflessione. Certo, la riflessione è lenta, ma perché è lenta? È perché deve fare tutto ciò che devi fare, ad esempio, attivare il nome del campo.

    
risposta data 23.04.2016 - 21:49
fonte

Leggi altre domande sui tag