Strategy Pattern em ColdFusion

On 17 de abril de 2012, in CFML, Design Pattern, by andersonstraube

Strategy é um padrão de projeto de software (do inglês design pattern). O objetivo é representar uma operação a ser realizada sobre os elementos de uma estrutura de objetos. O padrão Strategy permite definir novas operações sem alterar as classes dos elementos sobre os quais opera. Definir uma família de algoritmos e encapsular cada algoritmo como uma classe, permitindo assim que elas possam ter trocados entre si. Este padrão permite que o algoritmo possa variar independentemente dos clientes que o utilizam.
Fonte: http://pt.wikipedia.org/wiki/Strategy

Motivação para usar o Padrão Strategy?

– Quando um sistema possui vários componentes que têm semelhança estrutural porém com comportamentos diferentes;
– Quando tem um algoritmo cujo cálculo pode variar dependendo dos parâmetros fornecidos;
– Você não quer que o componente principal seja alterado e/ou “inflado” toda vez que um novo modo (estratégia) é desenvolvida/solicitada.

O diagrama abaixo representa o Padrão Strategy:

UML class diagram
* Fonte da imagem: DoFactory.com

Para exemplificarmos o uso deste Pattern vamos criar uma aplicação que fará o cálculo do imposto de uma nota fiscal, porém hipoteticamente cada estado assume um tipo diferente de cálculo.

NotaFiscal.cfc
O mais comum seria fazer essa verificação dentro da nossa classe e calcular o valor do imposto:


<cfcomponent name="NotaFiscal" output="false">

	<!--- estou desprezando outros atributos por não ser o foco aqui --->
	<cfscript>
		variables.valor = 0;
	</cfscript>

	<cffunction name="calcularImposto">
		<cfargument name="estadoID" type="numeric" required="true">

		<!--- Imposto em SC  --->
		<cfif arguments.estadoID eq 1 >
			<cfreturn this.getValor() * 0.2 />

		<!--- Imposto no PR  --->
		<cfelseif arguments.estadoID eq 2 >
			<cfreturn (this.getValor() * 0.15) + 35 />
		</cfif>

	</cffunction>

	<cffunction name="setValor" access="public" returntype="numeric">
		<cfargument name="valor" type="numeric" required="true" />

		<cfset variables.valor = arguments.valor />
	</cffunction>

	<cffunction name="getValor" access="public" returntype="numeric">

		<cfreturn variables.valor />
	</cffunction>

</cfcomponent>

Dessa forma vai funcionar, mas e se por ventura surgir outro estado que seja só uma taxa fixa? Mais uma vez vamos inserir este estado em nossa classe:


<cffunction name="calcularImposto">
	<cfargument name="estadoID" type="numeric" required="true">

	<!--- Imposto em SC  --->
	<cfif arguments.estadoID eq 1 >
		<cfreturn this.getValor() * 0.2 />
	
	<!--- Imposto no PR  --->
	<cfelseif arguments.estadoID eq 2 >
		<cfreturn (this.getValor() * 0.15) + 35 />
	
	<!--- Imposto no RS --->
	<cfelseif arguments.estadoID eq 3 >
		<cfreturn 20 />
	
	</cfif>

</cffunction>

Dessa forma nosso componente perde a legibilidade, sem falar que adicionamos regras de negócios que na verdade não é ou pelo menos não deveria ser da responsabilidade da classe “NotaFiscal”. Imagine uma classe que é frequentemente utilizada pelo sistema, ao alterarmos qualquer bloco de código corremos o risco de criar um bug que afetará outras áreas do sistema, sem falar que começamos a “inflar” a classe com vários “IF’s” de acordo com cada estado.

A solução: como o Padrão Strategy resolve esse problema?

O Padrão Strategy é a solução perfeita para este exemplo, onde o cálculo do imposto se divide em um componente específico da sua própria responsabilidade, ou seja, para cada estado haverá um CFC, exemplo: ImpostoSC.cfc, ImpostoPR.cfc e ImpostoRS.cfc – todos eles herdarão uma única interface para realizar o cálculo do imposto.

IImposto.cfc
Criamos a nossa interface para que cada estado faça sua implementação:


<cfinterface>

	<cffunction name="calcular" returntype="numeric">
		<cfargument name="valor" type="numeric" required="true">
	</cffunction>

</cfinterface>

ImpostoSC.cfc
Vamos criar as classes responsáveis pelos cálculos do imposto nos 3 estados que implementa “IImposto.cfc”:


<cfcomponent name="ImpostoSC" implements="IImposto">

	<cffunction name="calcular" returntype="numeric">
		<cfargument name="valor" type="numeric" required="true">

		<cfreturn arguments.valor * 0.2 />
	</cffunction>

</cfcomponent>

ImpostoPR.cfc


<cfcomponent name="ImpostoPR" implements="IImposto">

	<cffunction name="calcular" returntype="numeric">
		<cfargument name="valor" type="numeric" required="true">

		<cfreturn (arguments.valor * 0.15) + 35 />
	</cffunction>

</cfcomponent>

ImpostoRS.cfc


<cfcomponent name="ImpostoRS" implements="IImposto">

	<cffunction name="calcular" returntype="numeric">
		<cfargument name="valor" type="numeric" required="true">

		<cfreturn 20 />
	</cffunction>

</cfcomponent>

NotaFiscal.cfc
Agora que já temos as classes responsáveis pelo cálculo, vamos alterar a NotaFiscal (método “calcularImposto”) para utilizar este pattern:


<cfcomponent name="NotaFiscal" output="false">

	<!--- estou desprezando outros atributos por não ser o foco aqui --->
	<cfscript>
		variables.valor = 0;
	</cfscript>

	<cffunction name="calcularImposto">
		<cfargument name="imposto" type="IImposto" required="true">

		<cfreturn arguments.imposto.calcular( this.getValor() ) />
	</cffunction>

	<cffunction name="setValor" access="public" returntype="numeric">
		<cfargument name="valor" type="numeric" required="true" />

		<cfset variables.valor = arguments.valor />
	</cffunction>

	<cffunction name="getValor" access="public" returntype="numeric">

		<cfreturn variables.valor />
	</cffunction>

</cfcomponent>

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

teste.cfm


<cfset notaFiscal = CreateObject("component", "NotaFiscal") />
<cfset notaFiscal.setValor(200) />

<!--- ImpostoSC => strategy --->
<cfset imposto = CreateObject("component", "ImpostoSC") />
<cfoutput>Imposto em SC: R$ #notaFiscal.calcularImposto(imposto)#<br></cfoutput>

<!--- ImpostoPR => strategy --->
<cfset imposto = CreateObject("component", "ImpostoPR") />
<cfoutput>Imposto no PR: R$ #notaFiscal.calcularImposto(imposto)#<br></cfoutput>

<!--- ImpostoRS => strategy --->
<cfset imposto = CreateObject("component", "ImpostoRS") />
<cfoutput>Imposto no RS: R$ #notaFiscal.calcularImposto(imposto)#<br></cfoutput>

A saída será:


Imposto em SC: R$ 40
Imposto no PR: R$ 65
Imposto no RS: R$ 20

Utilizando este padrão temos um código mais limpo, onde o cálculo do imposto é realizado separadamente e a classe NotaFiscal não se importa com cálculo do imposto assim como o tipo de classe que fará isso desde que ele implemente a interface “IImposto”. Com esse tipo de implementação a NotaFiscal não sofre nenhum impacto caso o número de classes derivadas aumente (um novo imposto de um estado por exemplo) e nem precisamos alterar o algoritmo de qualquer método “calcular” existente, basta criar uma nova classe, implementar a interface e abstrair o cálculo – desta forma o código que está usando esse componente pode decidir em tempo de execução qual cálculo utilizar sem afetar o componente principal.

Veja mais sobre outros Patterns:

Singleton em ColdFusion
Observer Pattern em ColdFusion

Até a próxima, abraço.

Tagged with:  

One Response to Strategy Pattern em ColdFusion

  1. […] – Singleton em ColdFusion – Observer Pattern em ColdFusion – Strategy Pattern em ColdFusion […]

Deixe uma resposta

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