Dipende del significato reale di a
, b
e getProduct
.
Lo scopo di Getter è di essere in grado di cambiare l'implementazione effettiva mantenendo allo stesso tempo l'interfaccia dell'oggetto. Ad esempio, se un giorno getA
diventa return a + 1;
, la modifica è localizzata in un getter.
I casi di scenari reali 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 versione successiva, è possibile aggiungere la memorizzazione nella cache per ottimizzare le prestazioni. Se getProduct
continua a utilizzare la versione calcolata, non trarrà vantaggio dalla memorizzazione nella cache (o il manutentore farà la stessa modifica due volte).
Se ha perfettamente senso per getProduct
l'uso a
e b
direttamente, usali. In caso contrario, utilizzare getter per prevenire problemi di manutenzione in un secondo momento.
Esempio in cui si utilizzerebbero i 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 aziendale, non è escluso che la logica nel costruttore verrà migrata al getter al fine di evitare di fare il lavoro di 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;
}
Successivamente, è possibile aggiungere la memorizzazione nella cache (in C #, si potrebbe usare Lazy<T>
, rendendo il codice breve e semplice; 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 campo di supporto, il codice rimanente non era interessato. Se, invece, avessi usato un campo anziché un getter in getPriceWithRebate
, avrei dovuto riflettere anche i cambiamenti lì.
Esempio in cui si potrebbero probabilmente usare 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 C # readonly
) 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 getId
potrebbe essere rimosso in futuro se sembra che non sia usato all'esterno (come nel precedente pezzo di codice).
const
: Suppongo che ciò significhi che il compilatore inseriràgetId
comunque una chiamata e ti consentirà di apportare modifiche in entrambe le direzioni. (In caso contrario, sono pienamente d'accordo con le tue ragioni per usare i getter.) E nelle lingue che forniscono la sintassi delle proprietà, ci sono anche meno motivi per non usare direttamente la proprietà piuttosto che il campo di supporto.