Engenharia de Software: Código Limpo na Prática

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação. Você receberá um email de confirmação. Somente depois de confirmar é que poderei lhe enviar o conteúdo exclusivo por email.

Email inválido.
Blog /Desenvolvimento Web /Engenharia de Software: Código Limpo na Prática

Engenharia de Software: Código Limpo na Prática

Vinícius Thiengo24/01/2016, Domingo, às 22h
(1127) (3) (45)
Go-ahead
"O pensamento brilhante é raro, mas a coragem é ainda mais rara do que o gênio."
Peter Thiel
Código limpo
Capa do livro Refatorando Para Programas Limpos
TítuloRefatorando Para Programas Limpos
CategoriaEngenharia de Software
AutorVinícius Thiengo
Edição
Ano2017
Capítulos46
Páginas598
Comprar Livro
Conteúdo Exclusivo
Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Opa, blz?

Nesse post aplico alguns dos ensinamentos da escola de engenharia de software, mais precisamente, do livro Código Limpo de Robert C. Martin. Martin é um dos precursores do Agile Software e junto a outros nomes da computação como Kent Beck e Martin Fowler, criou muitas técnicas e padrões eficientes de desenvolvimento de software.

Antes de começar lhe adianto que o que parece ser muito trabalhoso, na verdade vai facilitar em muito nossa vida no decorrer do desenvolvimento completo de um projeto. Classes e métodos menores, focados na resolução de poucas tarefas propiciando uma fácil reutilização de código e não repetição de scripts de lógica.

No decorrer do post vamos destrinchar uma classe. A principio é uma classe responsável por nos dar controle sobre o conteúdo de uma tag Iframe, muito utilizada para embed codes de vídeos. Em nosso projeto essa classe terá de trabalhar com embed codes do YouTube e do Vimeo. Nosso foco mesmo será na captura da url de um Thumb (mini imagem) por meio da tag Iframe fornecida.

Note que a linguagem utilizada é o PHP, mas não vai interferir no entendimento se seu backend for outra linguagem, a implementação ficou simples.

Nossa tarefa será melhorar todo o código a ponto de deixá-lo com que a expansão para a busca de Thumbs de outras plataformas de vídeo (DailyMotion, por exemplo) seja simples e em entidades especificas, sem misturar tudo em um grande código.

Vamos começar pela apresentação do código atual de nossa classe VideoIframe, vamos em partes para ficar fácil o entendimento.

Primeiro temos a declaração da classe com suas respectivas variáveis e constantes:

class VideoIframe
{
const THUMB_SMALL = 1;
const THUMB_MEDIUM = 2;
const THUMB_LARGE = 3;

const THUMB_YOUTUBE_SMALL = 'default.jpg';
const THUMB_YOUTUBE_MEDIUM = 'mqdefault.jpg';
const THUMB_YOUTUBE_LARGE = 'maxresdefault.jpg';

const THUMB_VIMEO_SMALL = "thumbnail_small";
const THUMB_VIMEO_MEDIUM = "thumbnail_medium";
const THUMB_VIMEO_LARGE = "thumbnail_large";

public $url;
public $idVideo;
public $tag;
public $size;

...
}

 

Logo depois temos o método setSize() que permite que o desenvolvedor entre com uma constante da própria classe VideoIframe para então configurar o tamanho na variavel de instancia “$size" do thumb que será utilizado.

...
public function setSize( $contantSize ){
$this->url = $this->getUrlFromTag();

switch( $contantSize ){
case self::THUMB_SMALL:
if( substr_count($this->url, 'youtube.com') > 0 ){
$this->size = self::THUMB_YOUTUBE_SMALL;
}
else{
$this->size = self::THUMB_VIMEO_SMALL;
}
break;
case self::THUMB_MEDIUM:
if( substr_count($this->url, 'youtube.com') > 0 ){
$this->size = self::THUMB_YOUTUBE_MEDIUM;
}
else{
$this->size = self::THUMB_VIMEO_MEDIUM;
}
break;
default:
if( substr_count($this->url, 'youtube.com') > 0 ){
$this->size = self::THUMB_YOUTUBE_LARGE;
}
else{
$this->size = self::THUMB_VIMEO_LARGE;
}
break;
}
}
...

 

Então seguimos para o método getThumb() que é o responsável por fazer a maior parte do trabalho pesado na classe:

...
public function getThumb(){
$this->url = $this->getUrlFromTag();
$this->idVideo = $this->getIdFromUrl();
$thumb = null;

if( substr_count($this->url, 'youtube.com') > 0 ){
$thumb = 'http://img.youtube.com/vi/'.$this->idVideo.'/'.$this->size;
}
else if( substr_count($this->url, 'vimeo.com') > 0 ){
$jsonData = $this->downloadVimeoJsonContent();
$thumb = $jsonData[0]->{$this->size};
}
return( $thumb );
}
...

 

O método getThumb() é um pouco mais complicado justamente porque ele faz mais de uma tarefa e para duas possíveis entradas, um Iframe do YouTube ou um Iframe do Vimeo.

Então temos o método getUrlFromTag(), esse tem a tarefa de utilizar a tag Iframe que está armazenada na variável de instancia “$tag” para obter a url que se encontra no atributo src (source). Mais precisamente, é utilizada a entidade nativa do PHP, DOMDocument, para percorrermos a tag Iframe sem a necessidade de trabalharmos com expressões regulares.

...
private function getUrlFromTag(){

$doc = new DOMDocument();
$doc->loadHTML( $this->tag, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );

$tags = $doc->getElementsByTagName('iframe');
foreach( $tags as $tag ){
return( $tag->getAttribute('src') );
}
return(null);
}
...

 

Logo depois temos o método getIdFromUrl(). Nesse método utilizamos a url para extrair o id do vídeo na plataforma utilizada:

...
private function getIdFromUrl(){
$id = null;

if( substr_count($this->url, 'youtube.com') > 0 ){

$urlParts = explode('/', $this->url);

/* A ÚLTIMA POSIÇÃO DO URL DO IFRAME DO YOUTUBE É ONDE SE ENCONTRA O EXTERNAL ID DO VÍDEO */
$id = $urlParts[ count($urlParts) - 1 ];
}
else if( substr_count($this->url, 'vimeo.com') > 0 ){

$urlParts = explode('/', $this->url);
$urlParts = $urlParts[ count($urlParts) - 1 ];
$urlParts = explode('?', $urlParts);

/* DEPOIS DO TRABALHO NO CÓDIGO, A PRIMEIRA POSIÇÃO É O EXTERNAL ID DO VÍDEO NO VIMEO */
$id = $urlParts[0];
}
return( $id );
}
...

 

Novamente a complexidade aplicada via condicionais. O problema surge quando vamos da suporte para outras plataformas e o número de if...else não para de crescer.

Para finalizar o conteúdo da classe VideoIframe temos o método downloadVimeoJsonContent() que é útil somente no caso do thumb vindo do Vimeo, pois esse tem uma url especifica que está disponível somente em um arquivo json que temos de realizar o download:

...
private function downloadVimeoJsonContent(){
$handler = fopen('http://vimeo.com/api/v2/video/'.$this->idVideo.'.json', 'f');

$lineContent = '';
while( $line = fgets($handler) ){
$lineContent .= $line;
}
fclose($handler);

return( json_decode($lineContent) );
}
...

 

Abaixo o conteúdo completo da classe VideoIframe:

class VideoIframe
{
const THUMB_SMALL = 1;
const THUMB_MEDIUM = 2;
const THUMB_LARGE = 3;

const THUMB_YOUTUBE_SMALL = 'default.jpg';
const THUMB_YOUTUBE_MEDIUM = 'mqdefault.jpg';
const THUMB_YOUTUBE_LARGE = 'maxresdefault.jpg';
const THUMB_VIMEO_SMALL = "thumbnail_small";
const THUMB_VIMEO_MEDIUM = "thumbnail_medium";
const THUMB_VIMEO_LARGE = "thumbnail_large";

public $url;
public $idVideo;
public $tag;
public $size;


public function setSize( $contantSize ){
$this->url = $this->getUrlFromTag();

switch( $contantSize ){
case self::THUMB_SMALL:
if( substr_count($this->url, 'youtube.com') > 0 ){
$this->size = self::THUMB_YOUTUBE_SMALL;
}
else{
$this->size = self::THUMB_VIMEO_SMALL;
}
break;
case self::THUMB_MEDIUM:
if( substr_count($this->url, 'youtube.com') > 0 ){
$this->size = self::THUMB_YOUTUBE_MEDIUM;
}
else{
$this->size = self::THUMB_VIMEO_MEDIUM;
}
break;
default:
if( substr_count($this->url, 'youtube.com') > 0 ){
$this->size = self::THUMB_YOUTUBE_LARGE;
}
else{
$this->size = self::THUMB_VIMEO_LARGE;
}
break;
}
}


public function getThumb(){
$this->url = $this->getUrlFromTag();
$this->idVideo = $this->getIdFromUrl();
$thumb = null;

if( substr_count($this->url, 'youtube.com') > 0 ){
$thumb = 'http://img.youtube.com/vi/'.$this->idVideo.'/'.$this->size;
}
else if( substr_count($this->url, 'vimeo.com') > 0 ){
$jsonData = $this->downloadVimeoJsonContent();
$thumb = $jsonData[0]->{$this->size};
}
return( $thumb );
}


private function getUrlFromTag(){

$doc = new DOMDocument();
$doc->loadHTML( $this->tag, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );

$tags = $doc->getElementsByTagName('iframe');
foreach( $tags as $tag ){
return( $tag->getAttribute('src') );
}
return(null);
}


private function getIdFromUrl(){
$id = null;

if( substr_count($this->url, 'youtube.com') > 0 ){

$urlParts = explode('/', $this->url);

/* A ÚLTIMA POSIÇÃO DO URL DO IFRAME DO YOUTUBE É ONDE SE ENCONTRA O EXTERNAL ID DO VÍDEO */
$id = $urlParts[ count($urlParts) - 1 ];
}
else if( substr_count($this->url, 'vimeo.com') > 0 ){

$urlParts = explode('/', $this->url);
$urlParts = $urlParts[ count($urlParts) - 1 ];
$urlParts = explode('?', $urlParts);

/* DEPOIS DO TRABALHO NO CÓDIGO, A PRIMEIRA POSIÇÃO É O EXTERNAL ID DO VÍDEO NO VIMEO */
$id = $urlParts[0];
}
return( $id );
}


private function downloadVimeoJsonContent(){
$handler = fopen('http://vimeo.com/api/v2/video/'.$this->idVideo.'.json', 'f');

$lineContent = '';
while( $line = fgets($handler) ){
$lineContent .= $line;
}
fclose($handler);

return( json_decode($lineContent) );
}
}

 

Segue código de como ficaria a utilização da instancia dessa classe para a obtenção dos thumbs:

/* YOUTUBE THUMB */
$video = new VideoIframe();
$video->tag = '<iframe width="420" height="315" src="https://www.youtube.com/embed/Z980dZEzgzY" frameborder="0" allowfullscreen></iframe>';
$video->setSize( Video::THUMB_SMALL );
echo $video->getThumb();

/* VIMEO THUMB */
$video = new VideoIframe();
$video->tag = '<iframe src="https://player.vimeo.com/video/152614354?color=ffffff&title=0&byline=0&portrait=0" width="500" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>';
$video->setSize( Video::THUMB_SMALL );
echo $video->getThumb();

 

A principio tudo funcionando como desejado, obtendo a url do Thumb do iframe informado. Porém seguindo algumas linhas do Agile Software, que fornece meios para melhorarmos nosso código, temos que nossa classe realiza mais de uma tarefa e tem dentro dela entidades que podem ter suas próprias classes.

Os códigos dos métodos podem ser ainda mais refatorados. Métodos que não utilizam condicionais, ou seja, têm a mesma instrução tanto para YouTube iframe como para Vimeo, esses métodos podem ser enviados para uma classe acima em uma possível hierarquia (extends) que pode ser aplicada como estratégia de refatoração.

Adicionar suporte a outras plataformas de vídeos será tranquilo, somente mais alguns if…else em alguns métodos, porém a reutilização de código é perdida aos poucos, o código começa a se tornar confuso mesmo com comentários (deveríamos evitar comentários com código auto comentado), devido a quantidade de condicionais que somente cresce.

A divisão em vários métodos, porém ainda com a mesma classe pode ser uma boa, apenas na primeira refatoração, pois a ideia é termos classes pequenas e que fazem somente o necessário segundo o escopo dela (e não limpeza de tag para acessar url e download de conteúdo no mesmo lugar, por exemplo).

Vamos começar o processo de refatoração.

Primeiro passo, temos alguns métodos em comum para a versão de Iframe do YouTube e do Vimeo, logo podemos generalizar e criar uma classe de mais alto nível, onde as entidades em comum vão estar.

A classe será uma abstract class, VideoIframeAbstract, pois alguns métodos que não são de código comum para YouTube e Vimeo devem ser implementados por ambas as classes especificas (vamos criá-las ainda), logo uma abstract class nos permite ter implementações concretas que são as partes comuns entre YouTube e Vimeo e nos permite também termos uma espécie de contrato onde entidades não abstratas que herdam dela devem implementar os métodos abstratos. Com isso forçamos também que a possível expansão para outras plataformas de vídeo tenha as classes com as mesmas configurações (assinaturas) dos métodos obrigatórios, abstratos:

abstract class VideoIframeAbstract
{
private $tagIframe;
protected $url;
protected $externalId;
protected $thumbSize;


protected function __construct( $tagIframe=null )
{
$this->tagIframe = $tagIframe;
}

...
}

 

Note as configurações das variáveis de instancia, somente “$tagIframe" é private, isso porque ela é somente acessada na abstract class VideoIframeAbstract, logo não há motivo para liberar o acesso dela nas entidades especializadas.

Note que temos “$tagIframe” e “$externalId” que são melhores em entendimento no contexto da classe do que “$tag” e “$id”, essa última então pode ser a causa do maior problema para outros developers que forem utilizar a classe, pois da a entender que o id é interno ao sistema, quando na verdade é da plataforma de vídeo sendo utilizada.

Nessa classe vamos colocar o método generateUrlFromTagIframe() que tem um nome mais especifico que getUrlFromTag() para que não seja necessária a utilização de comentários (isso é um exemplo de código auto comentado). Note que como convenção vamos utilizar “generate" para métodos private ou protected e “get" para os públicos que permitem recuperação de conteúdo por entidades externas a hierarquia de VideoIframeAbstract:

...
private function generateUrlFromTagIframe(){
$doc = new DOMDocument();
$doc->loadHTML( $this->tagIframe , LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);

$tags = $doc->getElementsByTagName('iframe');
foreach( $tags as $tag ){
$this->url = $tag->getAttribute('src');
break;
}
}
...

 

O método acima se aplicará da mesma maneria a qualquer plataforma de vídeo que trabalhe com Iframe, logo a deixamos na classe no topo da hierarquia, reaproveitando código.

Logo depois temos o método generateUrl(), que somente colocará uma url na variável de instancia "$url" se ela estiver vazia:

...
protected function generateUrl(){
if( empty($this->url) ){
$this->generateUrlFromTagIframe();
}
}
...

 

Vamos ter também os métodos getters e setters das variáveis “$tagIframe” e “$url”. Isso é necessário para que seja possível criar instancias das classes especificas (YouTubeIframe e VimeoIframe) sem a necessidade da prévia configuração do construtor com a tag Iframe somente:

...
public function getTagIframe()
{
return($this->tagIframe);
}
public function setTagIframe( $tagIframe )
{
$this->tagIframe = $tagIframe;
}


public function getUrl()
{
return($this->url);
}
public function setUrl( $url )
{
$this->url = $url;
}
...

 

Agora vamos colocar as assinaturas dos métodos abstratos que deverão ser implementados pelas entidades especializadas não abstratas:

...
abstract public function getThumb();
abstract protected function generateExternalId();
abstract public function setThumbSize( $constantThumbSize );
...

 

Acima estão somente a assinatura dos métodos, pois como dito anteriormente, eles são comuns as classes especializadas, porém com implementações especificas.

Antes de prosseguir com as classes especializadas vamos trabalhar com a convenção de que constantes vão ficar em interfaces semelhantes em nome as classes. Logo vamos colocar as constantes de VideoIframe em VideoIframeConstantesImpl. Esse “Impl" será utilizado para qualquer interface em nosso mini sistema. Note que isso é apenas uma convenção que adotei, não é nenhum padrão. Você pode tenrar uma implementação com Enums se preferir. Segue código:

interface VideoIframeConstantsImpl
{
const THUMB_SMALL = 1;
const THUMB_MEDIUM = 2;
const THUMB_LARGE = 3;
}

 

Aproveitando, já podemos criar a interface de constantes da classe YouTubeIframe, no caso a interface YouTubeIframeConstantsImpl:

interface YouTubeIframeConstantsImpl
{
const DOMAIN = 'youtube.com';
const THUMB_SMALL = 'default.jpg';
const THUMB_MEDIUM = 'mqdefault.jpg';
const THUMB_LARGE = 'maxresdefault.jpg';
}

 

Agora podemos iniciar com as classes especializadas. Começando pela YouTubeIframe:

class YouTubeIframe extends VideoIframeAbstract implements YouTubeIframeConstantsImpl
{
public function __construct( $tagIframe=null )
{
parent::__construct( $tagIframe );
$this->generateUrl();
$this->generateExternalId();
$this->setThumbSize( VideoIframeConstantsImpl::THUMB_MEDIUM );
} ...
}

 

Note que já estamos herdando de VideoIframeAbstract, algo que já nos obriga a implementar os métodos abstratos dessa. A implementação da interface YouTubeIframeConstantsImpl é somente para explicitar o acoplamento forte que quero entre as duas entidades, classe e interface YouTubeIframe. 

Temos os métodos generateExternalId(), getThumb() e setThumbSize() que são bem simples e similares as implementações correspondentes da classe VideoIframe. Não temos mais if…else, pois estamos em uma classe especializada, essa é a primeira maneria de acabar com ifs..elses encadeados, aplicando polimorfismo. Segue código:

...
protected function generateExternalId()
{
$urlParts = explode('/', $this->url);
$this->externalId = $urlParts[ count($urlParts) - 1 ];
}


public function getThumb()
{
return( 'http://img.youtube.com/vi/'.$this->externalId.'/'.$this->thumbSize );
}


public function setThumbSize( $constantThumbSize )
{
switch( $constantThumbSize ){
case VideoIframeConstantsImpl::THUMB_SMALL:
$this->thumbSize = self::THUMB_SMALL;
break;
case VideoIframeConstantsImpl::THUMB_MEDIUM:
$this->thumbSize = self::THUMB_MEDIUM;
break;
case VideoIframeConstantsImpl::THUMB_LARGE:
$this->thumbSize = self::THUMB_LARGE;
break;
default:
throw new Exception('Constante de tamanho de thumb YouTube inválida.');
}
}
...

 

Note que os métodos que iniciam com "generate", além de serem somente internos a hierarquia eles não retornam nada, apenas configuram um valor em alguma variável de instancia. Isso é uma convenção adotada aqui, não é padrão de software.

No construtor de YouTubeIframe temos algumas chamadas a métodos que já lhe adianto que serão iguais em todas as outras entidades de iframe vídeo em nossa hierarquia, logo vamos generalizar essas chamadas colocando-as no construtor da classe abstrata VideoIframeAbstract. Primeiro deixe o construtor de YouTubeIframe como abaixo:

class YouTubeIframe extends VideoIframeAbstract implements YouTubeIframeConstantsImpl
{
public function __construct($tagIframe = null)
{
parent::__construct($tagIframe);
}
...
}

 

Logo depois altere a classe VideoIframeAbstract colocando o método init():

...
public function init(){
$this->generateUrl();
$this->generateExternalId();
$this->setThumbSize( VideoIframeConstantsImpl::THUMB_MEDIUM );
}
...

 

O método init() será público, pois apesar de termos os getters e setters de "$tagIframe" e "$url", não permitimos o acesso direto as variáveis "$externalId" e "$thumbSize", por isso o init() public, a chamada a ele iniciará essas variáveis

No construtor dessa classe coloque a seguinte verificação e chamada de método:

...
protected function __construct( $tagIframe=null ){
$this->tagIframe = $tagIframe;

if( is_string($tagIframe) ){
$this->init();
}
}
...

 

Utilize is_string() ao invés de !is_null(), o acréscimo do "!" tende a colocar o nível de entendimento necessário ainda maior, em códigos maiores isso fica mais evidente.

Com a alteração dos construtores conseguimos evitar mais repetição de código.

Abaixo o código completo da classe YouTubeIframe:

class YouTubeIframe extends VideoIframeAbstract implements YouTubeIframeConstantsImpl
{
public function __construct( $tagIframe=null )
{
parent::__construct( $tagIframe );
}


protected function generateExternalId()
{
$urlParts = explode('/', $this->url);
$this->externalId = $urlParts[ count($urlParts) - 1 ];
}


public function getThumb()
{
return( 'http://img.youtube.com/vi/'.$this->externalId.'/'.$this->thumbSize );
}


public function setThumbSize( $constantThumbSize )
{
switch( $constantThumbSize ){
case VideoIframeConstantsImpl::THUMB_SMALL:
$this->thumbSize = self::THUMB_SMALL;
break;
case VideoIframeConstantsImpl::THUMB_MEDIUM:
$this->thumbSize = self::THUMB_MEDIUM;
break;
case VideoIframeConstantsImpl::THUMB_LARGE:
$this->thumbSize = self::THUMB_LARGE;
break;
default:
throw new Exception('Constante de tamanho de thumb YuoTube inválida.');
}
}
}

 

Agora seguimos para a classe especializada VimeoIframe, porém antes vamos criar a interface VimeoIframeConstantsImpl que é responsável por manter as constantes referentes a classe VimeoIframe:

interface VimeoIframeConstantsImpl
{
const DOMAIN = 'vimeo.com';
const THUMB_SMALL = "thumbnail_small";
const THUMB_MEDIUM = "thumbnail_medium";
const THUMB_LARGE = "thumbnail_large";
}

 

A classe VimeoIframe é muito similar a classe YouTubeIframe, literalmente implementamos somente os métodos necessários de maneira um pouco diferente, logo vou colocar todo o código da classe abaixo:

class VimeoIframe extends VideoIframeAbstract implements VimeoIframeConstantsImpl
{
public function __construct( $tagIframe=null )
{
parent::__construct( $tagIframe );
}


protected function generateExternalId()
{
$urlParts = explode('/', $this->url);
$containId = $urlParts[ count($urlParts) - 1 ];
$containIdParts = explode('?', $containId);
$this->externalId = $containIdParts[0];
}


public function getThumb()
{
$jsonObj = DownloadUtil::downloadJson('http://vimeo.com/api/v2/video/'.$this->externalId.'.json');
return( $jsonObj[0]->{$this->thumbSize} );
}


public function setThumbSize( $constantThumbSize )
{
switch( $constantThumbSize ){
case VideoIframeConstantsImpl::THUMB_SMALL:
$this->thumbSize = self::THUMB_SMALL;
break;
case VideoIframeConstantsImpl::THUMB_MEDIUM:
$this->thumbSize = self::THUMB_MEDIUM;
break;
case VideoIframeConstantsImpl::THUMB_LARGE:
$this->thumbSize = self::THUMB_LARGE;
break;
default:
throw new Exception('Constante de tamanho de thumb Vimeo inválida.');
}
}
}

 

Dê uma estudada no método generateId(). Nele, mesmo que não necessário, colocamos mais de uma variável local para dar sentido aos passos seguidos no script, isso é importante para que tenhamos um código auto comentado e não seja necessário o uso de comentários para auxiliar no entendimento.

Note que no método getThumb() temos a maior alteração, pois estamos utilizando uma outra classe, DownloadUtil. Essa não é nativa no PHP, criaremos ela no pacote útil de nosso sistema. O objetivo dessa classe é fornecer um meio simples para baixar qualquer arquivo, em nosso caso será apenas um arquivo json:

class DownloadUtil
{
static public function downloadJson( $url )
{
$content = self::getDownloadContent( $url );
return( json_decode($content) );
}


static private function getDownloadContent( $url )
{
$handler = fopen($url, 'f');
$content = '';

while( $line = fgets($handler) ){
$content .= $line;
}
fclose($handler);

return( $content );
}
}

 

Note que separamos o código comum a outros downloads em um único método, getDownloadContent(). Pois dessa forma não precisaremos trabalhar com código repetido para download de PNG, TXT, …

O método downloadJson() realiza o parse necessário e retorna o conteúdo. Devido a definição dos métodos como static não precisamos quebrar a cabeça colocando linhas a mais de código nas classes que utilizam a classe DownloadUtil, isso é bom, pois a classe download não é intrinsica ao contexto de Iframes, muito provavelmente somente o Vimeo precisa de download de arquivo json para obtenção do conteúdo do vídeo (isso já pensando nas próximas plataformas de vídeo a serem suportadas).

Para finalizar nossa refatoração temos de abstrair a inicialização dos objetos de VideoIframe. Poderíamos utilizar injeção de dependencia, mas o Pattern Factory é mais que suficiente para nós nesse mini project. Segue implementação completa da classe VideoIframeFactory:

class VideoIframeFactory
{
private function __construct(){}

static public function create( $tagIframe )
{

$obj = null;
if( substr_count( $tagIframe, YouTubeIframeConstantsImpl::DOMAIN ) > 0 ){
$obj = new YouTubeIframe( $tagIframe );
}
else if( substr_count( $tagIframe, VimeoIframeConstantsImpl::DOMAIN ) > 0 ){
$obj = new VimeoIframe( $tagIframe );
}
else{
throw new Exception("Iframe tag inválida, forneça uma de YouTube ou Vimeo.");
}

return( $obj );
}
}

 

Note a utilização das interfaces YouTubeIframeConstantsImpl e VimeoIframeConstantsImpl. Provavelmente você deve estar se perguntando: “A acoplação ainda está alta nessa classe”. Na verdade no Factory não temos muito para onde correr, mesmo se as constantes fossem locais, na adição de uma nova plataforma de vídeo seria inevitável voltar a Factory para atualizar o código.

O construtor é private para evitar instanciação da classe Factory, pois ela não é abstrata.

Abaixo o trecho do código de como ficaria a chamada para obtenção do thumb do YouTube Iframe e do Vimeo Iframe:

$youTubeVideoThumb = VideoIframeFactory::create('<iframe width="420" height="315" src="https://www.youtube.com/embed/Z980dZEzgzY" frameborder="0" allowfullscreen></iframe>');
$youTubeVideoThumb->setThumbSize( VideoIframeConstantsImpl::THUMB_LARGE );
echo $youTubeVideoThumb->getThumb().'<br>';
$vimeoVideoThumb = VideoIframeFactory::create('<iframe src="https://player.vimeo.com/video/152614354?color=ffffff&title=0&byline=0&portrait=0" width="500" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>');
$vimeoVideoThumb->setThumbSize( VideoIframeConstantsImpl::THUMB_LARGE );
echo $vimeoVideoThumb->getThumb().'<br>';

Bem mais simples e abstraido, não precisamos quebrar a cabeça em qual VideoIframe será criado, simplesmente fornecemos o embed code e continuamos com o código. Para adicionarmos um nova plataforma é apenas criar a classe da nova plataforma com o sufixo Iframe (por convenção), criar a interface de constantes com o sufixo IframeConstantsImpl e alterar o factory para retorna essa nova entidade também.

Note que a dição de uma nova plataforma apenas fez com que nós ampliássemos o código, tirando a Factory, não precisamos alterar nenhuma outra classe já criada nesse mini project.

Note que as classes ficaram bem menores, com número de tarefas também reduzido. Os método somente com uma tarefa direta, apenas o construtor do VideoIframeAbstract é um pouco mais atarefado.

Bom, essa refatoração final ainda não é a mais precisa, dá para diminuir o código ainda mais. Você provavelmente percebeu que o código do método setThumbSize() está sendo repetido, algo que provavelmente será um problema se quisermos colocar mais algum tamanho de thumb, logo deixo contigo essa tarefa.

No GitHub (https://github.com/viniciusthiengo/CleanCodeExample) você tem acesso ao código completo.

Vlw.

Receba em primeira mão o conteúdo exclusivo do Blog, além de promoções de livros e cursos de programação.
Email inválido

Relacionado

Persistência de Dados Com Realm no Android - Parte 1Persistência de Dados Com Realm no Android - Parte 1Android
PHP - Programando com Orientação a ObjetosPHP - Programando com Orientação a ObjetosLivros
KISS - Mantenha Isso Estupidamente SimplesKISS - Mantenha Isso Estupidamente SimplesAndroid
Iniciando com Mapbox Android SDK - Parte 1Iniciando com Mapbox Android SDK - Parte 1Android

Compartilhar

Comentários Facebook (1)

Comentários Blog (2)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
12/04/2016, Terça-feira, às 17h
Muito Bom!
Responder
plinhares9 (1) (0)
25/01/2016, Segunda-feira, às 23h
Legal Thiengo. post útil
Responder