Professional Documents
Culture Documents
Aula 20
Aula 20
Princípios
de
Projeto
de
Classes
‐
I
• A
classe
como
unidade
de
reúso
de
código
• Princípios
que
garantem
a
boa
práGca
da
programação
orientada
a
objetos
• Princípio
da
Abertura‐Fechamento
• Principio
da
SubsGtuição
de
Liskov
• Programando
para
interfaces
genéricas
• Antropomorfização
dos
dados.
O
que
é
uma
classe?
• Menor
unidade
compilável
na
linguagem
Java
nor
Qual é a me
unidade
p i l á ve l e m C?
com
• Implementação de um Gpo abstrato de dados
O que é um tipo
abstrato de
dados?
Princípios
de
Projeto
de
Classes
• PráGcas
que
orientam
o
desenvolvedor
na
criação
de
melhores
projetos
de
classes.
– Princípio
da
SubsGtuição
de
Liskov
– Princípio
da
Abertura‐Fechamento
– Princípio
da
Inversão
de
Dependências
– Princípio
da
Segregação
de
Interfaces
O que é
O objetivo desses x t en si b i li d ade?
O que é reúso?
e
princípios é
garantir reúso e
extensibilidade.
Reúso
de
SoVware
• Reusar
um
módulo
de
soVware
significa
uGlizar
aquele
módulo
sem
que
seja
necessário
ter
acesso
a
seu
código
fonte.
Quais as
cas
característi
ulo
que um mód
e ve r i a te r para
d
l?
ser reusáve
Extensibilidade
• Um
conjunto
de
módulos
é
extensível
quando
alterações
em
um
desses
módulos
não
leva
a
modificações
nos
outros.
• Alterações
de
atualização:
a
implementação
de
um
módulo
é
alterada.
• Alterações
de
expansão:
um
novo
módulo
é
adicionado
ao
conjunto.
eral, lidar
Em g
m a e xp a n são é
co
Por
mais difícil.
que?
Princípio
da
SubsGtuição
de
Liskov
• Em
qualquer
situação
em
que
se
espera
um
Gpo,
pode‐se
passar
um
subGpo.
public
interface
Shape
{
void
draw(Canvas
canvas,
Paint
paint);
}
public
class
Rectangle
implements
Shape
{
public
class
Square
extends
Rectangle
{
private
int
x,
y,
w,
h;
public
Square(int
x,
int
y,
int
side)
{
public
Rectangle(int
nx,
int
ny,
int
nw,
int
nh)
{
super(x,
y,
side,
side);
x
=
nx;
}
y
=
ny;
w
=
nw;
}
h
=
nh;
}
Esse princípio aplica-se
public
void
draw(Canvas
canvas,
Paint
paint)
{
naturalmente a linguagens que
canvas.drawRect(x,
y,
x
+
w,
y
+
h,
paint);
possuem polimorfismo de
}
}
subtipagem
SubGpagem
em
Ação
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ShapeWindow shapeWindow =
new ShapeWindow(this, 180, 200);
Shape s0 = new Rectangle(0, 0, 100, 180);
shapeWindow.addShape(s0);
Shape s1 = new Square(90, 90, 50);
shapeWindow.addShape(s1);
}
Precisamos
r
implementa
essa visão.
ShapeWindow.java
public
class
ShapeWindow
extends
View
{
private
List<Shape>
shapes
=
new
ArrayList<Shape>();
public
ShapeWindow(Context
context,
final
int
width,
final
int
height)
{
super(context);
Janela
de
Formas
setFocusable(true);
setMinimumWidth(width);
setMinimumHeight(height);
}
protected
void
onMeasure(int
widthMeasureSpec,
int
heightMeasureSpec)
{
setMeasuredDimension(getSuggestedMinimumWidth(),
getSuggestedMinimumHeight());
}
public
void
addShape(Shape
shape)
{
this.shapes.add(shape);
}
protected
void
onDraw(Canvas
canvas)
{
Paint
paint
=
new
Paint();
paint.setStyle(Style.FILL);
paint.setColor(Color.WHITE);
canvas.drawRect(1,
1,
this.getWidth()
‐
1,
this.getHeight()
‐
1,
paint);
paint.setColor(Color.BLACK);
paint.setStyle(Style.STROKE);
for
(Shape
shape
:
shapes)
{
shape.draw(canvas,
paint);
}
}
}
Especialização
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ShapeWindow shapeWindow =
new ShapeWindow(this, 180, 200);
Shape s0 = new Rectangle(0, 0, 100, 180);
shapeWindow.addShape(s0);
Shape s1 = new Square(90, 90, 50);
shapeWindow.addShape(s1);
Rectangle s2 = new Square(90, 90, 50);
shapeWindow.addShape(s2);
}
sa
Será que es
atribuição é
válida?
Generalização
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ShapeWindow shapeWindow =
new ShapeWindow(this, 180, 200);
E essa
Shape s0 = new Rectangle(0, 0, 100, 180);
shapeWindow.addShape(s0); atribuição?
é
Shape s1 = new Square(90, 90, 50); Será que ela
shapeWindow.addShape(s1);
v álida?
Rectangle s2 = new Square(90, 90, 50);
shapeWindow.addShape(s2);
Square s3 = new Rectangle(10, 10, 30, 40);
shapeWindow.addShape(s2);
}
Compilador
guardai‐nos!
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ShapeWindow shapeWindow =
new ShapeWindow(this, 180, 200);
esse s0 = new
Só que nShape SerRectangle(0,
á então 0, 100, 180);
possível
shapeWindow.addShape(s0);
caso o Shape s1 = new
g r a m a n ã o qu Square(90,
eb r ar P SL
90, 50);
pro shapeWindow.addShape(s1);
compila !
em Java?
Rectangle s2 = new Square(90, 90, 50);
shapeWindow.addShape(s2);
Square s3 = new Rectangle(10, 10, 30, 40);
shapeWindow.addShape(s2);
}
Quebrando
PSL
em
Java
public
class
Rectangle
implements
Shape
{
private
int
x,
y,
w,
h;
Será que faz
public
Rectangle(int
nx,
int
ny,
int
nw,
int
nh)
{
sentido que
ossua
x
=
nx;
Rectangle p
y
=
ny;
um método
w
=
nw;
setWidth?
h
=
nh;
}
public
int
getWidth()
{
return
w;
}
Nesse caso, como
public
int
getHeight()
{
return
h;
}
ficaria a
public
void
setWidth(int
nw)
{
w
=
nw;
}
implementação de
public
void
setHeight(int
nh)
{
h
=
nh;
}
Square?
public
void
draw(Canvas
canvas,
Paint
paint)
{
canvas.drawRect(x,
y,
x
+
w,
y
+
h,
paint);
}
}
Quadrados
Mutantes
public
class
Square
extends
Rectangle
{
public
Square(int
x,
int
y,
int
side)
{
• Ao
lado
temos
uma
super(x,
y,
x,
y);
}
implementação
public
void
setWidth(int
nw)
{
bastante
razoável
de
super.setWidth(nw);
Square
super.setHeight(nw);
}
• Essa
implementação
public
void
setHeight(int
nh)
{
quebra
PSL
super.setWidth(nh);
super.setHeight(nh);
}
Como é possível que
}
o princípio da
substituição seja
quebrado?
Área
=
Base
x
Altura
public void stretch(Rectangle r, int fact) {
int area = r.getWidth() * r.getHeight();
r.setWidth(r.getWidth() * fact);
r.setHeight(r.getHeight() * factor);
assert(area * fact * fact == r.getWidth() * r.getHeight());
}
• CollecGons.unmodifiableList
não
suporta
a
operação
add.
Ainda
que
sua
interface
contenha
tal
operação.
• CollecGons.unmodifiableList(dots)
não
respeita
o
Princípio
da
SubsGtuição
de
Liskov!
A
SanGdade
dos
Contratos
• Se
uma
classe
S
estende
outra
Class
T,
então
S
deve
respeitar
o
contrato
de
T.
– Toda
invariante
de
T
deve
ser
válida
em
S.
• Dica:
objetos
mutáveis
têm
mais
chances
de
sofrer
quebras
de
contrato.
• Um
programa
é
uma
enGdade
orgânica,
que
cresce
e
se
modifica.
– Não
dá
para
esperar
o
soVware
alcançar
sua
úlGma
evolução
para
começar
a
usá‐lo.
Como é um
O que causa programa que
mudanças em lida com
programas?
mudanças?
Transladando
Formas
Em que situações
Escreva um pequeno programa que
translade por um certo fator (H, V)
esse problema
cada forma na lista de formas.
ocorre?
Como
implementar
uma solução?
Uma
Solução
public
List<Shape>
moveByFactor(int
h,
int
v,
List<Shape>
shapes)
{
List<Shape>
newShapes
=
new
LinkedList<Shape>();
for
(Shape
shape
:
shapes)
{
if
(shape
instanceof
Rectangle)
{
Rectangle
r
=
(Rectangle)
shape;
newShapes.add(new
Rectangle(r.getX()
+
h,
r.getY()
+
v,
r.getWidth(),
r
.getHeight()));
}
else
if
(shape
instanceof
Square)
{
Square
s
=
(Square)
shape;
newShapes.add(new
Square(s.getX()
+
h,
s.getY()
+
v,
s.getWidth()));
}
else
if
(shape
instanceof
Dot)
{
Dot
d
=
(Dot)
shape;
newShapes.add(new
Dot(d.getX()
+
h,
d.getY()
+
v,
d.getColor(),
d
.getDiameter()));
}
else
{
throw
new
RunGmeExcepGon("Forma
desconhecida!");
}
}
return
newShapes;
}
DrawShapesAcGvity.java
Usando
a
primeira
solução
public
List<Shape>
moveByFactor(int
h,
int
v,
List<Shape>
shapes)
{
@Override
List<Shape>
newShapes
=
new
LinkedList<Shape>();
public
void
onCreate(Bundle
savedInstanceState)
{
for
(Shape
shape
:
shapes)
{
super.onCreate(savedInstanceState);
if
(shape
instanceof
Rectangle)
{
setContentView(R.layout.main);
Rectangle
r
=
(Rectangle)
shape;
List<Shape>
shapes
=
new
LinkedList<Shape>();
newShapes.add(new
Rectangle(r.getX()
+
h,
r.getY()
+
v,
r.getWidth(),
r
shapes.add(new
Rectangle(0,
0,
10,
18));
.getHeight()));
shapes.add(new
Square(90,
90,
25));
}
else
if
(shape
instanceof
Square)
{
shapes.add(new
Dot(12.0F,
13.0F,
Color.BLACK,
6));
Square
s
=
(Square)
shape;
shapes.add(new
Dot(22.0F,
43.0F,
Color.CYAN,
6));
newShapes.add(new
Square(s.getX()
+
h,
s.getY()
+
v,
s.getWidth()));
shapes.add(new
Dot(8.0F,
33.0F,
Color.GREEN,
6));
}
else
if
(shape
instanceof
Dot)
{
ShapeWindow
shapeWindow
=
new
ShapeWindow(this,
180,
200);
Dot
d
=
(Dot)
shape;
for
(Shape
shape
:
moveByFactor(50,
50,
shapes))
{
newShapes.add(new
Dot(d.getX()
+
h,
d.getY()
+
v,
d.getColor(),
d
shapeWindow.addShape(shape);
.getDiameter()));
}
else
{
}
((LinearLayout)
findViewById(R.id.root)).addView(shapeWindow,
0);
throw
new
RunGmeExcepGon("Forma
desconhecida!");
}
shapeWindow.invalidate();
}
}
return
newShapes;
}
Esse programa
Por que?
Movendo
Responsabilidades
• Podemos
dar
para
cada
forma
a
inteligência
para
transladar‐se.
– Como
a
forma
faz
isso
não
"é
da
nossa
conta".
public
interface
Shape
{
public
final
class
Dot
implements
Shape
{
void
draw(Canvas
canvas,
Paint
paint);
public
void
transpose(int
newX,
int
newY)
{
void
transpose(int
x,
int
y);
this.x
=
newX;
}
this.y
=
newY;
}
public
class
Rectangle
implements
Shape
{
}
private
int
x,
y,
w,
h;
Como ficaria o
public
void
transpose(int
newX,
int
newY)
{
método
this.x
=
newX;
this.y
=
newY;
moveByFactor
}
agora?
}
Interface
mais
genérica
public
List<Shape>
newMoveByFactor(int
h,
int
v,
List<Shape>
shapes)
{
for
(Shape
shape
:
shapes)
{
shape.transpose(h,
v);
Como esse método
}
deveria ser
return
shapes;
modificado, se for
}
criada uma forma
nova?
• Uma
vez
que
a
forma
"sabe"
se
transpor,
podemos
pedir‐lhe
que
execute
essa
tarefa
sem
precisar
conhecer
o
"como"
a
tarefa
é
realizada.
– Isso
é
Programação
Orientada
a
Objetos
Programação
Orientada
a
Objetos
public
List<Shape>
newMoveByFactor(int
h,
int
v,
List<Shape>
shapes)
{
for
(Shape
shape
:
shapes)
{
shape.transpose(h,
v);
Mas, o quê é
}
programação
return
shapes;
orientada a
}
objetos?
• Uma
vez
que
a
forma
"sabe"
se
transpor,
podemos
pedir‐lhe
que
execute
essa
tarefa
sem
precisar
conhecer
o
"como"
a
tarefa
é
realizada.
– Isso
é
Programação
Orientada
a
Objetos
Antropomorfização
dos
Dados
• O
Cerne
da
POO
é
a
"antropomorfização"
dos
dados.
– Dados
são
"inteligentes".
– Dados
"sabem"
fazer
as
coisas.
– A
lista
"sabe"
dizer
quantos
elementos
ela
contêm,
e
o
ponto
sabe
dizer
quais
são
suas
coordenadas
X
e
Y,
por
exemplo.
Por que essa
"inteligência" é
tão importante
?
Inteligência
Leva
ao
Encapsulamento
• Se
o
Gpo
"sabe"
realizar
uma
ação,
o
"como"
tal
ação
é
realizada
passa
a
não
ser
relevante.
• Torna‐se
possível,
assim,
que
o
"como",
isto
é,
a
implementação,
seja
encapsulada.
• Encapsulação
foi
uma
idéia
esperta
Nível
10
em
ciência
da
computação.
• Antropomorfização
foi
uma
idéia
esperta
Nível
9
em
ciência
da
computação.
Que outras
idéias espertas
?
em CC existem
Encapsulamento
• Se
o
Gpo
"sabe"
realizar
uma
ação,
o
"como"
tal
ação
é
realizada
passa
a
não
ser
relevante.
Por
que
encapsulamento
• Torna‐se
possível,
assim,
que
o
"como",
isto
é,
a
implementação,
seja
encapsulada.
é
tão
importante?
• Encapsulação
foi
uma
idéia
esperta
Nível
10
em
ciência
da
computação.
• Antropomorfização
foi
uma
idéia
esperta
Nível
9
em
ciência
da
computação.
O
Porquê
do
Encapsulamento
• Encapsulamento
existe
para
reduzir
o
custo
de
modificar
um
programa.
Por que
Por que o
programas têm Qual o custo de o
encapsulament
de ser modificar um
diminui esse
modificados?
programa?
custo?
Estudo
de
caso:
strings
em
C
#include
<stdio.h>
O que faz o
int
main(int
argc,
char**
argv)
{
programa ao
int
i
=
1;
lado?
for
(;
i
<
argc;
i++)
{
int
j
=
0;
for
(;
argv[i][j]
!=
'\0';
j++)
;
prinˆ("Size
of
%s
=
%d\n",
argv[i],
j);
}
}
Estudo
de
caso:
strings
em
C
#include
<stdio.h>
int
main(int
argc,
char**
argv)
{
Compare essas duas
int
i
=
1;
implementações do
for
(;
i
<
argc;
i++)
{
mesmo programa.
int
j
=
0;
Qual a vantagem de
for
(;
argv[i][j]
!=
'\0';
j++)
;
uma com relação à
prinˆ("Size
of
%s
=
%d\n",
argv[i],
j);
outra?
}
}
#include
<stdio.h>
#include
<string.h>
int
main(int
argc,
char**
argv)
{
int
i
=
1;
for
(;
i
<
argc;
i++)
{
prinˆ("Size
of
%s
=
%d\n",
argv[i],
strlen(argv[i]));
}
}
Estudo
de
caso:
strings
em
C
#include
<stdio.h>
int
main(int
argc,
char**
argv)
{
A implementação ao lado
int
i
=
1;
depende de um detalhe da
for
(;
i
<
argc;
i++)
{
implementação de strings em C.
int
j
=
0;
Se o comitê da linguagem decidir
for
(;
argv[i][j]
!=
'\0';
j++)
;
mudar esse detalhe, esse
prinˆ("Size
of
%s
=
%d\n",
argv[i],
j);
programa deixa de funcionar.
}
}
#include
<stdio.h>
#include
<string.h>
int
main(int
argc,
char**
argv)
{
int
i
=
1;
for
(;
i
<
argc;
i++)
{
prinˆ("Size
of
%s
=
%d\n",
argv[i],
strlen(argv[i]));
}
}
Estudo
de
caso:
strings
em
C
#include
<stdio.h>
O programa abaixo não depende
int
main(int
argc,
char**
argv)
{
desse detalhe de implementação.
int
i
=
1;
Ele utiliza uma interface. Se a
for
(;
i
<
argc;
i++)
{
implementação de strings mudar, o
int
j
=
0;
for
(;
argv[i][j]
!=
'\0';
j++)
;
programa abaixo não precisa ser
prinˆ("Size
of
%s
=
%d\n",
argv[i],
j);
modificado, desde que a
}
implementação da interface se
}
adeque à nova realidade.
#include
<stdio.h>
#include
<string.h>
int
main(int
argc,
char**
argv)
{
int
i
=
1;
for
(;
i
<
argc;
i++)
{
prinˆ("Size
of
%s
=
%d\n",
argv[i],
strlen(argv[i]));
}
}
Estudo
de
caso:
strings
em
C
#include
<stdio.h>
A linguagem C não possui
int
main(int
argc,
char**
argv)
{
nenhum mecanismo que force o
int
i
=
1;
for
(;
i
<
argc;
i++)
{
programador a não utilizar
int
j
=
0;
detalhes de implementação.
for
(;
argv[i][j]
!=
'\0';
j++)
;
Encapsulação se dá via
prinˆ("Size
of
%s
=
%d\n",
argv[i],
j);
disciplina de programação.
}
}
#include
<stdio.h>
Linguagens mais #include
<string.h>
modernas disponibilizam int
main(int
argc,
char**
argv)
{
recursos sintáticos para
int
i
=
1;
encapsular dados.
for
(;
i
<
argc;
i++)
{
prinˆ("Size
of
%s
=
%d\n",
argv[i],
strlen(argv[i]));
Quais recursos
}
java provê?
}
Encapsulamento
Sempre?
• Propriedades
de
objetos
imutáveis
não
precisam
ser
necessariamente
encapsuladas.
public
class
ImmutableCircle
implements
Shape
{
O que é um
public
final
int
x,
y,
r;
objeto
public
Circle(int
x,
int
y,
int
r)
{
imutável?
this.x
=
x;
this.y
=
y;
Quais as
this.r
=
r;
vantagens
}
de objetos
public
void
draw(Canvas
canvas,
Paint
paint)
{
imutáveis?
Por que não é
canvas.drawCircle(x,
y,
r,
paint);
necessário
}
encapsular
public
void
transpose(int
x,
int
y)
{
propriedades
throw
new
RunGmeExcepGon("Circle
is
imutable!");
imutáveis?
}
}
Sempre
a
Balança
• Propriedades
de
objetos
imutáveis
não
precisam
ser
necessariamente
encapsuladas.
public
class
ImmutableCircle
implements
Shape
{
O que é um
public
final
int
x,
y,
r;
objeto
public
Circle(int
x,
int
y,
int
r)
{
imutável?
this.x
=
x;
Que
this.y
=
y;
Quais as princípio
this.r
=
r;
esta
vantagensmos
}
ferindo aqu
de objetos i?
public
void
draw(Canvas
canvas,
Paint
paint)
{
imutáveis?
Por que não é
canvas.drawCircle(x,
y,
r,
paint);
necessário
}
Como encapsular
public
void
transpose(int
x,
int
y)
{
poderíamos propriedades
throw
new
Run6meExcep6on("Circle
is
imutable!");
resolver isso?
imutáveis?
}
}
Inspeção
de
Tipos
Dinâmica
public
List<Shape>
moveByFactor(int
h,
int
v,
List<Shape>
shapes)
{
List<Shape>
newShapes
=
new
LinkedList<Shape>();
Inspeção de
for
(Shape
shape
:
shapes)
{
tipos é um
if
(shape
instanceof
Rectangle)
{
sintoma de
Rectangle
r
=
(Rectangle)
shape;
que o código
newShapes.add(new
Rectangle(r.getX()
+
h,
r.getY()
+
v,
r.getWidth(),
r
não adere ao
.getHeight()));
princípio da
}
else
if
(shape
instanceof
Square)
{
abertura-
Square
s
=
(Square)
shape;
fechamento.
newShapes.add(new
Square(s.getX()
+
h,
s.getY()
+
v,
s.getWidth()));
}
else
if
(shape
instanceof
Dot)
{
Dot
d
=
(Dot)
shape;
Mas há
newShapes.add(new
Dot(d.getX()
+
h,
d.getY()
+
v,
d.getColor(),
d
situações em
.getDiameter()));
que o uso
}
else
{
dessa forma
throw
new
RunGmeExcepGon("Forma
desconhecida!");
de reflexão é
}
positivo.
}
Quais?
return
newShapes;
}
Inspeção
Dinâmica
do
Bem
public
List<ImutableCircle>
getImutableCircles(List<Shape>
shapes)
{
List<ImutableCircle>
circles
=
new
LinkedList<ImutableCircle>();
for
(Shape
shape
:
shapes)
{
if
(shape
instanceof
ImutableCircle)
{
circles.add((ImutableCircle)shape);
}
}
return
circles;
Por que nesse Ainda assim
}
caso a inspeçã esse código não
o
dinâmica é é de todo
benéfica?
agradável.
O
Segredo
da
Abetura‐Fechamento
A
chave
para
desenvolvermos
soVware
extensível
é
programarmos
para
as
interfaces
mais
genéricas.
s mais
Por que interface
o
genéricas tornam
f t wa r e m a is s im ples
so
de estender?
Interfaces
Genéricas
public
List<Shape>
moveByFactor(int
h,
int
v,
List<Shape>
shapes)
{
for
(Shape
shape
:
shapes)
{
Shape
shape.transpose(h,
v);
}
return
shapes;
}
Rectangle
Ellipses
e
Porque a interfac
là
acima é preferíve
interface abaixo? Square
Trapezoid
Dot
Circle
public
List<Rectangle>
moveByFactor(int
h,
int
v,
List<Rectangle>
shapes)
{
for
(Rectangle
shape
:
shapes)
{
shape.transpose(h,
v);
}
return
shapes;
}
Recapitulando
• Princípio
da
SubsGtuição
de
Liskov:
código
que
espera
um
Gpo
T
deve
poder
receber
um
Gpo
S
que
seja
subGpo
de
T.
• Princípio
da
Abertura‐Fechamento:
código
deve
ser
fechado
para
uso,
e
aberto
para
extensões.