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?

13 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!

  6. Gostei do artigo! Mas daria pra incluir um “autoload” aí, e evitar o uso de sufixo “class” nos arquivos, para manter o padrão da PSR-4.

  7. William Bruno, estou começando a estudar PHP Orientado a Objetos, ainda não sei bem onde usar. Estou criando um blog do zero, bem simples, sem uso de CMS. Faz sentido pensar em criar uma classe orientada aos posts do blog?
    Tendo, por exemplo, o título do post, a data de publicação e o conteúdo do post como propriedades da classe que seriam obtidos de um DB MySQL, e uma função ExibirPost() como método da classe? Isso faz sentido? É uma boa prática de programação?
    Obrigado! 🙂

Deixe uma resposta

Your email address will not be published.

*