MAC 413/5715 - Tópicos de Programação Orientada
a Objetos
Aula 4 - 20/08/2003
Aumentando a Flexibilidade
- Na vida real, sistemas de software tendem a sobreviver vários
anos.
- Mesmo sistemas desenvolvidos para ter vida curta, acabam vivendo muito
mais tempo do que o esperado.
- No entanto,
- os requisitos tendem a variar ao longo da vida do sistema,
- os sistemas são estendidos de diferentes formas para acrescentar
novas funcionalidades não antecipadas e
- partes de um sistema são utilizadas em outros sistemas (reutilização).
- Portanto, os sistemas devem ter flexibilidade para que possam ser
- adaptados a diferentes contextos e
- estendidos em diferentes direções.
- Mas é preciso cuidado. Lição de Programação
eXtrema:
- Não antecipe necessidades, pois elas podem não aparecer.
- Não incorpore flexibilidade desnecessária.
- Possível abordagem para enfrentar esta dicotomia:
- A arquitetura do sistema deve ser sempre o mais simples possível.
- Less is more.
Mies van der Rohe
.
- Outra forma importante de incorporar flexibilidade sem aumentar a complexidade:
- maximizar a coesão
- minimizar o acoplamento
Maximizando a Coesão
- Coesão é uma medida da diversidade dos "tópicos"
abordados por uma entidade.
- Quanto menor a diversidade de assuntos abordados por uma entidade,
maior a sua coesão.
- Uma entidade bem focada em um determinado aspecto do sistema, é
uma entidade bem coesa.
- Sistemas onde os aspectos estão difusos são pouco flexíveis
e dificultam extensões, modificações e reutilizações.
- Cada classe deve ser o mais coesa quanto possível.
- Cada classe deve representar apenas uma abstração.
- Classes não coesas podem trazer, pelo menos, 3 problemas:
- a classe é mais difícil de entender
- está se supondo que as duas (ou mais) abstrações
representadas pela classe tem sempre uma relação de 1 p/ 1
entre si.
- pode ser necessário especializar a classe em várias
dimensões baseadas nas várias abstrações
- Exemplo:
- Uma classe para uma conta bancária que inclui informações
sobre o correntista (nome, endereço, etc.)
- Problema (2 acima): não permitirá contas conjuntas,
se um correntista tem várias contas, os seus dados serão duplicados
em cada conta o que gerará problemas de atualização
e consistência.
- Problema (3): especialização na dimensão do tipo
de conta (corrente, poupança) e no tipo de correntista (pessoa física
ou jurídica).
- FIGURA
4.1 e
4.2
- Solução: aumentar a coesão criando uma
classe separada para o correntista
- FIGURA
4.4
- Regrinhas simples para maximizar coesão:
- um atributo deve possuir um valor simples, não uma estrutura
- um atributo deve descrever uma instância da classe (essa regra
é violada pelo exemplo da figura
4.1)
- Outro exemplo:
- Representação de vôos e seus respectivos aeroportos
- FIGURA
4.6
- Problema: se o vôo é repetido todos os dias, estaremos
duplicando os objetos que representam o intinerário todos os dias.
- Isso ocorre pois estamos representando tanto o intinerário
quanto uma instância do vôo na mesma classe.
- Solução: separar as duas abstrações
- FIGURA
4.7
- Flight Segment representa uma instância daquele vôo em
um determinado dia, poderia incluir informações sobre a tripulação,
por exemplo.
- Os designs mais coesos descritos acima eliminam a redundância.
- Redundância desperdiça espaço e dificultam a atualização
dos dados.
- Mas, muitas vezes, redundância pode melhorar o desempenho do
sistema.
Minimizando o Acoplamento
- Acoplamento é
- O quão "amarrado" uma parte do sistema é às
outras partes.
- O quão dependente uma parte do sistema é das estruturas
internas das outras partes do sistema.
- Uma medida do quanto uma classe conhece do mundo à sua volta.
- Se uma classe A conhece (e depende) da interface de uma classe B, e
a interface da classe B é modificada, a classe A terá que ser
modificada também.
- Minimizando o acoplamento entre as classes (e objetos) de um sistema,
nós facilitamos as modificações no sistema.
- Há várias formas de acoplamento, entre elas:
- Acoplamento de Identidade
- se um objeto guarda uma referência para outro, ele conhece
a idêntidade do outro e, portanto, está acoplado a ele.
- reduz-se acoplamento de identidade eliminando associações
no diagrama de classes e implementando associações de forma
uni-direcional.
- Acoplamento de Representação
- ocorre quando um objeto referencia outro, i.e., acessa o outro
- quanto mais específico o acesso (e.g., mexer nas variáveis
públicas do outro objeto) maior o acoplamento.
- usando um método "acessor" diminui um pouco o acoplamento
(pois o método esconde a implementação do dado).
- Acoplamento de Subclasses
- quando um objeto acessa uma subclasse usando a interface da subclasse
ao invés de usar a interface da superclasse.
- um cliente deve sempre usar uma referência para o tipo mais
geral possível.
- exemplo: em Java usar InputStream ao invés de usar FileInputStream.
- FIGURA
4.8
- FIGURA
4.9
- Uma regra fundamental relacionada a esta é que devemos
sempre programar para as interfaces e nunca para a implementação
.
- Assim, nos diagramas UML, as classes deveriam, sempre que possível,
usar as <<interface>>s e não outras classes.
- Acoplamento de Herança
- Uma subclasse é acoplada à sua superclasse através
de acoplamento de herança.
- É provavelmente o tipo de acoplamento mais forte.
- Se manifesta fortemente em tempo de compilação. Mas
também em tempo de execução.
- O que uma classe herda em tempo de compilação não
pode ser descartado em tempo de execução.
- Isso difere de agregação, quando um objeto pode se
desfazer de seus agregados em tempo de execução.
- Exemplo: item de catálogo que é subclasse de item
em estoque. E se a empresa decidir não guardar o item em estoque e
encomendá-lo de terceiros toda vez que o item for encomendado?
- FIGURA
4.13
- FIGURA
4.14
Referência
Charles Richter. Designing Flexible Object-Oriented Systems with UML
. Capítulo 4: Flexibility Guidelines for Class Diagrams. Macmillan
Technical Publishing, 1999.
Próxima Aula
Aula Anterior
Página de MAC 413/5715
Página do Fabio
Página do DCC