Você está na página 1de 123

Engenharia de Software Moderna

Cap. 5 - Princípios de Projeto

Prof. Marco Tulio Valente


https://engsoftmoderna.info, @engsoftmoderna

Licença CC-BY; permite copiar, distribuir, adaptar etc; porém, créditos devem ser dados ao autor dos
1 slides
"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

5
Módulos
● Em Engenharia de Software:
○ Partes menores = módulos (pacotes, componentes,
classes, etc)

6
O que vamos estudar? (parte 1)
● Propriedades de "bons projetos" de software
○ Integridade Conceitual
○ Ocultamento de Informação
○ Coesão
○ Acoplamento

7
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
8 ○ Substituição de Liskov
Propriedades de Projeto

9
Integridade Conceitual

10
Integridade Conceitual = coerência e padronização
de funcionalidades, projeto e implementação

Exemplo Contra-Exemplo
11
Mais um exemplo
Por que falta integridade conceitual nessas capas de slides?

12
Integridade Conceitual
● Decisões de um sistema devem ser coerentes
● Sejam elas sobre:
○ Funcionalidades
○ Interface com usuário
○ Projeto
○ Implementação
○ etc
13
Exemplo (referente a 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 são mostrados com 2 casas decimais

14
Exemplo
(referente a
funcionalidades)

15
Fonte: https://www.shopify.com/partners/blog/feature-creep
Integridade Conceitual vale também para o
projeto e código de um sistema

16
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

17
Integridade conceitual é a consideração mais
importante no projeto de sistemas -- Fred Brooks

18
Motivo: integridade conceitual facilita uso e
entendimento de um sistema

19
Ocultamento de Informação
(Information Hiding)

20
Origem do conceito (David Parnas, 1972)

21
22
placa modelo

23
Construtora, cria a
Hashtable

24
Problema: clientes precisam manipular uma estrutura de dados
interna da classe, para estacionar um veículo
25
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

26
Agora uma versão com ocultamento de
informação

27
1

28
2

29
3

Resultado: classe Estacionamento fica livre para alterar a sua


estrutura de dados interna
30
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

31
Bom módulos são semelhantes a icebergs
(pequena parte pública e visível; grande parte submersa e privativa )

32
Fonte: Bertrand Meyer, Object-oriented software construction, 1997 (pág. 51)
Coesão

33
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.

34
Contra-exemplo 1

35
Contra-exemplo 1

Deveria ser quebrada em duas funções: sin e cos

36
Contra-exemplo 2

37
Contra-exemplo 2

Deveria ser quebrada em duas classes:


Estacionamento e Gerente

38
Exemplo

Todos esses métodos manipulam os elementos da Pilha

39
Acoplamento

40
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
41
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

42
Classe Estacionamento depende (está
acoplada) à classe Hashtable, mas
esse acoplamento é aceitável

43
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

44
Como uma classe A pode depender de uma
classe B sem ser via a interface de B?

45
B A

grava lê

"arq1.db
"
46
B A

grava lê

"arq1.db
"
47
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
"

48
Tornando acoplamento ruim em bom

49
Tornando acoplamento ruim em bom

50
Resumo
● Acoplamento estático (ou estrutural):
○ Classe A depende de uma classe B
○ No código de A, existe uma referência explícita para B
○ Pode ser um acoplamento bom ou ruim
● Acoplamento evolutivo (ou lógico):
○ No código de A, não existe uma referência para B
○ Porém, mudanças em B podem impactar a classe A
51 ○ Tipo de acoplamento sempre ruim
Recomendação comum em projeto de software:

Maximize a coesão, minimize o acoplamento

Mas cuidado: minimize (ou elimine)


principalmente o acoplamento ruim

52
Exercícios

53
1. Suponha um programa em que todo o código está no método
“main”. Ele tem um problema de coesão ou acoplamento? Justifique.
2. Dê um outro exemplo de acoplamento evolutivo entre classes A e
B, semelhante ao que mostramos antes, mas que não seja baseado
em um arquivo compartilhado.
3. Ao implementar qualquer nova funcionalidade que implique na
modificação de classes A e B localizadas em arquivos diferentes, um
programador conclui a tarefa movendo as classes para o mesmo
arquivo. Ou seja: antes de terminar, ele escolhe uma das classes,
digamos B, e a move para o mesmo arquivo de A. Agindo dessa
maneira, ele estará melhorando qual propriedade de projeto? E qual
propriedade estará sendo afetada de modo negativo? Justifique.
54
4. Genericamente falando, qual dos seguintes projetos é melhor?
Justifique (nodos = módulos; arestas = dependências)

Fonte: Bertrand Meyer, Object-oriented software construction, 1997 (pág. 47)

55
5. Qual dos seguintes módulos é melhor? Justifique.

50 LOC interface

interface 500 LOC

implementação 500 LOC

950 LOC implementação (B)

Inspirado em conceitos propostos no livro A Philosophy of


56 (A) Software Design. John Ousterhout
Princípios de Projeto

57
"Diretriz" Consequência (o que
vamos ganhar
58
seguindo o princípio)
59 Figura extraída de um blog post da ThoughtWorks (link)
Princípios SOLID

Robert Martin

60
(1)Princípio da Responsabilidade Única

61
Princípios da Responsabilidade Única
● Toda classe deve ter uma única responsabilidade
● Deve existir um único motivo para modificar uma classe

62
Responsabilidade #1: calcular índice de
desistência
63
Responsabilidade #2: imprimir índice de
desistência
64
Agora versão com separação de
responsabilidades

65
66
Uma única responsabilidade: interface com o usuário

67
Uma única responsabilidade: "lógica ou regra de negócio"

68
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

69
(2) Princípio da Segregação de Interfaces

70
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

71
Interface genérica: trata de funcionários CLT e de
funcionários públicos

O que "getSIAPE" retorna para funcionários CLT?


72
Agora versão que atende segregação de
interfaces

73
74
Comum para todos funcionários

75
Específica para funcionários CLT

76
Específica para funcionários públicos

77
(3) Princípio da Inversão de Dependências

78
Inversão de Dependências
● Na verdade, vamos chamar esse princípio de "Prefira
Interfaces a Classes"
● Pois transmite melhor a sua ideia!

79
80
Nos clientes, quando declarar
variáveis ou parâmetros prefira
sempre uma interface

Ou seja, use I em vez de C1 ou C2

81
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

82
Exemplo

Cliente sendo instanciado


com objeto da classe C1

83
Exemplo

Objeto da mesma classe (Cliente),


mas instanciado com objeto do tipo C2

84
(4) Prefira Composição a Herança

85
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 reúso em larga escala, etc.

86
Herança
● Relação "é-um"
● Exemplo: MotorGasolina é-um Motor
● No código:
class MotorGasolina extends Motor {
... // herda atributos e métodos de motor

87
Composição
● Relação "possui"
● Exemplo: Painel possui ContaGiros
● No código:
class Painel {
ContaGiros cg; // possui um atributo
...
}

88
Prefira Composição a Herança ⇒ não force o
uso de herança

89
Uso "forçado" de herança

Herança

90
Uso "forçado" de herança

Herança

em vez de:

Composição

91
(5) Princípio de Demeter

92
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

93
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

94
Define quais chamadas de métodos são
"permitidas" no corpo de um método

95
96
97
98
99
Cenário não recomendado pelo Princípio de Demeter:

100 https://medium.com/@evan.hopkins.us/the-law-of-demeter-and-its-application-to-react-ab1e054f13c5
Mais um exemplo (e contra-exemplo)

101
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

102
(6) Princípio Aberto/Fechado

103
Princípio Aberto/Fechado
● Proposto por Bertrand Meyer
● Ideia: uma classe deve estar fechada para modificações,
mas aberta para extensões

104
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 editar o código da classe

105
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
106
Exemplo Ordena a lista passada com parâmetro

107
Exemplo Ordena uma lista passada com parâmetro

108
Mas agora eu quero ordenar as strings da lista
pelo seu tamanho, isto é, pelo número de chars

109
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

110
Solução:

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
111
Resumindo: ao implementar uma classe, pense
em pontos de extensão!

112
(7) Princípio de Substituição de Liskov

113
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

114
Primeiro: vamos entender o termo "substituição"

115
116
117
● 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.

118
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

119
Para concluir, vou dar um exemplo do dia-a-dia

120
● 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

121
● 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

122
Fim

123

Você também pode gostar