Nó phụ thuộc vào ý nghĩa thực tế của a
, b
và getProduct
.
Mục đích của getters là để có thể thay đổi việc thực hiện thực tế trong khi vẫn giữ giao diện của đối tượng như cũ. Ví dụ, nếu một ngày, getA
trở thành return a + 1;
, thay đổi được bản địa hóa thành một getter.
Các trường hợp kịch bản thực đôi khi phức tạp hơn trường sao lưu không đổi được chỉ định thông qua một hàm tạo được liên kết với một getter. Ví dụ, giá trị của trường có thể được tính hoặc tải từ cơ sở dữ liệu trong phiên bản gốc của mã. Trong phiên bản tiếp theo, bộ nhớ đệm có thể được thêm vào để tối ưu hóa hiệu suất. Nếu getProduct
tiếp tục sử dụng phiên bản được tính toán, nó sẽ không được hưởng lợi từ bộ nhớ đệm (hoặc người bảo trì sẽ thực hiện thay đổi tương tự hai lần).
Nếu nó có ý nghĩa hoàn hảo getProduct
để sử dụng a
và b
trực tiếp, sử dụng chúng. Nếu không, sử dụng getters để ngăn chặn các vấn đề bảo trì sau này.
Ví dụ trong đó người ta sẽ sử dụng getters:
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;
}
Mặc dù hiện tại, getter không chứa bất kỳ logic nghiệp vụ nào, không loại trừ rằng logic trong hàm tạo sẽ được di chuyển sang getter để tránh thực hiện công việc cơ sở dữ liệu khi khởi tạo đối tượng:
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;
}
Sau đó, bộ nhớ đệm có thể được thêm vào (trong C #, người ta sẽ sử dụng Lazy<T>
, làm cho mã ngắn và dễ dàng; Tôi không biết liệu có tương đương trong C ++ không):
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;
}
Cả hai thay đổi đều tập trung vào getter và trường sao lưu, mã còn lại không bị ảnh hưởng. Nếu, thay vào đó, tôi đã sử dụng một lĩnh vực thay vì một getter getPriceWithRebate
, tôi cũng sẽ phải phản ánh những thay đổi ở đó.
Ví dụ trong đó người ta có thể sử dụng các trường riêng:
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;
}
Trình getter rất đơn giản: đó là một đại diện trực tiếp của trường hằng số (tương tự như C # readonly
) không được dự kiến sẽ thay đổi trong tương lai: rất có thể, ID getter sẽ không bao giờ trở thành giá trị được tính toán. Vì vậy, giữ cho nó đơn giản, và truy cập vào các lĩnh vực trực tiếp.
Một lợi ích khác là getId
có thể được loại bỏ trong tương lai nếu có vẻ như nó không được sử dụng bên ngoài (như trong đoạn mã trước).
const
: Tôi cho rằng điều đó có nghĩa là trình biên dịch sẽ thực hiệngetId
cuộc gọi bằng mọi cách và nó cho phép bạn thay đổi theo một trong hai hướng. (Nếu không, tôi hoàn toàn đồng ý với lý do của bạn để sử dụng getters.) Và trong các ngôn ngữ cung cấp cú pháp thuộc tính, thậm chí còn có ít lý do hơn để không sử dụng thuộc tính thay vì trực tiếp trường sao lưu.