O encapsulamento tem um propósito, mas também pode ser mal utilizado ou abusado.
Considere algo como a API do Android, que possui classes com dezenas (se não centenas) de campos. Ao expor esses campos, o consumidor da API dificulta a navegação e o uso, além de fornecer ao usuário a falsa noção de que ele pode fazer o que quiser com aqueles campos que podem entrar em conflito com a forma como devem ser usados. Portanto, o encapsulamento é ótimo nesse sentido para manutenção, usabilidade, legibilidade e evitar bugs malucos.
Por outro lado, POD ou tipos de dados antigos simples, como uma estrutura do C / C ++, na qual todos os campos são públicos, também podem ser úteis. Ter getters / setters inúteis como os gerados pela anotação @data no Lombok é apenas uma maneira de manter o "Padrão de encapsulamento". Uma das poucas razões pelas quais fazemos getters / inúteis "inúteis" em Java é que os métodos fornecem um contrato .
Em Java, você não pode ter campos em uma interface; portanto, você usa getters e setters para especificar uma propriedade comum que todos os implementadores dessa interface possuem. Em idiomas mais recentes, como Kotlin ou C #, vemos o conceito de propriedades como campos para os quais você pode declarar um setter e um getter. No final, getters / setters inúteis são mais um legado que o Java tem que conviver, a menos que a Oracle adicione propriedades a ele. O Kotlin, por exemplo, que é outra linguagem da JVM desenvolvida pela JetBrains, possui classes de dados que basicamente fazem o que a anotação @data faz no Lombok.
Também aqui estão alguns exemplos:
class DataClass
{
private int data;
public int getData() { return data; }
public void setData(int data) { this.data = data; }
}
Este é um caso ruim de encapsulamento. O getter e o setter são efetivamente inúteis. O encapsulamento é usado principalmente porque esse é o padrão em linguagens como Java. Na verdade, não ajuda, além de manter a consistência na base de código.
class DataClass implements IDataInterface
{
private int data;
@Override public int getData() { return data; }
@Override public void setData(int data) { this.data = data; }
}
Este é um bom exemplo de encapsulamento. Encapsulamento é usado para impor um contrato, neste caso IDataInterface. O objetivo do encapsulamento neste exemplo é fazer com que o consumidor dessa classe use os métodos fornecidos pela interface. Embora o getter e o setter não façam nada sofisticado, agora definimos uma característica comum entre o DataClass e outros implementadores do IDataInterface. Assim, eu posso ter um método como este:
void doSomethingWithData(IDataInterface data) { data.setData(...); }
Agora, ao falar sobre encapsulamento, acho importante abordar também o problema de sintaxe. Muitas vezes vejo pessoas reclamando da sintaxe necessária para impor o encapsulamento em vez do próprio encapsulamento. Um exemplo que vem à mente é de Casey Muratori (você pode ver o discurso dele aqui ).
Suponha que você tenha uma classe de jogador que usa encapsulamento e deseja mover sua posição em 1 unidade. O código ficaria assim:
player.setPosX(player.getPosX() + 1);
Sem encapsulamento, ficaria assim:
player.posX++;
Aqui ele argumenta que os encapsulamentos levam a muito mais digitação sem benefícios adicionais e isso pode, em muitos casos, ser verdade, mas observe alguma coisa. O argumento é contra a sintaxe, não o próprio encapsulamento. Mesmo em linguagens como C que não possuem o conceito de encapsulamento, muitas vezes você vê variáveis em estruturas pré-expressas ou sufixadas com '_' ou 'my' ou qualquer outra coisa que signifique que elas não devem ser usadas pelo consumidor da API, como se fossem privado.
O fato é que o encapsulamento pode ajudar a tornar o código muito mais sustentável e fácil de usar. Considere esta classe:
class VerticalList implements ...
{
private int posX;
private int posY;
... //other members
public void setPosition(int posX, int posY)
{
//change position and move all the objects in the list as well
}
}
Se as variáveis fossem públicas neste exemplo, um consumidor dessa API ficaria confuso sobre quando usar posX e posY e quando usar setPosition (). Ao ocultar esses detalhes, você ajuda o consumidor a usar melhor sua API de maneira intuitiva.
A sintaxe é uma limitação em muitos idiomas. No entanto, os idiomas mais novos oferecem propriedades que nos dão a boa sintaxe dos membros da publicação e os benefícios do encapsulamento. Você encontrará propriedades em C #, Kotlin, mesmo em C ++, se você usar o MSVC. Aqui está um exemplo no Kotlin.
classe VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {campo = y; ...}}
Aqui conseguimos o mesmo que no exemplo Java, mas podemos usar posX e posY como se fossem variáveis públicas. Porém, quando tento alterar o valor deles, o corpo do setter set () será executado.
No Kotlin, por exemplo, isso seria o equivalente a um Java Bean com getters, setters, hashcode, equals e toString implementados:
data class DataClass(var data: Int)
Observe como essa sintaxe nos permite fazer um Java Bean em uma linha. Você percebeu corretamente o problema de uma linguagem como Java na implementação do encapsulamento, mas isso é culpa do Java, não do próprio encapsulamento.
Você disse que usa o @Data do Lombok para gerar getters e setters. Observe o nome, @Data. Ele deve ser usado principalmente em classes de dados que armazenam apenas dados e devem ser serializadas e desserializadas. Pense em algo como um arquivo salvo de um jogo. Mas em outros cenários, como em um elemento de interface do usuário, você definitivamente deseja setters, pois apenas alterar o valor de uma variável pode não ser suficiente para obter o comportamento esperado.
"It will create getters, setters and setting constructors for all private fields."
- Do jeito que você descreve essa ferramenta, parece que ela está mantendo o encapsulamento. (Pelo menos em um sentido frouxo, automatizado e de certa forma anêmico). Então, qual é exatamente o problema?