• Nenhum resultado encontrado

05 Padroes Command

N/A
N/A
Protected

Academic year: 2021

Share "05 Padroes Command"

Copied!
49
0
0

Texto

(1)

Padrão Command

Padrões e Frameworks

Roberto A. Bittencourt

Jairo Calmon

(2)
(3)

Situação-Problema

• Vamos manter este exemplo simples =)

• Nosso personagem executa 3 ações:

– Ao clicar em um área, o player vai para a posição

do mouse.

– Ao apertar key1 (A), o player dá um giro de 360°

– Ao apertar key2 (S), o player muda de cor.

• Todas as ações possuem respectivos tempo de

execução.

(4)

Situação-Problema

• Exemplo em um jogo conhecido: The Sims.

– Você quer que seu Sim acorde, faça a cama, use o

banheiro, tome um banho, vá para a cozinha e

tome o café da manhã.

– Você clica em cada uma das ações, e essas ações

são enfileiradas no canto superior da tela. O Sim

passa a executá-las.

(5)
(6)

Situação-Problema

• Começaremos com uma possível implementação da nossa nave

public class Nave : MonoBehavior { ITween iTween = new ITween(); public Nave(){}

public void move(Vector3 pos){

iTween.MoveTo(gameObject, pos, 2.0f); }

public void shoot(){

iTween.RotateAdd (gameObject, new Vector3 (0, 0, 360), 2.0f); }

int flag = 0;

public void gather(){

iTween.ColorTo (gameObject, new Color (1, flag, flag), 1.0f); flag = flag == 1 ? 0 : 1;

}

public bool isBusy(){

return iTween.tweens.Count > 0; }

(7)

Situação-Problema

• O Tween é basicamente um interpolador.

• Exemplo: iTween.MoveTo(gameObject, pos, 2.0f);

– gameObject é o objeto que guardas as informações gráficas do Sprite como

posição, orientação, translação, cor etc.

– Então o tween pode pegar a posição atual do gameObject. Passamos uma

nova posição “pos”.

– E passamos também o tempo de duração da interpolação.

• Podemos acessar as interpolações em “iTween.tweens”

• Criamos um método “isBusy” que verifica se algum tween

está em execução, o que nos permite controlar a execução

das ações.

(8)

Situação-Problema

• Agora uma possível implementação da nossa aplicação

public class CommandDemo {

public Nave nave;

// Update is called once per frame

void Update () {

if (Input.GetKeyDown(KeyCode.Mouse0)){

Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition);

mp = new Vector3(mp.x, mp.y, 0.0f);

nave.move(mp);

}

else if(Input.GetKeyDown(KeyCode.A)){

nave.shoot();

}

else if(Input.GetKeyDown(KeyCode.S)){

nave.gather();

}

}

}

(9)

Situação-Problema

• Alguns problemas:

– As teclas estão “hardcoded”. Isso tira a

possibilidade de customização.

– Os comandos atuais, não respeitam os comandos

anteriores em execução.

(10)

Situação-Problema

• “AHHHHH! Já sei!!! Vou usar o método isBusy para prevenir execuções!”

public class CommandDemo { public Nave nave;

// Update is called once per frame void Update () {

if(!nave.isBusy()){

if (Input.GetKeyDown(KeyCode.Mouse0)){

Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition); mp = new Vector3(mp.x, mp.y, 0.0f);

nave.move(mp); } else if(Input.GetKeyDown(KeyCode.A)){ nave.shoot(); } else if(Input.GetKeyDown(KeyCode.S)){ nave.gather(); } } } }

(11)

Situação-Problema

• Bom... Parou de interromper... =)

• ... Mas perdemos comandos =(

• “Ah... Mas é claro. Temos que salvar essas

chamadas para quando a ação anterior acabar

de ser executada”.

(12)

Situação-Problema

• “Que tal se eu salvar o input?”

public class CommandDemo { public Nave nave;

List<KeyCode> codes = new List<KeyCode>(); void Update () {

if (Input.GetKeyDown(KeyCode.Mouse0)) codes.add(KeyCode.Mouse0); else if(Input.GetKeyDown(KeyCode.A)) codes.add(KeyCode.A);

else if(Input.GetKeyDown(KeyCode.S)) codes.add(KeyCode.S);

if(!nave.isBusy()){ processInput(codes.popFront()); } } void processInput(KeyCode k){ if (k == KeyCode.Mouse0){ Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition); mp = new Vector3(mp.x, mp.y, 0.0f);

nave.move(mp); }

else if(k == KeyCode.A) nave.shoot(); else if(k == KeyCode.S) nave.gather(); }

(13)

Situação-Problema

• Será que funciona?

• Quase.

• Apesar dos comandos esperarem para ser

executados... A posição do mouse está errada.

• Hmm... A posição deveria ter sido armazenada no

momento da captura da entrada. Mas onde iríamos

armazená-la? Ihh... Complicou.

(14)

Que tal…

• Que tal criarmos uma interface para a chamada de

um método, e as classes implementariam poderiam

salvar os dados que quisessem! =D

(15)
(16)

Command

• O padrão Command visa justamente isso!

• Segundo GoF:

“Encapsular uma solicitação como um objeto, permitindo desta forma

parametrizar clientes com diferentes solicitações, enfileirar ou fazer o

registro (log) de solicitações e suportar operações que podem ser

desfeitas.”

“Commands é uma implementação de callbacks em POO”.

• Segundo GPP:

(17)

Definindo interface e implementação

• Criamos uma interface (provavelmente a mais comum do planeta)

• Criamos uma classe que encapsula os dados e a chamada ao método.

• Note que ShootCommand, neste exemplo, é bem específico – e geralmente

interface ICommand { void execute(); }

class ShootCommand implements ICommand { Nave nave;

public ShootCommand(Nave nave){ this.nave = nave;

}

public void execute(){ nave.shoot();

} }

(18)

Definindo interface e implementação

• O GatherCommand é bem parecido:

class GatherCommand implements ICommand { Nave nave;

public GatherCommand(Nave nave){ this.nave = nave;

}

public void execute(){ nave.gather(); }

(19)

Definindo interface e implementação

• O MoveCommand é semelhante mas armazena dados adicionais:

class MoveCommand implements ICommand { Nave nave;

Vector3 pos;

public MoveCommand(Nave nave, Vector3 pos){ this.nave = nave;

this.pos = pos; }

public void execute(){ nave.move (pos); }

(20)

Implementação Inicial (Retomando)

• Retomando a implementação inicial

public class DemoSemCommand { public Nave nave;

// Update is called once per frame void Update () {

if (Input.GetKeyDown(KeyCode.Mouse0)){

Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition); mp = new Vector3(mp.x, mp.y, 0.0f);

nave.move(mp); } else if(Input.GetKeyDown(KeyCode.A)){ nave.shoot(); } else if(Input.GetKeyDown(KeyCode.S)){ nave.gather(); } } }

(21)

Implementação Inicial com Command

• Introduzindo o Command

public class DemoCommand { public Nave nave;

// Update is called once per frame void Update () {

ICommand cmd = null;

if (Input.GetKeyDown(KeyCode.Mouse0)){

Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition); mp = new Vector3(mp.x, mp.y, 0.0f);

cmd = new MoveCommand(nave, mp); } else if(Input.GetKeyDown(KeyCode.A)){ cmd = new ShootCommand(nave); } else if(Input.GetKeyDown(KeyCode.S)){ cmd = new GatherCommand(nave, mp); } if(cmd) cmd.execute();

(22)

Tudo igual?

• Tudo permanece igual…

(23)

O que muda então?

• Tudo permanece igual…

• Mas… o que ganhamos com isso?

– Nós transformamos uma chamada em um objeto.

– Isso facilita realizar algumas atividades:

• Postergar a execução da chamada.

• Empilhar ou Enfileirar a execução da chamada.

• Cancelar a execução da chamada.

• Com algum cuidado: Desfazer e refazer a execução da chamada.

• …

(24)

Command sem Fila (Retomando)

• Sem fila:

public class DemoCommand { public Nave nave;

void Update () {

ICommand cmd = null;

if (Input.GetKeyDown(KeyCode.Mouse0)){

Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition); mp = new Vector3(mp.x, mp.y, 0.0f);

cmd = new MoveCommand(nave, mp); } else if(Input.GetKeyDown(KeyCode.A)){ cmd = new ShootCommand(nave); } else if(Input.GetKeyDown(KeyCode.S)){ cmd = new GatherCommand(nave, mp); } if(cmd) cmd.execute(); } }

(25)

Implementação com Fila - Command

• Para implementar o empilhar é simples:

public class DemoCommand { public Nave nave;

private Queue<ICommand> commands = new Queue<ICommand>(); void Update () {

ICommand cmd = null;

if (Input.GetKeyDown(KeyCode.Mouse0)){

Vector3 mp = Camera.main.ScreenToWorldPoint(Input.mousePosition); mp = new Vector3(mp.x, mp.y, 0.0f);

cmd = new MoveCommand(nave, mp); } else if(Input.GetKeyDown(KeyCode.A)){ cmd = new ShootCommand(nave); } else if(Input.GetKeyDown(KeyCode.S)){ cmd = new GatherCommand(nave, mp); } if(cmd) commands.Enqueue(cmd);

(26)
(27)

Diagrama

NaveInput Manager ICommand ShootCommand execute() nave : Nave shoot() move(pos) gather() Nave Application

(28)

Command

• Objetivo:

– Encapsular uma solicitação como um objeto, permitindo desta forma

parametrizar clientes com diferentes solicitações, enfileirar ou fazer o

registro (log) de solicitações e suportar operações que podem ser

desfeitas.”

• Também conhecido como

– Action, Transaction

• Contexto

– Se deseja modelar a evolução no tempo de um programa.

– O que precisa ser feito: enfileirar requisições, alarmes, condições…

– O que está sendo feito, quais partes foram executadas?

(29)
(30)

Participantes

• Command (ICommand)

– Declara uma interface para a execução de uma operação.

• ConcreteCommand (MoveCommand, ShootCommand, GatherCommand)

– Define uma vinculação entre um objeto Receiver e uma ação.

– Implementa execute() através da invocação da(s) correspondente(s)

operação(ções) no Receiver.

• Client(Application - CommandDemoComplete)

– Cria um objeto ConcreteCommand e estabelece o seu receptor (Receiver).

• Invoker(NaveInputManager*)

– Solicita ao Command a execução da solicitação.

• Receiver(Nave)

– Sabe como executar as operações associadas a uma solicitação.

– Qualquer classe pode funcionar como um Receiver.

(31)

Colaborações

• O cliente cria um objeto ConcreteCommand e especifica o seu receptor.

• Um objeto Invoker armazena o objeto ConcreteCommand.

• O Invoker emite uma solicitação chamando execute() no Command.

Quando os comandos podem ser desfeitos, ConcreteCommand armazena

estados para desfazer o comando antes de invocar execute().

• O objeto ConcreteCommand invoca operações no seu Receiver para

executar a solicitação.

(32)

Consequências

• Command desacopla o objeto que invoca a

operação daquele que sabe como executá-la.

• Podemos desfazer/refazer comandos

– Cada commando deve armazenar o que precise

para restaurar o estado.

• Podemos armazenar commandos em pilhas e

filas

(33)

Consequências

• Commands são objetos de primeira classe, ou

seja, podem ser manipulados e estendidos

como qualquer outro objeto (Composite).

• Um comando pode ser composto por outros

comandos.

• Fácil de adicionar novos comandos, uma vez

que não é necessário mudar classes existentes

• Cria muitas classes pequenas

(34)

Implementando “Desfazer”

• Apesar de não ser obrigatório, muitos

commands incluem a função de refazer.

• Para isso é adicionada à interface o método

undo()

interface ICommand {

void execute();

void undo();

(35)

Implementando “Desfazer”

• Então, antes de executar o receiver,

precisamos salvar o estado

class MoveCommand implements ICommand {

Nave nave;

Vector3 pos;

Vector3 posBefore;

public MoveCommand(Nave nave, Vector3 pos){

this.nave = nave;

this.pos = pos;

}

public void execute(){

posBefore = nave.transform.position;

nave.move(pos);

}

(36)

Implementando “Desfazer”

• Ao invés de uma fila, implementamos uma

lista onde o ponteiro de execução passa a

andar.

• Para tratar, observamos a ação do usuário

para desfazer:

if(Input.GetKeyDown(KeyCode.Z) && pointer >= 0){

commands[pointer].undo();

commands.RemoveRange(pointer, commands.Count - pointer);

pointer--;

Esperando Execução

Já Executada

(37)

Implementando “Desfazer”

• Observe que por simplicidade eu exclui tudo

que ainda não foi executado.

• Mas poderia deixar guardado para ser possível

fazer o “redo” (refazer). Ou seja, adiaria a

remoção para quando o usuário realizasse

uma ação.

(38)
(39)

Exemplo 2. Entrada de Usuário

• Configurando entrada de usuário

void

InputHandler::handleInput

() {

if

(

isPressed

(

BUTTON_X

)) player.

jump

();

else

if (

isPressed

(

BUTTON_Y

)) player.

fireGun

();

else

if (

isPressed

(

BUTTON_A

)) player.

swapWeapon

();

(40)

Exemplo 2. Entrada de Usuário

• Definindo a interface

• Definindo os comandos

public interface

Command {

public:

public

void

execute

();

};

class

JumpCommand

implements

Command

{

Player player;

public

JumpCommand(Player player){

this.player = player;

}

public

void

execute

() {

player.

jump

();

(41)

• Nosso invoker:

Exemplo 2. Entrada de Usuário

class

InputHandler {

private

Command

buttonX

;

private

Command

buttonY

;

private

Command

buttonA

;

private

Command

buttonB

;

public

void

handleInput

(){

if

(

isPressed

(

BUTTON_X

))

buttonX.execute

();

else

if (

isPressed

(

BUTTON_Y

))

buttonY.execute

();

else

if (

isPressed

(

BUTTON_A

))

buttonA.execute

();

else

if (

isPressed

(

BUTTON_B

))

buttonB.execute

();

(42)

• Nota: Observe que handleInput não verifica se

os objetos são “null”.

• Uma outra vantagem de encapsular a ação em

um objeto é a possibilidade de se utilizar um

outro padrão: Null Object

• Basicamente criamos um Command cujo

execute() não faz nada (semelhante a um

Adapter)

(43)

Exemplo 2. Entrada de Usuário

class

NullCommand {

public

void

execute(){}

}

class

InputHandler {

private

Command

buttonX

= new NullCommand();

private

Command

buttonY

= new NullCommand();

private

Command

buttonA

= new NullCommand();

private

Command

buttonB

= new NullCommand();

public

void

handleInput

(){

if

(

isPressed

(

BUTTON_X

))

buttonX.execute

();

else

if (

isPressed

(

BUTTON_Y

))

buttonY.execute

();

else

if (

isPressed

(

BUTTON_A

))

buttonA.execute

();

(44)

• Adiciona-se uma “indirection layer”:

(45)

• O cliente pode então ser responsável por criar os

comandos, mapear com o receiver (player) e

configurar o invoker (InputHandler)

Exemplo 2. Entrada de Usuário

void main(){

InputHandler inputHandler = new InputHandler();

Player player = new Player();

inputHandler.setButtonXCmd(new JumpCommand(player));

inputHandler.setButtonYCmd(new FireCommand(player));

inputHandler.setButtonACmd(new SwapCommand(player));

inputHandler.setButtonBCmd(new

(46)

Considerações Finais

• É possível surgir um grande número de classes

de commandos.

– Para facilitar é possível criar um classe base

concreta com vários métodos de conveniência.

Isso torna o método execute() em um Subclass

(47)

Considerações Finais

• É possível termos uma hierarquia objetos que

respondem pelos comandos.

– Ele pode tratar da execução

– Ou passar adiante…

(48)

Considerações Finais

• Alguns commandos são pedaços de puro

comportamento sem estado (como o

JumpCommand.

– Ter mais de uma instância pode gastar memória.

– Todas as instâncias são equivalentes.

– O padrão Flyweight trata disso.

(49)

Material e Referências

• http://gameprogrammingpatterns.com/comm

and.html

• http://dsc.ufcg.edu.br/~jacques/cursos/map/h

tml/pat/command.htm

• https://www.youtube.com/watch?

v=l0P8q0gWsR8

Referências

Documentos relacionados

o34eto es'ec-9ico de estdo, elas ser/o reto0adas na discss/o do contedo da Didática descrito 'or 'ro9essores

Outros alunos não atribuíram importância a Química, não apresentando exemplos do seu cotidiano, o que gera preocupação, pois os sujeitos da pesquisa não

Escola Nacional da Magistratura, Associação dos Magistrados Brasileiros e Faculdades de Direito da Universidade Clássica de Lisboa e da Universidade Nova de

De seguida, vamos adaptar a nossa demonstrac¸ ˜ao da f ´ormula de M ¨untz, partindo de outras transformadas aritm ´eticas diferentes da transformada de M ¨obius, para dedu-

Os testes de desequilíbrio de resistência DC dentro de um par e de desequilíbrio de resistência DC entre pares se tornarão uma preocupação ainda maior à medida que mais

As cadeias laterais variam em certo nível entre as diferentes formas de clorofila encontradas em diferentes organismos, mas todas possuem uma cadeia fitol (um terpeno ) ligada

(Henriettea e Henriettella (Melastomataceae; Miconieae) no Rio de Janeiro, Brasil) É apresentado o tratamento taxonômico dos gêneros Henriettea e Henriettella na flora do estado do

Mário Jabur Filho, conforme dispõe a legislação vigente, comunica que estarão abertas as inscrições para seleção dos candidatos para preenchimento de vaga para Médico