2007-07-03 04:58:16
tags: 

O Zen e a arte cavalheiresca da programação orientada a objeto (Parte 17)

Para ver os artigos anteriores desta série, clique aqui.

Olá pessoal. Obrigado por todos os comentários e emails! Estou tendo um prazer enorme escrevendo essa série, e fico ainda mais feliz quando vejo que realmente tem gente curtindo.

Mas vamos ao que realmente interessa. No nosso último encontro expliquei que existem diferentes formas, ou patterns, para lidar com buscas e coleções de objetos, mas que eu ainda não estava satisfeito com aquilo que conheço. Como já disse em outras oportunidades, aprendi o pouco que sei de PHP nos becos escuros da internet, entre latões de lixo e scripts procedurais escritos por sabe-se lá quem. Não tenho, portanto, nenhuma pretensão de conhecer todos os sutras da programação orientada a objetos. Mas, outro dia, eu tive uma idéia que achei interessante, nada original, mas um pouco diferente das duas implementações que mostrei pra vocês.

Um dos design patterns mais interessantes (e, para mim, mais difíceis de entender) que conheço chama-se Iterator. Devo ter lido as definições e exemplos umas 1000 vezes sem entender direito como usar essa coisa. Pra quem vem de um passado procedural, especialmente usando uma linguagem como o PHP que lida muito bem com arrays, é muito difícil entender o benefício de usar os tais iterators.

Os iterators, juntamente com um outro pattern chamado Collection, vão formar a base do nosso novo desenho. O que estou pensando é relativamente simples: assim como temos a classe base Model, para operações com modelos, quero criar uma classe ModelCollection, que vai cuidar das operações com coleções de modelos. Dessa forma, temos um paralelo interessante: assim como podemos carregar as informações do modelo fazendo $model->load e assim por diante, poderemos carregar as informações de coleções fazendo $modelcollection->load.

O que eu gosto nesse desenho é que tudo fica claramente definido: um $model é sempre um objeto, enquanto que os $modelcollections são sempre coleções de objetos. Uma função de um $model jamais vai retornar uma coleção de objetos; uma $modelcollection jamais vai retornar um objeto (caso uma busca retorne apenas um resultado, ainda assim será uma coleção com apenas um objeto).

Parêntesis: uma das coisas que me deixa maluco no PHP é o fato de você poder misturar absolutamente tudo - uma mesma função pode retornar uma string, um array, um objeto... É o que os estudiosos chamam de loosely typed language, uma linguagem que não exige a definição dos dados usados pelas variáveis. Claro que essa característica pode ser usada para o bem: muitas implementações interessantes usam essa característica para flexibilizar e agilizar o desenvolvimento. Mas com o tempo, depois de passar anos da minha vida tentando decifrar códigos de scripts PHP por aí, eu afirmo categoricamente: tentar entender um script ou classe de alguma outra pessoa escrita em uma linguagem loosely typed é muito mais difícil.

Mas por que esse parêntesis tão grande? Porque no desenho que estou propondo vamos sempre lidar com objetos; Porque as Collections nos oferecem possibilidades muito, mas muito interessantes mesmo pra trabalhar com objetos e coleções de classes diferentes de forma transparente.

Não entendeu? Bom, vamos aos nossos exemplos então. Imagine que temos dois modelos completamente diferentes. Tipo "Post" e "Carro". Logicamente, dentro do nosso desenho, vamos ter os modelos $post e $carro, além das collections $postcollection e $carrocollection. Onde queremos chegar? Bem, como tanto a classe PostCollection quanto a classe CarroCollection são "filhas" da classe ModelCollection temos a certeza que estas subclasses implementam as mesmas funções. E por que isso é lindo? Porque eu posso agora implementar, por exemplo, a classe HtmlHelper, cuja função buildTable aceita qualquer ModelCollection e imprime uma tabela html. Pra usar, basta fazer $htmlhelper>buildTable($postcollection) ou $htmlhelper->buildTable($carrocollection).

Sacaram a beleza? Esse é só um exemplo do que podemos fazer com as collections e os iterators, mas o importante é ver que esse design faz com que qualquer coleção se comporte de forma igual, pra que a gente possa implementar coisas genéricas que funcionam pra qualquer uma delas. A coisa que imediatamente vêm à mente é que essa classe ModelCollection vai ter um método genérico pra fazer paginação, um pra fazer ordenação dos dados, e assim por diante.

Quando nossas classes estiverem prontas, olha só o que vamos precisar fazer pra imprimir uma tabela html:

(controller)

/* A função ModelController::loadPage() carrega a página indicada no $_GET['page'] e no $_GET['pagesize']

já na ordem indicada por $_GET['orderby'] e $_GET['direction'] */

$this->postcollection->loadPage();

(view)

$htmlhelper->buildTable($postcollection); // imprime a tabela

$htmlhelper->createPreviousPageLink($postcollection); //cria o link para a próxima página

$htmlhelper->createNextPageLink($postcollection); //cria o link para a próxima página

E acabou. Não é legal isso?

Até a próxima!

16 Comentários:

[toninho]  ôpa. só agradecendo a iniciativa. tá me salvando e incentivando  [REPLY]

[danilo]  Valeu Toninho!  [REPLY]

[Thiago]  Isso ai Danilão ! Só não deixe agente esfriar !!! Quando vamos começar a codar ? Grande abraços !!!  [REPLY]

[danilo]  Oi Thiago! Olha, segura a onda, ainda demora um pouco, temos que definir muita coisa ainda! Mas esse é o caminho: pensar bastante antes de começar a codar, pra ter menos dores de cabeça depois! hehehe Grande Abraço!  [REPLY]

[Fabiano M]  É... bye bye php de rua. Muito bom, muito bom mesmo!!!   [REPLY]

[danilo]  Valeu Fabiano! Grande abraço.  [REPLY]

[Murilo Adriano]  Olá Danilo, primeiramente quero te parabenizar pelos artigos série que realmente estão ficando bons. E te dizer que estou fazendo um sistema para um site de anúncios de empresas com PHPOOP5 (saindo da marginalidade do "php de rua") e vi no parte 16 que não era só eu quem tinha a seguinte dúvida: de quem é a responsabilidade de buscar? (o $tenista->findAll() lembra?). No meu caso a classe é uma empresa e eu simplesmente não concordava na idéia de uma empresa buscar outras empresas(e foi procurando uma solução cabível que achei seu blog, aproveitei e li a série toda). A solução que...  [REPLY]

[danilo]  Oi Murilo! Pois é exatamente isso: nesse desenho você teria uma EmpresaCollection que colecionaria EmpresaModelos. Eu só não modelaria usando EmpresaBusca: colocaria uma função "load" na colecao, assim como já temos no Modelo. Você faria, então, EmpresaCollection->load() pra buscar no banco, assim como vc já faz EmpresaModelo->load(). Entendeu? Grande Abraço!  [REPLY]

[danilo]  É um problema de semantica chato esse! É por isso que eu prefiro usar "load", ou "carregar", pois estamos "preenchendo" o modelo com dados... então, talvez ficasse melhor usar EmpresaCollection->loadByCondition("NumeroEmpregados >100") e EmpresaModelo->loadById(35). Aqui estamos dizendo "Carregue uma coleção com todas as empresas cujo numero de empregados seja maior que 100". Deu pra entender? Abraço grande.   [REPLY]

[Murilo Adriano]  Olá novamente Danilo. Obrigado pela resposta tão rápida! Olhe só, ontem eu tinha comentado apenas imaginando como seria a implementação. Após comentar eu fui modelar o UML para ter uma idéia de como as classes irão ficar dispostas. Resultado: bani o EmpresaBusca e implementei as buscas no EmpresaModelo mesmo. Construí também uma classe chamada EmpresaLista (que é mais ou menos uma collection de empresas com um modelo de iterator bastante simples). Olhe só minha idéia: $modelo = new EmpresaModelo; $lista = $modelo->buscarPorCategoria(5); //Retorna um objeto do tipo EmpresaLista com...  [REPLY]

[danilo]  Oi Murilo, então esse é exatamente o padrão ActiveRecord... Minha questão com ele é que o modelo pode retornar uma lista de objetos ou um objeto... Mas é uma ótima implementação, se faz sentido pra você vá em frente! Vou fazer um novo post sobre isso pra tentar levar essa discussão pra frente. Abraço grande e obrigado!  [REPLY]

[danilo]  Acho que a diferença que estou propondo é que as funções nunca retornam objetos, só TRUE or FALSE. A função $model->loadById(5) retornaria TRUE se os dados do registro com id 5 fossem carregados corretamente no objeto. Assim sempre estamos operando no próprio objeto, e não passando objetos por aí... Sacou?  [REPLY]

[Murilo Adriano]  Então, será sempre uma lista. Mesmo se for com 1 ou zero elementos será uma lista. Assinei seu feed e vou aguardar ansiosamente os próximos posts. Valeu.  [REPLY]

[danilo]  Entendo, Murilo. Este realmente é o padrão do Active Record, talvez com a diferença de retornar, no caso do Cake, um array, e não um objeto da classe lista. O que não gosto é justamente o fato de vc pedir para o modelo uma lista. Vou escrever mais sobre isso. Grande Abraço e boa sorte!  [REPLY]

[Junio]  Legal?-Excelente a idéia, principalmente sua forma didática de transmitir conhecimento.Paz e saúde para ti!  [REPLY]

[Friv Games]  thanks all for sharing  [LINK]  [REPLY]