23 de setembro de 2010

Pegadinha Java IV

Os posts marcados como "Pegadinha Java" têm a intenção de testar a sagacidade dos meus alunos iniciantes na Programação em Java. São tópicos curiosos e divertidos, e não têm a intenção de medir o conhecimento na linguagem Java.

A vocês, meus alunos, lembrem-se que a idéia é bater o olho no código e apontar a resposta correta. Naturalmente, trata-se de pegadinhas, ou seja, são trechos de código bastante mal-intencionados... :-)

Nesta nova questão, uma perguntinha básica: em Java é possível dar new em Interface?

Antes de me responder, analise o código abaixo: temos uma interface chamada "Exemplo" e no mesmo arquivo, uma inner class chamada "Classe". Logo na primeira linha da classe, encontramos um bendito "new Exemplo()", ou seja, new em Interface.

Em meio ao código, alguns comentários. A parte chata da estória: este código compila sem erros!!!

Mas como? Isto não é proibido?

Copie todo o código junto, cole em um mesmo arquivo com o nome da interface pública (Exemplo.java), compile e ateste: sem erros! Por quê????

Abraço a todos e até o próximo post! :P



/*/ Isto é apenas comentário: abaixo está a interface /*/

public interface Exemplo {
    void mostra();
}

/*/ A classe abaixo vai dar new em interface! /*/
class Classe{
    Exemplo exemplo = new Exemplo() /*/ (Aqui está!) // ;
    /*/ /*/ [Sabia que isto era possível???] /*/

    /*/} Eu não sabia! /*/{

    public void mostra() {
       System.out.println("Interessante!");
    }

    /*/ >> Pior que isto compila sem erros!! << //

    //*; { Por quê? /*/};

}





1 de junho de 2010

e-T.I.queta

Regras de etiqueta para o profissional de T.I. (Tecnologia da Informação)

Você já teve um professor de T.I. que parecia explicar o assunto para quem já soubesse do assunto?

Já foi a alguma palestra de T.I. em que o palestrante passou a imagem de ser um expert - mas você não entendeu nada do que ele disse?

Infelizmente, isto acontece com muita frequência, especialmente em nossa área de tecnologia.

Talvez o professor e o palestrante acima precisassem saber de algumas regras, como as listadas a seguir.

Se você é um profissional de T.I. e vai apresentar uma aula, ou uma palestra, precisa entender que:

1) Você vai ser bem sucedido em sua apresentação apenas se os espectadores entenderem com clareza o que você apresentou! Se eles ficarem com a impressão de que você deve conhecer muito bem o assunto, mas não entenderem nada do que você apresentou, acredite: você falhou em seu propósito!

2) Falar muitas siglas deve impressionar algumas pessoas, e na área de T.I. é quase impossível não utilizá-las: JEE, EJB, SOA... Mas se você vai, por exemplo, apresentar uma palestra sobre SOA, considere a possibilidade das pessoas não saberem o quê significa SOA. Faça uma breve introdução e esclareça o significado das principais siglas que você vai utilizar. A propósito: SOA significa "Service-oriented architecture" (Arquitetura Orientada a Serviços).

3) Se você se propôs a proferir uma palestra, assuma a missão de esclarecer, e não complicar, o assunto. O palco da apresentação não é o lugar mais apropriado para você demonstrar suas habilidades em falar difícil. Se o seu propósito é apenas exibir seus conhecimentos, inscreva-se numa maratona.

4) Não confunda "Marketing Pessoal" com "Exibicionismo Vazio". Você fará um excelente marketing de si mesmo ao demonstrar que possui boa didática e que consegue esclarecer um assunto complicado. Quem vai assistir a sua palestra, ou a sua aula, tem a intenção (mínima que seja) de aprender sobre o tema.

5) Ao preparar o material da sua apresentação (como slides, etc) procure manter o máximo de simplicidade. Seja objetivo, e elimine tudo o que não for estritamente necessário. Por exemplo: se você já disse que "o sistema funciona como se fossem 3 módulos se comunicando" então o gráfico abaixo é inteiramente inútil:


Ele se torna mais inútil ainda se estiver desenhado em 3 dimensões, com 32 cores. Não precisa passar a impressão de que você é um grande conhecedor de ferramentas gráficas.

6) Se você tem receio de que, ao apresentar sua palestra, alguém possa roubar a sua idéia genial sobre o tema, então não se ofereça a dar uma palestra sobre o tema. Seja um palestrante somente se você estiver disposto a explicar com clareza o assunto em questão.

7) Depois da sua apresentação, procure obter opiniões dos espectadores. Seu endereço de e-mail para contato, escrito no último slide da apresentação, é uma ótima idéia. Diga que você aceita sugestões para a melhoria constante da sua aula ou palestra.

8) Leia os e-mails com as sugestões enviadas pelos espectadores. Procure assimilar, com senso crítico, aquilo que costuma aparecer repetidamente como sugestão.


Acredito que desta forma, com estas regras simples, possamos tornar a área de T.I. melhor a cada dia, com melhores profissionais, dispostos a compartilhar o conhecimento entre si. Fique à vontade para enviar o link deste post ao seu professor que complica mais do que explica. Fique à vontade para me enviar mais regras de etiqueta que você gostaria de acrescentar aqui. E, por fim, fique à vontade para me enviar críticas ou sugestões sobre qualquer assunto deste blog.

Um abraço e até o próximo post!

IF bom é IF morto!

O título deste post se refere a uma boa prática em programação Orientada a Objetos. Nossa equipe de desenvolvimento já adotou este lema, e sempre que chega um colega novato, fazemos questão de apresentá-lo a esta lei: "É proibido utilizar IFs negociais", ou, melhor ainda: "IF BOM É IF MORTO".

Ao ouvir esta lei, a primeira pergunta óbvia que escutamos é: - "Se não posso utilizar IF, então vou utilizar o quê? Switch-Case?"

A resposta é: Não!

Na verdade, não devemos utilizar nem IFs, nem Switch-Case, nem qualquer outra estrutura condicional em nosso código!

Aqui, vamos chamar toda e qualquer estrutura de decisão - seja IF, Switch-Case, ou qualquer outra - apenas como IF pra facilitar.

E por quê não podemos utilizar IF em nosso código?

Pelo seguinte motivo: queremos nos beneficiar das inúmeras vantagens da Orientação a Objetos, e o IF nos remete à velha e ultrapassada programação estruturada. Aliás, o IF é a própria estruturação do código que desejamos evitar:

SE isso 
       FAÇA aquilo
SENÃO
       FAÇA aquilo outro

SWITCH(numero)
       CASO 1: faça aquilo
       CASO 2: faça aquilo outro
       etc etc
Ou seja, é a própria essência da estruturação, ou pelo menos, a característica mais engessada da programação estruturada! Ela demanda várias manutenções no código, sempre que um novo Caso for identificado. Ora, mas dentre outras coisas, não queremos utilizar a OO para minimizar o impacto das alterações?

Então, tome nota desta Lei: IF BOM É IF MORTO.

O questionamento que escutamos em seguida pelos programadores novatos é: - "Mas se não posso usar nem IF, nem Switch-Case, nem nada parecido, COMO é que vou tratar isso?"

É o que veremos a seguir. A regra do nosso exemplo diz que: o programa deverá receber do usuário uma informação "PF" ou "PJ", indicando se vai tratar de Pessoa Física ou Pessoa Jurídica.

SE for pessoa física, o sistema imprime na tela "Pessoa Física", consulta e grava dados num banco Oracle, envia um e-mail automático para a Empresa X, gera um LOG numa pasta específica, e imprime um Relatório de PF.

SE for pessoa jurídica, o sistema imprime na tela "Pessoa Jurídica", consulta e grava dados num banco DB2, envia 3 e-mails automáticos para a Empresa X, Y, Z, gera um LOG em outra pasta específica, e imprime 2 Relatórios de PJ.

Implementando da maneira estruturada, teríamos o seguinte:
public class  Executavel{
  public static void main(String[] args){
      String tipo = args[0];
      if(tipo.equals("PF")){
          System.out.println("Pessoa Física");
          System.out.println("Consultando Oracle...");
      }
      if(tipo.equals("PJ")){
          System.out.println("Pessoa Jurídica");
          System.out.println("Consultando DB2...");
      }
      GeraMail.trataTipo(tipo);
      TrataLog.grava(tipo);
      Relatorio.imprime(tipo);
  }
}
Um exemplo simples, que recebe o parâmetro do usuário na linha de comando, sem muito tratamento, apenas para ilustrar os malefícios da abordagem estruturada com IFs.

O "arquiteto" que projetou a solução acima, baseou toda a solução nos IFs. Note que, na chamada a GeraMail.trata(tipo), ele precisa enviar a informação do usuário (PF ou PJ), e dentro desta classe vamos ter outra estrutura de IFs para tratar o envio de e-mail. O mesmo acontece com TrataLog e Relatorio, replicando a estrutura de IFs por diversas partes do código.

Ao projetar a "brilhante" solução acima, o "arquiteto genial" pensou: - "Bem, os 2 únicos tipos possíveis neste caso para pessoa é Física ou Jurídica. Então, vou fazer com IFs mesmo". Aí então, 4 meses de trabalho depois, com toda a "arquitetura" pronta e faltando 1 semana para o prazo final da entrega do projeto, ele recebe um telefonema do Cliente, que diz: - "Preciso tratar mais um tipo! Se refere à PJS - Pessoa Jurídica optante pelo Simples, que, no meu requisito, vai possuir regras completamente diferentes dos outros tipos".

Pânico! Desespero! Ele precisa alterar o método main() acima para inserir mais um tratamento, e precisa alterar a classe GeraMail para tratar mais um IF, e precisa dar manutenção também na classe TrataLog, e também na Relatorio, e em mais 7.586 classes do sistema... :-) [...geralmente é o que acontece na prática, além de ter que testar tudo novamente...] Ou seja, muitas noites sem dormir, trabalho no sábado e domingo, aumento enorme da probabilidade de falhas no sistema, e muito provavelmente, mais um projeto que vai estourar custo e prazo!

Tudo porque ele negligenciou o nosso lema: IF BOM É IF MORTO.

Como fazer sem usar IFs? Assim:

Vou criar a interface Pessoa:
 public interface Pessoa{
        public void imprime();
 }
Vou criar a classe PessoaFisica, que vai conter todos os tratamentos que são de responsabilidade de PF (gravar no Oracle, enviar e-mail para X, etc), e a classe PessoaJuridica, que vai tratar regras de PJ (grava no DB2, etc, etc). Ambas as classes vão implementar a interface Pessoa.

Apenas para que possamos rodar o nosso exemplo, e ver a solução funcionando na prática, vou fazer com que as classes PF e PJ exibam na tela suas informações, sem tratar acesso a banco nem como gerar logs, etc (não é assunto deste post).

A classe PessoaFisica.java ficaria assim:
public class PessoaFisica implements Pessoa{
   public void imprime(){
      System.out.println("Pessoa Física");
      System.out.println("Consultando Oracle...");
   }
}
A classe PessoaJuridica.java ficaria assim:
public class PessoaJuridica implements Pessoa{
   public void imprime(){
      System.out.println("Pessoa Jurídica");
      System.out.println("Consultando DB2...");
   }
}
Agora, sem os malditos IFs, a mesma classe Executavel.java do início do exemplo:
public class Executavel{
  public static void main(String[] args){
     Map mapaDePessoas = new HashMap();
     mapaDePessoas.put("PF", new PessoaFisica());
     mapaDePessoas.put("PJ", new PessoaJuridica());
     String tipo = args[0];
     Pessoa pessoa = (Pessoa) mapaDePessoas.get(tipo);
     pessoa.imprime();
  }
}
Compile, rode e ateste a eficácia da solução:

/> java Executavel PF
Pessoa Física
Consultando Oracle...

/> java Executavel PJ
Pessoa Jurídica
Consultando DB2...

Pronto! De acordo com a informação dada pelo usuário, executei regras diferentes sem utilizar 1 único IF (nem Switch-Case...)

E se eu precisar de um novo tipo para Pessoa Jurídica optante pelo Simples - PJS? Aí então eu vou criar esta nova classe PJS, com suas regras específicas, e adicionar uma entrada no Mapa:

mapaDePessoas.put("PJS", new PJS());

E só! Nenhum outro ponto em todo o Projeto precisaria ser alterado!

Por fim, vale ressaltar que a desvantagem do IF é esta propagação de manutenções que ele demanda em caso de alteração. Entretanto, para utilizar o mecanismo da inicialização tardia, podemos fazer o trecho abaixo sem problemas:
public Conexao  getConexao(){
   if(this.conexao == null){
        this.conexao = new Conexao();
   }
   return this.conexao;
}
Você pode estar pensando: - "Mas você disse que é proibido utilizar IFs". Não, eu não disse isso!

O que eu disse foi: é proibido usar IFs negociais! Este não é um IF negocial, ele apenas testa o valor nulo. Ou, como diz a nossa Lei: "IF BOM É IF MORTO". Exatamente! Este IF acima está morto! Ou seja, não há a menor possibilidade de ele vir sofrer alterações futuramente. Na inicialização tardia, eu testo se o valor é nulo, e isto nunca vai ser alterado. O Cliente pode mudar todos os seus requisitos que, ainda assim, o método getConexao() acima vai continuar como está!

O exemplo aqui apresentado é uma implementação do GoF Pattern Strategy. As classes PessoaFisica e PessoaJuridica podem ser entendidas como estratégias que encapsulam seus algoritmos. Pretendo abordar o Strategy em outro post, específico sobre este padrão, para que possamos ver outros exemplos e discutir as abordagens do GoF. Até lá!

(*PS: não deixe de enviar seu comentário, dúvida, crítica, sugestões, discordâncias, perguntas, reclamações...)

28 de maio de 2010

Pegadinha Java III

Os posts marcados como "Pegadinha Java" têm a intenção de testar a sagacidade dos meus alunos iniciantes na Programação em Java. São tópicos curiosos e divertidos, e não têm a intenção de medir o conhecimento na linguagem Java.

A vocês, meus alunos, lembrem-se que a idéia é bater o olho no código e apontar a resposta correta. Naturalmente, trata-se de pegadinhas, ou seja, são trechos de código bastante mal-intencionados... :-)

Nesta terceira questão, considere o seguinte trecho de código:
public class Executavel{
public static void main(String[] args){
http://java.sun.com
System.exit(0);
}
}
Você deve estar pensando (ou pelo menos deveria): - Ei, esta terceira linha de código não é uma instrução válida do Java!

Exatamente! No entanto, eu digo a você que este trecho compila e roda sem erros!

E, infelizmente, você não pode afirmar o contrário, pois o contrário nem existe entre as respostas válidas... :-)

Portanto, marque abaixo a única alternativa correta:

a - compila e roda sem erros, mas não acontece nada.
b - compila e roda sem erros, e abre o browser com a página da Sun.
c - não é possível que isto compile!! A 3ª linha nem é código-java.
d - se isto compilar e rodar sem erros, minha JVM ficou maluca!
e - tem certeza que isto compila???

E a alternativa correta é a letra.......................... A

Muito bem! A resposta correta, como vocês podem conferir na máquina de vocês, é: "Compila e roda sem erros, mas não acontece nada". Mas o quê eu queria saber mesmo é:

Por quê isto compila e roda sem erros, se a 3ª linha não é código java?

Abraços a todos e até o próximo post!

Pegadinha Java II

Os posts marcados como "Pegadinha Java" têm a intenção de testar a sagacidade dos meus alunos iniciantes na Programação em Java. São tópicos curiosos e divertidos, e não têm a intenção de medir o conhecimento na linguagem Java.

A vocês, meus alunos, lembrem-se que a idéia é bater o olho no código e apontar a resposta correta. Naturalmente, trata-se de pegadinhas, ou seja, são trechos de código bastante mal-intencionados... :-)

Nesta segunda questão, vocês devem se lembrar do trecho de código abaixo:

System.out.println("010" + 10)

Todos vão se lembrar que o resultado não vai ser 20 e sim 01010. A explicação é fácil, pois o primeiro fator da soma é um String, logo ele vai converter o segundo fator também para String e concatenar os dois fatores.

Mas e o techo abaixo:

System.out.println(010 + 010)

O primeiro fator é numérico, logo o resultado da soma vai ser...

Muito bem! 16 (!!!)

Por quê?

Àqueles que descobriram a resposta, tenham a gentileza de não estragar a surpresa... :-)

Só me enviem mensagens se tiverem dúvidas, mas não revelem a resposta para os demais, certo?

Um abraço e até a próxima!

Pegadinha Java I

Os posts marcados como "Pegadinha Java" têm a intenção de testar a sagacidade dos meus alunos iniciantes na Programação em Java. São tópicos curiosos e divertidos, e não têm a intenção de medir o conhecimento na linguagem Java.

A vocês, meus alunos, lembrem-se que a idéia é bater o olho no código e apontar a resposta correta. Naturalmente, trata-se de pegadinhas, ou seja, são trechos de código bastante mal-intencionados... :-)

Nesta primeira questão, pergunto: vocês sabem somar 21 + 5?

Bem simples, certo? Então me digam o que será mostrado no trecho de código abaixo:
public class Executavel{
public static void main(String [] args){
System.out.println(2l + 5);
}
}
Muito bem! O resultado não é 26. Mas, por quê não é 26?

Bom, esta é a resposta que eu espero de vocês, esforçados alunos! Copiem o código acima, colem no editor Java, compilem, rodem, e me digam por quê o resultado mostrado não foi 26. (*Lembrem-se, vocês têm que copiar e colar o código acima, não vale digitar vocês mesmos).

Àqueles que descobriram a resposta, tenham a gentileza de não estragar a surpresa... :-)

Só me enviem mensagens se tiverem dúvidas, mas não revelem a resposta para os demais, certo?

Um abraço e até a próxima!

Adapter

De acordo com GoF, Adapter serve para "converter a interface de uma classe em outra interface esperada pelos clientes. O Adapter permite que certas classes trabalhe em conjunto, pois de outra forma seria impossível por causa de suas interfaces incompatíveis".

Para exemplificar, vou exibir uma situação simples em que se pode implementar o Adapter. A idéia deste blog é fazer com que você possa entender como funcionam os Padrões, e a partir daí, possa utilizá-lo em sua situação prática.

No nosso exemplo, vamos imaginar que você tenha uma classe responsável por imprimir seus relatórios. O nome desta classe é PrintaRelatorio, e ela vai possuir um atributo chamado out do tipo PrintStream. É um objeto que todos os desenvolvedores Java conhecem bem, utilizado sempre que mostramos algo na tela com System.out.println("texto"); é o mesmo System.out que nossa classe vai utilizar. Ela terá um método chamado imprime que receberá uma String como parâmetro e imprimirá na tela o resultado.

O código completo de PrintaRelatorio.java será:
public class PrintaRelatorio {
private PrintStream out;

public PrintStream getOut(){
return this.out;
}
public void setOut(PrintStream out) {
this.out = out;
}
public void imprime(String texto){
getOut().println(texto);
}
}
Um atributo out do tipo PrintStream, seus getters e setters, e o método imprime() recebendo como parâmetro o texto a ser mostrado no console da aplicação com println(). Bastante simples!

Para utilizar a funcionalidade da classe acima, bastaria fazer em nossa classe Executável:
public static void main(String[] args) {
PrintaRelatorio printa = new PrintaRelatorio();
printa.setOut(System.out);
printa.imprime("texto");
}
O requisito do Cliente era apenas este: ter uma classe que permita imprimir no console uma informação. Está feito!

Entretanto, como todo analista sabe muito bem, um requisito serve para ser alterado pelo Cliente, várias vezes, durante todo o projeto!

Em nosso exemplo, o Cliente quer agora que a informação seja exibida em uma "telinha bonitinha" e não mais no console da linha de comando. Porém, a classe PrintaRelatorio já está homologada em produção, sendo utilizada por vários outros programas, permeada em diversos sistemas... e o fato é que você não vai poder mexer em PrintaRelatorio.java!

E assim surge o nosso primeiro problema: para exibir a informação na tela que o Cliente deseja, precisaremos utilizar JOptionPane.showMessageDialog() no lugar de System.out.println(). Porém, eu não posso fazer printaRelatorio.setOut(JOptionPane) porque este método espera algo do tipo PrintStream. Além do mais, o método imprime() de printaRelatorio invoca o println(), e eu preciso invocar showMessageDialog(). Como resolver isso sem alterar a classe PrintaRelatorio?

Resposta: aplicando o padrão Adapter. De novo, GoF: "converte a interface de uma classe em outra interface esperada pelos clientes". É a mesma situação que temos!

Vou criar uma classe chamada AdaptadoraParaPainel que vai estender PrintStream:

public class AdaptadoraParaPainel extends PrintStream

Ao fazer isso, ela me obriga a inserir um construtor para informar um Arquivo, visto que a especialidade dela é trabalhar com streams. Mas eu não vou trabalhar com arquivos! Então vou inserir o construtor que ela espera, declarar a exceção que ela espera, apenas para o compilador parar de reclamar. Ou seja:

//Construtor
public AdaptadoraParaPainel() throws FileNotFoundException{
.....super(new File("nada"));
}

Aí então vou inserir o método que a classe cliente espera que eu chame, com a mesma assinatura, mas invocando o mecanismo que eu preciso agora, que é showMessageDialog(). Isto é:

public void print(String texto){
.....JOptionPane.showMessageDialog(null,texto);
}

Portanto, assim fica o código completo de AdaptadoraParaPainel.java :
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import javax.swing.JOptionPane;

public class AdaptadoraParaPainel
extends PrintStream {

public AdaptadoraParaPainel()
throws FileNotFoundException{

super(new File("nada"));
}
public void print(String texto){
JOptionPane.showMessageDialog(null,texto);
}
}
Com isso, minha classe cliente vai poder continuar invocando o método setOut() que espera um PrintStream: a minha classe adaptadora é um PrintStream! O código completo da classe Executavel.java seria:
import java.io.FileNotFoundException;
import java.io.PrintStream;

public class Executavel {
public static void main(String[] args) throws
FileNotFoundException {
PrintaRelatorio printa = new PrintaRelatorio();
printa.setOut(new AdaptadoraParaPainel());
printa.imprime("texto");
}
}
Pronto! A informação impressa em uma tela e não mais no console, como queria o Cliente, e a classe PrintaRelatorio permanece intocada.

Mas eu ainda tenho algo importante a dizer. Na situação apresentada aqui, o padrão Adapter precisou ser utilizado, de fato. Porém, é preciso notar que: um bom projetista, um arquiteto competente, não teria deixado isto acontecer! A forma como foi construída a classe PrintaRelatorio deixou o mecanismo amarrado ao PrintStream! Esta foi uma péssima decisão! Um bom arquiteto teria notado que:

"Peraí! O Cliente está me pedindo um objeto para imprimir no console. Mas vai que amanhã ele mude de idéia e queira imprimir numa tela? Ou numa página html? Ou direto na impressora? Ou enviar por e-mail? Afinal de contas, um requisito do Cliente foi feito para ser alterado, diversas vezes, durante o projeto..."

Um bom arquiteto teria construído sua classe aplicando o Padrão Bridge. Diz o GoF sobre o Bridge: "desacopla uma abstração da sua implementação, de modo que as duas possam variar independentemente". Se o Padrão Bridge tivesse sido aplicado, não teríamos a necessidade de adaptar mais tarde.

O livro do GoF, na página 160, diz que:
"O padrão Adapter é orientado para fazer com que classes não-relacionadas trabalhem em conjunto. Ele é normalmente aplicado a sistemas que já foram projetados. Por outro lado, Bridge é usado em um projeto, desde o início, para permitir que abstrações e implementações possam variar independentemente."

Mas o Padrão Bridge é assunto para outro post. Não deixe de me enviar uma mensagem em caso de críticas, sugestões ou dúvidas. Obrigado e até a próxima.

26 de maio de 2010

Decorator

Segundo o GoF, "os Decorators fornecem uma alternativa flexível a subclasses para extensão da funcionalidade".

A palavra importante aí é "extensão". Ao se criar um objeto decorador, ele não deverá ter conhecimento da classe que ele vai decorar. Um decorador "barra de rolagem" só deverá saber que ele vai extender a funcionalidade, acrescentando o mecanismo da barra de rolagem. Será utilizado num campo texto? Será utilizado num treeview? Bom, ele não precisa (nem pode) saber!

Por quê eu digo que "nem pode" saber? Porque se ele tiver um conhecimento, uma referência à classe que ele vai decorar, ele vai ficar amarrado a esta classe! Caso você precise agora decorar uma tabela com barra de rolagem, a classe decoradora terá que ser alterada também, para incluir uma nova referência ao objeto tabela. E isso é ruim, muito ruim! Todo novo objeto a ser decorado com barra de rolagem demandará manutenção no código da classe decoradora! Além do mais, isso fere um dos princípios mais básicos das boas práticas da Orientação a Objetos: o Acoplamento, isto é, o grau em que uma classe conhece outra classe. Devemos buscar sempre o
Baixo Acoplamento.

Logo, o objeto decorador "barra de rolagem" só precisa saber que ele vai adicionar este mecanismo ao objeto. Precisa de barra de rolagem num campo texto? Basta fazer campo.add(barraDeRolagem); agora eu preciso em uma tabela... Ok, basta fazer tabela.add(barraDeRolagem); o decorador sofreu alguma alteração no código? Absolutamente
nenhuma!

Utilizando o exemplo do objeto Pizza que deverá ser decorado com Condimentos, obteremos o seguinte: tanto Pizza quanto Condimentos serão produtos alimentícios, dos quais eu vou querer saber o preço e descrição. Teremos:

public interface ProdutoAlimenticio {
public double getCusto();
public String getDescricao();
}
O meu Decorator também será do gênero produto alimentício (queijo, orégano, etc), logo, vai implementar a interface acima. Se eu pedir a descrição dos condimentos da pizza, precisarei imprimir "Queijo, Orégano, Calabreza, etc", ou seja, sobrescrevo o getDescricao() para concatenar com vírgulas. Logo, teremos:
public abstract class CondimentoDecorator
implements ProdutoAlimenticio {

public abstract String getDescricaoDoComplemento();
public String getDescricao() {
return ", " + getDescricaoDoComplemento();
}
}
A classe Pizza deveria implementar a interface ProdutoAlimenticio, porém, vou adicionar uma camada intermediária, entre Pizza e ProdutoAlimenticio, com o intuito de flexibilizar a adição de novos pratos além de Pizza, como por exemplo HotDog. Vou chamar esta classe de Refeição, e veremos na prática como isto vai facilitar o reaproveitamento de código, o que é, aliás, uma das grandes vantagens da Orientação a Objetos.

Portanto, a classe Refeição deverá implementar a interface ProdutoAlimenticio, mas ainda será uma classe abstrata, pois ela ainda não sabe informar o custo da refeição - getCusto() - o que vai ser responsabilidade de suas subclasses, até porque este custo vai ser um para Pizza e outro para HotDog, etc.

Note que na classe Refeição não há qualquer referência a decoradores ou qualquer outro objeto que não interessa a ela. Tudo o que a classe Refeição sabe é que ela é um Produto Alimentício, que refeições têm um custo e uma descrição, e que refeições podem adicionar complementos - que, por sua vez, também serão Produtos Alimentícios. Logo, a classe Refeicao.java fica assim:
public abstract class Refeicao
implements ProdutoAlimenticio{

private String descricao;
private double custo;
protected abstract double getPrecoDaRefeicao();

// Construtor da classe com String como parâmetro
protected Refeicao(String descricao){
this.descricao = descricao;
this.custo = getPrecoDaRefeicao();
}
public double getCusto() {
return custo;
}
public String getDescricao() {
return descricao;
}
public void add(ProdutoAlimenticio complemento) {
custo += complemento.getCusto();
descricao += complemento.getDescricao();
}
}
O que o método add acima faz, é simplesmente incrementar o custo da própria refeição com o custo de seu complemento, e da mesma forma, concatenar sua descrição com a descrição do complemento. Note que este método recebe um ProdutoAlimenticio como parâmetro; é tudo o que a refeição precisa saber - que ela pode adicionar complementos alimentícios. E só!

Agora vamos implementar uma classe concreta de Decorator, por exemplo, Queijo. Você concorda comigo que Queijo poderá decorar tanto Pizzas quanto HotDogs? E que poderá decorar pratos que ainda nem foram criados? Que, se amanhã o cliente inventar um novo prato, e precisar decorá-lo com Queijo, este decorador deverá ser capaz de atendê-lo? Ou seja, está bastante evidente que na classe Queijo NÃO poderá haver referências a pratos, nem a pizzas, nem a refeições. Até porque, na classe CondimentoDecorator.java acima, a regra de concatenar sua descrição com vírgula já foi implementada. Logo, para a classe Queijo.java sobrou apenas informar o seu custo e sua descrição - responsabilidades pertinentes apenas à classe Queijo e à mais ninguém! Portanto, Queijo.java fica apenas assim:
public class Queijo extends CondimentoDecorator {
public double getCusto() {
return 5;
}
public String getDescricaoDoComplemento() {
return "Queijo";
}
}
Ou seja, a classe Queijo é um CondimentoDecorator; e Queijo informa seu custo e sua descrição. Você sequer precisa descobrir quais métodos implementar na classe Queijo! Como Queijo extends CondimentoDecorator, a própria compilação te informa que você precisa implementar o método abstrato getDescricaoDoComplemento(); e que você precisa implementar o método abstrato getCusto() , uma vez que CondimentoDecorator não o implementou de ProdutoAlimenticio. Isto é, não tem como errar! É tão fácil adicionar novos condimentos que iremos adicionar mais um: Calabreza.java :
public class Calabreza extends CondimentoDecorator {
public double getCusto() {
return 7;
}
public String getDescricaoDoComplemento() {
return "Calabreza";
}
}
Agora vamos implementar a classe Pizza. Adivinha o que sobrou para ser implementado em Pizza? Exato! Apenas a responsabilidade do que é de Pizza, ou seja, assim como Queijo e Calabreza, a classe Pizza só precisa informar seu custo e sua descrição! Aqui eu vou delegar ao construtor da classe Pizza a atribuição da descrição, certo? Só para permitir ao Cliente fazer: Pizza marguerita = new Pizza("Marguerita") ou Pizza simples = new Pizza("Mussarela"), etc. Logo, a classe Pizza.java fica apenas assim:
public class Pizza extends Refeicao {
//Construtor atribui a descrição
public Pizza(String descricao) {
super(descricao);
}
public double getPrecoDaRefeicao() {
return 10;
}
}
Da mesma forma, Pizza é uma Refeição. Aqui também a compilação te informa o quê é preciso implementar. Caso você se esqueça de algo (ou algum colega novato na equipe...) será possível identificar o problema em tempo de compilação - o que torna o nosso código muito mais resistente a falhas! Importante perceber também que Pizza.java não tem referência a nenhum decorador. Claro! Nem poderia! Iria ter referência a qual decorador? Queijo? Aqui, da forma como está feito, caso o Cliente crie um novo decorador (orégano, por exemplo), tudo o que eu preciso fazer é criar uma nova classe Oregano.java. Só isso! A classe Pizza.java (nem qualquer outra) não vai sofrer nenhuma alteração.

Bom, vamos testar o projeto numa classe Executavel.java, que contenha um método main():
public class Executavel {
public static void main(String[] args) {
Pizza p = new Pizza("Pizza Completa");
p.add(new Queijo());
p.add(new Calabreza());
System.out.println("Descricao: "+p.getDescricao());
System.out.println("Total: R$ "+p.getCusto());
}
}
Pronto! Tudo o que eu preciso fazer para decorar o objeto Pizza com um novo comportamento é passar o Decorator como parâmetro ao método add(). Note que, como este método precisa de um ProdutoAlimenticio, e Pizza é um ProdutoAlimenticio, eu poderia fazer:

Pizza pizza = new Pizza("Pizza Dupla");
pizza.add(new Pizza(""));

Como exercício, fica pra você a tarefa de criar um novo prato HotDog.java e um novo decorador para o complemento Oregano.java, e acrescentar orégano à Pizza já existente, e também reaproveitar Queijo e Calabreza para adicionar ao HotDog. (Abaixo está a solução)
Tudo se resume a isso:

public class HotDog extends Refeicao {
public HotDog(String descricao) {
super(descricao);
}
public double getPrecoDaRefeicao() {
return 9;
}
}
public class Oregano extends CondimentoDecorator {
public double getCusto() {
return 2;
}
public String getDescricaoDoComplemento() {
return "Orégano";
}
}
public class Executavel {
public static void main(String[] args) {
Pizza p = new Pizza("Pizza");
p.add(new Queijo());
p.add(new Calabreza());
p.add(new Oregano());

HotDog dog = new HotDog("Hot Dog");
dog.add(new Queijo());
dog.add(new Calabreza());

System.out.println("Descricao: "+p.getDescricao());
System.out.println("Total: R$ "+p.getCusto());

System.out.println("Descricao: "+dog.getDescricao());
System.out.println("Total: R$ "+dog.getCusto());
}
}
Bem, espero que tenham gostado! Qualquer dúvida deixe uma mensagem que terei o maior prazer em respondê-la! Também escrevam para enviar sugestões. Pretendo colocar exemplos bem explicativos de todos os 23 padrões do GoF, que é um assunto obrigatório para quem quiser programar orientado a objetos, e onde ainda vejo muita deficiência por aí... Abraços.