You are on page 1of 119

Engenharia de Software

Cap. 5 - Princípios de Projeto

Prof. MSc. Presleyson Lima

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

● Analisador Léxico (como if, for, while, x, +, etc.).


● Analisador sintático que vai analisar as tokens e verificar se elas respeitam a gramática
da linguagem fonte.
● Analisador semântico detecta, por exemplo, erros de tipo; e o gerador de código, que vai
converter a representação do programa para uma linguagem de mais baixo nível.
5
Módulos
● Em Engenharia de Software:
○ Partes menores = módulos (pacotes, componentes,
classes, etc)

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

Resultado: classe Estacionamento fica livre para alterar a sua


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

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

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

37
Contra-exemplo 2

38
Contra-exemplo 2

Deveria ser quebrada em duas classes:


Estacionamento e Gerente

39
Exemplo

Todos esses métodos manipulam os elementos da Pilha

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

Maximize a coesão, minimize o acoplamento

Mas cuidado: minimize (ou elimine)


principalmente o acoplamento ruim

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

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


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

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

Ou seja, use I em vez de C1 ou C2

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

Cliente sendo instanciado


com objeto da classe C1

79
Exemplo

Objeto da mesma classe (Cliente),


mas instanciado com objeto do tipo C2

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?

Mas, mantendo o seu código fechado, isto é, sem ter que


mexer no seu código

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

O Princípio de Substituição de Liskov determina as condições — semânticas e não sintáticas — que as


subclasses devem atender para que um programa como o anterior funcione.
Suponha que as subclasses B1, B2, …., Bn redefinam o método g() de A, que é um método chamado no
corpo de f. O Princípio de Substituição de Liskov prescreve que essas redefinições não podem violar o
contrato da implementação original de g em A.
118
Fim

119

You might also like