Professional Documents
Culture Documents
Engenharia de Software - Cap. 5 - Princípios de Projeto
Engenharia de Software - Cap. 5 - Princípios de Projeto
1
"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
2
Definição de Projeto de Software
● Frase de Ousterhout é 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"
3
Project vs Design
● Em Inglês, temos duas palavras:
○ Project: esforço colaborativo para resolver problemas
○ Design: desenho ou proposta de uma solução
● Em Português, temos uma única palavra: projeto
● Neste módulo, projeto = design
4
Projetar = Quebrar em partes menores
● Exemplo: compilador
6
Mais um conceito: Abstração
● Conceito que permite "usar" um módulo sem conhecer
detalhes de sua implementação
● Exemplo: scanner (analisador léxico)
○ Implementar um scanner pode ser difícil
○ Mas usar é simples:
String token;
token = Scanner.next_token();
7
O que vamos estudar? (parte 1)
● Propriedades de "bons projetos" de software
○ Integridade Conceitual
○ Ocultamento de Informação
○ Coesão
○ Acoplamento
8
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
9 ○ Substituição de Liskov
Propriedades de Projeto
10
Integridade Conceitual
11
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
12
Exemplos
● 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 são mostrados com 2 casas decimais
13
Integridade Conceitual vale também para o
projeto e código de um sistema
14
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
15
Integridade Conceitual = coerência e padronização de
funcionalidades, projeto e implementação
Exemplo Contra-Exemplo
16
Integridade conceitual é a consideração mais
importante no projeto de sistemas -- Fred Brooks
17
Motivo: integridade conceitual facilita uso e
entendimento de um sistema
18
Ocultamento de Informação
(Information Hiding)
19
Origem do conceito (David Parnas, 1972)
20
Ocultamento de Informação
Desenvolvimento em paralelo
Suponha que um sistema X foi implementado por meio de
classes C1, C2, …, Cn. Quando essas classes ocultam suas
principais informações, fica mais fácil implementá-las em
paralelo, por desenvolvedores diferentes.
21
Ocultamento de Informação
Flexibilidade a mudanças
Por exemplo, suponha que descobrimos que a classe Ci é
responsável pelos problemas de desempenho do sistema.
Quando detalhes de implementação de Ci são ocultados do
resto do sistema, fica mais fácil trocar sua implementação por
uma classe Ci, que use estruturas de dados e algoritmos mais
eficientes.
22
Ocultamento de Informação
Facilidade de entendimento.
Por exemplo, um novo desenvolvedor contratado pela
empresa pode ser alocado para trabalhar em algumas classes
apenas.
23
24
placa modelo
25
Construtora, cria a
Hashtable
26
Problema: clientes precisam manipular uma estrutura de dados
interna da classe, para estacionar um veículo
27
Problema
● Classes precisam de um pouco 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
28
Agora uma versão com ocultamento de
informação
29
1
30
2
31
3
33
Coesão
34
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.
35
Contra-exemplo 1
36
Contra-exemplo 1
37
Contra-exemplo 2
38
Contra-exemplo 2
39
Exemplo
40
Acoplamento
41
Acoplamento
● Nenhuma classe é uma ilha ...
● Classes dependem uma 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
42
Acoplamento Aceitável
● Classe A depende de uma classe B:
○ Mas a classe B possui uma interface estável
○ Classe A somente chama métodos da interface de B
43
Classe Estacionamento depende (está
acoplada) à classe Hashtable, mas
esse acoplamento é aceitável
44
Acoplamento Ruim
● Classe A depende de uma classe B:
a. Mas interface da classe B é instável
b. Ou então a dependência não ocorre via interface de B
45
Como uma classe A pode depender de uma classe
B sem ser via a interface de B?
46
B A
grava lê
"arq1.db
"
47
B A
grava lê
"arq1.db
"
48
Problema
● Classe B não sabe que a classe A é sua "cliente"
● Logo, B pode mudar o formato do arquivo ou mesmo
deixar de salvar o dado que é lido por A
B A
grava lê
"arq1.db
"
49
Tornando acoplamento ruim em bom
50
Tornando acoplamento ruim em bom
51
Frase muito comum
52
Riscos de Acoplamento
Principalmente, quando envolve dependências para
bibliotecas de terceiros
53
https://xkcd.com/2347
Princípios de Projeto
54
"Diretriz" Consequência (o que
vamos ganhar
55
seguindo o princípio)
Princípios SOLID
● Single Responsibility Principle
● Open Closed/Principle
● Liskov Substitution Principle
● Interface Segregation Principle
Robert Martin
● Dependency Inversion Principle
56
(1)Princípio da Responsabilidade Única
57
Princípios da Responsabilidade Única
● Toda classe deve ter uma única responsabilidade
● Deve existir um único motivo para modificar uma classe
58
Responsabilidade #1: calcular índice de
desistência
59
Responsabilidade #2: imprimir índice de
desistência
60
Agora versão com separação de
responsabilidades
61
62
Uma única responsabilidade: interface com o usuário
63
Uma única responsabilidade: "lógica ou regra de negócio"
64
Vantagens
● Classe de negócio (Disciplina) pode ser usada por mais de
uma classe de interface (Console, WebApp, MobileApp ...)
● Divisão de trabalho:
○ Classe de interface: frontend dev
○ Classe de negócio: backend dev
65
(2) Princípio da Segregação de Interfaces
66
Segregação de Interfaces
● Interfaces devem ser pequenas, coesas e específicas para
cada tipo de cliente
● Caso particular do princípio anterior, mas voltado para
interfaces
67
Interface genérica: trata de funcionários CLT e de
funcionários públicos
69
70
Comum para todos funcionários
71
Específica para funcionários CLT
72
Específica para funcionários públicos
73
(3) Princípio da Inversão de Dependências
74
Inversão de Dependências
● Na verdade, vamos chamar esse princípio de "Prefira
Interfaces a Classes"
● Pois transmite melhor a sua ideia!
75
76
Nos clientes, quando declarar
variáveis ou parâmetros prefira
sempre uma interface
77
Por que?
● Cliente funciona com qualquer classe que implementa I
● Isto é, com objetos das classes C1 e C2
● E também com uma nova classe (por exemplo, C3) que
venha a ser criada
78
Exemplo
79
Exemplo
80
(4) Prefira Composição a Herança
81
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.
82
Herança
● Relação "é-um"
● Exemplo: MotorGasolina é-um Motor
● No código:
class MotorGasolina extends Motor {
... // herda atributos e métodos de motor
83
Composição
● Relação "possui"
● Exemplo: Painel possui ContaGiros
● No código:
class Painel {
ContaGiros cg; // possui um atributo
...
}
84
Prefira Composição a Herança ⇒ não force o uso
de herança
85
Uso "forçado" de herança
Herança
86
Uso "forçado" de herança
Herança
em vez de:
Composição
87
(5) Princípio de Demeter
88
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
89
Motivo
● Longas cadeias de chamadas quebram "encapsulamento"
● Não quero passar por A, B, C, D até obter que eu preciso
● Elos intermediários tornam a chamada frágil
90
Define quais chamadas de métodos são
"permitidas" no corpo de um método
91
92
93
94
95
Cenário não recomendado pelo Princípio de Demeter:
96 https://medium.com/@evan.hopkins.us/the-law-of-demeter-and-its-application-to-react-ab1e054f13c5
Mas cuidado
● Demeter -- e demais princípios -- são recomendações
● Não devemos ser radicais e achar que cadeias de
chamadas de métodos são totalmente proibidas
● Casos isolados podem existir e não justificar uma mudança
no projeto
97
(6) Princípio Aberto/Fechado
98
Princípio Aberto/Fechado
● Proposto por Bertrand Meyer
● Ideia: uma classe deve estar fechada para modificações,
mas aberta para extensões
99
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
100
Como tornar uma classe aberta a extensões, mas
mantendo o seu código fechado para modificações?
● Parâmetros
● Funções de mais alta ordem
● Padrões de projeto
● Herança
● etc
101
Exemplo Ordena a lista passada com parâmetro
102
Exemplo Ordena uma lista passada com parâmetro
103
Mas agora eu quero ordenar as strings da lista
pelo seu tamanho, isto é, pelo número de chars
104
Será que o método sort está aberto (preparado) para permitir
essa extensão?
105
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
106
Resumindo: ao implementar uma classe, pense em
pontos de extensão!
107
(7) Princípio de Substituição de Liskov
108
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
109
Primeiro: vamos entender o termo "substituição"
110
111
112
● Tipo A pode ser substituído por B1, B2, B3,...
● Desde que eles sejam subclasses de A
● Em tempo de execução, método g chamado
vai ser aquele de B1, B2, B3, etc.
113
Princípio de Substituição de Liskov
● Redefinições de métodos em subclasses são possíveis
● Mas devem preservar o contrato do método da superclasse
● Preservar o contrato: tanto faz chamar A.g ou B1.g ou
B2.g ou B3.g
114
Para concluir, vou dar um exemplo do dia-a-dia
115
● Suponha um Médico A plantonista em um hospital
● Em um fim de semana, ele não poderá fazer seu plantão
● Então, ele pede para um colega B1 substituí-lo
● Quando a substituição vai funcionar?
○ Quando B1 tiver pelo menos a mesma competência de A
○ A substituição não vai afetar o funcionamento do hospital
○ Substituição de Liskov
116
● Quando a substituição não vai funcionar?
○ Exemplo: quando A for um Clínico Geral e B1 um Pediatra
○ Essa substituição vai prejudicar o funcionamento do hospital
117
void f(A a) {
...
a.g();
...
}
O método f pode ser chamado passando-se como parâmetros objetos de subclasses B1, B2, …, Bn da
classe base A, como mostrado a seguir:
f(new B1()); // f pode receber objetos da subclasse B1
...
f(new B2()); // e de qualquer subclasse de A, como B2
...
f(new B3()); // e B3
119