Back-End

13 mar, 2015

Traits combina com sexta-feira 13

Publicidade

Vamos lembrar dos princípios S.O.L.I.D. de desenvolvimento? Single responsibility; open closed; liskov (o incompreendido) substitution; interface segregation e dependency invertion.

O texto de busão de hoje vai falar de interface segregation. Se você lembra da série de Hangouts, onde discutimos os princípios, vai lembrar que interface segregation é a divisão das características/comportamentos suportados por uma classe em interfaces que devem ser implementadas (implements) por uma class interessada em ser caracterizada como uma classe que suporta aquela característica.

Esse princípio garante um contrato melhor para as classes, afinal, um contrato deve ter somente o imprescindível para atingir seu objetivo. Isso, na pratica, é fácil de ver desenvolvedores esquecendo.

Se começarmos a procurar pelos códigos da github sphere, vamos encontrar interfaces que representam muitos comportamentos. E vamos encontrar classes que implementam essas interfaces e resolvem o problema do suporte aos métodos do contrato que não lhe interessam, fazendo implementações vazias. Um crime, um history-crime, um crime a ser julgado pelo seldon. Um caso para o pré-crime, a ser tratado pela divisão de crimes futuros, ou ainda, um crime-pensamento!

“Se você implementar uma interface e notar esse comportamento de que parte do contrato não te interessa, segregue!”

Segregue fazendo isso de uma maneira incremental. Vou correr o risco e sugerir um procedimento real e preguiçoso para realizar essa tarefa:

  • Descubra a separação das intenções da interface;
  • Crie uma segunda classe com um nome que sugira “a capacidade de”. “Able To” dá suporte à característica que você resolveu separar;
  • Copie as declarações do comportamento que você deseja extrair e cole no arquivo da nova interface;
  • Apague o comportamento extraído da interface original;
  • Renomeie a interface original para um nome que se adeque ao novo objetivo;
  • Procure em todo o projeto por “implements [nome interface original]” e substitua por “implements [novo nome],[nova interface]”;
  • Rode os testes;
  • Pegue uma cerveja, olhe no espelho e repita 10 vezes o exercício “sensual e erótico” do Homer Simpson:

Certo, após tudo isso haverá menos filhotes focas mortos nos pólos do mundo e poderemos continuar com nossas vidas sem dar show/piti gritando!

“Agora, se você tem uma interface, é porque notou que certo comportamento seria compartilhado por uma classe de implementações, afinal, qual outra prerrogativa poderia existir? (pergunta retórica)”

Digamos que você notou que esse comportamento deveria chamar smokeable, dando suporte as seguintes ações:

interface smokeable{
   public function acenda();
   public function puxa();
   public function prende();
   public function passa();
   public function apaga();
}

E você vai adicionar o comportamento nas classes.

Você implementa o contrato smokeable em class Person, programa os métodos, depois faz o mesmo com class Gods e nota que vai ter de escrever os mesmos métodos. Como é a segunda vez você aceita o pênalti, eis que surge a class Batman e você vai fazer class Batman implements smokeable, mas percebe que não está disposto a copiar aquele código da implementação de suporte a smokeable novamente. O que fazer? Alguém lá do fundão da classe grita: abstract class smoker implements smokeable!!!

Não! Batman, Gods e Person não extends smoker. Nem que fossem fumantes inveterados. Essa não é a característica sobre a qual eles surgiram.

Não dá para declarar: “Isso” fuma, logo, ou ele é person, ou Gods ou o Batman! Extends significa isso. E se você extends a coisa errada, você está chamando urubu de meu louro. Não faça isso!

Mas os deuses do PHP, (aka Stefan Marr, Sara Golemon, Felipe Pena, Derick Rethans e outros responsáveis pelo PHP – alguns deles ligados diretamente a Traits) foram bons (ou maus) e roubaram o raio de Zeus, que o programador pode usar como isqueiro ou como fio de alta tensão caindo na banheira durante o banho com sais durante uma brincadeira de crossdressing (já pensou?).

“Traits senhores, traits. O raio de Zeus, aquele piadista, sentado no seu trono vestido com a toalha da mesa de jantar.”

Traits são perfeitas para suportar interface segregation. Vejamos:

Você não consegue dizer traits implements [interface], aliás vc não consegue dizer traits 11 vezes seguidas, bem rápido.

Você não consegue fazer de uma trait implements uma interface, mas ela suporta propriedades, métodos e escopos, então é perfeitamente possível e desejável que:

Trait smoker{

private whathastosmoke;

public function acende()
{
}

public function puxa()
{
}

public function prende()
{
}

public function passa()
{
}

public function apaga()
{
}

Class Batman <strong>implements smokeable</strong>
{
<strong>Use smoker</strong>;

}

Dessa maneira, o contrato tem uma implementação padrão do comportamento na Trait Smoker. Agora, e se uma classe for implementar o comportamento, mas o código da implementação (não a interface: input, output) for diferente, você pode implementar smokeable e desenvolver o código especial.

De uma hora para outra, podemos levar a sério a interface segregation e com isso passamos a ter um controle muito melhor dos nossos softwares, afinal, nunca devemos esquecer o mantra: programe para uma interface e não para uma implementação!

Mas lembre-se: de você usar traits de maneira correta:

Screenshot 2015-02-13 10.38.56 senão Screenshot 2015-02-13 10.39.03

E já que falamos da má aplicação de Traits, acho melhor, aconselhado pelo @alganet, expor aqui o que seria uma má prática de uso de Traits.

Pense traits como ele é: um recurso do PHP para ser utilizada em conjunto com o paradigma de Orientação a Objetos (tudo bem, podemos isso discutir depois!). Se você extrapolar esse pensamento vai resultar em:

“Pensar em Traits é também pensar em Orientação a Objeto. Então o que está errado em O.O. está errado com ou sem Traits”.

Por exemplo, você implementaria o método log(Message $message) na classe Batman? Ou melhor, você implementaria log(Message $message) em qualquer classe que não uma classe especializada em Log? A resposta correta é NÃO!

Logar não é uma característica de uma classe de negócio, mas uma necessidade que ela tem para que seja possível fazer o trace dos eventos e entender erros e processos. A prática diz que o recurso de logar é usado pelas classes e não é uma característica dela, mais ou menos como a diferença entre pensar que um ser humano precisa de ar para viver é diferente de pensar que ele faz fotossíntese para produzir ar para poder viver, entende?!

Então, a seguinte prática é errada, ruim e faz bebês focas morrerem:

Trait Log{
 public function logar(Message $message)
{
  //escrever log no arquivo....
}

Class Batman {
 use Smoker;
 use Log;
}

$fuckingFodaGuy = new batman();
$batman->log("Corre Bino, é uma cilada!");

Ufa, chega por hoje!