Dipende del significato reale di a, be 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 getAdiventa 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 getProductcontinua 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 getProductl'uso ae bdirettamente, 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 getIdpotrebbe 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àgetIdcomunque 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.