Site Meter

Estudo de Caso, Programação Orientada a Domínio

Postado por | Categorias Arquitetura de Softwares, Artigos, Artigos de Programação, ColdFusion, Design Pattern, OOP, Programação | Postado em 16-05-2012

Quando falamos de Programação Orientada ao Domínio ou Problema como achar melhor, costuma passar um entendimento meio superficial do assunto.

Então, resolvi escrever de uma forma mais prática sobre o assunto e implementar umas soluções baseadas em Programação Orientada a Domínio.

Para começar vamos definir que teremos uma nova camada em nossa aplicação. E esta camada será chamada de Domain.

Vamos no diagrama abaixo como ficaria nossas camadas de programação.

Diagrama UML

Na camada Domain ficarão nossos problemas, a ideia é transferir os problemas de implementação da nossa lógica de negócio para a camada de Domínios.

Dessa maneira reduziremos nossos códigos na camada de negócio bruscamente, além de conseguir implementações mais limpas.

Como implementar

Não usaremos framework nenhum e sim, implementaremos nossa camada de domínio com código próprio para entendermos a resolução do problema em si e assim entenderemos muito melhor como funciona esse conceito.

Vamos abordar somente a parte da camada de negócios, não vamos ver consultas em banco propriamente e sim trabalhar com as chamadas e em resultados imaginários. Pois dessa forma conseguiremos um artigo prático e não muito grande.

Vamos começar

Para criarmos nossa prática, vamos criar um cenário de refactoring, onde temos uma camada de lógica e refatoraremos a mesma inserindo o conceito de orientação a domínios.

Nosso problema consiste em uma aplicação de gestão de tarefas, onde múltiplos usuários recebem e executam tarefas diariamente. Então trabalharemos num relatório de tarefas executadas por período, quantidade e nota.

Hoje teríamos em nossa classe de controle Relatoras-gerais um método chamado: carregarTarefasPorPeriodoQuantidadeNotas().

Nosso objetivo é ter tarefas divididas pelos seguimentos contidos no nome do método:


<cfscript>
	public function constructor() {
		this.instance.factory 	= Application.factory;
		this.tarefasTopDez 		= [];
		this.tarefasTopCinco 	= [];
		this.totalTopDez		= 0;
		this.totalTopCinco		= 0;
	}

	public function carregarTarefasPorPeriodoQuantidadeNotas( periodoInicial , periodoFinal ) {
		var tarefas = this.instance.factory.getService("Tarefas").getTarefasConcluidasPorPeriodo(
			periodoInicial : arguments.periodoInicial , periodoFinal : arguments.periodoFinal
		);

		// primeiro precisamos separar tarefas com quantidade de
		// atividades acima de 10 e com notas de avalição  igual a 10
		loop query="tarefas" {

			// carrega tarefasTopDez
			if( tarefas.quantidadeAtividades > 10 ) {
				if( tarefas.notaAvaliatoria == 10 ) {
					this.totalTopDez++;
					ArrayAppend( this.tarefasTopDez , this.instance.factory.getService("Tarefas").get( tarefas.id ) );
				}
			}

			// carrega tarefasTopCinco
			if( tarefas.quantidadeAtividades == 5 ) {
				if( tarefas.notaAvaliatoria == 10 ) {
					this.totalTopCinco++;
					ArrayAppend( this.tarefasTopCinco , this.instance.factory.getService("Tarefas").get( tarefas.id ) );
				}
			}

			this.totalTopDez 	= ArrayLen( this.tarefasTopDez );
			this.totalTopCinco 	= ArrayLen( this.tarefasTopCinco );
		}
	}
</cfscript>

Bom, visto o código acima, entendemos que temos uma lógica dentro do nosso controller, essa lógica na hora de fazer refactoring deve ser abstraída e tratada com um problema. Inicialmente fizemos uma lógica simples, que inclusive poderia ser resolvida no banco, a ideia é demonstrar um problema básico para que o entendimento fique mais claro.

Agora vamos entender como resolver esse problema usando a camada de Domínio.

A ideia de se ter uma camada de Domínio que pode ser um framework, é retirar esse tipo de problemas do código, é torná-lo mais fácil de se entender, mas temos que atentar para: O mais fácil de entender, pode não ser o mais simples de implementar.

Um refactoring não vai garantir que vai reduzir a quantidade de código e arquivos que sua aplicação tem, pelo contrário, a refatoração existe para melhorar a qualidade do seu código, tornando-o mais legível, mais claro e bem estruturado. Além de permitir que a aplicação tenha uma abertura maior para receber manutenção ou até em alguns casos, torna a aplicação acessível a escalabilidade.

Entendemos então com isso, que usar Programação Orientada a Domínios, pode fazer você acabar escrevendo mais, porém vai garantir uma divisão de responsabilidade maior e um domínio do problema.

Para aplicarmos nossa refatoração, precisamos criar alguns métodos privados em nossa classe e transferir alguns recursos para a camada de domínio, vamos ver como funciona.

Domain.cfc


<cfcomponent>
	<cfscript>
		package Void function construtor() {
			this.solucaoProblema = "";
			this.objeto			 = "";
		}

		/**
		*	Executa uma resolução de problemas
		*	@name selecionar
		*	@return Domain
		**/
		package Domain function selecionar( objeto , argumentParams ) {
			this.objeto			 = arguments.objeto;
			this.solucaoProblema = [];

			if( IsArray( objeto ) == true ) {
				this.solucaoProblema = this.arrayObjeto( this.objeto , arguments.argumentParams );
			}

			if( IsQuery( objeto ) == true ) {
				solucaoProblema = this.queryObjeto( this.objeto , arguments.argumentParams );
			}

			return this;
		}

		/**
		*	Permite adicionar mais um parâmetro de lógica
		*	@name and
		*	@return Domain
		**/
		package Domain function and( argumentParams ) {
			this.select( this.objeto , arguments.argumentParams );

			return this;
		}

		/**
		*	Define operador Equals
		*	@name $Eq
		*	@return String
		**/
		package String function $Eq( val ) {
			return 'EQ|$ ' & arguments.val;
		}

		/**
		*	Define operador Not Equals
		*	@name $Neq
		*	@return String
		**/
		package String function $Neq( val  ) {
			return 'NEQ|$ ' & arguments.val;
		}

		/**
		*	Define operador Less Then
		*	@name $Lt
		*	@return String
		**/
		package String function $Lt( val ) {
			return 'LT|$ ' & arguments.val;
		}

		/**
		*	Define operador Less Then or Equals
		*	@name $Lte
		*	@return String
		**/
		package String function $Lte( val ) {
			return 'LTE|$ ' & arguments.val;
		}

		/**
		*	Define operador Greater Then
		*	@name $Gt
		*	@return String
		**/
		package String function $Gt( val ) {
			return 'GT|$ ' & arguments.val;
		}

		/**
		*	Define operador Greater Then or Equals
		*	@name $Gte
		*	@return String
		**/
		package String function $Gte( val ) {
			return 'GTE|$ ' & arguments.val;
		}

		/**
		*	Soma Total de uma query
		*	@name SomaQuery
		*	@return Numeric
		**/
		package Numeric function somarQuery( objeto ) {
			return objeto.recordCount;
		}

		/**
		*	Soma Total de um array
		*	@name somarArray
		*	@return Numeric
		**/
		package Numeric function somarArray( objeto ) {
			return ArrayLen( objeto );
		}

		/* ====== PRIVATE METHODS ====== */

		/**
		*	Trata lógicas de objetos do tipo Array
		*	@name arrayObjeto
		*	@return Array
		**/
		private Array function arrayObjeto( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			this.solucaoProblema = this.realizarOperacao( this.objeto , arguments.argumentParams );

			return this;
		}

		/**
		*	Define qual tipo de operação será realizada e já a realiza de acordo com o parametro passado
		*	@name realizarOperacao
		*	@return Array
		**/
		private Array function realizarOperacao( objeto , argumentParams ) {
			var structParametro = {
				   indice : ListGetAt( param , 1 , "|$" ) ,
				argumento : ListGetAt( param , 2 , "|$" )
			};

			switch( structParametro.indice ) {
				case 'EQ' :
					return this.arrayObjetoEqual( objeto , structParametro.argumento );
				break;
				case 'NEQ' :
					return this.arrayObjetoNotEqual( objeto , structParametro.argumento );
				break;
				case 'LT' :
					return this.arrayObjetoLessThen( objeto , structParametro.argumento );
				break;
				case 'LTE' :
					return this.arrayObjetoLessThenOrEqual( objeto , structParametro.argumento );
				break;
				case 'GT' :
					return this.arrayObjetoGreaterThen( objeto , structParametro.argumento );
				break;
				case 'GTE' :
					return this.arrayObjetoGreaterThenOrEqual( objeto , structParametro.argumento );
				break;
			}
		}

		/**
		*	Trata lógicas de objetos do tipo Array usando o operador Equals
		*	@name arrayObjetoEqual
		*	@return Array
		**/
		private Array function arrayObjetoEqual( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
				if( this.objeto[idx] == arguments.argumentParams ) {
					AppendArray( this.solucaoProblema , this.objeto[idx] );
				}
			}

			return this.solucaoProblema;
		}

		/**
		*	Trata lógicas de objetos do tipo Array usando o operador Not Equals
		*	@name arrayObjetoNotEqual
		*	@return Array
		**/
		private Array function arrayObjetoNotEqual( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
				if( this.objeto[idx] != arguments.argumentParams ) {
					AppendArray( this.solucaoProblema , this.objeto[idx] );
				}
			}

			return this.solucaoProblema;
		}

		/**
		*	Trata lógicas de objetos do tipo Array usando o operador Less Then
		*	@name arrayObjetoLessThen
		*	@return Array
		**/
		private Array function arrayObjetoLessThen( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
				if( this.objeto[idx] < arguments.argumentParams ) {
					AppendArray( this.solucaoProblema , this.objeto[idx] );
				}
			}

			return this.solucaoProblema;
		}

		/**
		*	Trata lógicas de objetos do tipo Array usando o operador Less Then or Equals
		*	@name arrayObjetoLessThenOrEqual
		*	@return Array
		**/
		private Array function arrayObjetoLessThenOrEqual( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
				if( this.objeto[idx] <= arguments.argumentParams ) {
					AppendArray( this.solucaoProblema , this.objeto[idx] );
				}
			}

			return this.solucaoProblema;
		}

		/**
		*	Trata lógicas de objetos do tipo Array usando o operador Greater Then
		*	@name arrayObjetoGreaterThen
		*	@return Array
		**/
		private Array function arrayObjetoGreaterThen( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
				if( this.objeto[idx] > arguments.argumentParams ) {
					AppendArray( this.solucaoProblema , this.objeto[idx] );
				}
			}

			return this.solucaoProblema;
		}

		/**
		*	Trata lógicas de objetos do tipo Array usando o operador Greater Then or Equals
		*	@name arrayObjetoGreaterThenOrEqual
		*	@return Array
		**/
		private Array function arrayObjetoGreaterThenOrEqual( objeto , argumentParams ) {
			var this.solucaoProblema = [];

			for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
				if( this.objeto[idx] >= arguments.argumentParams ) {
					AppendArray( this.solucaoProblema , this.objeto[idx] );
				}
			}

			return this.solucaoProblema;
		}

		private Array function castQueryToArray( query ) {
			// fazer o cast da query para um array
		}
	</cfscript>

	<!---
	*	Trata lógicas de objetos do tipo Query
	*	@name queryObjeto
	*	@return Query
	--->
	<cffunction name="queryObjeto" access="private" returntype="query">
		<cfargument name="objeto" 			type="query" required="true" />
		<cfargument name="argumentParams" 	type="string" required="true" />

		<cfset var this.solucaoProblema />

		<cfquery name="solucaoProblema" dbtype="query">
			SELECT * FROM this.objeto WHERE arguments.argumentParams
		</cfquery>

		<cfreturn this.solucaoProblema />
	</cffunction>
</cfcomponent>

Feita nossa classe de Domínio, vamos refatorar nosso controller:


<cfcomponent extends="Domain">
<cfscript>
	public function constructor() {
		super.construtor();

		this.instance.factory 	= Application.factory;
		this.tarefasTopDez 		= [];
		this.tarefasTopCinco 	= [];
		this.totalTopDez		= 0;
		this.totalTopCinco		= 0;
	}

	public function carregarTarefasPorPeriodoQuantidadeNotas( periodoInicial , periodoFinal ) {
		var tarefas = this.instance.factory.getService("Tarefas").getTarefasConcluidasPorPeriodo(
			periodoInicial : arguments.periodoInicial , periodoFinal : arguments.periodoFinal
		);

		this.tarefasTopDez 	 = super.castQueryToArray( super.selecionar( tarefas , this.quantificarMaiorQue(10) )
									.and( this.avaliarIgualA(10) )
									.solucaoProblema );
		this.tarefasTopCinco = super.castQueryToArray( super.selecionar( tarefas , this.quantificarIgualA(5) )
									.and( this.avaliarIgualA(10) )
									.solucaoProblema );
		this.totalTopDez	 = super.somarArray( tarefasTopDez );
		this.totalTopCinco	 = super.somarArray( tarefasTopCinco );
	}

	private String function quantificarMaiorQue( parametro ) {
		return super.$Gt( arguments.parametro );
	}

	private String function quantificarIgualA( parametro ) {
		return super.$Eq( arguments.parametro );
	}

	private String function avaliarIgualA( parametro ) {
		return super.$Eq( arguments.parametro );
	}
</cfscript>
</cfcomponent>

Como vimos acima, concentramos a resolução dos problemas na camada de Domínio e em nosso método publico do controller, teremos somente uma implementação básica deixamos as responsabilidades para métodos privados e/ou para a camada de domínio.

Gente, o conceito é esse, não tem uma regra de como desenvolver e sim o entendimento de que problemas são resolvidos por quem deve resolver problemas e as responsabilidades internas devem ser direcionadas para classes privadas.

Só atentar que sua classe de Domínio deve ser o mais genérica possível, para que lide com vários tipos de situações sem precisar ter uma classe de domínio para cada classe sua.

Lembrando também que a Programação Orientada a Domain, não deve ser somente aplicada na camada de controle… se houver necessidade da mesma em outras camadas, favor usar.

A implementação que fizemos neste artigo, é bem simples, não quis fazer algo muito complexo para não ficar grande demais, por isso usem para estudos e para formar um conceito nas suas cabeças. Na vida real, esse tipo de definição é bem mais complexa.

Não testei os códigos, no momento nem tenho como testá-los.

Grande abraço

PHP e o Pattern Method Chaining

Postado por | Categorias Arquitetura de Softwares, Artigos, Artigos de Programação, OOP, PHP, Programação | Postado em 10-05-2012

Como o nome já diz, Encadeamento de Métodos, essa técnica permite invocar múltiplas chamadas de métodos e cada método retorna um objeto, possivelmente contendo esse objeto em si.

Com o Method Chaining, dentro de nossa classe temos setters que gravam seus valores e retornam o objeto.

É bem simples e de fácil aplicação vamos aos exemplos:

Pessoa.php


<?php
	class Pessoa {
		private $nome;
		private $sobrenome;
		private $idade;

		public function __construct() {
			$this->nome		="";
			$this->sobrenome	= "";
			$this->idade		= 0;

			return $this;
		}

		public function setNome( $val ) {
			$this->nome = $val;

			return $this;
		}

		public function setSobrenome( $val ) {
			$this->sobrenome = $val;

			return $this;
		}

		public function setIdade( $val ) {
			$this->idade = $val;

			return $this;
		}

		public function introduce() {
			echo( "Nome: " . $this->nome . " Sobrenome: " . $this->sobrenome . "<br />Idade: " . $this->idade );
		}

		public function main( $nome , $sobrenome , $idade ) {
			$pessoa = new Pessoa();
			$pessoa ->setNome( $nome )
				->setSobrenome( $sobrenome )
				->setIdade( $idade )
				->introduce();
		}
	}
?>

Fazemos a implementação assim:


<?php
	$pessoa = new Pessoa();
	$pessoa->main( "Paulo" , "Teixeira" , 33 );
?>

Se não formos usar o padrão de encademento, faríamos a classe da seguinte forma:

Pessoa.php


<?php
	class Pessoa {
		private $nome;
		private $sobrenome;
		private $idade;

		public function __construct() {
			$this->nome		="";
			$this->sobrenome	= "";
			$this->idade		= 0;

			return $this;
		}

		public function setNome( $val ) {
			$this->nome = $val;
		}

		public function setSobrenome( $val ) {
			$this->sobrenome = $val;
		}

		public function setIdade( $val ) {
			$this->idade = $val;
		}

		public function introduce() {
			echo( "Nome: " . $this->nome . " Sobrenome: " . $this->sobrenome . "<br />Idade: " . $this->idade );
		}

		public function main( $nome , $sobrenome , $idade ) {
			$this->setNome( $nome )
			$this->setSobrenome( $sobrenome )
			$this->setIdade( $idade )
			$this->introduce();
		}
	}
?>

Fazemos a implementação da mesma forma, como podemos ver abaixo:


<?php
	$pessoa = new Pessoa();
	$pessoa->main( "Paulo" , "Teixeira" , 33 );
?>

Concluímos com isso que usar o padrão torna a implementação mais simples, encadeando os métodos.

Bom, espero que tenham gostado desse padrão.

Fonte: Wikipedia

Até a próxima.

Aplicando Fluent Interfaces com PHP

Postado por | Categorias Arquitetura de Softwares, Artigos, Artigos de Programação, OOP, PHP, Programação | Postado em 09-05-2012

A cada dia que trabalhamos com arquitetura de softwares, pensamos como melhorar ainda mais a legibilidade de nossos códigos.

A cada mudança no projeto, criamos mais e mais classes, agora o problema é maior quando temos que modificar uma classe já existente… E quando pensamos em refactoring? A vida complica.

Pensando em facilitar a vida de desenvolvedores usei em um projeto o padrão Fluent Interface.

Esse padrão faz uso do padrão que publiquei no artigo anterior de Encadeamento de Métodos (Entendendo o padrão Method Chaining), porém o Fluent Interface deixa o código ainda mais limpo, permitindo uma abstração ainda melhor de certas classes.

De início, talvêz você achará um pouco complicado de implementar, e pensará que não tem onde adotar esse tipo de padrão, quando se trabalha com arquitetura, esses padrões vem na cabeça e te fazer resolver sérios problemas.

Vamos a exemplos de implementação:

Nesses exemplos vamos usar os arquivos abaixo:

ConfiguradorFluente.php
IConfiguradorFluente.php
Configurador.php
IConfigurador.php
Pessoa.php

Como vemos temos Classes e Interfaces fluentes e não-fluentes.

Pois vou demonstrar um exemplo usando Fluent Interface e outro não usando, mas seguindo o padrão de usar Interface na arquitetura.

Vamos aos códigos:

IConfiguradorFluente.php


<?php
	interface IConfiguradorFluente {
		public function setNome( $val );
		public function setSobrenome( $val );
		public function setIdade( $val );
	}
?>

ConfiguradorFluente.php


<?php
	class ConfiguradorFluente implements IConfiguradorFluente {
		private $nome;
		private $sobrenome;
		private $idade;

		public function __construct() {
			$this->nome 	 = "";
			$this->sobrenome = "";
			$this->idade	 = 0;

			return $this;
		}

		public function setNome( $val ) {
			$this->nome = $val;

			return $this;
		}

		public function setSobrenome( $val ) {
			$this->sobrenome = $val;

			return $this;
		}

		public function setIdade( $val ) {
			$this->idade = $val;

			return $this;
		}
	}
?>

IConfigurador.php


<?php
	interface IConfigurador {
		public function setNome( $val );
		public function setSobrenome( $val );
		public function setIdade( $val );
	}
?>

Configurador.php


<?php
	class Configurador implements IConfigurador {
		private $nome;
		private $sobrenome;
		private $idade;

		public function __construct() {
			$this->nome 	 = "";
			$this->sobrenome = "";
			$this->idade	 = 0;

			return $this;
		}

		public function setNome( $val ) {
			$this->nome = $val;
		}

		public function setSobrenome( $val ) {
			$this->sobrenome = $val;
		}

		public function setIdade( $val ) {
			$this->idade = $val;
		}
	}
?>

Pessoa.php


<?php
	class Pessoa {
		private $nome;
		private $sobrenome;
		private $idade;

		public function __construct() {
			$this->nome 	 = "";
			$this->sobrenome = "";
			$this->idade	 = 0;

			return $this;
		}

		public function main( $nome , $sobrenome , $idade  ) {
				$fluentConfig = "";
				$config = new Configurador();

				// implementação não-fluente
				$config->setNome( $nome );
				$config->setSobrenome( $sobrenome );
				$config->setIdade( $idade );

				print_r($config);

				// implementação fluente
				$fluentConfig = new ConfiguradorFluente();
				$fluentConfig->setNome( $nome )
					->setSobrenome( $sobrenome )
					->setIdade( $idade );

				print_r($fluentConfig);
		}
	}
?>

Vimos como implementar as classes e as interfaces para ambos os casos e vimos como implementar as chamadas dentro da classe, ao instânciar a classe Pessoa e executar o método main temos os retornos a seguir:

Instanciando a classe Pessoa


<?php
	require_once("IConfiguradorFluente.php");
	require_once("IConfigurador.php");
	require_once("ConfiguradorFluente.php");
	require_once("Configurador.php");
	require_once("Pessoa.php");

	$pessoa = new Pessoa();
	$pessoa->main( "Paulo" , "Teixeira" , 32 );
?>

Resultado das implementações

Observamos que obtivemos na implementação que usamos o Fluent Interface, que obtivemos o mesmo resultado do padrão do artigo anterior.

Espero ter colaborador com esse artigo abraços a todos.

Veja mais detalhes de Arquitetura de Softwares nos links abaixo:

- Divisão da Responsabilidade

- Uma visão sobre arquitetura de softwares

- Como começar um projeto de software

Até a próxima.

Design Pattern Builder em PHP

Postado por | Categorias Arquitetura de Softwares, Artigos, Artigos de Programação, OOP, PHP, Programação | Postado em 09-05-2012

Quando definimos nossos padrões de desenvolvimento, muitas vezes esquecemos de pensar em situações pouco prováveis, mas de alto risco.

Vamos imaginar um cenário onde termos um DTO (Data Transfer Object) dessa forma:


<?php
	class pessoa {
		private codigo;
		private nome;

		public function __construct(){
			$this->codigo = 0;
			$this->nome = "";

			return $this;
		}

		/* setters */
		public function setCodigo( $value ) {
			$this->codigo = $value;
		}

		public function setNome( $value ) {
			$this->nome = $value;
		}

		/* getters */
		public function getCodigo() {
			return $this->codigo;
		}

		public function getNome() {
			return $this->nome;
		}
	}
?>

Em uma implementação no controllers um desenvolvedor da sua equipe, implementa um código assim:


<?php
	class PessoaController {
		private factory;

		public function __construct() {
			$this->factory = new Factory();

			return $this;
		}

		public function salvarUsuario() {
			$dto 	 = $this->factory->getDTO("Pessoa");
			$service = $this->factory->getService("Pessoa");

			$dto->setNome("Paulo");
			$service->save(dto);
		}
	}
?>

Até então tá certo, mas no método save() do service o desenvolvedor implementou algo assim:


<?php
	class PessoaService {
		private factory;

		public function __construct() {
			$this->factory = new Factory();

			return $this;
		}

		public function save( Pessoa $dto ) {
			$dao = $this->factory->getDAO("Pessoa");

			$dto->setNome("Luiz");
			$dao->save($dto);
		}

	}
?>

Dentro do service o “desenvolvedor do mau” subscreveu o valor setado no dto. É lógico que esse tipo de coisa em equipes comuns e bem estruturadas não acontece… mas sabe como são os desenvolvedores né? Se bobear já era.

Uma solução para isso, pode ser o padrão Builder.

O Pattern Builder é um padrão de desenvolvimento e sua intenção é abstrair alguns passos de construção de objetos e proporcionar diferentes maneiras de implementação desses passos para criar diferentes representações de um objeto. O Builder pode ser usado para construir instâncias seguindo um padrão de composição. Como uma interface, mas um pouco mais além de simplesmente assinaturas padrões e obrigatórias.

Dessa forma, podemos criar implementações de instâncias de objetos complexos e garantir também integridade, como evitar o problema representado no exemplo acima.

Vamos ver um exemplo de implementação desse padrão.

No exemplo abaixo, vou demonstrar uma maneira de padronizar instâncias e a requisição de sugestões de pratos de um chefe de cozinha.

A ideia é possibilitar que o desenvolvedor busque objetos contendo pratos completos e não independentes. Não há possibilidade de implementar o objeto e recuperar somente a bebida, o objeto tem que conter a sugestão completa.

Vejamos:

Builder.php

O Builder é a interface de abstração do objeto


<?php

	class Builder {

		private $obj;
		private $done;

		public function __construct() {
			$this->obj = new Sugestoes();
			$this->done = false;

			return $this->obj;
		}

		public function build() {
			$this->done = true;

			return $this->obj;
		}

		public function setPrato( $prato ) {
			$this->check();
			$this->obj->prato = $prato;

			return $this;
		}

		public function setBebida( $bebida ) {
			$this->check();
			$this->obj->bebida = $bebida;

			return $this;
		}

		public function setSobremesa( $sobremesa ) {
			$this->check();
			$this->obj->sobremesa = $sobremesa;

			return $this;
		}

		private function check() {
			if ( $this->done ) {
				throw('Implementação Inválida: ',"Para usar uma outra Sugestão, crie uma nova instância");
			}
		}
		</cfscript>
	}
?>

Sugestoes.php

Sugestoes é a mapeamento do objeto que será implementado.


<?php
	class Sugestoes {
		private $prato;
		private $bebida;
		private $sobremesa;
		private $str;

		public function __construct() {
			$this->prato 	 = "";
			$this->bebida 	 = "";
			$this->sobremesa = "";
			$this->str 	 = "";

			return $this;
		}

		public function createBuilder() {
			return new Builder();
		}

		public function getPrato() {
			return $this->prato;
		}

		public function getBebida() {
			return $this->bebida;
		}

		public function getSobremesa() {
			return $this->sobremesa;
		}

		public function toString() {
			if ( strLen( $this->str ) == 0 ) {
				$this->str = "Prato:" . $this->prato . " Bebida:" . $this->bebida . " Sobremesa:" $this->sobremesa;
			}

			return $this->str;
		}
	}
?>

SugestoesBuilder.php

Esta classe é a responsável pelas implementações, ela determinará os padrões e somente através dela, teremos acesso as sugestões do chefe.


<?php
	class SugestoesBuilder {
		private $sugestao;

		public function __construct() {
			$this->sugestao = new Sugestoes();
		}

		public function pratoDeVerao() {
			$sugestaoDoChefe = $this->sugestao->createBuilder()
						->setPrato("Antepasto crocante")
						->setBebida("Coquetel de pêssego")
						->setSobremesa("Abacaxi refrescante")
						->build();

			return $sugestaoDoChefe;
		}

		public function pratoDeInverno() {
			$sugestaoDoChefe = $this->sugestao.createBuilder()
						->setPrato("Bife a Rolê")
						->setBebida("Capuccino")
						->setSobremesa("Pudin com Calda Quente")
						->build();

			return $sugestaoDoChefe;
		}
	}
?>

Bom, como vemos, as classes se relacionam e uma depende da outra para funcionar.

A implementação da SugestoesBuilder(), devem seguir o padrão, não há possibilidade de implementar um método da seguinte forma:


<?php
	public function pratoDeInverno() {
		$sugestaoDoChefe = $this->sugestao->createBuilder();
		$this->sugestao->setPrato("Bife a Rolê");
		$this->sugestao->setBebida("Capuccino");
		$this->sugestao->setSobremesa("Pudin com Calda Quente");
		$this->sugestao->build();

		return $sugestaoDoChefe;
	}
?>

Se você tentar implementar dessa forma, verá que retornará a Instância de Sugestões em branco.

Outra coisa e se você tentar fazer um set depois de já ter implementado o objeto, receberá uma mensagem de erro, informando que só pode ser implementado uma sugestão por instância. Para re-implementar deverá criar uma nova instância do objeto.

A ideia é essa mesmo, dessa forma, garantimos que o atributo jamais seja setado fora da instância padrão.

Para testar a implementação do padrão acima, façamos assim:


<?php
	$prato = new SugestoesBuilder();

	$pratos = { verao : $prato->pratoDeVerao(), inverno : $prato->pratoDeInverno() };

	print_r( $pratos );
?>

Bom espero que tenham entendido o conceito do padrão.

Veja mais sobre arquitetura de software em:

Como começar um projeto de software

Até a próxima.

Aplicando o Design Pattern Decorator no PHP

Postado por | Categorias Arquitetura de Softwares, Artigos, Artigos de Programação, OOP, PHP, Programação | Postado em 08-05-2012

Quando lemos o nome do padrão Decorator traduzindo seria Decorador imaginamos, que nome bobo, sem sentido. Bom não é tão sem sentido assim.

O Decorator funciona exatamente como o nome o descreve.

Definição:

Decorator, é um padrão de projeto de software que permite adicionar um comportamento a um objeto já existente em tempo de execução, ou seja, agrega dinamicamente responsabilidades adicionais a um objeto.

Este padrão acrescenta responsabilidades dinamicamente a uma classe, gerando alternativas mais flexíveis para que certas classes usem subclasses através de herança ou composição.

Usando Decorator, conseguimos ter mais dinamismo na herança, conseguimos usar herança sem o problema de transformar nossas classes em classes Deus.

Para que possamos entender melhor o padrão, vamos criar um cenário e codificar um exemplo.

Cenário

Imaginem que estamos desenvolvendo um sistema bancário, onde uma Conta Corrente, pode transferir fundos para outras Contas Correntes ou Contas Poupanças.

Temos um problema para resolver:

Quando fazemos a transferência, temos Conta Corrente e Conta Poupança. Seguindo os padrões da Orientação a Objetos, devemos respeitar a divisão de responsabilidade.

Então a Conta Corrente deverá ser uma classe e a Conta Poupança deverá ser outra classe.

Nosso problema consiste em creditar os valores na conta certa e definir a forma que faremos isso de forma limpa e de fácil manutenção.

O Decorator pode nos ajudar exatamente nisso, ele vai acrescentar a responsabilidade receberFundos() na nossa classe no momento certo, inclusive fazendo a validação que determinará em que tipo de conta o valor será creditado. Vamos ver um exemplo:

ContaCorrente.php


<?php
	class ContaCorrente extends Decorator {

		public $numContaCorrente;
		public $numSaldoAtual;

		public function __construct() {
			$this->numContaCorrente 	= 0;
			$this->numSaldoAtual 		= 0;

			return $this;
		}

		public function transferirFundos( $numContaCorrente , $valorTransferencia ) {
			$sucessoNaOperacao = false;

			$sucessoNaOperacao = parent::realizarTransferenciaDeFundos( $this, $numContaCorrente , $valorTransferencia );

			return $sucessoNaOperacao;
		}

		public function receberFundosTransferidos( ContaCorrente $numContaPagadora , $numContaCorrente , $valorTransferencia ) {
			// implementação da lógica

			$resultado = "Transferência com sucesso em Conta Corrente da Conta: " . $numContaPagadora->numContaCorrente . " Para a conta: " . $numContaCorrente . " Valor Transferido: " . $valorTransferencia;

			return $resultado;
		}
	}
?>

ContaPoupanca.php


<?php
	class ContaPoupanca extends Decorator {

		public function receberFundosTransferidos( ContaCorrente $numContaPagadora , $numContaCorrente , $valorTransferencia ) {
			// implementação da lógica

			$resultado = "Transferência com sucesso em Conta Poupança da Conta: " . $numContaPagadora->numContaCorrente . " Para a conta: " . $numContaCorrente . " Valor Transferido: " . $valorTransferencia;

			return $resultado;
		}
	}
?>

Decorator.php


<?php
	class Decorator {
		private $DIGITOS_VERIFICADORES_CONTA_POUPANCA = 500;

		protected function realizarTransferenciaDeFundos( ContaCorrente $numContaPagadora , $numConta  , $valorTransferencia ) {
			$conta = "";

			if( $this->validarContaPoupanca( $numConta ) ) {
				$conta = new ContaPoupanca();
			}
			else {
				$conta = new ContaCorrente();
			}

			return $conta->receberFundosTransferidos( $numContaPagadora , $numConta , $valorTransferencia );
		}

		private function validarContaPoupanca( $numConta ) {
			// regra boba para simular uma validacao
			if( substr( $numConta , -3 , 3 )  == $this->DIGITOS_VERIFICADORES_CONTA_POUPANCA ) {
				return true;
			}
			else {
				return false;
			}
		}
	}
?>

Implementação


<?php
	require_once("Decorator.php");
	require_once("ContaCorrente.php");
	require_once("ContaPoupanca.php");	

	$contaCorrente = new ContaCorrente();
	$contaCorrente->numContaCorrente = 26558454200;

	// transferencia para conta corrente
	$resultadoTransferencia = $contaCorrente->transferirFundos( 556558474154 , 6000.00 );

	echo("<p>" . $resultadoTransferencia . "</p>");

	// transferencia para conta poupança
	$resultadoTransferencia = $contaCorrente->transferirFundos( 556558474500 , 2365.50 );

	echo("<p>" . $resultadoTransferencia . "</p>");
?>

Bom como vimos acima, fizemos duas transferências para duas contas diferentes. Sem precisar informar que tipo de conta seria e sem ter que instanciar nada a mais.

Com a instância da nossa conta, fizemos a transferência e o Decorator inseriu a responsabilidade correta no nosso método de transferência de fundos.

Esse padrão é bem simples e muito fácil de utilizar.

Espero que tenham gostado, até a próxima e bons códigos.

Veja mais artigos de arquitetura no link abaixo:

Arquitetura de Softwares