Você está na página 1de 55
Programação
 em
Java
para
a
 Plataforma
Android
–
AULA
21
 Princípios 
de


Programação
 em
Java
para
a
 Plataforma
Android
–
AULA
21


Princípios 
de
 Projeto
de
Classes
‐
II


Princípios 
de
 Projeto
de
Classes
‐
II
 •   A
classe 
 como
 unidade 
de


A
classe 
 como
 unidade 
de
 reúso
de
 código
 Princípios 
 que 
 garantem
a
boa
 práCca
 da 
 programação
 orientada
a
 objetos
 Principio
 da 
 Segregação
de
Interfaces
 Principio
 da 
 Inversão
de
 Dependências 
 O
 padrão 
de
 projetos
template
 A
lei
das
interfaces
mais 
 genéricas


Princípio
da
Inversão
de
Dependências


Classes
 mais 
 abstratas
 não 
 devem
 depender
 de
classes
 mais 
 concretas.


Na
verdade,
justamente
 o 
 contrário
 deve
 acontecer.


Em 
 outras
 palavras:
classes
provedoras
 devem
 ser
mais 
 abstratas,
e
classes
 clientes
 podem 
 ser
mais 
 concretas.


que é uma classe cliente? E uma classe provedora?
que é
uma classe
cliente? E uma
classe
provedora?
Quando uma classe depende de outra?
Quando uma
classe depende
de outra?

O que significa uma classe ser "mais abstrata"?

O que significa uma classe ser "mais abstrata"?

Princípio 
 da 
 Inversão
de
 Dependências 


É um truque comum na programação de interfaces de jogos desenharmos somente as formas que podem ser vistas pelo usuário.

Formas que estão fora da área de visão, ou que são muito pequenas para serem vistas não precisam ser desenhadas.

Aplicaremos essa técnica em nossa aplicação, desenhando somente aquelas formas que possuem uma área grande.

Implemente um método hasLargeArea que receba uma lista de formas, e retorne a sublista com as formas de área grande, isso é, área maior que um certo limiar.

receba uma lista de formas, e retorne a sublista com as formas de área grande, isso
receba uma lista de formas, e retorne a sublista com as formas de área grande, isso

Primeira
 TentaCva



private
List<Shape>
hasLargeArea(List<Shape>
shapes,
float
area)
{
 



List<Shape>
 largeShapes
=
new
LinkedList <Shape>();






for
(Shape
shape
:
shapes)
{
 





if
(shape
 instanceof
Rectangle)
{










Rectangle
r
=
(Rectangle)
shape;
 







if
( r.getWidth()
*
 r.getHeight()
>=
area)
{
 largeShapes.add(r );
}
 





}
else
if
(shape
 instanceof
Dot)
{
 







Dot
 d 
=
(Dot)
shape;
 







if
( d.getDiameter()
*
 Math.PI
>=
area)
{
 largeShapes.add(d );
}
 





}
else
if
(shape
 instanceof
 ImmutableCircle)
{
 







 ImmutableCircle
 c
=
( ImmutableCircle)
shape;
 







if
( c.r
*
 Math.PI
>=
area)
{
 largeShapes.add(c );
}
 





}
else
{
throw
new
RunCmeExcepCon("Forma
 desconhecida !");
}
 



}
 



return
largeShapes;
 

}


Qual o problema com esse código?
Qual o
problema
com
esse
código?

Testando
via
um
 cliente
simples


Testando
via
um
 cliente
simples
 DrawShapesAcCvity.java
 
public
void
 onCreate(Bundle

Testando
via
um
 cliente
simples
 DrawShapesAcCvity.java
 
public
void
 onCreate(Bundle


DrawShapesAcCvity.java



public
void
 onCreate(Bundle
 savedInstanceState)
{
 



 super.onCreate(savedInstanceState);
 



 setContentView(R.layout.main);
 



List<Shape>
shapes
=
new
 LinkedList <Shape>();






 shapes.add(new 



 shapes.add(new 



 shapes.add(new 



 shapes.add(new 



 shapes.add(new





 ShapeWindow 
 shapeWindow 
=
new
ShapeWindow(this ,
280,
300);
 



for
(Shape
shape
:
 hasLargeArea
(shapes,
20.0F))
{
 





 shapeWindow.addShape(shape );
 



}
 



(( LinearLayout)
 findViewById(R.id.root)).addView(shapeWindow,
0);
 



 shapeWindow.invalidate();




}



Rectangle(0,
0,
10,
18));



Square(90,
90,
25));



Dot(12,
13,
Color.BLACK ,
6));



Dot(22,
43,
Color.CYAN,
6));



Dot(8,
33,
Color.GREEN,
6));


Novamente
a
 Abertura/Fechamento


O
 código
anterior
não 
 adere
 ao 
 princípio 
 da 
 Abertura/Fechamento.


A
inspeção
de
 Cpo 
 dinâmica 
 compromete
a
 extensibilidade 
do
 código.
 Podemos
resolver
isso 
 passando 
 o 
 conhecimento
 de
 área
 para
 cada
forma:


public
interface
Shape
{
 

void
 draw(Canvas
canvas,
Paint
paint);
 

void
 transpose(int
 x ,
int
 y);
 

double
 getArea();
 }


Área
 é
 coisa
de
Forma


public
interface
Shape
{
 

void
 draw(Canvas
canvas,
Paint
paint);
 

void
 transpose(int
 x ,
int
 y);
 

double
 getArea();
 }


public
final
class
Dot
implements
Shape
{
 

private
final
int
diameter;
 

//
 
 

public
double
 getArea()
{
return
 this.diameter
*
 Math.PI;
}
 }


public
class
 ImmutableCircle
implements
Shape
{
 

public
final
 r;
 

//
…
 

public
double
 getArea()
{
return
 this.r 
*
 Math.PI;
}
 }


public
class
Rectangle
implements
Shape
{
 

private
w,
h ;
 

//
 
 

public
double
 getArea()
{
 



return
this.w 
*
 this.h ;
 

}
 }


Doce
 para
 os 
 Olhos 


O
 resultado
final
 é,
sem
 dúvida ,
muito 
 mais 
 agradável:



private
List<Shape>
hasLargeArea2(List<Shape>
shapes,
float
area)
{






List<Shape>
 largeShapes
=
new
LinkedList <Shape>();
 



for
(Shape
shape
:
shapes)
{
 





if
( shape.getArea()
>
area)
{
 







 largeShapes.add(shape );
 





}
 



}
 



return
largeShapes;
 

}


Mas 
 e
 o 
 reúso?


O
 resultado
final
 é,
sem
 dúvida ,
muito 
 mais 
 agradável:



private
List<Shape>
hasLargeArea2(List<Shape>
shapes,
float
area)
{






List<Shape>
 largeShapes
=
new
LinkedList <Shape>();






for
(Shape
shape
:
shapes)
{
 





if
( shape.getArea()
>
area)
{
 







 largeShapes.add(shape );
 





}
 



}
 



return
largeShapes;
 

}


Em que outras situações poderíamos essa função? usar
Em
que outras
situações
poderíamos
essa
função? usar

Seria possível projetar um código mais reusável?

Seria possível projetar um código mais reusável?
Qual a estrutura essencial desse código?
Qual a estrutura
essencial
desse
código?

Filtros 


O
 algoritmo
anterior
é
um
 filtro .


Dada
 uma 
 lista
 L 0 ,
e
um
 predicado
 p ,
o 
 filtro
 produz 
 uma 
 segunda 
 lista
 L 1 
 que 
 contém
 todo 
 elemento
 e
 em
 L 0 
 tal
 que 
 p(e) 
 seja
 verdade.
 Esse 
 filtro
 é
a
 estrutura
 essencial 
 da 
 função
 anterior.
 A
criação
de
 funções
 reusáveis
involve
uma 
 constante
 busca
 pela 
 estrutura
 essencial 
de
 cada
 programa.


Mas… como programar um filtro reutilizável?
Mas… como
programar
um
filtro reutilizável?

Classes
 Abstratas
 em
 Ação


public
 abstract
class
Filter <E>
{




public
List<E>
 filter(Iterable<E>
inputs)
{
 



List<E>
results
=
new
LinkedList <E>();
 



for
(E
 e
:
inputs)
{
 





if
( predicate(e))
{
 







 results.add(e );
 





}
 



}
 



return
results;
 

}




abstract
boolean 
 predicate(E
 e);
 }


O que é esse <E> na declaração da classe?
O que
é esse <E>
na
declaração
da
classe?

O que significa essa palavra abstract?

O que significa essa palavra abstract ?
Como utilizar essa classe?
Como utilizar
essa classe?

Simulando 
 Funções
de
Alta
Ordem


Qual padrão de projetos acabamos de implementar?
Qual padrão de
projetos
acabamos
de
implementar?

A linguagem Java não permite que um método seja passado como parâmetro de outro método.

A linguagem Java não permite que um método seja passado como parâmetro de outro método.
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa
um método seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa

Desenvolvedores fazem longas ginásticas para contornar essa limitação.

Desenvolvedores fazem longas ginásticas para contornar essa limitação.
Desenvolvedores fazem longas ginásticas para contornar essa limitação.
seja passado como parâmetro de outro método. Desenvolvedores fazem longas ginásticas para contornar essa limitação.

public
abstract
class
Filter<E>
{




public
List<E>
 filter(Iterable<E>
inputs)
{
 



List<E>
results
=
new
LinkedList <E>();
 



for
(E
 e
:
inputs)
{








if
( predicate(e))
{
 







 results.add(e );
 





}
 



}
 



return
results;
 

}


for
(Shape
shape
:
(new
Filter<Shape>()
{
 

@Override
 

 boolean 
 predicate(Shape 
 s )
{






return
s.getArea()
>
20.0;




}}).filter(shapes))
{
 

 shapeWindow.addShape(shape );
 }




abstract
boolean 
 predicate(E 
 e);
 }


Templates
 Qual a vantagem da programação orientada a
Templates

Qual
a vantagem
da programação
orientada
a
templates?
templates?
uso de templates O chamado é de Inversão de Dependências.
uso de templates
O chamado
é
de
Inversão de
Dependências.

Um
template
é
um
 algoritmo
com
uma 
lacuna.
 Essa 
 é
a
base
dos
 arcabouços
de
so~ware.
 Essas 
lacunas
 são 
 representadas
 pelas 
 interfaces
sobre
as
 quais 
 o 
template
atua.
 Clientes
do
template
devem
 implementar
as
 interfaces
previstas
 em
 seu 
 contrato.


Por que?
Por que?

Estudo 
de
 Caso 


Considere 
 o 
 projeto
de
um
 jogo,
Glóbulos ,
que 
 depende
 de
 duas 
 importantes
 bibliotecas :




 animanix.jar ,
que 
 provê
 facilidades 
 para
 o 
 desenvolvimento
 de
 animações




 easy_physics.jar ,
que 
 provê
 várias
 funções
 para
 o 
 cálculo 
 de
 tensores
 e
 forças
 elásCcas.


animanix.jar 


Glóbulos 


Ambas
as
 bibliotecas 
 são 
 código
 livre ,
e
 seus 
 desenvolvedores
 costumam
 modificar
as
 suas 
interfaces
com
uma 
 certa
 frequência .


É
 interessante
 que 
 o 
 projeto
de
 Glóbulos 
 possa 
 conCnuar
 uClizando 
as
 úl@mas
 versões
das
 bibliotecas 
 externas,
mas
 é
 ainda 
 mais 
 importante
 que 
 o 
 código
de
 Glóbulos 
 não 
precise
ser
muito 
 modificado 
 sempre
 que 
novas
versões
das
 bibliotecas 
 forem
 lançadas.


O
 que 
 fazer?


easy_physics.jar


Adaptadores
 em
 Ação


Uma
forma
de
aCngir 
as
 metas
 propostas
 é
 interpor
entre
o 
 projeto
de
 Glóbulos 
 e
as
 bibliotecas 
 externas
 camadas
de
 adaptadores.


Nesse
 caso,
são 
 necessárias
 duas 
 camadas,
uma 
 para
 animanix.jar 
 e
 outra
 para
 easy_physics.jar.


Quando 
as
 bibliotecas 
 forem
 modificadas ,
apenas 
 os 
 adaptadores
 sofrerão
 alterações.
A
 lógica
de
 Glóbulos ,
contudo,
permanecerá
 inalterada.


Essa 
 é
 uma 
forma
de
inversão
 de
 dependências ,
pois 
a
 implementação
de
 Glóbulos 
 passa 
a
 depender
das
 interfaces
dos
 adaptadores,
as
quais 
 precisam
ser
implementadas
de
forma
mais 
 genérica
 possível .


animanix.jar 


animaCon_layer 


Glóbulos 


physics_layer


easy_physics.jar


Adaptadores


Adaptador
 é
um
 padrão 
de
 projetos.


Possivelmente
 o 
 mais 
simples
 padrão 
 estrutural
 de
 projetos.


Um
adaptador
 é
um
 objeto
 que 
 adapta
a
 interface
de
uma 
 classe 
 provedora
 à 
interface
 esperada
 pela 
 classe 
 cliente.


O tempo todo nós vimos um adaptador. Qual?
O tempo todo
nós vimos um
adaptador.
Qual?

Em 
 Busca
 da 
 Estrutura
 Essencial 


Para
implementarmos
so~ware
reuClizável,
é
 preciso 
 sermos
 capazes
de
 encontrar
a
 estrutura
 essencial 
de
 cada
 aplicação.


A
forma
de
preencher
 essa
 estrutura
 é
 irrelevante.


essa
 estrutura
 é
 irrelevante.
 O que vocês acham que esse Qual a estrutura desenho
O que vocês acham que esse Qual a estrutura desenho essencial dessa representa? aplicação?
O que vocês
acham
que esse
Qual a estrutura
desenho
essencial
dessa
representa?
aplicação?

Em 
 Busca
 da 
 Estrutura
 Essencial 


Para
implementarmos
so~ware
reuClizável,
é
 preciso 
 sermos
 capazes
de
 encontrar
a
 estrutura
 essencial 
de
 cada
 aplicação.


A
forma
de
preencher
 essa
 estrutura
 é
 irrelevante.


essa
 estrutura
 é
 irrelevante.
 Temos aqui uma relação de escutador/ tratador. O
Temos aqui uma relação de escutador/ tratador. O nosso já conhecido padrão Observer.
Temos aqui
uma
relação
de
escutador/
tratador.
O nosso já
conhecido
padrão
Observer.

Princípio 
 da 
 Segregação
de
Interfaces


Clientes
 não 
 devem
ser
forçados
a
 depender
 de
interfaces
que 
 eles
 não 
 usam .


Quando esse inconveniente pode acontecer?
Quando esse
inconveniente
pode acontecer?
Quais os problemas de não se respeitar esse princípio?
Quais os
problemas de
não se respeitar
esse princípio?
Existe algum truque para se preservar esse princípio?
Existe algum
truque para se
preservar esse
princípio?

A
Beleza
do
Simples


O
Simples
 é
Belo…


mas
 não 
 vende!


Explique isso.
Explique isso.

A
Beleza
do
Simples


O
Simples
 é
Belo…


mas
 não 
 vende!


Em 
 geral,
quanto
 mais 
"capacidades"
um
 produto
tem,
ao 
 mesmo
 custo,
mas
 procurado
 ele
 é.
 Essa 
 idéia ,
contudo,
não 
 pode 
ser
aplicada 
 com
tanto
 sucesso 
 em
 desenvolvimento
de
 programas.
 Porque?

public
class
 ShapeWindow 
extends
View
{




private
List<Shape>
shapes
=
new
ArrayList<Shape>();

 

public
 OldShapeWindow(Context 
context,
final
int
width,
final
 int
height)
{
 



 super(context);
 



 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);
}




}


Já vimos essa classe antes. Onde?
Já vimos
essa
classe
antes.
Onde?
Quais os problemas desse projeto?
Quais os
problemas
desse projeto?
Como melhorá-lo?
Como
melhorá-lo?

}


Gerando
 Formas
 Aleatoriamente


public
class
 ShapeWindow 
extends
View
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();

 

public
 OldShapeWindow(Context 
context,
final
int
width,
final
 int
height)
{
 



 super(context);
 



 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);
 



}
 

}
 }


Implemente um método que crie formas aleatoriamente. Esse método é útil para testarmos nossa aplicação. É claro que mesmo um método tão simple requer decisões de projeto.

Que tal se o nosso método recebesse uma "Janela de Formas", e inserisse as formas diretamente na janela?

Gerando
 Formas
 Aleatoriamente


public
void
 generateRandomShapes(ShapeWindow
window,
int
 numShapes ,
int
 maxX,
 



 int
 maxY)
{
 

Random
 r
=
new
Random();




for
(int
 i 
=
0;
i 
<
 numShapes ;
i ++)
{
 



 int
 rx
=
 r.nextInt()
%
 maxX;
 



 int
 ry
=
 r.nextInt()
%
 maxY;
 



 int
size
=
r.nextInt()
%
100;






switch
(r.nextInt()
%
3)
{








case
0:










 window.addShape(new 







break;








case
1:










 window.addShape(new 







break;
 





default:
 







 window.addShape(new 







break;
 



}
 

}
 }


Esse possui projeto

problemas.

Quais?

Esse possui projeto p r o b l e m a s . Quais?


 Rectangle(rx,
ry,
size,
size));



 Square(rx ,
ry,
size));



 ImmutableCircle(rx,
ry,
size));


Gerando
 Formas
 Aleatoriamente


public
void
 generateRandomShapes(ShapeWindow
window,
int
 numShapes ,
int
 maxX,
 



 int
 maxY)
{
 

Random
 r
=
new
Random();




for
(int
 i 
=
0;
i 
<
 numShapes ;
i ++)
{
 



 int
 rx
=
 r.nextInt()
%
 maxX;
 



 int
 ry
=
 r.nextInt()
%
 maxY;
 



 int
size
=
r.nextInt()
%
100;






switch
(r.nextInt()
%
3)
{








case
0:










 window.addShape(new 







break;








case
1:










 window.addShape(new 







break;
 





default:
 







 window.addShape(new 







break;
 



}
 

}
 }


Esse método funciona somente com janelas de formas. Ele poderia ser mais geral. Qual é a sua estrutura essencial?

Quais parâmetros o método deveria receber? Em outras palavras: qual a menor interface que atende a esse método?


 Rectangle(rx,
ry,
size,
size));



 Square(rx ,
ry,
size));



 ImmutableCircle(rx,
ry,
size));


Gerando
 Formas
 Aleatoriamente


public
void
 generateRandomShapes(MonotonicSet<Shape>
set,
int
 numShapes ,
 



 int
 maxX,
int
 maxY)
{
 

Random
 r
=
new
Random();




for
(int
 i 
=
0;
i 
<
 numShapes ;
i ++)
{
 



 int
 rx
=
 r.nextInt()
%
 maxX;
 



 int
 ry
=
 r.nextInt()
%
 maxY;
 



 int
size
=
r.nextInt()
%
100;






switch
(r.nextInt()
%
3)
{








case
0:










 set.add(new
 Rectangle(rx,
ry,
size,
size));
 







break;








case
1:










 set.add(new
 Square(rx ,
ry,
size));
 







break;
 





default:
 







 set.add(new
 ImmutableCircle(rx,
ry,
size));
 







break;
 



}
 

}
 }


Como seria essa interface?
Como seria
essa
interface?

Será que ela teria somente o método add?

Será que ela teria somente o método add ?

Equilíbrio 


public
void
 generateRandomShapes(MonotonicSet <Shape>
set,
int
 numShapes ,
 



 int
 maxX,
int
 maxY)
{




Random
 r
=
new
Random();
 

for
(int
 i 
=
0;
i 
<
 numShapes ;
i ++)
{
 



 int
 rx
=
 r.nextInt()
%
 maxX;
 



 int
 ry
=
 r.nextInt()
%
 maxY;


Precisamos sempre tentar encontrar um equilíbrio entre o simples e o geral. Uma interface precisa ser simples (pequena), porém ela deve conter todos os métodos de sua responsabilidade.





 int
size
=
r.nextInt()
%
100;


public
interface
MonotonicSet <E>
{




void
 add(E 
 e);





 boolean 
 contains(E
 e);




 int
size();


}






switch
(r.nextInt()
%
3)
{








case
0:










 set.add(new
 Rectangle(rx,
ry,
size,
size));










break;








case
1:










 set.add(new
 Square(rx ,
ry,
size));


como ficaria E ShapeWindow agora?
como ficaria
E
ShapeWindow
agora?









break;








default:










 set.add(new
 ImmutableCircle(rx,
ry,
size));










break;






}




}


}


Janelas 
 e
 Listas


public
class
 ShapeWindow 
extends
View
implements
MonotonicSet <Shape>
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();




public
void
 add(Shape 
 e)
{
 



 this.shapes.add(e );
 

}




public
 boolean 
 contains(Shape
 e)
{
 



return
this.shapes.contains(e);
 

}




public
 int
size()
{
 



return
this.shapes.size ();
 

}


}


Java
não 
 provê
 herança
 múlCpla .


Mas 
 podemos 
 simular 
 esse
 recurso
 por 
 meio 
de
 interfaces.


View


MonotonicSet 


ShapeWindow 


Janelas 
 e
 Listas


Porque Java não provê herança múltipla?
Porque
Java
não
provê
herança
múltipla?





 this.shapes.add(e );
 

}


public
class
 ShapeWindow 
extends
View
implements
MonotonicSet <Shape>
{




private
List<Shape>
shapes
=
new
ArrayList<Shape>();




public
void
 add(Shape 
 e)
{


Java
não 
 provê
 herança
 múlCpla .




public
 boolean 
 contains(Shape
 e)
{
 



return
this.shapes.contains(e);
 

}


Qual a diferença entre estender

múltiplas classes, e implementar

múltiplas

interfaces?



public
 int
size()
{






return
this.shapes.size ();




}


}


Mas 
 podemos 
 simular 
 esse
 recurso
 por 
 meio 
de
 interfaces.


View


MonotonicSet 


ShapeWindow 


Mas ,
será
 que 
a
 mistura
 faz
 senCdo ?


Poderíamos
 tentar
 reCrar
a
 lista
de
 formas
 da 
 janela .
 Isso,
contudo,
não 
 é
simples.
 Por
 que?


public
class
 ShapeWindow 
extends
View
implements
MonotonicSet <Shape>
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();
 

@Override
 

protected
void
onDraw(Canvas
canvas)
{
 



 
 

}
 

public
void
 add(Shape 
 e)
{
 this.shapes.add(e );
}
 

public
 boolean 
 contains(Shape
 e)
{
return
this.shapes.contains(e);
}
 

public
 int
size()
{return
this.shapes.size ();
}
 }


Mas ,
será
 que 
a
 mistura
 faz
 senCdo ?


Poderíamos
 tentar
 reCrar
a
 lista
de
 formas
 da 
 janela .
 Isso,
contudo,
não 
 é
simples.
 Por
 que?


public
class
 ShapeWindow 
extends
View
implements
MonotonicSet <Shape>
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();




@Override
 

protected
void
onDraw(Canvas
canvas)
{
 



 
 

}
 

public
void
 add(Shape 
 e)
{
 this.shapes.add(e );
}




public
 boolean 
 contains(Shape
 e)
{
return
this.shapes.contains(e);
}
 

public
 int
size()
{return
this.shapes.size ();
}


E, além do mais, pode ser que não seja interessante fazer essa dissociação. Por que?

}


ACvando/DesaCvando 
 Formas


ACvando/DesaCvando 
 Formas
 Que objeto irá lidar com os eventos de toque? Que infos existem Será
Que objeto irá lidar com os eventos de toque? Que infos existem Será que poderíamos
Que objeto irá
lidar com os
eventos de
toque?
Que infos
existem
Será que
poderíamos
usar um filtro?
um evento
em de
toque?
Que
propriedade
precisa
ser
filtrada?
em de toque? Que propriedade precisa ser filtrada? Modifique a Janela de Formas, para que ela

Modifique a Janela de Formas, para que ela possa tratar eventos de toque.

Uma vez que o usuário encosta em um ponto da janela, somente as formas que estão naquele ponto devem ser mostradas.

Uma vez que o usuário encosta em um ponto da janela, somente as formas que estão
Uma vez que o usuário encosta em um ponto da janela, somente as formas que estão

PerCnência
 em
 Formas


public
final
class
Dot
implements
Shape
{
 

private
int
 x ,
y;
 

private
final
int
diameter;
 

public
 boolean 
 contains(int
 x ,
int
 y)
{
 



 int
 px
=
 Math.abs(this.x
‐
 x )
*
 Math.abs(this.x
‐
 x );
 



 int
 py
=
 Math.abs(this.y
‐
 y)
*
 Math.abs(this.y
‐
 y);
 



return

Math.sqrt(px
+
 py)
<
 this.diameter;
 

}
 }


public
class
Rectangle
implements
Shape
{
 

private
int
 x ,
y,
w,
h ;
 

public
 boolean 
 contains(int
 x ,
int
 y)
{
 



 boolean 
 inX 
=
 x 
>
 this.x 
&&
 x 
<
 this.x 
+
 this.w ;
 



 boolean 
 inY 
=
 y
>
 this.y
&&
 y
<
 this.y
+
 this.h ;
 



return
inX 
&&
 inY;
 

}
 }


Vamos
 evitar
 quebrar
 o 
 princípio 
 da 
 abertura
 fechamento:


Uma
forma
sabe 
 dizer
se
ela 
 contém
um
 ponto
 ou 
 não .


Precisamos tratar o evento de toque.

public
interface
Shape
{
 

void
 draw(Canvas
canvas,
Paint
paint);
 

void
 transpose(int
 x ,
int
 y);
 

double
 getArea();
 

 boolean 
 contains(int
 x ,
int
 y);
 }


public
class
 ImmutableCircle
implements
Shape
{
 

public
final
 int
 x ,
y,
r;
 

public
 boolean 
 contains(int
 x ,
int
 y)
{
 



 int
 px
=
 Math.abs(this.x
‐
 x )
*
 Math.abs(this.x
‐
 x );
 



 int
 py
=
 Math.abs(this.y
‐
 y)
*
 Math.abs(this.y
‐
 y);
 



return

Math.sqrt(px
+
 py)
<
 this.r ;
 

}
 }


O que estamos tramando com esses dois vetores?

O que estamos tramando com esses dois vetores?

TouchListener


public
class
 ShapeWindow 
extends
View
 



implements
MonotonicSet <Shape>,
 OnTouchListener
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>(),
 currentShapes;




public
 ShapeWindow(Context
context,
final
int
width,
final
 int
height)




{





}




protected
void
onDraw(Canvas
canvas)
{





}




public
 boolean 
 onTouch(View 
 v,
MoConEvent
event)




{





}


Como seria o método onTouch?

Como seria o método onTouch ?
Como filtrar as formas?
Como filtrar as
formas?
Podemos usar Filter novamente?
Podemos usar
Filter
novamente?

}


Usando 
 o 
 Filtro 
 Novamente


public
 boolean 
 onTouch(View 
 v,
MoConEvent
event)
{
 

final
 int
 cx
=
( int)event.getX();
 

final
 int
cy
=
(int)event.getY();
 

 currentShapes
=
 (new
Filter<Shape>(){








@Override
 





 boolean
 predicate(Shape
 e )
{
 





return
e.contains(cx,
cy);
 





}}).filter(this.shapes);
 

 this.invalidate();
 

return
true;
 }


É óbvio que podemos usar o filtro novamente:

ele foi feito para ser reutilizável!

É óbvio que podemos usar o filtro novamente: ele foi feito para ser reutilizável!
Como ficaria o método onDraw?
Como ficaria o
método
onDraw?

Por que?

P o r q u e ?

Desenhando 
as
 Formas
 Filtradas 


@Override
 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
:
 currentShapes)
{
 



 shape.draw(canvas,
paint);
 

}


esse método Em que difere de sua primeira versão?
esse
método
Em que difere
de
sua primeira
versão?
Agora falta atualizar construtor de o somente ShapeWindow.
Agora
falta
atualizar
construtor
de
o somente ShapeWindow.

}


Atualizando 
 o 
 Construtor
 da 
 Classe 


public
 ShapeWindow(Context
context,
final
int
width,
final
 int
height)
{
 

 super(context);
 

 setFocusable(true);




 setMinimumWidth(width );
 

 setMinimumHeight(height);
 

 setOnTouchListener(this);
 

 currentShapes
=
shapes;
 }


O que faz essa chamada?
O que faz essa
chamada?
Qual o propósito disso?
Qual o propósito
disso?

interfaces

Que

ShapeWindow

implementa

agora?

interfaces Que ShapeWindow implementa agora?

Encontrando
a
 maior 
 área


Implemente um método getMaxArea que encontre a forma de maior área presente em uma Janela de Formas.

1)Esse método não deveria fazer parte da interface de ShapeWindow. Por que?

2)Mas então, como podemos encontrar a forma de maior área? Formas são dados privados de ShapeWindow.

Encontrando
a
 maior 
 área


Implemente um método getMaxArea que encontre a forma de maior área presente em uma Janela de Formas.

1)Esse método não deveria fazer parte da interface de ShapeWindow. Por que?

2)Mas então, como podemos encontrar a forma de maior área? Formas são dados privados de ShapeWindow.

Shape
 getMaxArea
 





(final
 Iterable<Shape>
shapes)
{
 

…
 }


Lendo 
 os 
 Pontos
de
Volta


Não
 é
 possível 
saber
quais 
 formas
 estão
 presentes
 em
 ShapeWindow.
 Resolva
 esse
 problema.


Quais 
 decisões 
de
 projeto
 devem
ser
 tomadas?
 De
que 
 formas
 diferentes
 podemos 
resolver
 esse
 desafio?


Vantagens,
desvantagens?


A
Interface
Iterable


Podemos
 adicionar 
 mais 
 uma 
interface
a
 ShapeWindow :
Iterable.


Objetos
 desse
 Cpo 
 podem 
ser
usados 
no
 laço
"for‐ each"
que 
 passou 
a
 exisCr
 em
Java
1.5
 Por
 exemplo:


for
(E
e
:
inputs)
{
 





if
( predicate(e))
{
 









 results.add(e );
 





}
 }


Quais são os passos para implementarmos Iterable?

Quais são os passos para implementarmos Iterable?

Implementando
 Iterable


public
class
 ShapeWindow 
extends
View
 



implements
MonotonicSet <Shape>,
 OnTouchListener,
Iterable<Shape>
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();




class
 ShapeWindowIterator
implements
Iterator<Shape>
{






public
 boolean 
 hasNext()
{






}


Como seria

a

implementação de cada um desses métodos?

Como seria a implementação de cada um d e s s e s m é t





public
Shape
next()
{






}






public
void
remove()
{






}








}




public
Iterator<Shape>
iterator()
{
return
new
ShapeWindowIterator();
}


}


Implementando
um
 Iterador


class
 ShapeWindowIterator
implements
Iterator<Shape>
{




private
int
pos
=
0;




public
 boolean 
 hasNext()
{
 



return
pos
<
 shapes.size();
 

}
 

public
Shape
next()
{
 



return
shapes.get(pos ++);
 

}




public
void
remove()
{






throw
new
RunCmeExcepCon








("Aáempt
to
remove
shapes
from
window.");

}






}


Maior 
 Área


Ainda 
 precisamos 
 implementar
 o 
 método
 getMaxArea .


Implemente esse

usando o

método template

nosso

Filter.

Implemente esse u s a n d o o m é t o d o t

Shape
 getMaxArea
 





(final
 Iterable<Shape>
shapes)
{
 

…
 }


O que deveria ser filtrado?

O que deveria ser filtrado?
Como obter o máximo?
Como obter o
máximo?

Filtrando 
 o 
 Maior 
 Elemento


Shape
 getMaxArea(final
 Iterable<Shape>
shapes)
{
 

List<Shape>
 largeShapes
=
(new
Filter<Shape>()
{
 





Shape
 maxSoFar
=
null;
 





@Override
 





 boolean 
 predicate(Shape
 e)
{
 







 boolean 
 hasChanged 
=
false;










if
( maxSoFar
==
null
||
 maxSoFar.getArea()
<
 e.getArea())
{
 









 maxSoFar
=
 e;












 hasChanged 
=
true;
 







}
 







return
hasChanged ;








}
 





}).filter(shapes);









return
largeShapes.get(largeShapes.size()
‐
1);


Esse código é realmente complicado. Explique-o.

E s s e c ó d i g o é realmente complicado. Explique-o.

Será que vale a

pena reusar quando o resultado fica assim?

código

Será que vale a pena reusar quando o resultado fica assim? código

poderíamos

Como

essa

implementar

função de forma

mais simples?

poderíamos Como essa implementar função de forma m a i s s i m p l

}


Simplificando 
 getMax


Shape
getMaxArea2(final
Iterable<Shape>
shapes)
{




Shape
 maxSoFar
=
null;
 

for
(Shape
 s 
:
shapes)
{






if
( maxSoFar
==
null
||
 s.getArea()
>
 maxSoFar.getArea())
{
 





 maxSoFar
=
 s ;
 



}




}




return
maxSoFar;


Shape
 getMaxArea(final
 Iterable<Shape>
shapes)
{
 

List<Shape>
 largeShapes
=
(new
Filter<Shape>()
{
 





Shape
 maxSoFar
=
null;
 





@Override
 





 boolean 
 predicate(Shape
 e)
{
 







 boolean 
 hasChanged 
=
false;
 







if
( maxSoFar
==
null
||
 maxSoFar.getArea()
<
 e.getArea())
{
 









 maxSoFar
=
 e;
 









 hasChanged 
=
true;
 







}
 







return
hasChanged ;
 





}
 





}).filter(shapes);









return
largeShapes.get(largeShapes.size()
‐
1);


}


}


Comparando as duas implementações, quando o reúso de código vale a pena?

Comparando as duas implementações, quando o reúso de código vale a pena?

O
 Bom 
 e
Velho
PSL


public
class
 ShapeWindow 
extends
View










implements
 MonotonicSet <Shape>,
 OnTouchListener,
Iterable<Shape>
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();
 

class
 ShapeWindowIterator
implements
Iterator<Shape>
{






private
int
pos
=
0;






public
 boolean 
 hasNext()
{
 





return
pos
<
 shapes.size();
 



}
 



public
Shape
next()
{








return
shapes.get(pos ++);
 



}
 



public
void
remove()
{
 





throw
new
RunCmeExcepCon("Aáempt
to
remove
shapes
from
window.");
 



}




 

}
 

public
 Iterator<Shape>
 iterator()
{return
new
ShapeWindowIterator();
}
 }


Por que a nossa implementação

iteradores

de

fere o Princípio da Substituição?

Por que a n o s s a implementação iteradores de fere o Princípio da Substituição?

Implemente um método que remove a forma

de

maior área.

Implemente um método que remove a forma de maior área.

Removendo
um
Item
do
Iterador


O
 método
 abaixo 
remove
uma 
forma
 específica
do
 iterador
de
 formas.
 Ele 
 é
 genérico,
e
 pode 
ser
uClizado 
 em
 diversos
 contextos
 diferentes.


void
 removeShape(final
 Iterator<Shape>
shapes,
Shape
 toBeRemoved)
{
 

for
(Shape
 s 
=
 shapes.next ();
 shapes.hasNext ();
 s 
=
 shapes.next ())
{
 



if
( s 
==
 toBeRemoved)
{








 shapes.remove();
 





return;
 



}
 

}
 }


Como usar esse método para remover a forma de maior área?

C o m o u s a r e s s e método para remover a

Removendo
a
Forma
de
Maior 
 Área


void
 removeMaxAreaShape(ShapeWindow 
shapes)
{
 



Shape
 maxAreaShape
=
 getMaxArea(shapes);
 



 removeShape(shapes.iterator(),
 maxAreaShape);
 }


E o que vai acontecer quando executarmos esse método?

E o que vai acontecer quando executarmos esse método?

void
 removeShape(final
 Iterator<Shape>
shapes,
Shape
 toBeRemoved)
{
 

for
(Shape
 s 
=
 shapes.next ();
 shapes.hasNext ();
 s 
=
 shapes.next ())
{
 



if
( s 
==
 toBeRemoved)
{
 





 shapes.remove();
 





return;
 



}
 

}
 }


Removendo
a
Forma
de
Maior 
 Área


void
 removeShape(final
 Iterator<Shape>
shapes,
Shape
 toBeRemoved)
{
 

for
(Shape
 s 
=
 shapes.next ();
 shapes.hasNext ();
 s 
=
 shapes.next ())
{
 



if
( s 
==
 toBeRemoved)
{
 





 shapes.remove();
 





return;
 



}
 

}
 }


void
 removeMaxAreaShape(ShapeWindow
shapes)
{
 



Shape
 maxAreaShape
=
 getMaxArea(shapes);
 



 removeShape(shapes.iterator(),
 maxAreaShape);
 }


removeShape(shapes.iterator(),
 maxAreaShape);
 }
 Não estamos seguindo o Princípio da Substituição.
Não estamos seguindo o Princípio da Substituição.
Não estamos
seguindo o
Princípio da
Substituição.
Como resolver esse problema?
Como resolver
esse problema?

Deixando 
 os 
 Tipos 
 Trabalharem


public
interface
ConstIterator<T>
{




void
start();




 boolean 
 hasNext();




T
next();


}


Essa interface não possui qualquer método para remover elementos da estrutura iterada.

Ele é exatamente o que nossa classe ShapeWindow precisa.

Mas então como

fica

a

de

implementação

ShapeWindow?

M a s e n t ã o c o m o fica a de implementação

A
Nova
Janela 
de
 Formas


public
class
 IterShapeWindow
extends
View
implements
MonotonicSet <Shape>,
 



 OnTouchListener,
ConstIterator<Shape>
{




private
List<Shape>
shapes
=
new
ArrayList<Shape>();




private
int
pos
=
0;


essa

Com

implementação

algo

perdemos

O quê

importante.

perdemos?

foi

que

essa Com implementação algo perdemos O quê importante. perdemos? foi que



public
 boolean 
 hasNext()
{
 



return
pos
<
 this.shapes.size ();
 

}




public
Shape
next()
{
 



return
this.shapes.get(pos ++);
 

}




public
void
start()
{






pos
=
0;




}


Como ficaria o método que recupera a forma de maior área?

Como ficaria o método que recupera a forma de maior área?

}


A
Nova
Janela 
de
 Formas


public
class
 IterShapeWindow
extends
View
implements
MonotonicSet <Shape>,
 



 OnTouchListener,
ConstIterator<Shape>
{
 

private
List<Shape>
shapes
=
new
ArrayList<Shape>();




private
int
pos
=
0;




public
 boolean 
 hasNext()
{
 



return
pos
<
 this.shapes.size ();




}



Shape
getMaxArea3(final
ConstIterator<Shape>
shapes)
{






Shape
 maxSoFar
=
null;






 shapes.start();






for
(Shape
 s 
=
 shapes.next ();
 shapes.hasNext ();
 s 
=
 shapes.next ())
{








if
( maxSoFar
==
null
||
 s.getArea()
>
 maxSoFar.getArea())
{










 maxSoFar
=
 s ;








}






}






return
maxSoFar;




}




public
Shape
next()
{






return
this.shapes.get(pos ++);




}




public
void
start()
{






pos
=
0;




}


}


Um
Apanhado 
de
Interfaces


View

View onMeasure onDraw

onMeasure

onDraw

MonotonicSet

add

contains

size

ConstIterator<Shape>

start

next

hasNext

OnTouchListener

onTouch

ShapeWindow shapes currentShapes pos ShapeWindow onMeasure onDraw add contains size onTouch start hasNext next
ShapeWindow
shapes
currentShapes
pos
ShapeWindow
onMeasure
onDraw
add
contains
size
onTouch
start
hasNext
next
onDraw add contains size onTouch start hasNext next Clientes podem usar instâncias de ShapeWindow como

Clientes podem usar instâncias de ShapeWindow como ShapeWindow, ou como View, ou como MonotonicSet, ou como…

E métodos que se aplicam a MonotonicSet (ou view, ou…) podem ser usados sobre ShapeWindow, ou sobre outros MonotonicSets.

Recapitulando 


Princípio 
 da 
 Inversão
de
 Dependências :
código
 mais 
 abstrato
 não 
 deve
 depender
de
 código
 mais 
 concreto.


Princípio 
 da 
 Segregação
de
Interfaces:
cada
 classe 
 deve
 ter
 somente
 uma 
 responsabilidade .