Fantochada

Dom Quixote lutava contra moinhos de vento. Muitos programadores estão lutando contra fantoches.  Generalizou-se a ideia de que objetos sem métodos outros que não modificadores (set) e acessores (get) são ruins porque são sempre manipulados por outros objetos: são fantoches.

Ora, esta ideia, em si mesma é que é a grande fantochada. Alguns objetos são simplesmente um saco de propriedades e não há nada que possamos fazer contra isso. Embutir artificialmente métodos nesses objetos só para que pareça que eles fazem alguma coisa é simplesmente errado. Ao tentar fugir dos fantoches criam-se monstros piores.

O Principio de Separação de Responsabilidade pede que cada objeto tenha apenas uma responsabilidade. Se a responsabilidade do objeto é apenas ser um saco de propriedades, então que seja. Exigir dele mais do que isso é violar o Principio de Separação de Responsabilidade por querer colocar no objeto coisas que ele não sabe fazer ou não pode fazer.

Um  exemplo clássico do que estou falando é o objeto Usuario. Muitos gostam de colocar um mudarSenha(String senhaAntiga,  String senhaNova). Já vi isto várias vezes. Dois erros:

  1. Porque precisa da senha antiga? O usuário já tem um getSenha que é esse valor. O argumento é que o usuário real tem que saber a senha atual e portanto senhaAntiga tem que bater com o valor em getSenha(). E o que acontece quando não base certo? Lança exception? E se eu não quiser fazer isso?
    Conclusão: é uma decisão que pode ter várias estratégias conforme quem decide, quando e onde. Não ha uma resposta possivel única. Logo, isso deve ser isolado em outro objeto.
  2. Quem muda a senha do usuário? A aplicação. Certo? Não é o próprio usuário que realmente muda a senha. É sempre alguém que o faz por ele. Esse alguem não é o usuário, logo, isso não é responsabilidade da classe usuário. É responsabilidade de um outro agente do sistema, outro objeto.

Outro é o uso do pseudo-ActiveRecord. Por alguma razão que me escapa ao entendimento muitos acham que  ter um método save() no proprio objeto que está sendo guardado, é  uma excelente ideia.  Pode até ser, se for bem implementado. Isso é a essência do padrão ActiveRecord. Só que o ActiveRecord tem contra indicações. Ele não é recomendado para sistemas grandes. Certo. Agora vai dizer que o seu sistema não é grande e portanto você pode usar o ActiveRecord. O sistema pode parecer pequeno agora, mas deixe passar um tempo e verá. ActiveRecord é bom para protótipos que vão – com certeza – ser jogados no lixo. Mas aceitemos que para o seu caso ActiveRecord é uma boa ideia; ai ,muita gente faz assim :


public class ObjectQualquer {

DAO daoObjectoQqualquer;

public void save(){
daoObjectoQualquer.save(this);
}
}

Não vou nem entrar no mérito se deve usar DAO ou Repositorio (para ActiveRecord é DAO mesmo) mas esse código é o exemplo de como é fácil associar responsabilidades aos objetos que eles não têm. Como sei isso? Porque o objeto simplesmente delega a outro. Sem alterar nada, sem produzir nenhum outro efeito. Depois vêm perguntar: “Como injeto o DAO neste objeto?” a resposta é “Porque raios você tem esse objeto ai para começo de conversa?!” É muito difícil chama o DAO apenas quando ele é necessário? Que raio de preguiça é essa?

Não é perguiça. É a falsa noção de que os objetos têm que ter métodos diferentes de get/set. E ai as coisas mais absurdas são feitas para atingir esse objetivo.

Um objecto é composto por três coisas: estado, comportamento e responsabilidade. Responsabilidade é o que os outros objetos esperam que ele faça. Comportamento é como esses objetos pedem que isso seja feito e estado é a forma que o objeto tem de “memorizar” o que fez.

A responsabilidade deve ser apenas uma. Isso é obvio. Quanto mesmos responsabilidade mais simples é o objeto. Lembre-se disto. O comportamento é um contrato publico. É algo do tipo : “quando chamarem por  desta maneira farei este trabalho, desta forma.” Isto não necessariamente significa que existe um método especifico que traduz o comportamento, mas sim que ha um contrato que é acessível por meio de um método. Por exemplo, ArrayList e CopyOnWriteArrayList têm comportamentos diferentes, embora as sua interface (o conjunto de métodos) seja a mesma.

Ao definir um objeto primeiro você define a sua responsabilidade: “Este é o objeto que é responsável por … “, depois como ele vai possibilitar essa responsabildiade. Só quando você o implementa é que você precisa de pensar no estado do objeto.

Mas e os objetos cuja responsabilidade é apenas portar dados? O comportamento deles é dar acesso aos dados e o estado deles são os dados (ou representações dos dados).

Tomemos como exemplo o objeto String. Ele porta dados (caracteres) e tem estado (um array de char) que representa esses dados.  Pronto. Preciso colocar um save() nele só porque ele vai ser gravado em banco de dados? Não. A responsabilidade de String é muito simples: portar caracteres. String é talvez o objeto mais manipulado em Java. Isso, com certeza, não faz dele um objeto fantoche. Então porque o meu TipoProduto que só contém uma String com o nome do tipo e um inteiro com um código para o tipo é um fantoche ? Não é.

Todo este delirio quixotesco de ver objetos fantoche onde só existem objetos simples e incólumes advém de um má interpretação daquilo que significa “comportamento” em um objeto. A mensagem original é muito simples e se prende com outra caracteristica do design orientado a objetos : inversão de controle.  A ideia é o seguinte : não deixe para os outros o que você pode fazer. Um objeto string pode informar quantos caracteres guarda. Isso é trivial, porquê delegar isso a outro objeto? Um objeto String pode informar qual é o caracter que está na posição n da cadeia. Por que delegar isso a outro objeto?

Existem comportamentos que são naturalmente associáveis com a responsabilidade do objeto e por isso o objeto ganha essas sub-responsabilidades. Isto é feito , não porque o Principio de Separação de Responsabilidade é fraco, mas sim porque ele é forte. Se deixassemos outro objeto nos informar de, por exemplo, o tamanho da String, teriamos um objeto com um metodo  objectoX.length(String s). Quando um objeto Y precisasses saber o tamnho da String ele teria que chamar X. Isso cria um acomplamento artificial entre X e Y. Então , atribuindo a responsabilidade ao String esse acoplamento não existe mais. Contudo, isto não significa que os métodos sempre devem ser transportados para o objeto que contém os dados.  O comportamento do método têm que ser natural à responsabilidade principal do objeto.

Um contra exemplo para mostrar isto é o objeto Date. Ele contém uma data. Se eu quiser saber a idade de uma pessoa eu preciso saber a data do seu nascimento. A primeira aproximação seria fazer calculaIdade(Date dataPessoa) e seguindo o exemplo atrás você poderia ser tentado a fazer dataPessoa.calculaIdade(). Ai você bateria de frente com o fato do Java não lhe permitir criar métodos novos em classes existentes e a sua conclusão seria que java é uma porcaria de linguagem e você migraria para ruby onde isso é possível.  Coitado do Java. Na realidade é você que é um péssimo designer. A idade é da Pessoa e não da data de nascimento. O método seria calculaIdade(Pessoa pessoa). E pessoa tem um atributo público que informa a data de nascimento. Claro que a idade é algo que depende de quando fazemos a pergunta. Então precisamos de mais uma informação que é a data atual: calculaIdade(Pessoa pessoa, Date atual)

Utilizando o método acima poderiamos pensar em fazer Pessoa.calculaIdade(Date atual). Seria isso certo? Pessoas podem calcular as suas idades?  Podem.

Animais não sabem calcular as suas idades. Significa isso que Cao.calculaIdade(Date atual) é errado? Não.
Por muito que você goste de abstrair o mundo em objetos e o princípio de separação de reponsabilidade tenha que ser cumprido, ele se aplica a Objectos e não a coisas do mundo. Então, embora um cão não saiba calcular a sua idade, o objecto Cao não É o cão. Ele é uma abstração humana daquilo que um cão É. Portanto, o objeto pode conter responsabilidades que um humano entenderia para o objeto e não as responsabilidades que o ente real, realmente tem. Ou seja, em termos simples: um objeto pode ter mais responsabilidades/comportamentos  que a sua contraparte real.

A mensagem é que diminua ao máximo o acoplamento atribuindo comportamentos naturais aos objetos que já tem no seu modelo. Sim, poderiamos pensar que um comportamento como “capitalize” (coloca todos os caracteres do principio das palavras em maiuscula) é um comportamento natural de um String. E ai você irá xingar o Java de novo por não lhe permitir adicionar esse comportamento. Mas será mesmo que é isso?

Não será que na realidade isso seria o comportamento esperado para um Texto? Ou mais especificamente ainda, para um Titulo? Qual é a diferença entre formatar um data para um String com certo padrão e formater um String para outra String com um certo padrão. Não será que na realidade Capitalize é a responsabilidade de um objeto de formatação?

Não existe uma única resposta para um design. É por isso que se chama design (desenho). Cada um descreve de uma forma. E sim, existe a corrente do Realismos (os objetos são as coisas), do Impressionismo (os objetos parecem as coisas) e até do Cubismo (todos os objetos são cúbicos). Da designer tem o seu estilo, e se funciona não está muito mal. Só que um design não “corre”. Ele funciona abstratamente e não no CPU. Um design que funciona (que é funcional) é aquele com baixa manutenção. E baixa manutenção envolve várias coisas além de gosto artistico por um estilo. Envolve decidir e fazer trade-off de muitas coisas simultaneamente. Se você não tem um estilo artistico de design, pelo menos conheça e use as tecnicas dos designers profissionais.

A proliferação de objetos do tipo bean (apenas como get/set) não é uma questão  derivada de mau design. Ela acontece porque esta é a forma padronizada que criar objectos que são sacos de propriedades (PropertyBag). Muitas outras formas existiriam: como usar Map ou criar uma classe PropertyBag. Contudo, classes genéricas como essas tirariam poder de abstração que é tão importante e pior que isso, tirariam tipagem forte. Repare que fazer bean.getNome() é muito mais forte e refactorável que propertyBag.get(“nome”). O uso de Strings é desaconselhado e por isso o uso de beans é tão comum. Contudo, sempre que o bean puder provêr comportamente extra que lhe seja natural, deve fazê-lo e não delegar isso para outro objeto (normalmente apenas com métodos estáticos). A palavra chave é puder. Às vezes ele quer, e até parece natural, mas ele não pode.

Não há problema nenhum em que um objeto só seja um bean com os seus get/set se isso é realmente o que ele deve ser. O problema real é você ter objetos (classes) apenas com métodos estáticos que fazem operações sobre objetos que um deles poderia fazer sozinho. Essa delegação excessiva é que é realmente o problema, ou colocar sempre os get/set sem pensar se é esse o comportamento esperado/necessário daquele objeto.

Não lutemos então contra fantoches  ou fantasmas anoréxicos que só existem na nossa cabeça.  Alguns objetos são realmente simples e com uma só responsabilidade e poucos comportamentos. Tão poucos que a única grande responsabilidade é guardar dados e prover acesso a essa memória.