terça-feira, 26 de abril de 2011

on

 

 

 

 

 

Padrões de Projeto


José de Oliveira Guimarães
Departamento de Computação – UFSCar
São Carlos – SP
jose@dc.ufscar.br



Introdução

Programadores freqüentemente empregam soluções que eles já utilizam antes em situações parecidas. Estas soluções envolvem o projeto de classes, redefinições de métodos, combinação de objetos, delegação de mensagens e asssim por diante. Padrões de projeto, que nada mais são que documentação destas soluções. Um padrão é composto por quatro elementos: um nome, uma descrição do problema que ele resolve, como ele pode resolver o problema (a solução) e as conseqüências de se usar o padrão.

O nome do padrão torna a comunicação entre programadores fácil. Um único nome descreve toda uma solução e traz com ela suas vantagens e desvantagens. A documentação dos programas fica mais abstrata,
pois é necessário descrever menos detalhes --- a descrição dos padrões não precisa ser repetida.

A descrição do problema diz em que situações o padrão se aplica, quando ele pode ser usado. A solução descreve o projeto que resolve o problema. O projeto consiste de classes, objetos e seus interrelacionamentos. A solução é abstrata --- ela pode ser  aplicada em diversas situações semelhantes e independe da linguagem utilizada.

Cada padrão possui vantagens e desvantagens, as conseqüências de  se usar o padrão. Este elemento descreve meios alternativos de implementação, como a manutenção é facilitada ou dificultada pelo padrão, quais são as compensações (trade-offs) de tempo/espaço, flexibilidade/rigidez envolvidos no uso do padrão.

Gamma et al. descrevem vinte e três padrões que podem ser utilizados em praticamente qualquer área de programação. Estes padrões se tornaram clássicos da orientação a objetos. Eles foram utilizados por muitos programadores muito antes do lançamento deste livro. Mas não tinham sido sistematicamento documentados e  catalogados. Os padrões de Gamma et al. são chamados de padrões da gangue dos quatro (GoF patterns).


Os padrões que estudaremos se encaixam em três categorias:


1.       padrões de criação. Eles auxiliam a criação de objetos, tornando o programa menos dependente do modo como os objetos são criados e compostos. Assim, estes padrões permitem que se mude as classes dos objetos criados em execução com pouco esforço do programador;
2.       padrões de estrutura. Descrevem modos flexíveis de compor classes e objetos. Como exemplo, o padrão Composite descreve como compor classes primitivas e compostas de tal forma que ambas possam ser utilizadas pela mesma interface (uma superclasse);
3.       padrões de comportamento. Estes padrões são relacionados a algoritmos e responsabilidades associadas a cada objeto. Mudando-se os objetos e classes utilizados, podemos mudar o comportamento do programa. Compondo um objeto a outro, podemos adicionar comportamento ao primeiro objeto.

Além de padrões de projeto, existem padrões de análise, manutenção, teste, documentação, etc. De fato, podem ser criados padrões para qualquer tópico onde uma solução é empregada várias vezes para um mesmo problema. Rising  [3] apresenta centenas de padrões das mais diversas áreas como contabilidade, defesa aérea, relacionamento servidor-cliente, criptografia, sistemas distribuidos, máquinas de estado finito, saúde, multimídia, paralelismo, eficiência, sistemas de tempo real, segurança, reconhecimento de voz, teste de software, treinamento, telecomunicações, desenvolvimento de sites para a Web, etc. Pode-se também criar padrões para tópicos fora da computação, como composição musical. De fato, padrões surgiram em arquitetura com o livro de Christofer Alexander [1]  "Timeless Ways of Building". 

Os padrões da GoF podem ser, em geral, utilizados independentemente uns dos outros. Este não é o caso geral. é mais freqüente um conjunto de padrões resolver um problema. Este conjunto é uma linguagem de padrões. Há uma linguagem de padrões para teste de software composta pelos padrões "Designers are our friends", "Get involved early", "Time to test", ... Existem muitos relacionamentos entre estes padrões descritos na documentação. Em geral, não se pode ou deve utilizar um padrão de uma linguagem de padrões isoladamente. Eles só fazem sentido dentro da linguagem.

Descrevemos a seguir os padrões da GoF [2].

Padrões de Criação.


Fábrica Abstrata --- Abstract Factory


Motivação

Um programa utiliza uma interface gráfica para interagir com o usuário. Esta interface utiliza janelas, botões, menus, janelas de texto, etc. O programa foi feito para ser utilizado no Windows e utiliza toda a padronização gráfica deste ambiente. Para fazê-lo funcionar com uma interface diferente no Windows ou em outra plataforma como X-window, teríamos que modificar todos os pontos de criação de objetos gráficos e sua manipulação.



O padrão fábrica abstrata permite fazer um programa que execute em diversar plataformas gráficas. Isto é feito definindo-se uma classe {\tt WidgetFactory} que retorna os objetos gráficos utilizados na plataforma --- veja a Figura. Deve haver um único objeto no programa da classe {\tt WidgetFactory}. Todos os objetos da interface gráfica são retornados pelos métodos de {\tt WidgetFactory}.

Pode-se modificar a plataforma gráfica utilizando-se um objeto de uma subclasse de {\tt WidgetFactory}. Esta subclasse retorna objetos daquela plataforma.

Aplicabilidade

Este padrão deve ser utilizado quando o programa deve ser configurado por famílias de classes. Todas as classes de uma mesma família devem ser utilizadas em conjunto. Fábrica abstrata impede que objetos de diferentes plataformas gráficas sejam misturadas em um mesmo programa, pois cada subclasse retorna objetos de uma mesma plataforma. Note que o programa não sabe as classes dos objetos gráficos que usa. Este conhecimento esta encapsulado no objeto fábrica abstrata.

Estrutura




Conseqüências

1.       fábrica abstrata permite a troca de toda uma coleção de classes sem grandes modificações no programa fonte;
2.       as classes concretas são conhecidas apenas pela fábrica abstrata;
3.       é difícil colocar classes novas no programa. é necessário acrescentar métodos na classe fábrica abstrata e em todas as subclasses;
4.       as classes dos produtos devem ter uma interface comum. Talvez seja necessário restringi-las para conseguir este objetivo.


Implementação

A seguir mostramos parte do código de um programa que utiliza o exemplo da Figura da motiva.

virtual class WidgetFactory {
  public static WidgetFactory factory;
  public virtual ScrollBar createScrollBar();
  public virtual Window    createWindow();
  ...
  }
 
class MotifWidgetFactory extends WidgetFactory {
  public ScrollBar createScrollBar() {
    return new MotifScrollBar;
    }
  public Window    createWindow() {
    return new MotifWindow();
    }
  ...
 }

class PMWidgetFactory extends WidgetFactory {
  public ScrollBar createScrollBar() {
    return new MotifScrollBar;
    }
  public Window    createWindow() {
    return new MotifWindow();
    }
  ...
 }

   
...
/* Este codigo deve ser executado antes de criar qualquer objeto
   grafico. So deve existir uma unica atribuicao a
   WidgetFactory.factory em todo o programa */
  
WidgetFactory.factory = new MotifWidgetFactory;
  


Construtor --- Builder



Motivação

Suponha que um editor de textos seja capaz de mostrar arquivos em RTF no vídeo. Além disto, ele pode converter arquivos RTF em ASCII, TeX ou outro formato. O número de possíveis conversões pode crescer indefinidamente. Como podemos acrescentar um novo formato para conversão reaproveitando o código dos conversores existentes ?



O padrão Construtor foi feito para resolver este problema. Um objeto RTFReader varre o texto RTF e pede a um objeto TextConverter para converter cada parte do texto para ASCII, \TeX ou outro formato. O objeto de TextConverter não retorna o texto convertido. Ele o mantêm e só o retorna quando solicitado. Para mudar o alvo da conversão, basta fazer a variável {\tt builder} de RTFReader apontar para um objeto de subclasse de {\tt TextConverter}. O objeto conversor é chamado de construtor.

Estrutura





Colaborações

A Figura abaixo mostra as colaborações entre os objetos deste padrão. Os métodos BuildPartA, BuildPartB, ... constroem o texto convertido, recuperado com GetResult.






Implementação

Este exemplo em Java ilustra a implementação deste padrão.


class RTFReader {
  RTFReader( File ptextFile, TextConverter textConverter )
  {
    textFile = ptextFile;
    builder = textConverter;
  }
  public File ParseRTF()
  {
    // scan the file calling the methods of builder
    ...
    return builder.GetResult();
  }
}

class TextConverter {
  public void convertChar( char ch ) { ... }
  public void convertParagraph( Paragrah p ) { ... }
  ...
  }
 
class ASCIIConverter extends TextConverter {
  public void convertChar( char ch ) { ... }
  public void convertParagraph( Paragrah p ) { ... }
  ...
  }  




Método Fábrica --- Factory Method


Motivação




Método fábrica permite que se decida na subclasse a classe de um objeto criado na superclasse. Um exemplo desta necessidade aparece em um programa que pode editar diversos tipos de documentos. A classe Application define algumas operações sobre documentos. Uma destas operações precisa criar um objeto da classe Document ou suas subclasses. Mas a classe exata só será conhecida na subclasse de Application. A solução é colocar em Application um método CreateDocument que retorna um objeto da classe documento.  Este método é utilizado por NewDocument para criar e abrir um documento.


Estrutura

Conseqüências

O método CreateDocument na subclasse cria objetos de uma classe específica, fixada em compilação. Se for necessário criar objetos de outra classe, uma outra subclasse de Application deve ser definida.

Naturalmente, podemos utilizar alguma técnica em que os objetos criados não estão atrelados à subclasse, mas aí teríamos um outro padrão, que pode ser Prototype.


Implementação


class Document {
  public void open() { ... }
  public void close() { ... }
  ...
  }
 
class DrawingDocument extends Document {
  // redefine os métodos herdados
  ...
  }
 
class Application {
  public virtual Document CreateDocument();
  public void NewDocument() {
    Document *doc = CreateDocument();
    docs.Add(doc);
    doc->open();
    ...
    }
  ...
  }
 
class DrawingApplication extends Application {
  // redefine CreateDocument
  public virtual Document CreateDocument() {
    return new DrawingDocument;
    }
  // possivelmente redefine tambem outros metodos
  }


 

Protótipo --- Prototype



Motivação

Um editor para composição musical utiliza uma interface gráfica que permite a adição de elementos gráficos à música tais como notas, linhas, traves de sol e outros indicadores musicais. Cada nota tem um botão associado. O usuário clica no botão para inserir a nota.

A classe GraphicTool é a superclasse da classe de todos os objetos botões que inserem objetos musicais. Graphic é a superclasse de elementos musicais como notas ou linhas. Cada objeto botão pertence a uma subclasse de GraphicTool que é associada a exatamente uma subclasse de Graphic. Quando o usário clica no botão de GraphicTool e adiciona o objeto gráfico na composição, o objeto de GraphicTool cria um novo objeto da subclasse de Graphic associada ao botão.  Existe então uma proliferação de subclasses de GraphicTool que torna mais difícil a incorporação de novos objetos musicais no programa.




Este problema só se resolve cortando-se a ligação entre as subclasses de GraphicTool e subclasses de Graphic. Isto é feito pelo padrão Protótipo. Um objeto de GraphicTool possui um ponteiro para um objeto de Graphic chamado de protótipo. Quando o usuário clica no botão, o protótipo associado é clonado. Então, pode-se utilizar apenas uma classe GraphicTool. Varia-se os objetos Graphic criados variando-se o protótipo.


Conseqüências

O padrão protótipo esconde as classes dos objetos gráficos dos clientes, aqueles que usam GraphicTool. Além disto,

¨       novos objetos podem ser adicionados a botões em execução;
¨       não há necessidade da criação de inúmeras subclasses de GraphicTool;
¨       implementar a operação de clonagem pode ser difícil.


Implementação


class GraphicTool extends Tool {
  public GraphicTool( Graphic *pproto ) {
    prototype = pproto;
    }
  public void Manipulate() {
    Graphic p = prototype.clone();
    ...
    }
  }
 
class Graphic  {
  public Graphic clone() { ... }
  ...
  }
 
class MusicalNote extends Graphic {
  // redefine clone
  public Graphic clone();
  ...
  }     

class HalfNote extends MusicalNote {
  ...
  }

Estas classes poderiam ser utilizadas como em

HalfNote p = new HalfNote(...);

GraphicTool HalfNoteGraphicTool = new GraphicTool(p);
...


Objeto  Único - Singleton


Motivação




Algumas classes devem ter uma única instância. Deve existir exatamente um planeta terra, um  print spooler, uma saída padrão e um gerenciador de janelas. O padrão singleton assegura que apenas um objeto da classe será criado e fornece um ponto onde este objeto é manipulado. A própria classe, através de seus métodos estáticos, criará sua instância.



Conseqüências

Uma classe singleton pode permitir um número variável de instâncias ou um número fixo maior do que um.


Implementação


class Singleton {
  public static Singleton Instance() {
    if ( _instance == null )
      _instance = new Singleton;
    return _instance;
    }
  // métodos normais da classe, não estáticos
  private static Singleton _instance;
  // dados privados da classe, não estáticos
  }

// em algum outro lugar ...
Singleton s = Singleton.Instance();
s.operation();
...


Padrões Estruturais



Adaptador - Adapter


Motivação

Suponha que a sua aplicação utilize uma classe Pilha e que você deseje utilizar objetos desta classe em uma biblioteca que exige objetos de uma classe Stack.  As interfaces destas classes são incompatíveis, tornando-as não intercambiáveis. A solução é utilizar um adaptador para adequar uma interface à outra. O exemplo de motivação de Gamma et al. [2] é mostrado abaixo.







TextView está sendo adaptado a uma visão Shape. Um objeto de TextShape delega algumas de suas mensagens a um objeto de TextView.

Aplicabilidade

¨       duas classes com interfaces incompatíveis devem ser utilizadas em conjunto;
¨       um adaptador de objetos (Figura abaixo) pode ser utilizado com objetos de Adaptee e suas subclasses;
¨       adaptador pode ser utilizado mesmo quando os métodos das classes não são iguais ou uma das classes não possui todos os métodos da outra.


Estrutura









Conseqüências

¨       uma classe adaptadora (Adapter) é ligada a uma única classe Adaptee. Enão não se pode adaptar subclasses de Adaptee a Target;
¨       uma classe adaptadora não funciona se o objeto a ser convertido a Target não foi criado por nós;
¨       um objeto adaptador (Adapter) pode adaptar objetos da classe Adaptee e suas subclasses;
¨       a adaptação pode ser uma simples conversão dos nomes dos métodos (push <-> empilha) como pode envolver algoritmos complexos


Ponto - Bridge


Motivação

Um aplicativo deve funcionar no X-Window e no Presentation Manager (PM), duas  bibliotecas de interface gráfica com o usuário. A classe Window poderia ter duas subclasses, como mostra a Figura 2.  Mas então uma subclasse IconWindow deveria também ter duas subclasses, levando a uma explosão do número de subclasses.
                O usuário seria forçado a escrever o nome de uma classe específica ao criar um objeto, tornando o aplicativo difícil de funcionar em outra plataforma.
                A solução é desacoplar a interface de Window e suas duas implementações --- veja a Figura 1. As classes da hierarq           uia de Window delegam operações para objetos de WindowImp e suas subclasses.

Aplicabilidade

Use este padrão quando
¨       existir uma proliferação de subclasses que diferem entre si apenas pela implementação utilizada como na Figura 1;
¨       uma classe não deve estar ligada à sua implementação para que a implementação possa ser recompilada sem afetar a classe. Ou quando a implementação deve variar em execução.








Figura 1: Crescimento no número de subclasses





Figura 2: O padrão Bridge



Estrutura






Implementação

abstract class WindowImp {
  public abstract void DeviceRect( Coord a, Coord b, Coord c, Coord d); 
  public abstractvoid DeviceText( String s, Coord a, Coord b );
  ...
  }

class Window {
  Window( View *contents ) { ... }
  public void DrawLine( Point a, Point b ) { ... }
  public void DrawText( String s, Point a) { ... }
  public void DrawRect( Point a, Point b ) { ... }
  ...
  private WindowImp _imp;
  };


Compositor - Composite



Motivação

Um programa gráfico utiliza objetos das classes Line, Rectangle, Text, todas subclasses de Graphicl A classe Picture representa um conjunto de objetos Graphic. O programa tem que tratar de forma diferente objetos básicos, de subclasses de Graphic, e objetos de Picture. Mesmo se eles suportam algumas operações semelhantes.
                O padrão Compositor faz Picture herdar de Graphic, tornando possível tratar objetos básicos e compostos da mesma maneira.







Estrutura







Conseqüências

¨       o código do aplicativo se simplifica, já que objetos compostos e folhas são tratados uniformemente;
¨       objetos de Picture irão trabalhar com qualquer subclasse de Graphic que seja acrescentada ao programa

Implementação

abstract class Graphic {
  public abstract void Draw();
  ...
  }

class Line extends Graphic {
  public void Draw() { ... }
  ...
  }

class Picture extends Graphic {
  public void Draw() {
    for all g in graphics
      g.Draw();
    }
  public void Add(Graphic g) { ... }
  ...
  }


Decorador - Decorator


Motivação


Suponha que tenhamos uma classe TextView que mostra um texto em uma janela. A janela poderia ser incrementada com uma borda e/ou uma barra de rolagem. Isto poderia ser feito por herança criando-se três subclasses.







                Uma alternativa a herança é o uso do padrão Decorador. Um objeto de TextView é decorado por um objeto de ScroolDecorator e/ou BorderDecorator:





O ponteiro component aponta para o próximo decorador ou objeto de TextView. Uma mensagem Draw enviada a aBorderDecorator causa a execução do método Draw de BorderDecorator. Este método chama o método Draw de aScrollDecorator e desenha a borda. O método Draw de ScrollDecorator chama o método Draw de aTextView e desenha a barra de rolagem. Qualquer outra mensagem enviada a aBorderDecorator é simplesmente delegada a aScrollDecorator que a delega a aTextView.



Estrutura



Conseqüências

¨       decoradores podem ser retirados e acrescentados em execução. Mas ponteiros para aTextView continuam enxergando o objeto sem decoração;
¨       não há necessidade de criar inúmeras subclasses.

Implementação

abstract class VisualComponent {
  public abstract void Draw();
  public abstract void Do();
  ...
  }

class TextView extends VisualComponent {
  public void Draw() { ... }
  public void Do() { ... }
  ...
  }

class Decorator extends VisualComponent {
  public Decorator( VisualComponent c ) {
    component = c;
    }
  public void Draw() {
    component.Draw();
    }
  public void Do() {
    component.Do();
    }
  private VisualComponent component;
  }

class BorderDecorator extends Decorator {
  public void Draw() {
    super.Draw();
    DrawBorder();
    }
  public void DrawBorder() { ... }
  ...
  }


Fachada - Facade

 
Motivação

Um compilador possui classes Scanner, Parser, ProgramNode, etc. Objetos destas classes são utilizados por clientes do compilador, que então precisam conhecer inúmeras classes mesmo que utilizem poucas  funcionalidades de cada uma delas.
                O padrão fachada resolve este problema providenciando uma classe facade que abstrai todo o subsistema "compilador". Torna-se mais fácil utilizar o compilador porque há apenas uma interface a ser compreendida.

Estrutura






Aplicabilidade

Este padrão deve ser usado quando

¨       clientes de um subsistema forem utilizar apenas parte da funcionalidade dele. Então fachada provê uma interface mais fácil de usar do que todo o subsistema;
¨       há muitas dependências entre dois subsistemas, dificultando a manutenção.

Conseqüências

¨       o subsistema é mais fácil de usar;
¨       subsistemas são mais independentes dos clientes, já que não existe uma comunicação direta entre eles.


Implementação

class Scanner {
  public Token scan() { ... }
  ...
  }

class Parser {
   public void parse( Scanner s, ProgramNodeBuilder p ) { ... }
   ...
  }

class ProgramNodeBuilder {
  ...
  }

class CodeGenerator {
  ...
  }

class Compiler {
  public void compile( File input, BytecodeStream output ) {
    Scanner scanner(input);
    programNodeBuilder builder;
    Parser parser;

    parser.parse(scanner, builder);
    RISCCodeGenerator gen(output);
    ...
    }
  ...
  }


Peso Mosca - Flyweight


Motivação

Um editor de textos utiliza um objeto para cada elemento do texto como caracteres, figuras, colunas, linhas. Este projeto é altamente ineficiente, pois uma quantidade enorme de objetos deve ser criada em execução.
                O padrão Flyweight permite-nos utilizar um objeto para cada caráter sem utilizar uma grande quantidade de objetos. Caracteres idênticos em posições diferentes do texto utilizam o mesmo objeto. Idealmente, cada objeto conhece sua posição no texto, sua fonte, cor, etc. mas estas informações não são armazenadas nos objetos caracteres. Se fossem, as possibilidades de compartilhamento são diminuidas. Estas informações são passadas ao objeto quando necessário através de um objeto contexto. Então a posição, fonte e cor de um caracter deve ser armazenada fora do objeto de forma eficiente. Isto é, armazenar fora  do objeto deve usar menos espaço do que armazenar estas informações em cada objeto.

















Aplicação

Este padrão deve ser usado se todos os itens abaixo forem verdadeiros:
¨       um programa usa uma quantidade grande de objetos que podem ser compartilhados;
¨       a memória deve ser poupada;
¨       o estado dos objetos armazenados fora deles ocupa menos espaço do que se armazenados dentro deles;
¨       a identidade dos objetos não é importante.

Conseqüências

O tempo de execução aumenta porque o estado de cada objeto deve ser calculado.

Implementação

class Glyph {
  public void draw( Window w, GlyphContext context ) { ... }
  ...
  }

class Character extends Glyph {
  Character( char ch ) { _charCode = ch; }
  public void draw( Window w, GlyphContext context ) { ... }
  private char _charCode;
  }

GlyphContext possui métodos para retornar a fonte, cor, posição e outros dados do caráter não armazenados em Character.


Procurador - Proxy


Motivação

Considere um editor de texto que pode embeber gráficos no texto, como o Word. Gráficos usualmente ocupam grande quantidade de memória, o que atrasa consideravelmente a leitura de um arquivo do disco quando um documento é aberto. Pode ser que o gráfico sequer seja mostrado no vídeo, tornando a sua leitura desnecessária.





                O padrão Procurador permite que o gráfico só seja lido do disco quando for necessário. Isto é, quando ele for mostrado no vídeo. Mas a formatação do documento depende das dimensões da figura. Estas dimensões são lidas do disco e armazenadas na memória, embora o gráfico não o seja. O documento armazena inicialmente um objeto de ImageProxy. Quando este objeto receber uma mensagem Draw, a figura é lida do disco e colocada em um objeto de Image, referenciada pelo objeto de ImageProxy.


Aplicabilidade

Este padrão pode ser aplicado em muitas situações:
¨       um procurador pode representar um objeto que está em outra máquina. As chamadas são enviadas pela rede ao objeto remoto;
¨       um proxy pode controlar o acesso a um outro objeto, proibindo operações críticas.



Estrutura











Implementação

abstract class Graphic {
  public abstract void Draw();
  public abstract Extent getExtent();
  ...
  }

class Image extends Graphic {
  public void Draw(){ ... }
  public Extent getExtent() { ... }
  ...
  }

class ImageProxy extends Graphic {
  ImageProxy( String pfileName ) { fileName = pfileName; ... }
  public void Draw() {
    if ( image == null )
      image = LoadImage(fileName);
    image.Draw();
    }
  public Extent getExtent() {
    if ( image == null )
      image = LoadImage(fileName);
    return image.getExtent();
    }
  ...
  }


Padrões Comportamentais


Cadeia de Responsabilidades - Chain of Responsibilities


Motivação

Uma interface gráfica de um programa possui um help sensível ao contexto. Ao se pressionar F1 estando a seta do mouse sobre uma opção de um menu, o help daquela opção será mostrado. Se este help não existir, o help do menu será mostrado. Se este não existir, o help da aplicação é que será exibido.
                Este tipo de organização de help é facilmente implementado usando-se o padrão Cadeia de Responsabilidades.

O requerimento de help é passado pela cadeia de objetos, seguindo-se handler, até que alguém se dispunha a atendê-lo.












Aplicabilidade

Use este padrão quando vários objetos podem responder um requerimento, mas a decisão de quem responde só pode ser resolvida em execução.

Estrutura






Conseqüências

¨       o acoplamento é reduzido. O emissor do requerimento e o receptor não se conhecem;
¨       a responsabilidade por responder a um requerimento pode ser modificada em execução;
¨       pode ser que ninguém na cadeia de objetos possa responder a um requerimento --- insegura.


Implementação

abstract class HelpHandler {
  HelpHandler( HelpHandler hh ) { handler = hh; }
  public void HandleHelp() { handler.HandleHelp(); }
  private HelpHandler handler;
  }

class Widget extends HelpHandler {
  ...
  }

class Button extends Widget {
  public HandleHelp() {
    if can handle
       showHelp();
    else
      super.HandleHelp();
    }
  public ShowHelp() { ... }
  ...
  }

Comando - Command


   
Motivação

Em uma interface gráfica, uma operação deve ser executada ao se escolher um item de um menu. Esta operação pode ser encapsulada em um objeto. Com isto, desacoplamos o menu da operação --- eles podem ser acoplados e desacoplados dinamicamente.  Este é o padrão Comando.




                Um objeto da classe Command é passado a cada item do menu. Ao se escolher o item com o mouse, método Clicked é chamado. Este método, por sua vez, chama o método Execute do objeto Command.




Aplicabilidade

Use este padrão quando:
¨       operações deveriam ser passadas como parâmetro, o que é chamado de callback em linguagens procedurais;
¨       for necessário registrar as operações executadas ou desfazer a operação mais tarde.




Estrutura


Conseqüências

¨       desacopla o objeto que invoca a operação daquele que a executa;
¨       operações se tornam objetos de primeira classe;
¨       operações podem ser adicionadas sem perturbar as classes existentes

Implementação

class Document {
  public void Open() { ... }
  ...
  }

abstract class Command {
  public abstract void Execute();
  }

class PasteCommand extends Command {
  PasteCommand( Document pdoc ) { doc = pdoc; }
  public void Execute() {
    doc.paste();
    }
  private Document doc;
  }


Interpretador - Interpreter


Motivação

Um editor de texto aceita sentenças de busca com conectivos |, & e * para or, and, e repetição de um item. Assim, a sentença
                raining & (dogs | cats)*
poderia encontrar no texto
                raining dogs cats
               raining cats cats dogs
Como seria impossível fazer um algoritmo para cada combinação dos conectivos, usa-se um interpretador para interpretar a sentença. Este interpretador utiliza classes para representar a gramática:



Cada classe possui o método Interpret para interpretar aquele item. A sentença "raining & (dogs | cats)*" é representada pela árvore





Aplicabilidade

¨       é necessário interpretar sentenças em uma linguagem. Usualmente, cada sentença poderia, em uma implementação ineficiente, ser substituída por um algoritmo específico para a sentença.
¨       a gramática não é muito complexa;
¨       a eficiência não é crucial.

Estrutura

Um objeto contexto é passado a cada método Interpret.







Colaborações

¨       o cliente constrói a árvore de sintaxe com terminais e não terminais, inicializa o contexto e chama Interpret da raiz;
¨       método Interpret usa o contexto  para ver e alterar o estado do interpretador.

Conseqüências

¨       é fácil estender a gramática por herança;
¨       pode-se interpretar a árvore de sintaxe de outra maneira acrescentando outro método. Poder-se-ia, por exemplo, imprimir a árvore de sintaxe.

Implementação

abstract class RegularExpression {
  public abstract void Interpret( Context c );
  }

class LiteralExpression extends RegularExpression {
  LiteralExpression( String s ) { exp = s; }
  void Interpret( Context c ) {
    if ( c.lookUp().compare(exp) )  // é o  item corrente igual a exp ?
      erro();
    c.next();
    }
  private String exp;
  }

class AlternativeExpression extends RegularExpression {
  AlternativeExpression( String pexp1, String pexp2 ) {
    exp1 = pexp1;
    exp2 = pexp2;
    }
  void Interpret( Context c ) {
    if ( c.lookUp().compare(exp1) )
      c.next();
    else if ( c.lookUp().compare(exp2) )
      c.next();
    else
      erro();
    }
  private String exp1, exp2;
  }


Iterador - Iterator


Motivação

Iterador permite o acesso aos elementos de uma estrutura de dados sem revelar a implementação da estrutura. Por exemplo, o iterador de uma lista poderia percorrer a lista pelo código

List list = new List;
...
ListIterator iter;
iter = new ListIterator(list);
iter.First(); // aponta para o primeiro
while ( ! iter.IsDone() ) {  // acabou ?
  System.out.println( iter.CurrentItem() );   // pega item corrente
  iter.Next();        // passa para o próximo
  }


Aplicabilidade

Use este padrão
¨       para suportar diferentes estratégias de iterações (do último para o primeiro, filtrando itens, saltando itens, ...);
¨       para prover uma interface de iteração que independe da estrutura de dados a ser iterada.

Estrutura





Conseqüências

Vários iteradores podem percorrer uma mesma estrutura e cada um deles pode utilizar um algoritmo diferente de iteração.

Implementação

class List {
  public Object get(int i) { ... } // get o i-ésimo elemento
  public int getSize()  { ... }    // retorna o tamanho da lista
  ...
  }

class ListIterator {
  ListIterator( List plist ) { list = plist; }
  public void First() { i = 0; }
  public void Next()  { i++; }
  public Object CurrentItem() { return list.get(i); }
  public boolean IsDone() { return i < list.getSize(); }
  private int i;
  private List list;
  }

Mediador - Mediator


Motivação

Em uma interface com o usuário,





há inúmeros relacionamentos entre os vários elementos. Estes relacionamentos, se colocados explicitamente, deixariam o programa difícil de manter. O padrão mediador concentra todas as iterações em um único objeto mediador, removendo as referências entre os objetos gráficos.






Aplicabilidade

Use o padrão mediador quando:
¨       um conjunto de objetos se comunicam de maneira complexa;
¨       o comportamento das iterações deve ser customizável sem a criação de inúmeras subclasses.

Conseqüências
¨       desacopla colegas;
¨       simplifica a comunicação entre os colegas;
¨       abstrai os relacionamentos entre colegas no mediador.

Implementação

Mostraremos um exemplo onde um objeto abstrai o relacionamento entre livros retirados de uma biblioteca e usuários desta biblioteca.

class Livro {
  ...
  }

class Usuario {
  ...
  }

class RelLivroUsuario {
  public void setUsuario( Usuario pusuario ) {
    usuario = pusuario;
    }
  public void addLivro( Livro livro ) {
    lista.add(livro);
    }
  ...
  private Usuario usuario;
  private ListLivro lista;
  }
 



Recordador - Memento


O padrão memento é utilizado para armazenar e restaurar futuramente o estado de um objeto. Pode ser usado para  restaurar o estado de um editor de texto antes de um comando.

Aplicabilidade

Use memento quando:
¨       uma parte ou todo o estado de um objeto deve ser guardado;
¨       a obtenção direta do estado quebraria a proteção de informação.




Estrutura


Colaborações






Conseqüências

Objetos memento
¨       não violam a proteção de informação;
¨       podem usar muita memória.

Implementação

class State;

class Originator {
  public:
    Memento *CreateMemento();
    void SetMemento( const Memento * );
    // ...
  private:
    State *_state; 
  };

class Memento {
  public:
    // destrutor virtual
    virtual ~Memento();
  private:
    friend class Originator;
    Memento();

    void SetState(State *);
    State *GetState();
    ...
  private:
    State *_state;
    ...
  };


Observador - Observer


Motivação




Uma planilha, um gráfico de barras e um gráfico de pizza mostram os mesmos dados encapsulados em um objeto. Quando os dados mudarem, todas as suas representações gráficas devem mudar. Estas representações são chamadas de observadores. Os dados, de sujeito.

Os dados podem ser modificados por um cliente ou por um dos observadores.
                Evitar o acoplamento entre os observadores é a função do padrão Observador. Ele define um objeto sujeito e vários observadores. Quando o sujeito é modificado, os observadores são notificados. Os observadores são previamente registrados com o sujeito.

Aplicabilidade
Use este padrão quando uma mudança em um objeto deve causar mudança em outros. Não se sabe quais e quantos outros objetos.

Estrutura





Conseqüências

¨       há um acoplamento entre o sujeito e seus observadores;
¨       os observadores podem ser adicionados e removidos em execução.

Implementação

// C++
class Subject;

class Observer {
  public:
    virtual ~Observer();
    virtual void Update(Subject *theChangedSubject) = 0;
  protected:
    Observer();
  };

class Subject {
  public:
    virtual ~Subject();
    virtual void Attach( Observer * );
    virtual void Detach( Observer * );
    virtual void Notify();
  protected:
    Subject();
  private:
    List<Observer *> *_observers;
  };

void Subject::Attach( Observer *obs ) {
  _Observers->Append(obs);
  }

void Subject::Detach( Observer *obs ) {
  _observers->Remove(obs);
  }

void Subject::Notify() {
  ListIterator<Observer *)  i(_observers);

  for (i.First(); ! i.IsDone();  i.Next() ) {
    i.CurrentItem()->Update(this);
    }
  }


Estado - State


Motivação




Uma classe TCPConnection representa uma conexão de rede, que pode estar em diversos estados: estabelecido, ativo e fechado. O comportamento de um objeto desta classe varia de acordo com o estado ¾ como se o objeto trocasse os seus métodos.

Todas as mensagens são delegadas a um objeto de uma subclasse de TCPState. Quando muda o estado, muda-se o objeto State mudando-se então o comportamento do objeto de TCPConnection.

Aplicabilidade

Use este padrão quando o comportamento de um objeto depender de um estado.

Estrutura




Conseqüências

Este padrão
¨       separa os diferentes estados em diferentes classes
¨       torna explícita a transição entre estados.

Implementação

Descreveremos uma classe Hidroaviao que pode ser considerada um avião ou um barco conforme esteja na água ou no ar.

class Veiculo }
  public void acelera(float a) { ... }
  public void vira( float ang ) { ... }
  ...
  }

class Barco extends Veiculo {
  public void acelera(float a) { ... }  
  public void vira( float ang ) { ... }
  ...
  }

class Aviao extends Veiculo }
  public void acelera(float a) { ... }  
  public void vira( float ang ) { ... }
  ...
  }

class Hidroaviao extends Veiculo {
  Hidroaviao() {
    aviao = new Aviao;
    barco = new Barco;
    atual = barco;
    }
  public void decola() {
    atual = aviao;
    }
  public void pousa() {
    atual = barco;
    }
  public void acelera(float a) {
    atual.acelera(a);
    }
  public void liga() {
    atual.liga();
    }
  }


Estratégia - Strategy


Um editor de texto utiliza muitos algoritmos para quebra de linha. Para evitar fixar um destes algoritmos no programa, pode-se encapsulá-los em objetos. Este é o padrão Estratégia.





Um objeto de Composition  é responsável por quebrar as linhas do texto. Ele usa um objeto de uma subclasse de Compositor para isto.




Estrutura



Conseqüências

¨       uma hierarquia de classes define uma família de algoritmos. Partes comuns podem ser colocadas nas superclasses;
¨       algoritmos usados podem variar em execução.

Implementação

class Composition {
  public void Traverse() { ... }
  public void Repair() { compositor.Compose(...);  }
  private Compositor compositor;
  ...
  }

abstract class Compositor {
  public abstract void compose(...);
  }

class SimpleCompositor extends Compositor {
  public void Compose(...) { ... }
  }

class TeXCompositor extends Compositor {
  public void Compose(...) { ... }
  }


Método Esqueleto - Template Method


Motivação

Um método de uma classe é chamado, em envios de mensagens para this, por outros métodos da mesma classe. Redefinimos este método em uma subclasse, mudamos o comportamento dos outros métodos que o chamam.
                Como exemplo, o método OpenDocument de Application chama CanOpenDocument, redefinido na subclasse MyApplication. OpenDocument é parametrizado por CanOpenDocument, pois o seu comportamento depende dele. OpenDocument é um método template.






Estrutura




[1] Alexander, Christopher. Timeless Ways of Building.
[2] Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995.
[3] Rising, Linda. Pattern Almanac. Addison-Wesley, 2000.

Exercícios de Padrões de Projeto

1. Explique como funciona o padrão Command. Utilize a figura abaixo, se necessário.

2.
Faça a classe MenuItem representada na figura acima. Crie um objeto desta classe. Passe para o construtor um objeto de uma subclasse de Command. Chame o método Clicked do objeto de MenuItem.

3. Um objeto da classe File armazena todas as informações da sua conta bancária. Modificações neste objeto se refletirão no mundo real. Você deve passar este objeto como parâmetro a um método podeConfiar da classe Lalau:
       void podeConfiar( File toBeStolen )
Por precaução, proteja o método write do objeto que você passará. Este é o único método que pode modificar o arquivo. Implemente o código de proteção e diga que padrão você usou.

4. Explique como funciona o padrão factory method. Utilize o desenho abaixo se necessário.



5. No padrão factory method (figura acima), pode-se modificar a classe do documento em tempo de execução ? Isto é, pode-se modificar esta classe compondo-se objetos. Naturalmente, perguntamos se o padrão permite isto, não se isto é possível com algum outro mecanismo.

0 comentários:

Postar um comentário