Exemplo prático de Orientação a Objetos (php), diferenças e vantagens em relação à um código Estruturado

#SouDev, focado em #FrontEnd “atualmente”.
Já trabalhei com backend também, desde a modelagem das Entidades, até as interfaces e controllers com ajax.

Uma das minhas maiores dificuldades enquanto estava estudando sobre Orientação a Objetos, era entender de fato o que é um objeto.
E o que a palavra orientação tem a ver com eles. Afinal, o que é Orientação a Objetos?

Não basta sair criando classes, instanciando coisas, ou colocando todo o nosso código estruturado dentro de métodos public function foo()!
Isso não é Orientação a Objetos.

Precisamos ter em mente diversos conceitos de programação, e entender que a granulidade de nossos objetos(SRP), nos guiará a um código melhor, enxuto e menos custoso.

Exemplo Prático

Precisei recentemente, ler diversos feeds de redes sociais, e mostrar os 4~5~7 últimas atividades da timeline/posts/vídeos/fotos…
Eu já tinha cada código isolado mais ou menos pronto:
-> Ler rss do WordPress com php
-> Timeline Twitter com php
..

Ainda precisava ler do Flickr, do Facebook, e do Youtube.
Simplesmente, pegar cada rotina dessa e jogar na aplicação, me levaria a ter um código macarrônico/redundante:

Ok, a classe acima “faz” oque eu precisava, mas está incorreta. De uma forma visual e bem simples, note as áreas que circulei.

Uma “god class”, precisa ser urgentemente refatorada!
E ainda faltam o Facebook, o Flickr, o Youtube…

Refatorando

As repetições e inconsistências estão concentradas no loop que itera o objeto, e nas rotinas de testar se já existe ou criar um novo arquivo de cache.

<?php

/**
 * @file View.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
class View 
{

	
	public function draw( iSource $source, $limit=5 )
	{
		$data = $source->getData();
		
		$i = 0;
		$html = '';
		foreach( $data AS $item ){
			if( $i==$limit ) break;
		
			
			if( $source->criteria( $item ) ){
				$html .= $source->html( $item );
				$i++;
			}
		}
		return $html;
	}
	
}//View

E o cache, que também não deveria ser responsabilidade de cada “método” twitter(), wordpress()..

<?php

/**
 * @file Cache.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
class Cache
{
	private $path = '../cache/';
	private $view;
	
	public function setPath( $path )
	{
		$this->path = $path;
	}
	
	public function __construct( iSource $source, View $view )
	{
		$this->source = $source;
		$this->view = $view;
	}
	
	public function isCached( $url, $file, $limit=5 )
	{
		$this->source->setURL( $url );
		$cached_file = $this->path.$file;		
		
		
		if( is_file( $cached_file ) )
			return file_get_contents( $cached_file );
		
		$contents = $this->view->draw( $this->source, $limit );
		
		file_put_contents( $cached_file, trim( $contents ) );
		return $contents;			
	}

	
}//Cache

Note que para “dar certo” e fazer sentido, eu criei um contrato: A interface iSource:

<?php

/**
 * @file iSource.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
interface iSource
{
	public function setURL( $url );
	public function html( $data );
	
	public function getData();
	public function criteria( $item );
}

E então agora, cada “objeto”, afinal Twitter é uma coisa, Facebook é uma outra, e Flickr é outra ainda completamente diferente. Logo, objetos diferentes, podem implementar esse contrato, e garantir compatibilidade com as classes Cache e View.

Timeline do Twitter

<?php

include_once 'iSource.class.php';
/**
 * @file Twitter.class.php
 * @date 2012-05-04
 * @author William Moraes
 * @usage
 	
	$twitter = new Twitter( $reader );
	$twitter->setURL( 'http://twitter.com/statuses/user_timeline/locaweb.json?count=5' );
	echo $view->draw( $twitter );
	
 */
class Twitter implements iSource
{

	private $reader;
	private $url;

	public function __construct( Reader $reader )
	{
		$this->reader = $reader;
	}

	public function setURL( $url )
	{
		$this->url = $url;
	}


	/**
	 * @function html
	 */
	public function html( $item )
	{	
		$html = "\t".'<li><img src="'.$item->user->profile_image_url.'" alt="'.$item->user->screen_name.'" title="'.$item->user->screen_name.'" /> ';
			    
		$html .= $this->makeLinks( $item->text );
		$html .= '</li>'.PHP_EOL;
		
		
		return $html;
	}
	
	
	/**
	 * @function getData
	 */		
	public function getData()
	{
		return $this->reader->getJSON( $this->url );
	}
	
	
	/**
	 * @function criteria
	 */	
	public function criteria( $item )
	{
		return true;
	}
	
	
	/**
	 * @function makeLinks
	 */		
	private function makeLinks( $text )
	{
		return preg_replace(
			Array(
				'/(http:\/\/[\w\.\/]+)/',
				'/[^\w]@([\w]+)/',
				'/(#[\w]+)/'
			),
			Array(
				'<a href="$1" title="$1" rel="external">$1</a>',
				'<a href="http://twitter.com/#!/$1" title="$1" rel="external">@$1</a>',
				'<a href="http://twitter.com/#!/search/$1" title="$1" rel="external">$1</a>'
			),
			$text
		);		
	}
	
	
}//Twitter

Note a simplicidade do método Twitter:html();
Diferente do método LeitorRss:twitter(), agora só oque “realmente” interessa está ali. Afinal, oque mudava entre: LeitorRss:twitter() e LeitorRss:wordpress(), era a forma com que eu montava cada LI, e não o loop em si.

Timeline do Facebook

É simples também agora, plugar a class Facebook para ler a timeline:

<?php

include_once 'iSource.class.php';
/**
 * @file Facebook.class.php
 * @date 2012-05-04
 * @author William Moraes
 * @usage
 
 	$facebook = new Facebook( $reader );
	$facebook->setURL( 'https://graph.facebook.com/locaweb/posts&access_token=SEU_ACCESS_TOKEN&method=get' );
	echo $view->draw( $facebook );
	
 */
class Facebook implements iSource
{

	private $reader;
	private $url;

	public function __construct( Reader $reader )
	{
		$this->reader = $reader;
	}

	public function setURL( $url )
	{
		$this->url = $url;
	}
	
	
	/**
	 * @function html
	 */
	public function html( $item )
	{
	
		$ts = strtotime( $item->created_time );
		$date = date( 'd/m/Y à\s H:m', $ts );
		$pieces = explode('_', $item->id );	
		
	
		
		return '<li>'.substr( $item->message, 0, 140 ).'.. 
			postado dia <a href="https://www.facebook.com/locaweb/posts/'.$pieces[1].'" rel="external">'.$date.'</a></li>';
	}
	
	
	/**
	 * @function getData
	 */		
	public function getData()
	{
		$data = $this->reader->getJSON( $this->url );		
		return $data->data;
	}
	
	
	/**
	 * @function criteria
	 */	
	public function criteria( $item )
	{
		return isset( $item->message );
	}

}//Facebook

Usando:

<?php
	header ('Content-type: text/html; charset=utf-8');	
	
	date_default_timezone_set('America/Sao_Paulo');
	
	include 'class/Reader.class.php';
	
	include 'class/Twitter.class.php';
	include 'class/Facebook.class.php';
	include 'class/Wordpress.class.php';
	include 'class/Statusblog.class.php';
	include 'class/Flickr.class.php';	
	include 'class/View.class.php';
	include 'class/Cache.class.php';
	

	$reader = new Reader();
	$view = new View();
	
	
	$twitter = new Twitter( $reader );
	$cache = new Cache( $twitter, $view );
	echo $cache->isCached( 'http://twitter.com/statuses/user_timeline/locaweb.json?count=10', 'twitter-timeline.txt' );
	
	
	$facebook = new Facebook( $reader );
	$cache = new Cache( $facebook, $view );
	echo $cache->isCached( 'https://graph.facebook.com/locaweb/posts&access_token=XXX&method=get', 'facebook-timeline.txt' );
	
	
	$wordpress = new WordPress( $reader );
	$cache = new Cache( $wordpress, $view );
	echo $cache->isCached( 'http://feeds.feedburner.com/bloglocaweb', 'rss_blog.txt' );
	

	/* cache status blog */
	$statusblog = new Statusblog( $reader );
	$cache = new Cache( $statusblog, $view );
	echo $cache->isCached( 'http://statusblog.locaweb.com.br/feed', 'rss_statusblog.txt', 7 );

E enfim, o último participante:

Class Reader

Eu não vi sentido em colocar o cURL, o json_decode(), o file_get_contents()… dentro de cada uma das classes que representam as Mídias Sociais.
Por esse motivo, criei um outro objeto, especializado em “ler”, seja da forma que for, e retornar dados em forma de StandardClass.

<?php

/**
 * @file Reader.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
class Reader
{


	public function getXML( $url )
	{
		$file = $this->curlFile( $url );
		return simplexml_load_string( $file, 'SimpleXMLElement', LIBXML_NOCDATA );
	}
	public function getJSON( $url )
	{
		$result = $this->curlFile( $url );
		return json_decode( $result );
	}
	
	private function getContents( $url )
	{
		return file_get_contents( $url );
	}
	
	private function curlFile($url, $timeout=0)
	{
		$ch = curl_init();
		curl_setopt( $ch, CURLOPT_URL, $url );
		//curl_setopt ($ch, CURLOPT_HEADER, 1);
		curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );
		$content = curl_exec( $ch );
		curl_close( $ch );

		return $content;
	}
}//Reader

Eu disse não à herança

Geralmente, temos uma certa tendência a querer colocar esses métodos em uma class Abstrata, e então Twitter, Facebook, WordPress, extenderem essa class.
Porém, do ponto de vista da Orientação a Objetos, isso não é bacana. Afinal, nossos objetos usam pq precisam os métodos curlFile, getJSON, mas não são isso.

O objeto Twitter não deve ter a responsabilidade de ir buscar o jSON, nem de fazer o cache, nem de interar um loop.
Apenas deve saber oque fazer com os dados já mastigados, e assim por diante.

Vantagem

Uma vantagem, é que essa class Reader pode ser utilizada em outro projeto, sozinha. Desacoplei o código dela do restante da aplicação.

Extends pode e deve ser usado, mas na hora correta

Dentro deste mesmo projeto, eu precisava retornar dados, de mais de um blog WordPress. E para cada blog, havia um html diferente. Digamos que uma “especialização” do que a class WordPress fazia. Logo, aqui sim, coube usar herança:

<?php

include_once 'iSource.class.php';
/**
 * @file Statusblog.class.php
 * @date 2012-05-04
 * @author William Moraes
 * @usage
 
 	$Statusblog = new Statusblog( $reader );
	$Statusblog->setURL( 'http://feeds.feedburner.com/BlogLocaweb14elwSalvador' );
	echo $view->draw( $Statusblog );
	
	
 */
class Statusblog extends WordPress
{
	
	/**
	 * @overwrite parent::html()
	 * @function html
	 */	
	public function html( $item )
	{	
		return '<li>
			<a href="'.$item->link.'">'.$item->title.'</a>
			<div class="category">Categoria: '.$item->category.'</div>
			</li>'.PHP_EOL;
	}
	
}//Facebook

Concluindo

Agora sim, eu tenho um projeto Orientado a Objetos. Um objeto conversando com outro, poucas dependências sólidas e bem resolvidas.
É isso. O que acham ?

criticas? sugestões? dúvidas?

8 Comments

  1. Evandro Oliveira

    maio 9, 2012 at 16:38

    Muito bom. “Dê preferência à composição no lugar de herança”.
    A questão de OO é que é fácil adicionar um certo grau de complexidade à estrutura e organização do código. O que poucos percebem é que se pensa mais e programa-se menos. Apenas um parênteses quanto ao seu padrão de nomenclatura. Antigamente eu também usava prefixos de identificação como você faz com iInterface. Creio que vc seja plenamente consciente das consequências disso. O mais interessante é que não é tão difícil se livrar desse hábito quanto parece. :)
    Abraços e “keep up the good work!”

    • Olá, por favor, gostaria de saber quais as implicações de se usar por exemplo o prefixo i para a interface ou mesmo Abstrac[Class] para a aplicação. Procurei pesquisar sobre isso mas não encontrei e fiquei com essa puga chata atrás da orelha tentando descobrir quais as reais implicações negativas disto. Por favor, se possível, pode me enviar um email é arthur_scosta@yahoo.com.br. Obrigado pela atenção e se alguem souber agradeço de antemão…

  2. Não tenho a certeza se entendi o uso da interface então vamos… a interface serve então para garantir que uma determinada classe tenha os métodos e propriedades que precisamos?

    ou seja, se eu criar uma classe e nela precisar que, SEMPRE outra classe tenha determinados métodos/propriedades eu então utilizo no paramento que só vou aceitar o paramentos do tipo da interface, como vc fez aqui __construct( Reader $reader ) então se alguma outra classe quiser acessar essa classe ele obrigatoriamente deve implementar a interface e logo possuir os métodos obrigatórios? é isso mesmo? hahaha

    o Evandro falou que é errado usar essa nomenclatura de interface, sabe o pq?

    • é mais ou menos por ai sim @Renato,
      Note que outro ponto importante é que as interfaces são contratos. Todas as classes que assinarem o mesmo contrato(implementarem a mesma interface), terão que obrigatoriamente implementar os métodos definidos naquela interface.

      Então não importa a class que vamos utilizar, se vemos que ela implementa tal interface, podemos ter certeza que aqueles métodos declarados na interface existem, e podem ser usados.
      As interfaces são uma forma de garantir “consistência” no nosso código.

      Cara, qnto a nomenclatura, é um mero detalhe.. hehe, não se atente a isso. Não está “errado”, é apenas um excesso de precaução, e muito mais didático, por isso optei por essa forma.

  3. Tiago de Souza

    julho 1, 2012 at 14:52

    kkkk, entendi quase nada, OO não entra na cabeça… Entendi o que disse sobre contratos, twitter != wordpress, facebook, etc… Entendi como se chama uma função de uma classe, criar classe… mas o resto, eu não entendo de maneira alguma, é estranho, parece outra linguagem totalmente diferente de php procedural… Vou ter que estudar muito OO antes do PHP 6 :(
    No mais, ótimo post 😀

  4. MUITO BOM! Parabens! Me ajudou muito! Estou aprendendo OO ainda e esse artigo foi fantastico! Eu to lendo o livro”Use a Cabeca: Analise e Projeto Orientado a Objetos!” vc ja leu? Se sim, acha que cobre bem orientacao a Objetos?

  5. #Atrasado

    Artigo fantástico… tô sempre em busca dos conceitos de OO na prática, esse post é excelente!
    Espero poder aplicar toda essa teoria quando tiver minha ‘iniciação’ profissional!

Deixe uma resposta

Your email address will not be published.

*

Você pode usar estas tags e atributos de HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>