Depende do significado real de a
, b
e getProduct
.
O objetivo dos getters é poder alterar a implementação real, mantendo a interface do objeto igual. Por exemplo, se um dia getA
se tornar return a + 1;
, a alteração será localizada em um getter.
Casos de cenários reais às vezes são mais complicados do que um campo de apoio constante atribuído por meio de um construtor associado a um getter. Por exemplo, o valor do campo pode ser calculado ou carregado em um banco de dados na versão original do código. Na próxima versão, o armazenamento em cache pode ser adicionado para otimizar o desempenho. Se getProduct
continuar usando a versão computada, ela não se beneficiará do cache (ou o mantenedor fará a mesma alteração duas vezes).
Se faz sentido getProduct
usar a
e b
diretamente, use-os. Caso contrário, use getters para evitar problemas de manutenção posteriormente.
Exemplo onde alguém usaria 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;
}
Embora, no momento, o getter não contenha nenhuma lógica de negócios, não é de excluir que a lógica no construtor será migrada para o getter, a fim de evitar o trabalho do banco de dados ao inicializar o 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;
}
Posteriormente, o cache pode ser adicionado (em C #, seria usado Lazy<T>
, tornando o código curto e fácil; não sei se existe um equivalente em 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;
}
Ambas as alterações foram focadas no getter e no campo de suporte, o código restante não sendo afetado. Se, em vez disso, eu tivesse usado um campo em vez de um getter getPriceWithRebate
, também teria que refletir as alterações lá.
Exemplo em que alguém provavelmente usaria 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;
}
O getter é direto: é uma representação direta de um campo constante (semelhante ao de C # readonly
) que não deve mudar no futuro: é provável que o getter de ID nunca se torne um valor calculado. Portanto, mantenha-o simples e acesse o campo diretamente.
Outro benefício é que ele getId
poderá ser removido no futuro se parecer que não é usado fora (como no trecho de código anterior).
const
: Suponho que isso significa que o compilador fará umagetId
chamada em linha de qualquer maneira e permite que você faça alterações em qualquer direção. (Caso contrário, concordo plenamente com seus motivos para usar getters.) E nos idiomas que fornecem sintaxe de propriedade, há ainda menos motivos para não usar a propriedade em vez do campo de apoio diretamente.Normalmente, você usaria as variáveis diretamente. Você espera alterar todos os membros ao alterar a implementação de uma classe. Não usar as variáveis diretamente simplesmente torna mais difícil isolar corretamente o código que depende delas e torna mais difícil a leitura do membro.
É claro que isso é diferente se os getters implementarem lógica real; nesse caso, depende se você precisa utilizar a lógica deles ou não.
fonte
Eu diria que o uso de métodos públicos seria preferível, se não por qualquer outro motivo, mas para estar em conformidade com o DRY .
Sei que, no seu caso, você tem campos de apoio simples para seus acessadores, mas pode ter uma certa lógica, por exemplo, código de carregamento lento, que você precisa executar antes da primeira vez que usar essa variável. Portanto, convém chamar seus acessadores em vez de referenciar diretamente seus campos. Mesmo que você não tenha isso nesse caso, faz sentido manter uma única convenção. Dessa forma, se você mudar sua lógica, precisará alterá-la em um só lugar.
fonte
Para uma classe tão pequena, a simplicidade vence. Eu apenas usaria a * b.
Para algo muito mais complicado, eu consideraria fortemente usar getA () * getB () se quisesse separar claramente a interface "mínima" de todas as outras funções na API pública completa. Um excelente exemplo seria std :: string em C ++. Possui 103 funções de membro, mas apenas 32 delas realmente precisam de acesso a membros privados. Se você tivesse uma classe tão complexa, forçar todas as funções "não essenciais" a passar consistentemente pela "API principal" poderia facilitar muito a implementação de teste, depuração e refatoração.
fonte
getA() * getB()
é melhor a médio e longo prazo.