segunda-feira, 21 de abril de 2008

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.

segunda-feira, 24 de março de 2008

Padrão Baixo Acoplamento

Aulas 12 e 13


Acoplamento de Controle

Anteriormente discutimos o acoplamento de dados, o menos pior dos acoplamentos, foi explicado que a melhor solução para diminuir este tipo de acoplamento é o uso de interface, aliás, para diminuição de todos os tipos de acoplamento o melhor caminho é o uso de interface. No acoplamento de dados, como o próprio nome já diz é aquele em que um objeto conhece muito os dados de outro, no próximo tipo que iremos discutir, chamado acoplamento de controle, não existe este conhecimento, mas sim, um certo controle de um certo objeto sobre o outro, explicando melhor:

Objeto A manda uma mensagem a objeto B;

Objeto B usa um parâmetro da mensagem para decidir o que fazer;


Exemplo:

Considere a classe Circuito como objeto A e classe Lâmpada como o objeto B:

O objeto circuito instancia a classe lâmpada, e passa os parâmetros desligada, ligada e piscando, a classe lâmpada então atribui um valor a cada parâmetro e executa a o procedimento para conhecer o estado da lâmpada.

Problema: em termos mais diretos, quem comanda o interruptor? Claro que a classe circuito, mas qual o problema nisso? Afinal, alguém tem que fazer isso. O problema não é alguém fazer, mas a forma como é feito, se ao invés da classe circuito mandar uma mensagem passando um parâmetro para desligar a lâmpada, somente passasse uma mensagem para desligá-la e a classe responsável executasse o procedimento, da maneira que lhe fosse convincente, seria muito mais prudente, afinal as duas não precisariam andar sempre de mãos dadas, característica do alto acoplamento.

Como no exemplo acima, a classe circuito cria um novo objeto de lâmpada e ao invés de passar parâmetros, cria métodos para passar mensagens.

A classe lâmpada, anteriormente não tinha métodos, apenas as comparações de if e else, mas utilizando um princípio para diminuir o acoplamento em casos de acoplamento de controle, decompomos a operação em múltiplas operações, ou seja, o método “public void setLampada(int valor)” do primeiro exemplo de lâmpada, em diversos métodos, como visto no segundo exemplo.


Acoplamento de Dados Globais

O terceiro tipo de acoplamento é o de dados globais, este tipo de acoplamento ocorre da seguinte maneira:

Dois ou mais objetos compartilham estruturas de dados globais, estes tipos de dados podem ser enxergados por todas as classes do sistema, pois como o próprio nome já diz é global.

Uma chamada de método pode mudar um valor global e o código não deixa isso aparente.


Acoplamento de Dados Internos

O quarto e último tipo de acoplamento, e o pior de todos é o acoplamento de dados internos, ocorre quando um objeto consegue alterar os dados locais de outro objeto, exemplo, uma variável de classe pública ou protected em java.

Solução: sempre utilizar encapsulamento, ou seja, variáveis private e métodos get e set.

sexta-feira, 21 de março de 2008

Padrão Baixo Acoplamento

Aula 11


Acoplamento de Dados

Acoplamento de dados, como já vimos, é o tipo de acoplamento onde uma classe conhece os dados da outra, fazendo com que se houver mudança em uma, automaticamente implicará mudança em outra.

Observe o exemplo a seguir, de uma classe aluno e uma classe lista ordenada, a finalidade delas é obter a matrícula de um aluno e coloca-las em uma lista ordenada.

A classe aluno obtêm a matrícula e o nome do aluno através do método get, bem como cria um novo objeto novoAluno e adiciona a lista de alunos, chamando assim a classe lista ordenada. Observe agora a classe lista ordenada:

A classe lista ordenada recebe um objeto do tipo aluno, e agora já observamos um problema de acoplamento, a classe lista ordenada nunca poderá receber um objeto de outro tipo para que seja comparado. Continuando, através do método add, a classe compara os objetos através da variável matrícula, que só existe na classe aluno, e aí observamos mais um acoplamento, e se a variável matrícula deixar de existir na classe aluno? E se for necessário comparar os alunos não por matrícula mas por ordem alfabética?.

Para isso, vamos refatorá-las para que as duas não mais dependam uma da outra.

Uma das melhores soluções para reduzir o acoplamento é a utilização de interface, vamos relembrar o que vem a ser uma interface. Uma interface nada mais é que uma superclasse que adiciona funcionalidades à classe que a implementa, ou seja, se uma determinada classe implementa uma interface, é como se ela assinasse um contrato e em troca receberia a garantia de que iria implementar as funcionalidades que existem na interface.

Observe como ficou a classe aluno:

Qual a primeira mudança que notamos? Isso mesmo, a classe Aluno agora está implementando uma interface chamada “comparable”, que é a interface que permite fazer comparação de objetos. Em seguida encapsula as variáveis com os métodos get e set, o que também não acontecia no exemplo anterior, em seguida há o método compareTo, um método da interface comparable, esse método transforma aluno em uma variável do tipo comparable (Aluno aluno = (Aluno)o), e em seguida faz a comparação para retornar um inteiro à classe que irá fazer a ordenação, nesse caso a classe ListaOrdenada.

A classe ListaOrdenada instancia a lista, recebe o novo elemento, põe na ordem correta e o insere na lista.

Agora qual a diferença principal que notamos nessas duas classes? A classe listaordenada é capaz de ordenar qualquer tipo de objeto, e não somente a matrícula, como era no exemplo anterior, se houver uma mudança na classe aluno, a classe listaordenada continuará ordenando pois o objeto que é passado a ela não é um objeto do tipo aluno e sim do tipo comparable.

quinta-feira, 20 de março de 2008

Padrão Baixo Acoplamento

Aula 10


Em um sistema orientado a objetos, os objetos são todos interligados, uns mandando mensagens a outros, como já vimos até agora, existem aqueles que têm a responsabilidade de conhecer qual o responsável por determinada ação, ou quem possui determinada informação para executar tal ação, e existem aqueles com a responsabilidade de fazer as ações, de executa-las.

O padrão que vamos conhecer hoje é responsável por medir o quanto uma classe está ligada a outra, isso é possível através do diagrama de classe, quando se observa a ligação fisicamente, ou quando avaliamos uma classe e constatamos que ela tem muito conhecimento de outra, ou quando uma classe depende de muitos elementos de outra classe, isso se chama alto acoplamento.

O alto acoplamento traz diversos problemas para um sistema, entre eles:

1. Difícil entendimento: vamos imaginar uma situação em que você precisa fazer uma reengenharia de um sistema que já está pronto, ou que necessita encontrar um erro que está lhe dando dor de cabeça no seu sistema, ao começar analisar as classes descobre que para entender essa classe, precisa estudar a próxima que está ligada a ela, para também entender esta, precisa estudar a próxima, e assim por diante, imagine o tempo que irá desperdiçar nisso.

2. Difícil reutilização: vamos imaginar outra situação, digamos que seja necessário retirar uma determinada parte do sistema, algo como um módulo de calcular folha de pagamento de um sistema financeiro, para ser levada e adequada a outro sistema financeiro, imagine que as classes responsáveis por isso, estejam ligadas ao módulo de RH, você vai levar o módulo de RH junto?

3. Propagação de mudanças: continuando do item anterior, imagine que seja necessário mudar uma classe do módulo de folha de pagamento, para isso, teria que mudar a classe que está no módulo de RH, e se você não for o responsável pelo módulo de RH? E se além de ter que mudar esta classe, também mudar várias outras que estão ligadas à primeira.

A solução para isso seria minimizar o acoplamento entre as classes, ou seja, deixa-las com baixo acoplamento.

Existem quatro tipos de acoplamento:

  • Acoplamento de dados: quando uma classe conhece os dados da outra;
  • Acoplamento de controle: quando uma classe exerce controle sobre o comportamento de outra.
  • Acoplamento de dados globais: quando dois ou mais objetos compartilham os mesmos dados.
  • Acoplamento de dados Internos: quando um objeto altera os dados locais de outro objeto, isso em java não é difícil de evitar, é só utilizar variáveis private, ao invés de públicas.

O acoplamento fraco é um princípio a ser levado em conta em todas as decisões de projeto, pois é um princípio de avaliação que o projetista aplica enquanto avalia as decisões do projeto.

quinta-feira, 13 de março de 2008

Padrão Criador ( na prática )

Aula 9 (Laboratório)


Sabemos que a criação de objetos é uma das atividades mais comuns em um sistema orientado a objeto, e é útil ter um princípio para atribuição geral de responsabilidades de criação. Seguindo estes princípios o sistema apresentará maior clareza, acoplamento fraco, encapsulamento e reutilização, nos artigos anteriores discutimos todas essas qualidades, e portanto não há necessidade de defini-las novamente.

Veremos agora uma aplicação prática do padrão criador, utilizando os mesmos diagramas e praticamente o mesmo código da AULA 8, mas um pouco mais completo.

Pelo texto da postagem anterior, sabemos que a classe “Venda” é responsável por calcular o total de uma venda, a classe “ItemVenda” por calcular o subtotal e a classe “Produto” por saber o preço unitário de cada produto, agora a questão é: quem é responsável por criar quem? Qual dessas classes cria a instância de outra? Pelo primeiro diagrama, percebe-se que a classe “produto” não cria ninguém, ela é apenas criada, então vamos começar por ela.

Uma vez que a classe produto só possui métodos acessores e mutatórios, ela não precisa ser instanciada mas apenas que as outras classes invoquem, ou seja, chamem esses métodos contendo as variáveis da classe produto.


public class Produto {

private Integer idProduto;
private String descricaoProduto;
private Double valorUnitario;
public Produto() {
}
public Integer getIdProduto() {
return idProduto;
}
public void setIdProduto(Integer idProduto) {
this.idProduto = idProduto;
}
public String getDescricaoProduto() {
return descricaoProduto;
}
public void setDescricaoProduto(String descricaoProduto) {
this.descricaoProduto = descricaoProduto;
}
public Double getValorUnitario() {
return valorUnitario;
}
public void setValorUnitario(Double valorUnitario) {
this.valorUnitario = valorUnitario;
}
}

Já a classe “ItemVenda” além de possuir métodos acessores e mutatórios possui um método que obtêm a quantidade de cada produto, através da variável “qtde” e multiplica pela valor unitário de cada produto, informações estas pertencentes à classe produto, veja bem, a classe “ItemVenda” não está instanciando a classe “Produto”, apenas utilizando o método get que obtêm o valor unitário de cada produto.


public class ItemVenda {

private Double qtde;
private Produto produto ;
public ItemVenda() {
}
public Double getValorItemVenda(){
return getQtde()*getProduto().getValorUnitario();
}
public Double getQtde() {
return qtde;
}
public void setQtde(Double qtde) {
this.qtde = qtde;
}
public Produto getProduto() {
return produto;
}
public void setProduto(Produto produto) {
this.produto = produto;
}
}

E como a classe “ItemVenda” possui o método que calcula o valor individual de cada produto, ninguém mais interessada em utilizá-la do que a classe que calcula o total de uma venda, ou seja, a classe “Venda” e agora sim, estamos aplicando o padrão criador, pois a classe “Venda” instancia a classe “ItemVenda” para que esta realize o cálculo.


public class Venda {

private Integer idVenda;
private Date dataVenda;
private Set itemVendaList= new HashSet();

public Venda() {
}
public Integer getIdVenda() {
return idVenda;
}
public void setIdVenda(Integer idVenda) {
this.idVenda = idVenda;
}
public Date getDataVenda() {
return dataVenda;
}
public void setDataVenda(Date dataVenda) {
this.dataVenda = dataVenda;
}
public Double getValorTotal(){
Double valorTotal=0.0;
for(ItemVenda currentItem : itemVendaList){
valorTotal+=currentItem.getValorItemVenda();
}
return valorTotal;
}
public void addItem(ItemVenda i){
itemVendaList.add(i);
}
public void removeItem(ItemVenda i){
itemVendaList.remove(i);
}
public ItemVenda criarItemVenda(Double qtde, Produto produto){
ItemVenda itemVenda = new ItemVenda();
itemVenda.setProduto(produto);
itemVenda.setQtde(qtde);
addItem(itemVenda);
return itemVenda;
}
}

Finalizando, teremos um método main que instancia a classe "Venda" e cada um dos produtos.