To zależy od rzeczywistego znaczenia a
, b
i 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 getA
się 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 getProduct
nadal będzie używać wersji obliczonej, nie skorzysta z buforowania (lub opiekun dokona tej samej zmiany dwa razy).
Jeśli jest to sensowne w getProduct
użyciu a
i b
bezpoś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 getId
moż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 igetId
tak 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.