È una buona pratica evitare le costanti usando getter?


25

È buona norma sostituire le costanti utilizzate al di fuori delle classi dai getter?

Ad esempio, è meglio usare if User.getRole().getCode() == Role.CODE_ADMINo if User.getRole().isCodeAdmin()?

Ciò porterebbe a questa classe:

class Role {
    constant CODE_ADMIN = "admin"
    constant CODE_USER = "user"

    private code

    getRoleCode() {
       return Role.code
    }

    isCodeAdmin () {
       return Role.code == Role.CODE_ADMIN
    }

    isCodeUser () {
       return Role.code == Role.CODE_USER
    }
}

15
Preferirei usare qualcosa di simile User.HasRole(Role.Admin).
CodesInCos


4
Controlla il principio Tell not Ask .
Andy,

Metto in dubbio la premessa: User.getRole().getCode()è già una lettura spiacevole, confrontare un codice con un ruolo lo rende ancora più sgraziato.
msw,

Risposte:


47

Prima di tutto, tieni presente che fare qualcosa di simile entity.underlyingEntity.underlyingEntity.method()è considerato un odore di codice secondo la Legge di Demetra . In questo modo, stai esponendo al consumatore molti dettagli di implementazione. E ogni necessità di estensione o modifica di un tale sistema farà molto male.

Quindi, ti consiglio di avere un metodo HasRoleo IsAdminsul Usercommento di CodesInChaos. In questo modo, il modo in cui i ruoli vengono implementati sull'utente rimane un dettaglio di implementazione per il consumatore. Inoltre, è più naturale chiedere all'utente quale sia il suo ruolo invece di chiedergli i dettagli del suo ruolo e decidere in base a quello.


Si prega inoltre di evitare di usare strings se non necessario. nameè un buon esempio di stringvariabile perché i contenuti sono sconosciuti in precedenza. D'altra parte, qualcosa come roledove hai due valori distinti che sono ben noti al momento della compilazione, è meglio usare la tipizzazione forte. È qui che entra in gioco il tipo di enumerazione ...

Confrontare

public bool HasRole(string role)

con

public enum Role { Admin, User }

public bool HasRole(Role role)

Il secondo caso mi dà molte più idee su cosa dovrei passare. Inoltre mi impedisce di passare erroneamente un invalido stringnel caso in cui non avessi idea delle costanti del tuo ruolo.


La prossima è la decisione su come sarà il ruolo. Puoi usare enum direttamente memorizzato sull'utente:

public enum Role
{
    Admin,
    User
}

public class User
{
    private Role _role;

    public bool HasRole(Role role)
    {
        return _role == role;
    }

    // or
    public bool IsAdmin()
    {
        return _role == Role.Admin;
    }
}

D'altra parte, se vuoi che il tuo ruolo abbia un comportamento stesso, dovrebbe sicuramente nascondere di nuovo i dettagli di come viene deciso il suo tipo:

public enum RoleType
{
    User,
    Admin
}

public class Role
{
    private RoleType _roleType;

    public bool IsAdmin()
    {
        return _roleType == RoleType.Admin;
    }

    public bool IsUser()
    {
        return _roleType == RoleType.User;
    }

    // more role-specific logic...
}

public class User
{
    private Role _role;

    public bool IsAdmin()
    {
        return _role.IsAdmin();
    }

    public bool IsUser()
    {
        return _role.IsUser();
    }
}

Questo è comunque abbastanza dettagliato e la complessità aumenterebbe con ogni aggiunta di ruolo - di solito è così che il codice finisce quando si tenta di aderire pienamente alla Legge di Demetra. È necessario migliorare la progettazione, in base ai requisiti concreti del sistema da modellare.

Secondo la tua domanda, immagino che farai meglio con la prima opzione con enum direttamente su User. Se hai bisogno di più logica su Role, la seconda opzione dovrebbe essere considerata come un punto di partenza.


10
Vorrei che questo fosse il modo in cui la gente lo farebbe ovunque. Avere un getter su un attributo privatr altrimenti solo per il gusto di verificare se è uguale a qualcos'altro è una pratica così terribile.
Andy,

2
Ri "istanza.property.property.method () ..." Non è fluido ?
Peter Mortensen,

2
@PeterMortensen È diverso da un'interfaccia fluente. Un'interfaccia fluida sul tipo Xti consentirebbe sicuramente di effettuare stringhe di chiamate di funzione come X.foo().bar().fee().... In questa fluida interfaccia, foo, bar e fee sarebbero tutte funzioni all'interno della classe che Xrestituiscono un oggetto di tipo X. Ma per l '"istanza.property.property.method () menzionata in questo esempio, le due propertychiamate sarebbero effettivamente in classi separate. Il problema è che stai attraversando diversi strati di astrazione per ottenere dettagli di basso livello.
Shaz,

10
Buona risposta, ma la Legge di Demetra non è un esercizio di conteggio dei punti. instance.property.property.method()non è necessariamente una violazione o addirittura un odore di codice; dipende dal fatto che lavori con oggetti o strutture di dati. Node.Parent.RightChild.Remove()probabilmente non è una violazione di LoD (anche se puzzolente per altri motivi). var role = User.Role; var roleCode = role.Code; var isAdmin = roleCode == ADMIN;è quasi certamente una violazione, nonostante abbia sempre "usato solo un punto".
Carl Leth,

1
Capisco che instance.property.property.method()è una violazione del LoD, ma l'OP ha instance.method().method()quale dovrebbe andare bene. Nel tuo ultimo esempio, c'è così tanto codice del boilerplate Userche serve solo come facciata per il Role.
Bergi,

9

Sembra stupido avere una funzione per verificare se il codice memorizzato è il codice amministratore. Quello che vuoi davvero sapere è se quella persona è un amministratore. Quindi, se non vuoi esporre le costanti, allora non dovresti esporre che esiste un codice e chiamare i metodi isAdmin () e isUser ().

Detto questo, "se User.getRole (). GetCode () == Role.CODE_ADMIN" è davvero una manciata solo per verificare che un utente sia un amministratore. Quante cose deve ricordare uno sviluppatore per scrivere quella riga? Deve ricordare che un utente ha un ruolo, un ruolo ha un codice e la classe Role ha costanti per i codici. Sono molte informazioni che riguardano esclusivamente l'implementazione.


3
È peggio: che un utente abbia sempre esattamente un ruolo, né più né meno.
Deduplicatore

5

Oltre a ciò che altri hanno già pubblicato, dovresti tenere presente che l'uso diretto della costante ha un altro svantaggio: se qualcosa cambia il modo in cui gestisci i diritti degli utenti, anche tutti questi luoghi devono essere cambiati.

E rende orribile migliorare. Forse ti piacerebbe avere un tipo di superutente ad un certo punto, che ovviamente ha anche i diritti di amministratore. Con l'incapsulamento, è fondamentalmente un one-liner da aggiungere.

Non è solo breve e pulito, è anche facile da usare e da capire. E - forse soprattutto - è difficile sbagliare.


2

Mentre sono in gran parte d'accordo con i suggerimenti per evitare costanti e avere un metodo isFoo()ecc., Un possibile controesempio.

Se ci sono centinaia di queste costanti e le chiamate sono poco utilizzate, potrebbe non valere la pena di scrivere centinaia di metodi isConstant1, isConstant2. In questo caso particolare e insolito, l'uso delle costanti è ragionevole.

Si noti che l'uso di enum o hasRole()evita la necessità di scrivere centinaia di metodi, quindi è il migliore di tutto il mondo possibile.


2

Non credo che nessuna delle opzioni presentate sia fondamentalmente sbagliata.

Vedo che non hai suggerito l'unica cosa che definirei totalmente sbagliata: codificare i codici di ruolo in funzioni esterne alla classe Role. Questo è:

if (user.getRole().equals("Administrator")) ...

Direi che è decisamente sbagliato. Ho visto programmi che lo fanno e poi ottengono errori misteriosi perché qualcuno ha scritto male la stringa. Ricordo una volta che un programmatore ha scritto "stock" quando la funzione stava controllando "Stock".

Se ci fossero 100 ruoli diversi, sarei molto riluttante a scrivere 100 funzioni per verificarne ogni possibile. Presumibilmente li creeresti scrivendo la prima funzione e poi copiandola e incollandola 99 volte, e quanto vuoi scommettere che in una di quelle 99 copie ti dimenticheresti di aggiornare il test o ne scenderesti uno quando hai passato in rassegna la lista, quindi ora hai

public bool isActuary() { return code==Role.ACTUARY; }
public bool isAccountant() { return code==Role.ACTUARY; }
... etc ...

Personalmente, eviterei anche le catene di chiamate. Preferirei scrivere

if (user.getRole().equals(Role.FOOBATER))

poi

if (user.getRole().getRoleCode()==Role.FOOBATER_CODE)

e a quel punto perché nota scrivi:

if (user.hasRole(Role.FOOBATER))

Quindi fai sapere alla classe User come controllare un ruolo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.