quarta-feira, 30 de abril de 2008

Padrões GOF (Padrão Singleton)

Aulas 18 e 19

Quando falamos em java, vem logo a idéia de objetos que são criados a partir de instâncias de classes e de reutilizar essas classes para que sejam criados outros objetos quando não mais for necessários aqueles que estas classes criavam. Pois bem, podemos criar vários objetos a partir de instâncias de uma mesma classe, mas o que vamos ver nesse padrão, é totalmente ao contrário, ou seja, o padrão singleton, sugere que apenas uma instância da classe seja criada e garante que mais ninguém crie outra instância, mas em que casos precisaremos usar apenas uma instância de uma classe? Como disse Metsker em seu livro Padrões de Projeto em java, “é mais fácil explicar como garantir que uma classe só tenha uma instância do que explicar por que nós podemos desejar essa restrição”, mas vamos tentar dar um exemplo para que não fique muito vaga essa idéia do singleton. Em uma aplicação que necessite de um log de dados (termo utilizado para descrever o processo de registro de eventos relevantes num sistema computacional), pode-se implementar uma classe com o padrão singleton, desta forma existe apenas um objeto responsável pelo log em toda a aplicação que é acessível unicamente através da classe singleton.

Segundo Metsker, dispomos de várias maneiras para criar um singleton, todavia, o importante é garantir que outros desenvolvedores não criem novas instâncias da classe que queremos limitar, portanto, vamos à solução:

É de conhecimento comum que, para criar uma instância de uma classe, devemos chamar o seu construtor, normalmente, esses construtores são públicos, muitos desenvolvedores desconhecem, mas é possível criar um construtor privado, assim, a classe só pode ser instanciada dentro de algum dos seus próprios métodos.

Criada a classe com o método privado, agora temos que criar uma variável que armazene a instância dessa classe.

Talvez você esteja se perguntando porque utilizar variável estática, simplesmente porque uma variável estática não perde o seu valor cada vez que ela for instanciada, assim, se a classe singleton for instanciada, o valor da instância permanecerá na variável e quando alguém tentar instanciá-la novamente, um método implementado para essa finalidade retornará aquele valor que já havia nela quando foi instanciada pela primeira vez (essa é a funcionalidade principal de um singleton).

Assim, com o código acima, ao instanciarmos os objetos m1 e m2, será retornado o mesmo valor para os dois.

Mas ainda há um pequeno problema quanto a esse código. Em ambientes multi-threaded, pode ocorrer de duas threads tentarem instanciar o objeto ao mesmo tempo, nesse caso, haverá um problema, provavelmente as duas conseguirão instanciar e serão criados dois objetos, o que fere a mecânica do singleton, uma solução para este problema seria utilizar o atributo syncronized no método getInstance(), este atributo garante a sincronização das threads que tentam instanciar a classe singleton, em outras palavras, garante que outra thread não possa acessar a classe até que a thread que a acessou pela primeira vez tenha terminado de fazê-lo.

O singleton é um dos padrões que não devem ser utilizados frequentemente, pois o uso abusivo de singletons leva a soluções onde a dependência entre objetos é muito forte, além do que, uma situação em que realmente é necessária apenas uma instância de uma classe é difícil.


REFERÊNCIAS


WMagician, Artigo, Padrão Singleton em Java

Macoratti, José Carlos Macoratti, Artigo, O Padrão Singleton

Manual do PHP, Escopo de variáveis

Metsker, Steven John Metsker, Padrões de Projeto em Java

segunda-feira, 21 de abril de 2008

Padrões GOF (Padrão Adapter)

Aula 17

Como vamos utilizar a classificação de Metsker para nossos estudos sobre os padrões GOF, nesta postagem apresentaremos o padrão Adapter.

O principal objetivo do Adapter é facilitar a conversão da interface de uma classe para outra interface mais interessante para o cliente, fazendo com que várias classes possam trabalhar em conjunto independentemente das interfaces originais. O Adapter é utilizado quando uma classe já existente e sua interface não combinam com a esperada pelo cliente ou se deseja-se criar uma classe reutilizável que coopera com classes não relacionadas ou não previstas, ou seja, classes q não necessariamente tenham interfaces compatíveis, entre outras utilizações.

O padrão Adapter tem finalidade estrutural e abrange tanto escopo de classe quanto de objeto, por isso existe o adaptador de classe e o adaptador de objetos, o primeiro é utilizado quando o alvo é uma interface, consequentemente usa herança múltipla, o segundo é utilizado quando o alvo é uma classe e faz uso da agregação.

Imagine que utilizamos uma classe utilitária de um framework especializado em métodos para programadores iniciantes, nesta classe existe um método que será depreciado no futuro (sem utilização), ou seja, digamos que novas versões da classe sejam lançadas e o método tenha sido retirado definitivamente, como poderemos utilizar a nova versão da classe em nossa aplicação que utiliza a versão antiga do método? Se apenas substituirmos a classe, será disparado um erro afirmando que está sendo feita uma chamada que não existe.

È aí que entra o padrão adapter, com duas formas para resolver esse problema:

1. Criamos uma classe intermediária que servirá como interface entre as chamadas de código cliente e o código da classe, essa nova classe trabalhará basicamente como um filtro entre essas chamadas, o que caracteriza um adaptador de objetos, utilizando agregação.

2. Criamos uma classe que herdará da antiga e sobrescrevemos ou recriamos o método chamado, caracterizando dessa forma um adaptador de classes, utilizando herança.

A escolha da forma de implementação do adapter é uma decisão do arquiteto de software e dependerá do contexto da aplicação.Utilizaremos aqui a 1ª opção.

Vamos ao exemplo prático:

Imagine que você esteja aprendendo java e já sabe alguns padrões de projeto, então você é um programador iniciante, teremos então a classe INICIANTE.

Esta classe utiliza dois métodos, SlaverJava() que representa apenas os conhecimentos que você tem sobre java e Padrões() que representa os conhecimentos que você tem sobre Padrões.

Suponhamos que daqui um tempo você adquira mais conhecimentos em java, então o método SlaverJava() não terá utilização, além da necessidade de implementar um novo método na classe. Como mencionado anteriormente, se apenas substituirmos a classe, será disparado um erro ao ser feita qualquer chamada para os métodos dessa classe.

A solução seria criar uma nova classe adaptadora contendo uma chamada para uma outra classe que contém o novo método, servindo como interface entre as chamadas de código cliente para os métodos da classe Iniciante, implementando essas chamadas na nova classe.

Criamos então uma nova classe chamada programadorMédio, que seria a nova versão da classe Iniciante, se essa classe apenas herdasse da classe iniciante e reescrevesse esse novos métodos, não seria caracterizado a utilização do padrão adapter e sim uma simples herança. Sendo assim, vamos implementar a classe programadorMedio como uma interface contendo apenas o novo método que irá substituir o SlaverJava.

Criamos então a classe adaptadora, chamada IntermediarioAdapter que herda da classe Iniciante, reescreve o novo método e implementa na classe programadorMedio.


Com isso finalizamos o padrão adapter, um exemplo bem simples mas que explica bem o conceito principal deste padrão.



Download do aplicativo no Netbeans


REFERÊNCIAS

CEFET-PARANÁ, Padrões de Projeto, ARTIGO

Èrico Almeida, IMASTERS fórum, Padrões de Projeto-Adapter

Steven John Metsker, Design Patterns Java Workbook.Addison-Wesley, 2002

Introdução a Padrões GOF

Aula 16

Nos artigos anteriores vimos alguns dos padrões GRASP, suas finalidades, alguns exemplos de aplicação e com isso, conhecemos a importância desses padrões para um projeto de software de qualidade.

Nas próximas postagens, veremos alguns dos padrões GOF, mas antes, um breve resumo dos criadores dos padrões GRASP e GOF:

Como já sabemos, o conceito de padrões foi criado pelo arquiteto Christopher Alexander (Christopher Alexander. A Pattern Language. Estados Unidos da América: Oxford University Press, 1977), a partir dos conceitos criados por Alexander, os programadores Kent Beck e Ward Cunningham, propuseram os primeiros padrões para a área de ciência da computação, mas foi com o lançamento do livro Design Patterns, Elements of Reusable Object-Oriented Sofware, cujos autores são Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, conhecidos como a "Gangue dos Quatro" (Gang of Four ou GOF), que o movimento ao redor de padrões de projeto ganhou popularidade. Posteriormente, vários outros livros do estilo foram publicados, como Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development, que introduziu um conjunto de padrões conhecidos como GRASP (General Responsibility Assignment Software Patterns).

Nos artigos publicados, conhecemos os padrões GRASP, mas como visto no parágrafo anterior, o primeiro conjunto de padrões a surgir foram os padrões GOF. Existem várias formas de classificar os padrões GOF, por exemplo, em GAMMA(2000), são apresentados 23 padrões classificados em 3 categorias: Criacional, Estrutural e Comportamental, já em METSKER(2004), são apresentados diversos padrões, os quais são classificados em Interfaces, Responsabilidades, Construções, Operações e Extensões.

Abaixo temos uma ilustração dessas classificações:


Não entraremos em detalhes sobre todos esses padrões, mas ao longo das próximas postagens, veremos os mais importantes, bem como suas caracterizações e aplicações. Por hora vamos descrever alguns dos problemas mais comuns em um projeto OO, e como esses padrões podem ajudar a resolvê-los.

Problema I: Como descobrir e quais os objetos mais apropriados?
Design patterns ajudam a identificar as abstrações menos óbvias e objetos que podem representá-las.

Problema II: Qual a granularidade ideal para um sistema?
Design patterns oferecem várias soluções como por exemplo: Façade descreve como representar subsistemas inteiros como um único objeto, Flyweight descreve como suportar grandes quantidades de objetos nas menores granularidades, Abstract Factory, Builder, Visitor e Command limitam a responsabilidade de objetos.

Problema III:Como especificar interfaces?
Design patterns ajudam a definir interfaces ao identificar seus elementos-chave e tipos de dados que são passados
.

Problema IV: Como especificar implementações?
Design patterns oferecem formas de instanciar classes concretas em outras partes do sistema.

Problema V: Como fazer o reuso funcionar?
Design patterns usam delegação para tornar a composição tão poderosa para reuso quando a herança.

Problema VI: como distinguir estruturas estáticas (compile-time) e dinâmicas (run-time)?
Vários design patterns capturam a distinção entre estruturas run-time e compile-time.

Problema VII: como antecipar mudanças?
Padrões promovem desacoplamento e permitem que algum aspecto da estrutura do sistema varie independentemente de outros aspectos.

Em meio a uma certa quantidade de padrões, os mais inexperientes podem ficar um pouco perdidos na hora de escolher qual padrão utilizar em determinada situação, aqui vai algumas dicas de como selecionar esses padrões.

1. Considere como os padrões solucionam os problemas de projeto

2. Analise seu problema e compare com o objetivo de cada padrão

3. Veja como os padrões envolvidos se relacionam entre si

4. Estude padrões de propósito ou intenção similar (veja formas de classificação)

5. Examine causas comuns que podem forçar o redesign do seu sistema

6. Considere o que deve variar no seu design




REFERÊNCIAS:


UNIVERSIDADE TECNOLÓGICA DO PARANÁ, Introdução a Padrões de Projeto


RICARDO ISHIBASHI MOREIRA DE ALMEIDA, Utilização de padrões de projeto no desevolvimento de aplicações web com PHP 5


WIKIPÉDIA, Padrões de projeto de software

terça-feira, 1 de abril de 2008

Padrão controlador

aula 15 (breve resumo, sujeito a modificações)


Um evento de entrada de um sistema é um evento gerado por um ator externo. Ele está associado com operações do sistema, operações do sistema em resposta a eventos do sistema, da mesma forma que os métodos e as mensagens estão relacionados. O padrão responsável por tratar os eventos do sistema é chamado de padrão controlador.

Por exemplo, qualquer usuário de um sistema que pressione algum botão, como por exemplo o botão fechar de algum programa, ele está disparando um evento do sistema que indica que o programa está sendo encerrado.

A primeira categoria de controlador é um controlador fachada (facade) que representa todo o sistema, o dispositivo ou o subsistema. A idéia é escolher uma classe cujo nome sugira uma cobertura, ou fachada, sobre as outras camadas da aplicação e que seja o ponto principal para as chamadas provenientes de interface com usuário para as camadas abaixo. Os controladores fachada são adequados quando não existem muitos eventos de sistema, ou quando não é possível, para a interface de usuário, redirecionar mensagens de eventos de sistema para controladores alternativos.

Quando o controlador fachada começa a levar o projeto a ficar com baixa coesão e alto acoplamento, o que ocorre quando o controlador começa a ficar “inchado”, com excesso de responsabilidades, é ideal utilizar um controlador para cada caso de uso do sistema. Um controlador de caso de uso é uma boa escolha quando existem muitos eventos de sistema com diferentes processos, ele fatora o seu tratamento em classes separadas administráveis e também fornece uma base para conhecer o estado do cenário do processo em andamento.

Padrão Alta Coesão

Aula 14


Veremos agora um padrão que muito se parece com o estudado anteriormente, com a diferença que este mede o quão estão relacionadas as responsabilidades de uma classe enquanto que o outro foca a medida do quanto um elemento está conectado, tem conhecimento ou depende de outros elementos. O resultado é praticamente o mesmo, tanto que ao se aplicar um deles, consequentemente estará aplicando o outro, são raros os casos em que isso não acontece.

Ao estudarmos o padrão baixa coesão, surge um pouco dos conceitos que também já estudamos nas primeiras postagens, por exemplo:

Uma classe com baixa coesão faz muitas coisas não relacionadas ou executa muitas tarefas. Uma classe que executa muitas tarefas não possui a aplicação do padrão especialista, pois, não há delegação de responsabilidades, bem como tarefas que não são adequadas a ela. Classes com múltiplas responsabilidades ou com alta granularidade estão propensas a ter uma alta coesão.

Assim como o baixo acoplamento, a alta coesão é um dos princípios que devem ser levados em consideração ao se construir um projeto.

Da mesma maneira que o baixo acoplamento, a alta coesão também é dividida em tipos:

Ø Coesão coincidental: o pior tipo de coesão, há nenhuma ou pouca relação construtiva entre os elementos de um módulo, em outras palavras é uma classe inchada, com um punhado de métodos, todos executando tarefas diferentes, sem nenhuma relação com a classe que os implementa.

Ø Coesão lógica: melhor do que a coincidental mas não menos pior em um projeto, semelhante ao acoplamento de controle, onde um módulo faz um conjunto de funções relacionadas e uma das quais é escolhida através de um parâmetro para controlá-lo.

Ø Coesão temporal: os elementos estão agrupados no mesmo módulo simplesmente porque são processados no mesmo intervalo de tempo, semelhante aos arquivos .ini do windows xp, ao iniciar o xp esses arquivos são carregados para iniciar serviços ou aplicativos.

Ø Coesão procedural: o módulo só tem sentido sobre a aplicação associada, sem ela, há dificuldade em entendê-lo, basicamente é a coesão relacionada aos procedimentos executados pelos elementos do módulo.

Ø Coesão de comunicação: um módulo tem coesão de comunicação se os seus elementos usam a mesma entrada ou a mesma saída.

Ø Coesão seqüencial: a saída de um elemento é a entrada de outro e a solução é decompor em módulos menores, isso nós já vimos em tópicos passados, chamado também de acoplamento de dados.

Ø Coesão funcional: Um módulo funcionalmente coeso contém todos os elementos e apenas aqueles necessários para realizar uma única tarefa bem definida.

o Exemplos:
- calcular raiz quadrada;
- ler registo;
- determinar salário líquido de um empregado;
- calcular o ponto de impacto de um míssil.

Um módulo não será funcionalmente coeso se, para descrever sua função for necessário um, ou mais, dos seguintes items:


- Frase composta;
- Mais do que uma frase;
- Palavras que indiciam uma ligação temporal;
- Falta de um objectivo específico simples;
- Verbo ambíguo.