A partir do momento que você resolve utilizar uma tecnologia deve fazer o possível para entendê-la. Hoje eu estava conversando com um amigo e percebi que por mais que estejam espalhados os conceitos ainda não são entendidos. Falávamos sobre JPA e sobre como misturar lógica de negócios com metadados de persistência pode se tornar um problema em longo prazo. O argumento em favor da mistura foi “mas sua lógica de negócios não precisa ficar anotada, apenas seus dados”. Aí eu percebi que a discussão, na verdade, precisava passar por algo mais fundamental.
Eu expliquei o problema de usar VOs e BOs (que ele não chamava por estes nomes mas aplicava os conceitos) através de um exemplo simples.
Vamos supôr que exista uma função que define o novo salário de um funcionário (pseudo-codigo):
public void aumentaSalario(Funcionario f, Valor v){
Valor salario = v + calculaTaxasCargo(f.getCargo());
f.setSalario(salario);
}
E vamos supor que exista um tipo de funcionário chamado Gerente. O Gerente possui uma lógica de cálculo de salário um pouco diferente, dada pela formula:
salario = v + calculaTaxasCargo(f.getCargo()) + (f.getDepartamento().getOrcamento().getBonus() / 10)
Como adaptamos o código acima para que isso seja possível? Uma forma é criar um novo método:
public void aumentaSalario(Funcionario f, Valor v){
Valor salario = v + calculaTaxasCargo(f.getCargo());
f.setSalario(salario);
}
public void aumentaSalarioGerente(Gerente f, Valor v){
Valor salario = calculaSalario(f,v) + (f.getDepartamento().getOrcamento().getBonus() / 10);
f.setSalario(salario);
}
Mas desta forma nós sempre precisamos saber se estamos lidando com um gerente ou funcionário para saber qual função chamar. Se temos uma lista como a abaixo (que pode ser retornada do banco de dados, por exemplo) precisamos verificar cada tipo:
List<Funcionario> lista = new ArrayList<Funcionario>();
.
lista.add(new Gerente("João"));
lista.add(new Funcionario("Lucia"));
.
//aumentando o salario de todo mundo em 1000 dinheiros
for(Funcionario f: lista){
if(f instanceof Gerente){
aumentaSalarioGerente(f,1000);
}
else{
aumentaSalario(f,1000);
}
}
De que adianta ter uma abstração na forma de herança entre Funcionario e Gerente se cada vez que eu preciso manipulá-las eu preciso quebrar esta abstração com um if? Ok, é fácil de resolver isso criando uma função única:
public void aumentaSalario(Funcionario f, Valor v){
Valor salario = v + calculaTaxasCargo(f.getCargo());
.
if(f instanceof Gerente){
salario = calculaSalario(f,v) + (f.getDepartamento().getOrcamento().getBonus() / 10);
}
f.setSalario(salario);
}
.
//Aumentando salarios:
List>Funcionario> lista = new ArrayList>Funcionario>);
.
lista.add(new Gerente("João"));
lista.add(new Funcionario("Lucia"));
.
//aumentando o salario de todo mundo em 1000 dinheiros
for(Funcionario f: lista){
aumentaSalario(f,1000);
}
Mas o que eu fiz foi simplesmente mudar o problema de lugar, não acabar com ele (por isso minha métrica do if).
E qual a solução? Polimorfismo.
class Funcionario{
.
public void aumentaSalario(Valor v){
Valor salario = v + calculaTaxasCargo(this.getCargo());
this.salario = salario;
}
.
}
.
class Gerente extends Funcionario{
.
public void aumentaSalario(Valor v){
super.aumentaSalario(v);
//soma o bonus de gerente ao salario
f.setSalario(salario);
this.salario = this.salario+ (getDepartamento().getOrcamento().getBonus() / 10);
}
.
}
Daí nossa lista de aumento em massa pode fazer simplesmente:
//Aumentando salarios:
List>Funcionario> lista = new ArrayList>Funcionario>();
.
lista.add(new Gerente("João"));
lista.add(new Funcionario("Lucia"));
.
//aumentando o salario de todo mundo em 1000 dinheiros
for(Funcionario f: lista){
f.aumentaSalario(1000);
}
Porque cada objeto tem em si a lógica necessária para realizar a operação. Claro que este é um exemplo acadêmico, já identifiquei uma penca de refactorings necessários para o código ficar aceitável, mas a idéia está expressa nessa simples divisão de responsabilidade.
Aí é hora de alguém postar um comentário dizendo: “mas estes objetos ‘gordos’ têm toda a lógica? Fazem persistência?” a resposta é não. Objetos são ‘animais sociais e colaborativos’, o que significa que eles agem em conjunto com outros objetos para chegar a um fim. No exemplo acima, o cálculo sobre quanto é o bônus não é feito pelo funcionário, nem pelo gerente mas sim sobre quem é “dono” do conceito (dica: esta parte pode ser refatorada se você ler sobre o Princípio de Deméter).
Objetos dividem as responsabilidades e agem em conjunto para executar as regras de negócio de um sistema. Parece simples mas cada vez mais eu acho que as pessoas não gostam de conceitos simples :(
Ótimo post. Concordo 100%!
Em toda santa aula na Caelum, isso é exatamente o que eu tento transmitir… ;)
“…e sobre como misturar lógica de negócios com metadados de persistência pode se tornar um problema em longo prazo.”
Sim, de forma geral annotations no código tem suas considerações… Que tipo de problema vc fala? Usar XML resolveria?
“Objetos dividem as responsabilidades e agem em conjunto para executar as regras de negócio de um sistema. Parece simples…”
Concordo com vc, mas a simplicidade soh fica evidente nas iteracoes posteriores.
“…as pessoas não gostam de conceitos simples.”
As pessoas preferem aquilo que sabem fazer!
Pô, Phillip, esse é o exemplo mais simples de Strategy que vc achou?
P.S.: adorei seu Wiki!
@Veloso
Ainda não sei ao certo quais seriam os problemas, Carlos. Estou entrando no meu primeiro grande projeto com annotations de mapeamento, feedback em breve.
@Tiago
Na verdade não é Strategy, é polimorfimso puro. Seria um Strategy se o que variasse de acordo com a implementação fosse um delegate unido por meio de agregação ou composição à classe que o chama (o algoritmo faz parte dela).
Mas quanto á simplicidade, realmente não é fácil de explicar numa primeira vez com este exemplo mas o que eu também percebi é que várias vezes as pessoas já estudaram os exemplos clássicos e mais fáceis mas simplesmente não sabem como utilizá-los mno tal ‘mundo real’.
Me lembro de um time que participei onde haviam várias pessoas com bom conhecimento sobre OO e que produziam um software de BOs e VOs. Só fui entender porque durante o processo de análise: eles modelavam os objetos do domínio em diagramas e no Rational Rose mas simplesmente não entendiam que aqueles objetos deveriam ser mapeados 1-para-1 no código.
[]s
Muito bom artigo mas me permita uma dúvida rapida, isso se aplicaria no caso de ter 2 coleções em que ora se tira de uma ora se tira de outra(tirar tem uma certa logica de negocio envolvida)? Digo, é valido as duas extenderiam um List(ou outra) e cada implementaria esse metodo de retirar um item?
Oi, Matheus,
Tudo depoende de sobre o quê estamos falando. Enquanto elas forem apenas coleções de objetos qualquer resposta é válida, qualquer estrutura serve. O design orientado a objetos só passa a tuar quando se tem semântica no modelo, ou seja: quando eu sei o que é uma lista, o que ela guarda e qual seu papel no sistema.
[]s
Bem essa frase:
“Objetos dividem as responsabilidades e agem em conjunto para executar as regras de negócio de um sistema.”
P. Calçado
Se essa frase for entendida realmente, pode-se dizer que há como criar um sistema orientado a objetos (de verdade).
“Sistemas inteligentes”
Pra quem gostou do exemplo do Calçado, temos um igualzinho na apostila FJ-11 da Caelum! Depois ainda discutimos herança X composicao em cima desse mesmo caso (em vez de aumento de salario, é bonificacao de fim de ano).
http://www.caelum.com.br/caelum/apostila/caelum-java-objetos-fj11.pdf
Sobre a persistência, por isso que eu acho que ActiveRecord na maioria das vezes não é muito bacana. O conceito do objeto saber se salvar, pra mim, foge da responsabilidade dele. Um objeto precisa ter estado e comportamentos pra interagir com os outros “seres” do meio dele.
Mas não saber o que ele precisa fazer pra poder ser “lembrado” no futuro.