Prévia do material em texto
Engenharia de Software Moderna
Módulo 2 - Projeto & Arquitetura
Semana 1 - Princípios de Projeto
Prof. Marco Tulio Valente
"O problema mais fundamental em Ciência da Computação é a tarefa de decomposição de problemas: como dividir um problema complexo em partes que possam ser resolvidas de forma independente" -- John Ousterhout
‹#›
Definição de Projeto de Software
‹#›
Frase anterior é uma excelente definição
Projeto:
Quebrar um "problema grande" em partes menores
Resolução (ou implementação) das partes menores resolvem (ou implementam) o "problema grande"
Módulos
‹#›
Em Engenharia de Software:
Partes menores = módulos (pacotes, componentes, classes, etc)
O que vamos estudar? (parte 1)
‹#›
Propriedades de bons projetos de software
Integridade Conceitual
Ocultamento de Informação
Coesão
Acoplamento
O que vamos estudar? (parte 2)
‹#›
Princípios (ou diretrizes) para projeto de módulos com as propriedades que estudamos antes:
Responsabilidade Única
Segregação de Interfaces
Prefira Interfaces a Classes
Aberto/Fechado
Demeter
Substituição de Liskov
Propriedades de Projeto
‹#›
Integridade Conceitual
‹#›
Integridade Conceitual
‹#›
Funcionalidades de um sistema devem ser coerentes
Sistema não pode ser um amontoado de funcionalidades sem nenhuma coerência ou consistência
Exemplos (em nível de interface com o usuário)
‹#›
Botão "sair" é idêntico em todas as telas
Se um sistema usa tabelas para apresentar resultados:
Todas as tabelas têm o mesmo leiaute
Todos os resultados tem 2 casas decimais
Integridade Conceitual vale também para o projeto e código de um sistema
‹#›
Exemplos (em nível de projeto/código)
‹#›
Todas as variáveis seguem o mesmo padrão de nomes
contra-exemplo: nota_total vs notaMedia
Todas as páginas usam o mesmo framework (mesma versão)
Se um problema é resolvido com uma estrutura de dados X, todos os problemas parecidos também usam X
Integridade Conceitual = coerência e padronização de funcionalidades, projeto e implementação
‹#›
Exemplo
Contra-Exemplo
Integridade conceitual é a consideração mais importante no projeto de sistemas -- Fred Brooks
‹#›
Motivo: integridade conceitual facilita uso e entendimento de um sistema
‹#›
Ocultamento de Informação
(Information Hiding)
‹#›
Origem do conceito (David Parnas, 1972)
‹#›
‹#›
‹#›
placa
modelo
‹#›
Construtora, cria a
Hashtable
‹#›
Problema: clientes precisam manipular uma estrutura de dados interna da classe, para estacionar um veículo
Problema
‹#›
Classes precisam de "privacidade"
Até para evoluir de forma independente dos clientes
Código anterior: clientes manipulam a hashtable
Comparação: clientes não podem entrar na cabine do estacionamento e eles mesmo anotar os dados do seu carro no "livro" do estacionamento
Agora uma versão com ocultamento de informação
‹#›
‹#›
1
‹#›
2
‹#›
Resultado: classe Estacionamento fica livre para alterar a sua estrutura de dados interna
3
Ocultamento de Informação
‹#›
Classes devem ocultar detalhes internos de sua implementação (usando modificador private)
Principalmente aqueles sujeitos a mudanças
Adicionalmente, interface da classe deve ser estável
Interface = conjunto de métodos públicos de uma classe
Coesão
‹#›
Coesão
‹#›
Uma classe deve ter uma única função, isto é, oferecer um único serviço
Vale também para outras unidades: funções, métodos, pacotes, etc.
Contra-exemplo 1
‹#›
Contra-exemplo 1
‹#›
Deveria ser quebrada em duas funções: sin e cos
Contra-exemplo 2
‹#›
Contra-exemplo 2
‹#›
Deveria ser quebrada em duas classes: Estacionamento e Gerente
Exemplo
‹#›
Todos esses métodos manipulam os elementos da Pilha
Acoplamento
‹#›
Acoplamento
‹#›
Nenhuma classe é uma ilha ...
Classes dependem umas das outras (chamam métodos de outras classes, estendem outras classes, etc)
A questão principal é a qualidade desse acoplamento
Dois tipos:
Acoplamento aceitável ("bom")
Acoplamento ruim
Acoplamento Aceitável
‹#›
Classe A usa uma classe B e:
B oferece um serviço útil para A
B possui uma interface estável
A somente chama métodos da interface de B
‹#›
Classe Estacionamento depende (está
acoplada) à classe Hashtable, mas
esse acoplamento é aceitável
Acoplamento Ruim
‹#›
Classe A usa uma classe B:
Mas interface da classe B é instável
Ou então o uso não ocorre via interface de B
Como uma classe A pode depender de uma classe B sem ser via a interface de B?
‹#›
‹#›
"arq1.db"
A
lê
‹#›
"arq1.db"
A
B
lê
grava
Problema: Acoplamento ruim (do tipo evolutivo)
‹#›
Mudanças internas em B pode impactar A
Exemplo: B pode mudar o formato do arquivo ou mesmo deixar de salvar o dado que é lido por A
"arq1.db"
A
B
lê
grava
Como resolver esse problema?
Como tornar um acoplamento ruim em bom?
‹#›
Tornando acoplamento ruim em bom
‹#›
Tornando acoplamento ruim em bom
‹#›
Recomendação comum em projeto de software:
Maximize a coesão, minimize o acoplamento
‹#›
Mas cuidado: minimize (ou elimine)
principalmente o acoplamento ruim
Fim
‹#›
Princípios de Projeto
‹#›
‹#›
"Diretriz"
Consequência (o que vamos ganhar seguindo o princípio)
Princípios SOLID
Single Responsibility Principle
Open Closed/Principle
Liskov Substitution Principle
Interface Segregation Principle
Dependency Inversion Principle
‹#›
Princípio da Responsabilidade Única
‹#›
Princípios da Responsabilidade Única
‹#›
Toda classe deve ter uma única responsabilidade
Deve existir um único motivo para modificar uma classe
‹#›
Responsabilidade #1: calcular índice de desistência
‹#›
Responsabilidade #2: imprimir índice de desistência
Agora versão com separação de responsabilidades
‹#›
‹#›
‹#›
Uma única responsabilidade: interface com o usuário
‹#›
Uma única responsabilidade: "lógica ou regra de negócio"
Vantagens
‹#›
Classe de negócio pode ser usada por mais de uma classe de interface (Web, mobile, linha de comando, etc)
Divisão de trabalho:
Classe de interface: frontend dev
Classe de negócio: backend dev
(2) Princípio da Segregação de Interfaces
‹#›
Segregação de Interfaces
‹#›
Interfaces devem ser pequenas, coesas e específicas para cada tipo de cliente
Caso particular do princípio anterior, mas para interfaces
‹#›
Interface genérica: trata de funcionários CLT e de
funcionários públicos
O que "getSIAPE" retorna para funcionários CLT?
Agora versão que atende segregação de interfaces
‹#›
‹#›
‹#›
Comum para todos funcionários
‹#›
Específica para funcionários CLT
‹#›
Específica para funcionários públicos
(3) Princípio da Inversão de Dependências
‹#›
Inversão de Dependências
‹#›
Gostamos de chamar esse princípio de Prefira Interfaces a Classes
Pois transmite melhor a sua ideia!
Exemplo sem usar Inversão de Dependências
‹#›
class ControleRemoto {
TVSamsung tv;
... // métodos de controle remoto
}
class TVSamsung {
...
}
Possível problema: ControleRemoto só funciona com TVSamsung
Exemplo usando Inversão de Dependências
‹#›
class ControleRemoto {
TVGenerica tv;
... // métodos de controle remoto
}
interface TVGenerica {
... // métodos que toda TV deve implementar
}
class TVSamsung implements TVGenerica {
...
}
‹#›
>
ControleRemoto
>
TVSamsung
>
ControleRemoto
>
TVGenérica
>
TVSamsung
usa
usa
implementa
Sem DIP
Com DIP
Vantagem: controle remoto genérico, para qualquer TV
Posso mudar de TV, sem alterar o código da classe ControleRemoto
(4) Prefira Composição a Herança
‹#›
Contexto Histórico
‹#›
Na década de 80, quando orientação a objetos tornou-se popular, as pessoas começaram a abusar de herança
Achavam que herança iria ser uma bala de prata, promover reuso em larga escala, etc.
Herança
‹#›
Relação "é-um"
Exemplo: MotorGasolina é-um Motor
Código:
class MotorGasolina extends Motor {
... // herda atributos e métodos de motor
}
Composição
‹#›
Relação"possui"
Exemplo: Painel possui ContaGiros
Código:
class Painel {
ContaGiros cg; // possui um atributo
...
}
Prefira Composição a Herança ⇒ não force o uso de herança
‹#›
(5) Demeter
‹#›
Demeter
‹#›
Demeter era o nome de um grupo de pesquisa de uma universidade norte-americana
Evite longas cadeias de chamadas de métodos
Exemplo:
obj.getA().getB().getC().getD().getOqueEuPreciso();
objetos de passagem
Motivo
‹#›
Longas cadeias de chamadas quebram encapsulamento
Não quero passar por A, B, C,… até obter que eu preciso
Violando Demeter
‹#›
Define quais chamadas de métodos são "permitidas" no corpo de um método
(6) Princípio Aberto/Fechado
‹#›
Princípio Aberto/Fechado
‹#›
Proposto por Bertrand Meyer
Ideia: uma classe deve estar fechada para modificações, mas aberta para extensões
Explicando melhor
‹#›
Suponha que você vai implementar uma classe
Usuários ou clientes vão querer usar a classe (óbvio!)
Mas vão querer também customizar, parametrizar, configurar, flexibilizar e estender a classe!
Você deve se antecipar e tornar possível tais extensões
Mas sem que os clientes tenham
que alterar o código da classe
Como tornar uma classe aberta a extensões, mas com o seu código fechado para modificações?
‹#›
Parâmetros
Padrões de projeto
Herança
etc
Exemplo
‹#›
Ordena a lista passada com parâmetro
Exemplo
‹#›
Ordena uma lista passada com parâmetro
Mas agora eu quero ordenar as strings da lista pelo seu tamanho, isto é, pelo número de chars
‹#›
Será que o método sort está aberto (preparado) para permitir essa extensão?
Mas, mantendo o seu código fechado, isto é, sem ter que mexer no seu código
‹#›
Felizmente, sim!
‹#›
lista de strings
Objeto com um método compare, que vai comparar duas strings.
Não existe almoço grátis, cliente tem que implementar esse método
Resumindo: ao implementar uma classe ou método, pense em pontos de extensão!
‹#›
(7) Princípio de Substituição de Liskov
‹#›
Princípio de Substituição de Liskov
‹#›
Nome é uma referência à Profa. Barbara Liskov
Princípio define boas práticas para uso de herança
Especificamente, boas práticas para redefinição de métodos em subclasses
Primeiro: vamos entender o termo "substituição"
‹#›
‹#›
‹#›
‹#›
Tipo A pode ser substituído por B1, B2, B3,...
Desde que eles sejam subclasses de A
Princípio de Substituição de Liskov
‹#›
Substituições de A por B podem acontecer desde que B ofereça os mesmos serviços de A
Ou seja: para um código feito para usar A, a substituição de A por B é “indolor”
Exemplo que segue Substituição de Liskov
‹#›
class ControleRemoto {
// alcance de 10 m
}
class ControleRemotoPremium extends ControleRemoto {
// alcance de 20 m
}
Exemplo que não segue Substituição de Liskov
‹#›
class ControleRemoto {
// alcance de 10 m
}
class ControleRemotoXYZ extends ControleRemoto {
// alcance de 5 m
}
Fim
‹#›
image5.png
image12.png
image10.png
image9.png
image1.png
image3.png
image6.png
image16.png
image13.png
image2.png
image7.png
image4.png
image8.png
image22.png
image11.png
image18.png
image15.png
image14.png
image19.png
image25.png
image21.png
image17.png
image24.png
image26.png
image20.png
image30.png
image32.png
image27.png
image28.png
image23.png
image29.jpg
image31.jpg