Se vc quer evitar getter/setter somente para “economizar código”, então está pensando nos motivos errados. Código menor não é necessariamente melhor. Código desnecessário, esse sim, é ruim.
Enfim, supondo que vc tem uma classe assim:
public class AlgumaClasse {
private int valor;
public int getValor() {
return valor;
}
public void setValor(int valor) {
this.valor = valor;
}
}
Então de fato, o getter e o setter parecem inúteis, é normal pensar que o campo poderia ser público. Ou seja, em vez de fazer assim:
AlgumaClasse obj = new AlgumaClasse();
obj.setValor(10);
Por que não deixar o campo público e fazer assim?
AlgumaClasse obj = new AlgumaClasse();
// se o campo fosse público, poderia fazer assim
obj.valor = 10;
Validação
Vamos supor que um dia surge a necessidade de validar o valor. Por exemplo, ele só pode estar entre 0 e 10. Aí o setter começa a fazer mais sentido, pois a validação poderia ser feita lá:
public class AlgumaClasse {
//...
// agora o setter valida o valor antes de mudá-lo
public void setValor(int valor) {
if (valor < 0 || valor > 10)
throw new IllegalArgumentException("Valor deve estar entre 0 e 10");
this.valor = valor;
}
}
Ou seja, se eu passar um valor inválido, o setter não deixa eu mudar e lança uma exceção. Se o campo fosse público, seria possível alterá-lo sem passar pela validação. Mas com a validação no setter, eu garanto que em todos os lugares onde ele é chamado só podem ser passados valores válidos.
Cópia defensiva
Outra situação, supomos que agora o valor é uma lista:
import java.util.List;
public class AlgumaClasse {
private List<String> valores;
public List<String> getValores() {
return valores;
}
public void setValores(List<String> valores) {
this.valores = valores;
}
}
Desta forma, é possível alterar a lista fora da classe, veja:
AlgumaClasse obj = new AlgumaClasse();
List<String> lista = new ArrayList<>();
lista.add("abc");
lista.add("def");
// seta a lista contendo "abc" e "def"
obj.setValores(lista);
// obtém a lista
List<String> valores = obj.getValores();
// adiciona um valor e remove outro
valores.add("ghi");
valores.remove(0);
// a lista é alterada
System.out.println(obj.getValores()); // [def, ghi]
Mas caso eu não queira que a lista seja alterada, eu posso fazer com que o getter retorne uma cópia defensiva:
// getter retorna uma cópia da lista, que não pode ser modificada
public List<String> getValores() {
return Collections.unmodifiableList(valores);
}
Desta forma, qualquer tentativa de modificar a lista lançará uma exceção.
Ah, mas ainda é possível alterar a lista desta forma:
AlgumaClasse obj = new AlgumaClasse();
List<String> lista = new ArrayList<>();
lista.add("abc");
lista.add("def");
obj.setValores(lista);
// a lista original ainda pode ser modificada, e estas alterações se refletem na lista dentro de obj
lista.remove(0);
System.out.println(obj.getValores()); // [def]
Ou seja, o setter também precisa criar uma cópia defensiva:
public void setValores(List<String> valores) {
// cria uma cópia da lista, assim alterações feitas fora da classe não se refletem dentro dela
this.valores = new ArrayList<>(valores);
}
Desta forma, a alteração feita no código acima (lista.remove(0)
) não altera a cópia dentro de obj
.
Ou seja, o getter e o setter podem esconder (encapsular) uma lógica interna (seja para validar ou proteger os campos), que fica transparente para quem os usa. Se um dia a implementação mudar (por exemplo, além de retornar a cópia defensiva, também quero validar se a lista não está vazia, etc), isso só muda na classe AlgumaClasse
, e as outras classes que chamam o setter não precisam ser alteradas.
O “campo” não precisa ser um campo
Pois é, o campo propriamente dito nem precisa existir de fato. Por exemplo, eu tenho uma classe que guarda a temperatura em Celsius, mas ela também aceita valores em Fahrenheit. Poderia ser assim:
public class Temperatura {
private double celsius;
public Temperatura(double celsius) {
this.celsius = celsius;
}
public double getCelsius() {
return celsius;
}
public void setCelsius(double celsius) {
this.celsius = celsius;
}
public double getFahrenheit() {
return (celsius * 9 / 5) + 32;
}
public void setFahrenheit(double fahrenheit) {
this.celsius = (fahrenheit - 32) * 5 / 9;
}
}
Exemplo de uso:
Temperatura temp = new Temperatura(20);
System.out.println(temp.getFahrenheit()); // 68.0
temp.setFahrenheit(100);
System.out.println(temp.getCelsius()); // 37.77777777777778
Repare que não precisa existir o campo fahrenheit
, pois o cálculo pode ser feito na hora, tanto no getter quanto no setter.
Claro que também poderia ter os dois campos (celsius
e fahrenheit
), mas aí vc teria que atualizar ambos nos dois setters (setCelsius
também teria que atualizar fahrenheit
e vice-versa). Mas isso é detalhe de implementação que não interfere nas outras classes que usam os getters e setters.
Se os campos fossem públicos, toda essa lógica de conversão a cada mudança de temperatura seria bem mais complicada de fazer.
Conclusão
É claro que se nenhuma dessas situações ocorre, então o getter e setter ficam parecendo redundantes mesmo. Mas eu ainda os manteria, para facilitar manutenções futuras - pode surgir a necessidade de validar no futuro, por exemplo. Eu sei que isso é discutível e muita gente vai citar o YAGNI (“vc não vai precisar disso”), mas eu ainda prefiro não deixar o campo público.
Por fim, é importante lembrar também que vc não precisa sair criando getter e setter para todos os campos. Por exemplo, uma classe Usuario
que tem o campo nome
e cpf
. Será que faz sentido ter um setter? Pois mudar de nome e CPF não é tão simples, geralmente envolve um processo burocrático mais complexo, com muitas etapas e validações.
O mesmo para o clássico exemplo da ContaBancaria
, que não deveria ter um setSaldo
, afinal as únicas maneiras de alterar o saldo seriam as operações (transferir, depositar e sacar). E por aí vai, nem todo campo precisa ter getter e setter.
Resumindo, a ideia de criar os getters é definir quais informações uma classe expõe para as outras, enquanto os setters definem o que ela permite que seja alterado. Além disso, ambos controlam como isso é feito: por exemplo, criando uma cópia defensiva para que ninguém altere o valor dentro da classe, ou restringindo determinados valores para que o objeto não fique em um estado inválido.
É claro que para o caso mais simples (só retorna e seta o campo, sem nenhuma alteração ou validação) vai parecer inútil, mas na minha opinião não é (claro, partindo do pressuposto que vc já analisou que aquele getter/setter é necessário). O código fica bem mais simples de manter quando as classes se comunicam através de métodos em vez de expor campos públicos, pois assim vc pode mudar detalhes de implementação dos métodos sem afetar quem os chama (o que é bem mais difícil - e em muitos casos, inviável - acessando campos públicos diretamente).
Bônus - não siga à risca essas regrinhas de Internet
E só pra terminar, eu diria para não levar tão a sério esse object calisthenics. Sinceramente, é uma das maiores bobagens que eu já vi (e olha que a concorrência é grande), com regrinhas bastante questionáveis - sério mesmo que cada método só pode ter um nível de indentação? Que eu nunca posso usar else
em nenhum ponto do código? Que nenhuma classe pode ter mais que duas variáveis de instância? Pelo amor de deus, se vc seguir à risca essas regras, tem grande chance do código ficar pior em muitos casos.
Cada uma dessas regras é discutível, pois existem casos em que elas são a melhor opção, e casos em que elas pioram as coisas. E também tem casos em que tanto faz. Eu diria para não levar a sério esse tipo de coisa, ou pelo menos não ache que é uma regra imutável, uma lei sagrada escrita na pedra que deve sempre ser seguida à risca (não é).