Factory / Código sem utilidade
Autor: LG | Publicado: 2013-12-04T14:25:00-02:00

Recentemente, em um treinamento sobre design patterns, após apresentar o&nbsp; factory method,  pedi ao pessoal que fizesse um programa que, usando uma hierarquia de classes pré-existente que definia uma abstração para envio de arquivos, perguntasse ao usuário qual tipo de envio ele queria usar (entre FTP ou SMB) e enviasse um arquivo fictício usando o objeto de envio correto.

Para clarear a coisas, a abstração de envio e a hierarquia de classes correspondente era algo do tipo:


class FileSender

{

public:

&nbsp; virtual sendFile(string filename, string destination) = 0;

};

class FTPFileSender: public FileSender

{

public:

&nbsp; sendFile(string filename, string destination) {...}

};



class SMBFileSender: public FileSender

{

public:

&nbsp; sendFile(string filename, string destination) {...}

};


Claro, a ideia era que usassem um factory method para a criação dos objetos. E todos acertaram esta parte. Todos implementaram um método FileSender::create, estático que fazia algum return new, criando objetos das classes filhas FTPFileSender ou SMBFileSender e retornando um FileSender* apontando para o objeto criado.

Até aqui 100%. O problema é que a maioria fez o seguinte:


typedef enum {FTP, SMB} FileSenderType;



FileSender::create(FileSenderType type)

{

&nbsp;&nbsp; switch(type)

&nbsp; {

&nbsp;&nbsp;&nbsp; case FTP:&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new FTPFileSender;

&nbsp; &nbsp; case SMB:&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new SMBFileSender;

&nbsp; &nbsp; default:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Log error and abort.

&nbsp; }

}



void main()

{

&nbsp; cout &lt;&lt; "SMB ou FTP?" &lt;&lt; endl;

&nbsp; FileSenderType myType; 

&nbsp; // Alguma lógica para ler resposta do usuário e guardar em myType...



&nbsp; FileSender* fileSender = FileSender::create(myType);

&nbsp; fileSender-&gt;send(.....);

}


Você faria exatamente do mesmo jeito?

Ah, então me acompanhe...

Me descreva o que a "factory" está fazendo e o que ela está poupando de trabalho para seu usuário (a main, no caso). Com um pouco de cuidado você vai concluir que ela&nbsp; não poupa trabalho nenhum da main. Na verdade, a factory não está fazendo NADA. Ela é código inútil, pois não engloba nenhuma abstração, não poupa nenhum trabalho da camada acima. Pense o que aconteceria se você colocasse os news direto na main; haveria alguma desvantagem? Obviamente não. Por isso a "factory" acima implementada não serve para nada.

Não está convencido? Então imagine que agora queremos adicionar a possibilidade de envio de arquivos por SSH. Isto será feito criando uma nova classe SSHFileSender. Mas, com esta adição, vamos ter de mexer na main (código usuário), certo? Pois é. Agora imagine um caso mais real... Em vez de um ponto só de uso, um programa real pode ter muitos pontos de uso espalhados. E, para adicionar uma funcionalidade específica de FileSenders, vamos ter de sair procurando pelo código inteiro... Ruim, não? Pois é. E pensar que o pattern factory foi criado justamente para evitar isto...

Dá para melhorar? Dá sim. Vamos atentar para o fato de que a "factory" implementada não faz nada e é código inútil. Vamos tentar dar alguma utilidade para esta nossa FileSender::create(). Que tal se ela se responsabilizasse por perguntar ao usuário qual FileSender usar? Ou seja, colocar dentro dela o código que o pessoal colocou na main:


FileSender::create()

{

&nbsp; typedef enum {FTP, SMB} FileSenderType;


&nbsp; cout &lt;&lt; "SMB ou FTP?" &lt;&lt; endl;

&nbsp; FileSenderType chosenType; 

&nbsp; // Alguma lógica para ler resposta do usuário e guardar em chosenType...

&nbsp; switch(chosenType)

&nbsp; {

&nbsp;&nbsp;&nbsp; case FTP:&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new FTPFileSender;

&nbsp; &nbsp; case SMB:&nbsp;

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return new SMBFileSender;

&nbsp; &nbsp; default:

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Log error and abort.

&nbsp; }

}



void main()

{

&nbsp; FileSender* fileSender = FileSender::create();

&nbsp; fileSender-&gt;send(.....);

}

Você consegue ver que implementando desta forma a factory se tornou realmente útil? Você consegue ver que, agora, para adicionar um novo tipo de FileSender, eu só preciso mexer em entidades que estão dentro da hierarquia de classes FileSender? Você consegue perceber a vantagem disto?


Bom, por hoje era isto.