Iterator Pattern em ColdFusion

On 10 de outubro de 2014, in CFML, Design Pattern, by andersonstraube

Iterator, em programação de computadores, permite a “iteração” e um modo de acesso a elementos de um agregado de objetos, sequencialmente, sem exposição de estruturas internas.
Fonte: http://pt.wikipedia.org/wiki/Iterator

Em Orientação a Objetos frequentemente utilizamos estruturas de dados para listar algum conteúdo, normalmente há uma função no componente que retorna esses dados que podem ser de vários tipos, por exemplo: array com índice numérico, estrutura (struct) utilizando uma chave como índice, listas sequenciais, query, e por aí vai….


Por exemplo, em um carrinho de compras podemos armazenar os produtos em um array e criamos um método que retorna esse conjunto de produtos. Podemos exibir esse conteúdo do carrinho em vários lugares do sistema bem como apresentá-los de várias formas: exibir todos os itens, exibir um resumo, total dos produtos, dividir por categorias, etc..

Se por algum motivo precisamos alterar a forma como armazenamos esses dados de um array para uma estrutura ou gravá-los no banco de dados, temos então no retorno uma query, dessa forma precisaríamos atualizar em todos os lugares onde exibe essa listagem do carrinho de compras – alterar o loop de array para a nova estrutura de dados. Sempre que você tem uma função que retorna um objeto contendo uma estrutura de dados interno você está revelando detalhes sobre como aquele objeto foi implementado internamente.

O objetivo principal de uma boa arquitetura é minimizar a exposição dos objetos, ou seja, é ocultar informações de como aquele objeto foi implementado internamente, nós devemos abstrair isso.
O padrão Iterator fornece uma técnica generalizada onde nos permite acessar sequencialmente os elementos de um grupo de dados sem expor a estrutura interna ou seja, como esses dados são realmente armazenados internamente.

Resumidamente um Iterator é um objeto de acesso que armazena uma estrutura de dados e simplesmente controla o próximo elemento a ser exibido/disponibilizado. Há várias formas de implementar um Iterator, mas basicamente dois métodos são fundamentais nele: hasNext() e next().

O hasNext() retorna true ou false – ele indica se existe outro e/ou próximo ítem disponível naquele conjunto de dados.
O next() sempre retorna o próximo elemento do conjunto.

Após essa introdução vamos ver como usar o Iterator na prática simulando um carrinho de compras bem simples:

ArrayIterator.cfc
Vamos criar um ArrayIterator genérico que nos permite percorrer sobre os elementos de um array:


<cfcomponent output="false">

	<cfset variables.array = 0 />
	<cfset variables.indice = 0 />

	<cffunction name="init" returntype="ArrayIterator">
		<cfargument name="array" required="true">

		<cfset variables.array = arguments.array />
		<cfset variables.indice = 1 />

		<cfreturn this />
	</cffunction>

	<cffunction name="hasNext" returntype="boolean">
		<cfreturn variables.indice lte ArrayLen(variables.array) />
	</cffunction>

	<cffunction name="next" returntype="any">
		<cfset var elemento = variables.array[variables.indice] />
		<cfset variables.indice++ />

		<cfreturn elemento />
	</cffunction>

</cfcomponent>

CarrinhoCompras.cfc
Classe responsável pelo carrinho de compras:


<cfcomponent output="false">

    <cfset variables.instance = {} />
    <cfset variables.instance.produtos = [] />

    <cffunction name="init" returntype="CarrinhoCompras">
        <cfreturn this />
    </cffunction>

    <cffunction name="adicionarProduto" returntype="void" output="false">
        <cfargument name="produto" required="true">

        <cfset ArrayAppend(variables.instance.produtos, arguments.produto) />
    </cffunction>

    <cffunction name="getProdutos" returntype="ArrayIterator" output="false">
        <cfreturn CreateObject("component","ArrayIterator").init(variables.instance.produtos) />
    </cffunction>

</cfcomponent>

Por fim vamos criar o cfm responsável pelo teste:

teste.cfm


<!--- Aqui estou criando os produtos como struct, porém poderia ser uma classe/componente, query ou qualquer tipo --->
<cfset produto1 = {} />
<cfset produto1.nome = "Produto 1" />
<cfset produto1.valor = 10 />

<cfset produto2 = {} />
<cfset produto2.nome = "Produto 2" />
<cfset produto2.valor = 50 />

<cfset produto3 = {} />
<cfset produto3.nome = "Produto 3" />
<cfset produto3.valor = 650 />

<cfset carrinhoCompras = CreateObject("component","CarrinhoCompras") />
<cfset carrinhoCompras.adicionarProduto(produto1) />
<cfset carrinhoCompras.adicionarProduto(produto2) />
<cfset carrinhoCompras.adicionarProduto(produto3) />

<cfset itens = carrinhoCompras.getProdutos() />
<cfloop condition="itens.hasNext()">
	<cfset item = itens.next() />
	<cfdump var="#item#">
</cfloop>

A saída será:

dump_iterator1

Note que ao usar o padrão Iterator não temos conhecimento de como os dados foram originalmente armazenados – pode ter sido um array, uma struct, uma lista, query, etc., esta informação está escondida. Usando o Iterator não estamos preocupados com isso, qual a forma que foi armazenada, só precisamos de uma técnica para acessar todos os elementos, no caso fazendo o loop com a condição do itens.hasNext().

Da mesma forma eu poderia adicionar o produto como string e não mais como uma struct. Observe que o loop permanece igual, não precisamos alterar nenhuma lógica dentro da classe CarrinhoCompras:

teste.cfm


<cfset produto1 = "Produto 1" />
<cfset produto2 = "Produto 2" />
<cfset produto3 = "Produto 3" />

<cfset carrinhoCompras = CreateObject("component","CarrinhoCompras") />
<cfset carrinhoCompras.adicionarProduto(produto1) />
<cfset carrinhoCompras.adicionarProduto(produto2) />
<cfset carrinhoCompras.adicionarProduto(produto3) />

<cfset itens = carrinhoCompras.getProdutos() />
<cfloop condition="itens.hasNext()">
	<cfset item = itens.next() />
	<cfdump var="#item#">
</cfloop>

A saída será:

dump_iterator2

[UPDATE]
Dica do Paulo Teixeira:
O Railo já tem Iterator nativo para array:


<cfset produto = [] />
<cfset produto[1] = "Produto 1" />
<cfset produto[2] = "Produto 2" />
<cfset produto[3] = "Produto 3" />
<cfset produto[4] = "Produto 4" />
<cfset produto[5] = "Produto 5" />

<cfset itens = produto.iterator() />
<cfloop condition="itens.hasNext()">
	<cfset item = itens.next() />
	<cfdump var="#item#">
</cfloop>

Veja mais sobre outros Patterns:

Singleton em ColdFusion
Observer Pattern em ColdFusion
Strategy Pattern em ColdFusion

Até a próxima, abraço.

Tagged with:  

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *