To zależy od rzeczywistego znaczenia a, bi getProduct.
Celem modułów pobierających jest możliwość zmiany rzeczywistej implementacji przy jednoczesnym utrzymaniu interfejsu obiektu bez zmian. Na przykład, jeśli któregoś dnia stanie getAsię return a + 1;, zmiana jest zlokalizowana na getter.
Rzeczywiste przypadki scenariuszy są czasem bardziej skomplikowane niż stałe pole zaplecza przypisywane przez konstruktor powiązany z modułem pobierającym. Na przykład wartość pola może być obliczona lub załadowana z bazy danych w oryginalnej wersji kodu. W następnej wersji można dodać buforowanie w celu zoptymalizowania wydajności. Jeśli getProductnadal będzie używać wersji obliczonej, nie skorzysta z buforowania (lub opiekun dokona tej samej zmiany dwa razy).
Jeśli jest to sensowne w getProductużyciu ai bbezpośrednio, użyj ich. W przeciwnym razie użyj programów pobierających, aby zapobiec problemom z obsługą później.
Przykład użycia gettersa:
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;
}
Chociaż w tej chwili moduł pobierający nie zawiera żadnej logiki biznesowej, nie jest wykluczone, że logika w konstruktorze zostanie poddana migracji do modułu pobierającego, aby uniknąć wykonywania pracy bazy danych podczas inicjowania obiektu:
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;
}
Później można dodać buforowanie (w języku C # można użyć Lazy<T>, dzięki czemu kod jest krótki i łatwy; nie wiem, czy istnieje odpowiednik w 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;
}
Obie zmiany koncentrowały się na module pobierającym i polu zaplecza, pozostały kod pozostał nienaruszony. Gdybym zamiast tego użył pola zamiast gettera getPriceWithRebate, musiałbym również uwzględnić tam zmiany.
Przykład, w którym prawdopodobnie użyłbyś prywatnych pól:
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;
}
Moduł pobierający jest prosty: jest to bezpośrednia reprezentacja stałego (podobnego do C # readonly) pola, które nie powinno się zmieniać w przyszłości: istnieje prawdopodobieństwo, że moduł pobierający ID nigdy nie stanie się wartością obliczoną. Uprość to i uzyskaj bezpośredni dostęp do pola.
Kolejną korzyścią jest to, że getIdmoże zostać usunięty w przyszłości, jeśli okaże się, że nie jest używany na zewnątrz (jak w poprzednim fragmencie kodu).
const: Zakładam, że oznacza to, że kompilator igetIdtak wywoła połączenie i pozwoli ci dokonać zmian w obu kierunkach. (W przeciwnym razie w pełni zgadzam się z powodami, dla których warto korzystać z funkcji pobierających.) A w językach, które zapewniają składnię właściwości, istnieje jeszcze mniej powodów, aby nie korzystać z właściwości zamiast bezpośrednio pola kopii zapasowej.