Quando un metodo privato dovrebbe prendere la via pubblica per accedere ai dati privati?

11

Quando un metodo privato dovrebbe prendere la via pubblica per accedere ai dati privati? Ad esempio, se avessi questa immutabile classe "moltiplicatore" (un po 'inventata, lo so):

class Multiplier {
public:
    Multiplier(int a, int b) : a(a), b(b) { }
    int getA() const { return a; }
    int getB() const { return b; }
    int getProduct() const { /* ??? */ }
private:
    int a, b;
};

Ci sono due modi in cui posso implementare getProduct :

    int getProduct() const { return a * b; }

o

    int getProduct() const { return getA() * getB(); }

Poiché qui l'intenzione è di usare il valore di a , cioè ottenere a , usare getA() per implementare getProduct() sembra più pulito per me. Preferirei evitare di usare a a meno che non dovessi modificarlo. La mia preoccupazione è che non vedo spesso il codice scritto in questo modo, nella mia esperienza a * b sarebbe un'implementazione più comune di getA() * getB() .

I metodi privati dovrebbero mai utilizzare la strada pubblica quando possono accedere direttamente a qualcosa?

    
posta 0x5f3759df 10.01.2015 - 23:09
fonte

4 risposte

7

Dipende dal significato effettivo di a , b e getProduct .

Lo scopo dei Getters è di essere in grado di cambiare l'effettiva implementazione mantenendo allo stesso modo l'interfaccia dell'oggetto. Ad esempio, se un giorno, getA diventa return a + 1; , la modifica è localizzata in un getter.

I casi di scenario reale sono talvolta più complicati di un campo di supporto costante assegnato tramite un costruttore associato a un getter. Ad esempio, il valore del campo può essere calcolato o caricato da un database nella versione originale del codice. Nella prossima versione, è possibile aggiungere il caching per ottimizzare le prestazioni. Se getProduct continua a utilizzare la versione calcolata, non trarrà vantaggio dalla memorizzazione nella cache (o il maintainer eseguirà la stessa modifica due volte).

Se ha perfettamente senso per getProduct utilizzare a e b direttamente, usali. Altrimenti, usa i getter per evitare problemi di manutenzione più tardi.

Esempio di utilizzo dei getter:

class Product {
public:
    Product(ProductId id) : {
        price = Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPrice() {
        return price;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate); // ← Using a getter instead of a field.
    }
private:
    Money price;
}

Mentre per il momento, il getter non contiene alcuna logica di business, non è escluso che la logica nel costruttore verrà migrata al getter per evitare di fare il lavoro del database durante l'inizializzazione dell'oggetto:

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price,
            environment.currentCurrency
        )
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
}

In seguito, è possibile aggiungere il caching (in C #, si userebbe Lazy<T> , rendendo il codice breve e facile, non so se esiste un equivalente in C ++):

class Product {
public:
    Product(ProductId id) : id(id) { }

    Money getPrice() {
        if (priceCache == NULL) {
            priceCache = Money.fromCents(
                data.findProductById(id).price,
                environment.currentCurrency
            )

        return priceCache;
    }

    Money getPriceWithRebate() {
        return getPrice().applyRebate(rebate);
    }
private:
    const ProductId id;
    Money priceCache;
}

Entrambe le modifiche erano focalizzate sul getter e sul backing field, il restante codice non veniva modificato. Se, invece, avessi usato un campo invece di un getter in getPriceWithRebate , dovrei riflettere anche le modifiche.

Esempio in cui probabilmente si useranno campi privati:

class Product {
public:
    Product(ProductId id) : id(id) { }
    ProductId getId() const { return id; }
    Money getPrice() {
        return Money.fromCents(
            data.findProductById(id).price, // ← Accessing 'id' directly.
            environment.currentCurrency
        )
    }
private:
    const ProductId id;
}

Il getter è semplice: è una rappresentazione diretta di un campo costante (simile al readonly di C #) che non dovrebbe cambiare in futuro: è probabile che ID getter non diventerà mai un valore calcolato. Quindi mantienilo semplice e accedi direttamente al campo.

Un altro vantaggio è che la getId potrebbe essere rimossa in futuro se sembra che non sia utilizzata all'esterno (come nel codice precedente).

    
risposta data 10.01.2015 - 23:38
fonte
1

Tipicamente, useresti direttamente le variabili. Ti aspetti di cambiare tutti i membri quando cambi l'implementazione di una classe. Non usare direttamente le variabili rende semplicemente più difficile isolare correttamente il codice che dipende da loro e rende più difficile leggere il membro.

Questo è ovviamente diverso se i gettatori implementano la logica reale, in tal caso dipende dal fatto che sia necessario utilizzare la loro logica o meno.

    
risposta data 10.01.2015 - 23:46
fonte
1

Direi che usare i metodi pubblici sarebbe preferibile, se non fosse per qualsiasi altra ragione, ma per conformarmi a ASCIUTTO .

So che nel tuo caso hai campi di backing semplici per i tuoi accessors, ma potresti avere una certa logica, ad es. codice di caricamento lento, che è necessario eseguire prima della prima volta che si utilizza tale variabile. Pertanto, dovresti chiamare i tuoi accessors invece di fare riferimento direttamente ai tuoi campi. Anche se non hai questo in questo caso, ha senso attenersi a una singola convenzione. In questo modo, se modifichi la tua logica, devi solo modificarla in un unico punto.

    
risposta data 10.01.2015 - 23:43
fonte
0

Per una classe così piccola, la semplicità vince. Vorrei solo usare un * b.

Per qualcosa di molto più complicato, prenderei in considerazione l'utilizzo di getA () * getB () se volessi separare chiaramente l'interfaccia "minima" da tutte le altre funzioni nell'API pubblica completa. Un esempio eccellente sarebbe std :: string in C ++. Ha 103 funzioni membro, ma solo 32 di esse hanno bisogno di accesso a membri privati Se avevi una classe così complessa, forzare tutte le funzioni "non core" a passare costantemente attraverso la "core API" potrebbe rendere l'implementazione molto più facile da testare, eseguire il debug e refactoring.

    
risposta data 10.01.2015 - 23:45
fonte

Leggi altre domande sui tag