Design Pattern: Implementando o Decorator

Posted by Alberto Leal on June 23rd, 2009

Photo: http://static.howstuffworks.com/gif/teen-bedroom-decorating-ideas-2.jpg

O último livro que terminei de ler era sobre Design Pattern. Então, resolvi fazer alguns posts contendo algumas implementações. O padrão de projeto da vez é o Decorator.

——–

O poder da composição

Têm quem ame, e têm quem odeie herança. Existem casos que não há como fugir da herança, mas, sempre que possível, prefira composição a herança. Quando utilizamos herança em uma parte da nossa aplicação, e alguns comportamentos são herdados, estes  são definidos estaticamente em tempo de compilação. Por outro lado, se você estender o comportamento de um objeto por meio de composição, você consegue fazer isso dinamicamente, ou seja, consegue compôr os objetos de forma dinâmica. Com isso, se torna fácil adicionar novas funcionalidades escrevendo um código novo ao invés de alterar o código existente. Isso é bacana, pois o comportamento anterior não é modificado, assim as chances de introdução de erros no código que já estava funcionando é menor. Se algo der errado, basta remover a nova classe que foi criada, por exemplo. Resumindo, mantenha as classes abertas para extensão e fechadas para alteração.

O padrão de projeto decorator anexa novas responsabilidades em um objeto dinamicamente, utilizando  do poder da composição.

Vamos ver como isso funciona na prática. O exemplo que lhe será apresentado é a construção de uma pizza. No momento do pedido, o cliente poderá pedir ao Pizzaiolo que adicione  novos ingredientes na pizza, tais como: orégano e tomate. Exemplo simples e direto, apenas para ilustrar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package br.eti.albertoleal.decorator;
 
public abstract class Pizza {
 
private String description = "Unknown Pizza";
 
public void setDescription(String desc) {
description = desc;
}
 
public String getDescription(){
return description;
}
 
public abstract double cost();
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package br.eti.albertoleal.decorator;
 
public class Cheese extends Pizza {
 
	public Cheese(){
		setDescription("Cheese Pizza")
	}
 
	@Override
	public double cost() {
		return 19.90;
	}
 
}

As classes decorator devem ser espelhos das classes que elas vão decorar. Vamos utilizar da herança em nossos objetos do tipo CondimentDecorator. O motivo de se usar herança na classe Decorator é  pelo simples fato de se ter o mesmo tipo dos objetos que vão ser decorados. O uso de herança é justamente para atingir  essa correspondência de tipo.

1
2
3
4
5
package br.eti.albertoleal.decorator;
 
public abstract class CondimentDecorator extends Pizza {
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package br.eti.albertoleal.decorator;
 
public class Oregano extends CondimentDecorator {
	Pizza pizza;
 
	public Oregano(Pizza pz) {
		pizza = pz;
	}
 
	@Override
	public String getDescription() {
		return pizza.getDescription() + ", Oregano";
	}
 
	@Override
	public double cost() {
		return .50 + pizza.cost();
	}
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package br.eti.albertoleal.decorator;
 
public class Tomato extends CondimentDecorator {
	Pizza pizza;
 
	public Tomato(Pizza pz){
		pizza = pz;
	}
 
	@Override
	public String getDescription() {
		return pizza.getDescription() + ", Tomato ";
	}
 
	@Override
	public double cost() {
		return .10 + pizza.cost();
	}
 
}

Se você estava atento reparou que a cada condimento que é adicionado na pizza um valor correspondente ao condimento é somado ao valor da pizza. Essa é a idéia do decorator. Estou decorando um objeto pizza com diversos outros componentes. Um outro exemplo, bastante comum de se encontrar pela internet, são as janelas gráficas. O ato de decorar uma janela gráfica com diversos componentes de apresentação, tais como: barra de rolagem, campos de texto e por aí vai.

Chegou a hora de testarmos o nosso código, criar uma pizza e adicionar alguns condimentos a ela e constatar se a nossa “decoração” funciona:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package br.eti.albertoleal.decorator;
 
public class Pizzaiolo {
 
	public static void main(String[] args) {
 
		Pizza pz = new Cheese();
 
		pz = new Tomato(pz);
		pz = new Tomato(pz);
		pz = new Oregano(pz);
		pz = new Tomato(pz);
		pz = new Oregano(pz);
		pz = new Tomato(pz);
 
		System.out.println(pz.getDescription());		
 
	}
 
}

Concluindo:

  • A herança é uma maneira de estender comportamentos, mas não é a melhor maneira de obter flexibilidade;
  • Composição e delegação são boas maneiras de se adicionar funcionalidades em tempo de execução;
  • A utilização de um componente decorator é simplesmente decorar um objeto, e o objeto decorado não precisa conhecer os decoradores;
  • É muito importante que os objetos decoradores sejam espelhos do objeto que vão decorar, justamente para ter uma correspondência de tipo.

Git: Quem fez merda no meu código?

Posted by Alberto Leal on June 19th, 2009

Cenário: Você está em uma reunião participando de um code review, quando algum desenvolvedor vira e fala: “Quem comentou a linha 12? Eu fiz esse código e aquele fragmento era importante!”. Pergunta: O culpado aparece rápido? Pode ser que sim. Mas, caso não apareça  ninguém para assumir a culpa, utilize o comando ‘git blame’ para descobrir quem fez cada modificação.

Exemplo:

$ git blame workspace/ProjectXPTO/WebContent/file.jsp -L 10,18
73e51f4f (Desenvolvedor A 2009-03-10 09:07:53 -0400 10)
73e51f4f (Desenvolvedor A 2009-03-10 09:07:53 -0400 11)  <script>
73e51f4f (Desenvolvedor A 2009-03-10 09:07:53 -0400 12)         // var windowName;
7ue8d049 (Desenvolvedor B 2009-06-18 16:42:48 -0400 13)
7ue8d049 (Desenvolvedor B 2009-06-18 16:42:48 -0400 14) function cleanUpVariable(variable){
7ue8d049 (Desenvolvedor B 2009-06-18 16:42:48 -0400 15)  var = j;
7ue8d049 (Desenvolvedor B 2009-06-18 16:42:48 -0400 16)  j = variable.replace(/[^0-9A-Za-z]+/g, "");
7ue8d049 (Desenvolvedor B 2009-06-18 16:42:48 -0400 17)  return j;
7ue8d049 (Desenvolvedor B 2009-06-18 16:42:48 -0400 18) }

Se você não passar o parâmetro -L serão exibidas todas as linhas do seu arquivo.  No caso acima deu para perceber que o responsável por comentar a variável “windowName” foi o “Desenvolvedor A”.

Agora você já sabe como descobrir quem faz cada besteira nos seus arquivos.

Git requer estudo, sim

Posted by Alberto Leal on June 19th, 2009

Acredito que o título desse post diz muito por si só. Mas, vou tentar expandí-lo um pouco mais. Só para constar, fui impulsionado a escrever esse post devido a algumas mensagens que acompanhei pelo twitter.

Existem diversos controladores de versão no mercado, tais como: Harvest, CVS, SVN, Clear Quest e por aí vai. Porém, o Git possui uma idéia, filosofia diferente destes que acabei de citar. Git é um SCM distribuído. Não vou entrar em detalhes sobre as diferenças agora, vamos deixar para uma outra ocasião.

A mensagem que quero passar é a seguinte:
Git é difícil?  Não.
Uso SVN/CVS na minha empresa há muitos anos, posso mudar tudo de uma vez para o GIT - já que Git não é difícil? Não aconselho.

Usar um controlador de versão não envolve apenas adicionar arquivos e comitá-los. Existem diversas tarefas que, às vezes, temos que fazer, como por exemplo: Quebrando um commit e cancelando algumas alterações. Pode ser que você já saiba fazê-lo no outro SCM que você vem utilizando, mas ainda não sabe fazê-lo no Git.

Se você, simplesmente, mudar de SCM de uma hora para a outra fatalmente terá problemas e terá que recorrer ao grande amigo Google. Alguns problemas triviais não lhe tomará muito tempo, por outro lado, outros não serão tão triviais quanto possam parecer e tomarão mais tempo do que você gostaria.

Minha sugestão é a seguinte, comece utilizando Git em um projeto antes de migrar todos os outros. Desse jeito você perceberá como o Git trabalha e como tirar o maior proveito dele. Além de se deparar com os mais diversos tipos de problemas.

Até a próxima!

Git: Quando usar cherry-pick ou am/apply

Posted by Alberto Leal on June 18th, 2009

Você sabe quando usar o cherry-pick ou am/apply?

Algumas vezes eu ficava confuso em qual comando usar. Se você se confunde, aí vai a dica:

Uma utilização para esses comandos é quando você desejar fazer o rebase/merge entre branches que estão em árvores diferentes, ou seja, vamos imaginar que você tem um branch, chamado A, que foi criado à partir do branch Release0615, e tem outro branch, chamado B, que foi criado à partir do branch Release0630.

O seu branch A atrasou e suas alterações não entrarão mais na release 0615 (release do dia 15/06/2009), então você deve fazer o seu rebase/merge com o release 0630. Mas eles estão em árvores diferentes, com isso você pode ter uma série, digo novamente, uma série de problemas. Uma solução é levar apenas as suas modificações feitas no branch A para o branch B, removendo com isso os conflitos de arquivos que você sequer mexeu!

Agora, qual comando usar ‘git cherry-pick’ ou ‘git format-patch’ seguido de ‘git am -3′ ou ‘git apply’?

Se o branch estiver em ambiente local utilize ‘git cherry-pick’. Ele tentará fazer um auto-merge para você e se não existir conflito tudo estará pronto. Agora, por outro lado, se os commits não estiver no ambiente local, ou se alguém lhe enviou os arquivos .patch por email, você deve usar o comando ‘git am -3′ ou ‘git apply’. Prefira utilizar ‘git am -3′, pois com ele o git já vai saber que são commits de outra árvore e fará de tudo para não ter problema no merge.

Desfazer commit no git

Posted by Alberto Leal on June 9th, 2009

Tenho percebido que algumas pessoas estão caindo no meu blog procurando por “como desfazer commit no git”.

Aí vai uma dica rápida para aqueles que estão à procura de uma solução.

Sempre que for desejável desfazer um commit no git, basta você revertê-lo. Para isso, utilize o seguinte comando:

1
git revert nome_do_commit

Se você executar o comando acima por acidente, você consegue revertê-lo também. Fazendo o revert do revert você estará voltando para o estado inicial do seu commit.

Espero que ajude!

Git: O rebase pode te assustar

Posted by Alberto Leal on June 9th, 2009

Pessoal,

antes de iniciar o post, gostaria de agradecer à todos que estão me mandando emails, mensagens via twitter, via chat, agradecendo a série de dicas sobre Git. Para mim está sendo bastante prazeroso escrever sobre esse assunto.

Visando melhorar a série, estou adicionando um arquivo para download com o conteúdo desse post formatado. Não sei ao certo se isso vai ser bacana, para isso, preciso do feedback de vocês. O que vocês acham dessa opção de download do artigo formatado?

Obrigado, e boa leitura..


Post em PDF

Cenário: Você vêm trabalhando há um bom tempo em um branch. Mas, para facilitar o seu trabalho na hora de fazer o merge no repositório central, visando diminuir a quantidade expressiva de conflitos a serem resolvidos de uma única vez ao final do desenvolvimento, você faz diariamente um rebase com o repositório principal.

O projeto: O projeto em que estou trabalhando é um projeto Java. Estou fazendo algumas implementações de padrões de projeto utilizando a linguagem Java. A estrutura do projeto é a seguinte:

picture-7

O pacote contendo o padrão decorator está aberto, pois utilizaremos ele como exemplo.
Executando o comando git log, você verá todos os commits já realizados:

$ git log
commit d2e78a824c98c98a0eec6d083533f36dc35a7bfa
Author: Alberto Leal <albertonb@gmail.com>
Date:   Mon Jun 8 20:48:00 2009 -0300
 
Adding Factory Method
 
commit be3a2a8d22536d427098eaa8bd474f7cb8944bcc
Author: Alberto Leal <albertonb@gmail.com>
Date:   Mon Jun 8 20:47:46 2009 -0300
 
Adding Command
 
commit a0e3a5e1fe3f4c31a3bc3f20283b58ed0d16fc7b
Author: Alberto Leal <albertonb@gmail.com>
Date:   Mon Jun 8 20:47:38 2009 -0300
 
Adding Decorator
 
commit a33c008642c24028477a9c1e7e7d5fca6661c7c9
Author: Alberto Leal <albertonb@gmail.com>
Date:   Mon Jun 8 20:47:26 2009 -0300
 
Adding Abstract Factory
 
commit 52e4505b66ab91f2a8e8511b79ace49aa407e333
Author: Alberto Leal <albertonb@gmail.com>
Date:   Mon Jun 8 20:47:10 2009 -0300
 
Adding Observer

Repare que o padrão decorator foi o terceiro a ser implementado e comitado no git. Como estamos no último commit, conseguimos visualizar normalmente todas as alterações/inserções que nele se encontram.

Até o momento, estamos no branch principal, no master. Mas, foi solicitado a adição de um novo atributo na classe Pizza, chamado “size” que dirá se pizza é “pequena, média ou grande”.  Para realizar tal tarefa, cria-se um novo branch chamado “Ticket123”:

$ git checkout -b Ticket123

Agora que já estamos no branch relacionado ao ticket, fazemos a adição do novo atributo na classe Pizza. Eis o código da classe Pizza após a alteração:

package br.eti.albertoleal.decorator;
 
public abstract class Pizza {
 
private String description = "Unknown Pizza";
private String size;
 
public String getSize() {
return size;
}
 
public void setSize(String size) {
this.size = size;
}
 
public void setDescription(String desc) {
description = desc;
}
 
public String getDescription(){
return description;
}
 
public abstract double cost();
 
}

Imagine que durante 1(uma) semana você ficou trabalhando em uma série de alterações no código acima, ou até mesmo em outros. E como citado no cenário, ao final do dia você sempre faz um rebase com o projeto original para poder pegar as

alterações de outros desenvolvedores e manter o seu branch atualizado, consequentemente com uma quantidade menor de conflitos a serem resolvidos quando você for fazer a entrega do seu branch.

Para ficar mais evidente o que eu quero mostrar, crie um novo arquivo ou simplesmente faça alguma outra alteração em um código já existente, e depois faça o commit.  Aqui, eu vou criar um arquivo chamado “TestAprendendoGit.java”

Assim como você, existem outros desenvolvedores criando branch a partir do master e fazendo merge no mesmo.

Após um longo dia de trabalho, chegou a hora de fazer o rebase:

$ git rebase master
 
First, rewinding head to replay your work on top of it...
Applying: Adding a new field in the Pizza
error: patch failed: src/br/eti/albertoleal/decorator/Pizza.java:3
error: src/br/eti/albertoleal/decorator/Pizza.java: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging src/br/eti/albertoleal/decorator/Pizza.java
CONFLICT (content): Merge conflict in src/br/eti/albertoleal/decorator/Pizza.java
Failed to merge in the changes.
Patch failed at 0001 Adding a new field in the Pizza
 
When you have resolved this problem run "git rebase --continue".
If you would prefer to skip this patch, instead run "git rebase --skip".
To restore the original branch and stop rebasing run "git rebase --abort"

Oooops, conflito! Algum desenvolvedor fez alguma alteração na classe Pizza também.

Agora que vem o susto! Cadê o tal arquivo “TestAprendendoGit.java” que foi criado?

picture-10Quando você começa a fazer um rebase você não está em nenhum branch, você se encontra em uma área “neutra”, vamos por assim dizer. Não acredita em mim?! Agora que você já iniciou o seu rebase e obteve os conflitos, execute o comando:

git branch
* (no branch)
Ticket123
master

Viu só? =)

Quando eu falei que o git pode te assustar, o que eu queria dizer é o seguinte: Para aqueles que não estão acostumados com a forma que o Git trabalha, levará um baita susto ao ver que os arquivos e/ou alterações que ele criou, depois do commit que deu o conflito, “sumiram”. Não sumiu nada! Eles estão lá apenas aguardando você resolver todos os conflitos e executar todos os comandos “git rebase –continue” até chegar no último commit, o qual possui o estado final do seu código. Agora que terminou de resolver todos os conflitos e o rebase, olha o arquivo aí de novo (TestAprendendoGit.java):
picture-9

Tentado exemplificar  um pouco mais…

Como o rebase funciona?
O rebase analisa commit a commit. Ou seja, ele pega todo o seu histórico de commits e analisa um a um, do primeiro até o último!

Como um rebase pode te assustar?
Vamos supor que você tenha 5 commits. Você fez uma mudança específica no último commit, ok? Enquanto o rebase estava sendo executado, foi constatado um conflito no 2º commit. Então, para continuar com o rebase você deve fixar o(s) conflito(s).
Com isso, como o rebase se encontra no 2º commit, você não é capaz de ver as mudanças realizadas nos commits posteriores. Se você tentar encontrar aquelas alterações feitas nos commits acima, você não conseguirá visualizá-las.

Os lados de um Cubo Mágico

Posted by Alberto Leal on June 8th, 2009

Há algum tempo eu ganhei de presente da minha noiva um cubo mágico. Sim, um cubo mágico, ou cubo de Rubik se preferir. Há tempos eu  estava procurando por um, até que ela encontrou em São Paulo e me deu de presente. Desde então eu venho “desperdiçando” algumas horas diárias para tentar resolvê-lo.

Alguns amigos comentaram que existem diversos macetes, tutoriais de como resolver um cubo, bastava eu procurar no Google. Não tive interesse em lê-los, mas sim tentar por mim mesmo entendê-lo e resolvê-lo.

O meu cubo mágico é um dos mais tradicionais, de 6 (seis) lados.

Mas, o que um cubo mágico tem haver com a nossa área de desenvolvimento de software?

Após resolver um dos lados do cubo, não teve como não fazer uma analogia com a nossa área. Nela, lidamos com diversos lados áreas, tais como: infra-estrutura. desenvolvedores, arquitetos, designers, clientes, entre outros. Todos sabemos o quão difícil é manter a perfeita harmonia entre essas áreas. O mais importante é a  comunicação utilizada por elas, de modo a manter a perfeita integração, a sincronia entre os times.

img_02033

Algumas vezes, cada lado área tem seu próprio interesse, pensam em resolver do seu jeito, mesmo que esse não seja a melhor opção para o negócio do cliente. Tentam de qualquer maneira vender a própria solução para o cliente, só por achá-la mais conveniente, rápida e prática de se implementar. E repito, mesmo sabendo que essa não seria a melhor solução para o negócio do cliente. Isso existe sim, e somos obrigados a lidar com esse cenário, muitas vezes.

Mesmo que já exista uma solução “genérica” que atenda apenas alguns requisitos do cliente, não tente empurrá-la goela abaixo em seu cliente. Se essa solução, realmente, tiver como ser customizada com os requisitos que o cliente deseja, e retirar aqueles que o cliente não irá utilizar, facilmente, aí sim. Pois, desse modo você estará utilizando essa solução “genérica” como um ponto de partida.

Voltando ao nosso cubo mágico….

Não adianta resolver, apenas, um lado de um cubo mágico. Esse não é o objetivo, e sim resolver todos os lados. E para atingir esse objetivo, dificilmente você conseguirá resolver cada lado isoladamente, isto é, você não conseguirá resolver o lado branco, depois resolver o lado azul, em seguida o vermelho e assim por diante. Todos os lados devem ser movidos em conjunto até chegar o momento em que todos os lados estarão resolvidos.

Assim como ocorre com o cubo mágico, no desenvolvimento de software não adianta apenas resolver uma lado área. Mesmo que cada lado área tenha o seu próprio objetivo para atingir, todas devem trabalhar em conjunto para que o objetivo maior seja atingido, que é: Resolver o problema do cliente da melhor forma possível.

Vamos tentar imaginar o que acontece quando cada lado área resolve o seu objetivo isoladamente. De que adianta o Designer fazer uma bela arte para a parte visual, e os desenvolvedores não fazerem o “dever de casa”, isto é, integrar a aplicação com a arte feita pelo Designer? De que adianta o Designer fazer o “dever de casa”, o desenvolvedor idem, mas a equipe de infra-estrutura não disponibilizar um ambiente de produção para a aplicação? Acho que já deu para perceber o quanto uma lado área depende uma das outras, e como todas devem caminhar em conjunto.

Concluindo:

  • Mantenha os seus times sólidos, integrados, coesos;
  • Utilize dos mais diversos tipos de comunicação entre eles, estimule sempre o trabalho em equipe entre os times, não somente dentro dos times;
  • Dê sempre ouvidos ao seu cliente.  Coloque-o em primeiro lugar. Falando assim é bonito, soa bem. Mas nem sempre isso acontece. Você só coloca o seu cliente em primeiro lugar a partir do momento o qual você, ou sua empresa, começa a olhá-lo como pessoa, ao invés de olhá-lo apenas como o cara que vai colocar a mão do bolso e pagar as contas da sua empresa.

Git: Recuperando os nomes dos arquivos modificados

Posted by Alberto Leal on June 4th, 2009

Cenário: Seu cliente utiliza um outro SCM, por exemplo, o Harvest, no projeto o qual você trabalha. Mas, você e sua equipe resolvem utilizar o Git. Eis que você faz uma série de scripts de automação para pegar o seu release no Git e entregá-lo ao Harvest. Bacana, não? Para que isso funcione perfeitamente, você deve passar para o sistema de automação o nome dos arquivos que foram alterados, desse jeito, não será necessário enviar arquivos que sequer sofreram modificações, o que é bem interessante, diga-se de passagem.

Legal, existe o comando git whatchanged. Você pode passa como parâmetro o total de commits que você deseja analisar, utilizando a opção -n. Se você omití-lo, todas as modificações serão exibidas. (Se você informar o parâmetro -p, serão exibidas as modificações dentro dos arquivos).

Vamos dar uma olhada na saída desse comando:

$ git whatchanged -n 3
commit 8d726556aec75e1d6ef5ae477532fcc11770ac01
Author: Alberto Leal
Date:   Wed Jun 3 16:04:23 2009 -0400
 
Message 3
 
:100755 000000 0615380... 0000000... D  workspace/xxx/yyy/cfgXPTO.jnlp
:100755 000000 37076cc... 0000000... D  workspace/xxx/yyy/packageXYZ.jar
 
commit 3ab3a5e95fa031d8eb93154d161bfba228bc5f86
Author: Alberto Leal
Date:   Wed Jun 3 15:38:00 2009 -0400
 
Message 2
 
:100755 100755 849dc69... 1e7ef9d... M  workspace/xxx/src/com/xxx/yyy/ModelXYZ.java
:100755 100755 9828364... d85e5ee... M  workspace/xxx/WebContent/WEB-INF/jsp/viewXYZ.jsp
:000000 100755 0000000... d034c28... A  workspace/xxx/WebContent/htmlXPTO.html
 
commit 7e076b5130340f28e57526cf71ce96d7cc279538
Author: Alberto Leal
Date:   Wed Jun 3 10:04:56 2009 -0400
 
Message 1
 
:100755 100755 9c322de... 9828364... M  workspace/xxx/WebContent/WEB-INF/jsp/anotherXTPO.jsp

Repare que se você quiser pegar os arquivos modificados, você terá que copiar um a um. Trabalhoso, não? Existem diversas maneiras de se recuperar apenas os nomes dos arquivos. Uma sugestão seria:

def files_changed(commits, last_commit)
  File.open(commits, "r") do |infile|
  infile.each_line do |line|
    if line.include? last_commit
      break
    else
      er = line.match(/workspace(.*)/)
      puts er unless er.nil?
    end
  end
  end
end
 
files_changed(ARGV[0], ARGV[1])

Execute o seguinte comando no seu terminal para obter o resultado desejado:

$ git whatchanged > files.txt && ruby files_changed.rb files.txt 7e076b513

Se você quiser, simplesmente, pegar todos os arquivos modificado, você pode executar o comando abaixo:

$ git whatchanged | grep workspace | awk '{print($6)}'

Fica aí mais uma dica. Afinal, seria bem chato ter que copiar arquivo por arquivo =)


Copyright © 2007 Alberto Leal. All rights reserved.