Seu código está doente
E eu posso provar!
Utilizei nos exemplos a linguagem de programação Java. Porém os conceitos abordados nesse texto, podem ser aplicados à qualquer linguagem Orientada à Objetos.
Atenção para os sintomas:
- Seu projeto possuí classes com os sufixos, utils, validation ou helper`s?
- Você tem Pojos com atributos privados e métodos Getters e Setters ?
Se você disse sim, a pelo menos uma das peguntas acima, sinto lhe informar mas seu código está anêmico!
Semana passada, escrevi um texto falando sobre a problemática da utilização dos famosos métodos Getters e Setters. O que não falamos ainda, é que esse problema gera um efeito dominó, que pode começar em uma classe e afetar todo o seu projeto.
Vamos usar como exemplo a classe Usuario:
public class Usuario {
private String nome;
private String cpf;
private List<Contato> contatos;
// Getters e Setters
}
Além dos problemas já citados no post anterior, referentes a quebra do encapsulamento, provavelmente pra manipular essa classe e seus atributo, você vai criar uma classe Utils/Helper/Validation ou terceirizar esses trabalhos a um Service. Pois bem, você acabou de criar um Modelo Anêmico.
Mas o que é Modelo Anêmico?
Quando estamos aprendendo o paradigma de Orientação a Objetos, nos é ensinado que Classes possuem atributos e métodos. Onde os atributos, descrevem as características de um objeto e os métodos são as ações.
Você já deve ter feito uma classe com o nome de algum animal, Pato por exemplo. A classe Pato tem atributos como: cor, nome, etc.. E métodos, nadar, andar, voar… Isso é o que chamamos de Modelo Rico. Essa classe tem o domínio total dos seus atributos e ações.
Mas o que vemos em muitos projetos, são classes que dividem as responsabilidade de uma Classe em várias outras. Imagine se nosso Pato, só tivesse os atributos cor e nome, mas para nadar, andar ou voar, precisasse de outra Classe, “PatoActions” por exemplo, que diria ao pato como nadar, voar, andar etc… O problema disso é que qualquer um sabe como o Pato deve fazer as coisas, menos ele próprio. Isso não é Programação Orientada a Objetos, é Programação Procedural.
“.. A pior coisa desse anti-patterns é que ele é muito contrário à ideia básica do design orientado a objetos; que é combinar dados e processos juntos. O modelo de domínio anêmico é na verdade apenas um design de estilo procedural, exatamente o tipo de coisa que fanáticos por objeto como eu (e Eric Evans) têm lutado desde nossos primeiros dias em Smalltalk. O que é pior, muitas pessoas pensam que objetos anêmicos são objetos reais e, portanto, perdem completamente o objetivo do design orientado a objetos.”
— Martin Fowler
Mas como isso se aplica ao nosso exemplo ?
Imagine que um programador que não conhece o projeto e tem que criar um método que manipula uma instância da classe Usuario. Se ele não for avisado previamente que existe uma outra Classe, que é responsável por manipular ou/e validar os dados desse modelo, as chances dele imputar os dados de forma errada são muito grandes.
public class UsuarioService {
// Declaração depedências e Construtores;
public void criar(String cpf, String nome, List<Contatos> contatos) {
Usuario usuario = new User();
usuario.setNome(nome);
usuario.setCpf(cpf);
usuario.setContatos(contacts);
repository.save(user);
}
public void atualizar(String cpf, String nome, List<Contatos> contatos) {
Usuario usuario = new User();
boolean isCpfValido = UserValidation.validCpf(cpf);
boolean isNomeValido = UserValidation.validNome(nome);
if(isCpfValido && isNomeValido) {
usuario.setCpf(FormatterUtils.formatCpf(cpf));
usuario.setNome(FormatterUtils.formatName(name));
usuario.setContacts(filtraContatos(contacts));
repository.save(user);
}
}
// Qual o jeito correto?
}
Aqui, vemos duas maneiras distintas de criar um usuário. Na primeira o método não se preocupa em validar ou formatar os dados recebidos, assumindo que quem invocou já o fez. No segundo, o contrário, ele valida e formata os dados antes de salvar. Qual a maneira correta ?
O ideal seria, que a classe Usuario, que é dona dos atributos, conheça as regras para inserir os valores. Sendo assim, os método de validação e formatação deveriam ficar na classe Usuario. Vamos eliminar o método Set e criar um método AddCpf na classe Usuario.
public class Usuario {
private String nome;
private String cpf;
private List<Contato> contatos;
public void addCpf(String cpf) {
if(this.isCpfValido(cpf)) {
this.cpf = this.formataCpf(cpf);
} else {
throw new InvalidFormatterException();
}
}
// Implementações dos métodos isCpfValido e formataCpf
}
Agora, qualquer um que usar uma instância da classe Usuario terá que utilizar o método addCpf para inserir o valor, sem precisar procurar saber sobre regras de validação ou formatação desse atributo. Isso nos garantirá que sempre que alguém utilizar uma instância da classe Usuario as regras de validação e formatação serão respeitadas.
Mas e se, em outra parte do código precisarmos ter um atributo cpf com as mesmas regras. Como reutilizar esse código ?
Repare que CPF não é uma simples string, ele contém regras e um formato específico, assim como o atributo nome. Por esse motivo, uma boa prática é envolver os tipos primitivos e strings em classes de valor. Então vamos cirar essas classes:
public class Cpf {
private final String numero;
public Cpf(String numero) {
if (!isCpfValido(numero)){
throw new DocumentoInvalidoException("Número de CPF Inválido");
}
this.numero = this.removerFormatacao(numero);
}
public String getNumeroFormatado(){
return this.formataCpf(this.numero);
}
// Implementação dos método privados de validação e formatação
}
public class Nome {
private final String nome;
public Nome(String nome) {
if (!isNomeValido(nome)){
throw new NomeInvalidoException("O nome é Inválido");
}
this.numero = nome.trim();
}
public String get(){
return this.nome;
}
// Implementação dos método privados de validação e formatação
}
Agra que os atributos da classe Usuario também são classes e contém suas próprias regras de validação e formatação.
public class User {
private Nome nome;
private Cpf cpf;
private List<Contato> contatos;
}
Mas a classe Usuario agora ficou mais complexa e difícil de utilizar. Se ela fosse um DTO (Data Transfer Object) por exemplo, eu não conseguiria mais passar apenas um String simples para inserir um nome, teria que passar um objeto com um atributo string dentro, certo ? Errado!
É aqui que entra a magia do encapsulamento, quem está de fora não precisa entender como funciona a classe por dentro. Como entrada você pode continuar recebendo e retornando uma String.
public class User {
private Nome nome;
private Cpf cpf;
private List<Contatos> contatos;
public void setNome(String nome){
this.nome = new Nome(nome);
}
public String getNome(){
return this.nome.get();
}
public void setCpf(String numero){
this.cpf = new Cpf(numero);
}
public String getCpf(){
return this.cpf.getNumeroFormatado();
}
}
Listas também são bons exemplos de atributos que deviam ser envolvidos em classes. Geralmente quando você tem uma classe que contém um atributo do tipo lista, em algum momento você precisa criar um loop e fazer um filtro, ou criar alguma regra antes de inserir ou remover um dado da lista. Então por que não encapsular essas regras também ?
public class Contatos {
private final List<Contato> contatos;
public Contatos() {
this.contatos = new ArrayList<>();
}
public void adicionarNovoContato(Contato contato) {
if (!isContatoValido(contato)) {
throw new IlegalArgumentException("Contato informado é inválido");
}
this.contatos.add(contato);
}
public Contato buscaPorEmail(String email) {
return this.contatos.stream()
.filter(c -> c.getEmail.equals(email))
.findAny();
}
}
Conclusão
Esse é mais um conceito daqueles que aprendemos nas primeiras aulas de OO e não praticamos no dia-a-dia.
Como foi dito no início, é um efeito dominó que se espalha pelo seu projeto. Começa com um modelo anêmico que nos força a criar classes pouco coesas, ferindo princípios básicos da Orientação a Objetos e do SOLID, criando projetos difíceis de manter, com classes “mágicas” cheias de métodos estáticos que tentam ser genéricas pra atender várias situações onde nenhuma é de seu domínio.