Depende del significado real de a
, b
y getProduct
.
El propósito de los captadores es poder cambiar la implementación real manteniendo la interfaz del objeto igual. Por ejemplo, si un día se getA
convierte return a + 1;
, el cambio se localiza en un captador.
Los casos de escenarios reales a veces son más complicados que un campo de respaldo constante asignado a través de un constructor asociado con un getter. Por ejemplo, el valor del campo puede calcularse o cargarse desde una base de datos en la versión original del código. En la próxima versión, se puede agregar el almacenamiento en caché para optimizar el rendimiento. Si getProduct
continúa utilizando la versión calculada, no se beneficiará del almacenamiento en caché (o el mantenedor hará el mismo cambio dos veces).
Si tiene sentido getProduct
usarlo a
y usarlo b
directamente, úsalos. De lo contrario, use getters para evitar problemas de mantenimiento más adelante.
Ejemplo donde uno usaría 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;
}
Si bien por el momento, el captador no contiene ninguna lógica de negocios, no se excluye que la lógica en el constructor se migre al captador para evitar hacer el trabajo de la base de datos al inicializar el objeto:
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;
}
Más tarde, se puede agregar el almacenamiento en caché (en C #, uno usaría Lazy<T>
, haciendo que el código sea corto y fácil; no sé si hay un equivalente en 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;
}
Ambos cambios se centraron en el captador y el campo de respaldo, y el código restante no se vio afectado. Si, en cambio, hubiera usado un campo en lugar de un captador getPriceWithRebate
, también tendría que reflejar los cambios allí.
Ejemplo donde probablemente se usarían campos privados:
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;
}
El captador es sencillo: es una representación directa de un campo constante (similar al de C # readonly
) que no se espera que cambie en el futuro: lo más probable es que el captador de ID nunca se convierta en un valor calculado. Así que manténgalo simple y acceda al campo directamente.
Otro beneficio es que getId
podría eliminarse en el futuro si parece que no se usa en el exterior (como en el fragmento de código anterior).
const
: Supongo que eso significa que el compilador en líneagetId
llamará de todos modos y le permite hacer cambios en cualquier dirección. (De lo contrario, estoy totalmente de acuerdo con sus razones para usar getters). Y en los lenguajes que proporcionan sintaxis de propiedad, hay incluso menos razones para no usar la propiedad en lugar del campo de respaldo directamente.Por lo general, usaría las variables directamente. Espera cambiar todos los miembros al cambiar la implementación de una clase. No usar las variables directamente simplemente hace que sea más difícil aislar correctamente el código que depende de ellas y hace que sea más difícil leer el miembro.
Por supuesto, esto es diferente si los captadores implementan una lógica real, en ese caso depende de si necesita utilizar su lógica o no.
fuente
Diría que sería preferible utilizar los métodos públicos, si no por cualquier otro motivo, sino para cumplir con DRY .
Sé que en su caso, tiene campos de respaldo simples para sus accesores, pero podría tener cierta lógica, por ejemplo, código de carga diferida, que debe ejecutar antes de la primera vez que usa esa variable. Por lo tanto, querrá llamar a sus accesores en lugar de hacer referencia directa a sus campos. Aunque no tenga esto en este caso, tiene sentido apegarse a una sola convención. De esa manera, si alguna vez realiza un cambio en su lógica, solo tiene que cambiarlo en un solo lugar.
fuente
Para una clase tan pequeña, la simplicidad gana. Solo usaría a * b.
Para algo mucho más complicado, consideraría usar getA () * getB () si quisiera separar claramente la interfaz "mínima" de todas las otras funciones en la API pública completa. Un excelente ejemplo sería std :: string en C ++. Tiene 103 funciones de miembro, pero solo 32 de ellas realmente necesitan acceso a miembros privados. Si tuviera una clase tan compleja, forzar a todas las funciones "no centrales" a pasar constantemente por la "API principal" podría hacer que la implementación sea mucho más fácil de probar, depurar y refactorizar.
fuente
getA() * getB()
es mejor a medio y largo plazo.