• Nenhum resultado encontrado

07 Padroes State

N/A
N/A
Protected

Academic year: 2021

Share "07 Padroes State"

Copied!
51
0
0

Texto

(1)

Padrão State

Padrões e Frameworks

Roberto A. Bittencourt

Jairo Calmon

(2)

Situação

• Suponha que estamos trabalhando com um pequeno

jogo plataforma.

• Nosso trabalho é implementar o herói

• O personagem deve responder à entrada de usuário

• Aperte B e ele deve pular.

(3)

Situação

• De maneira simples:

• Percebeu algum bug?

void Heroine::handleInput(Input input){ if (input == PRESS_B){

yVelocity_ = JUMP_VELOCITY; setGraphics(IMAGE_JUMP); }

(4)

Situação

• Nada impede que ele pule no ar

• O jogador pode ficar apertando B e ele vai ficar

flutuando para sempre.

• Um forma simples de corrigir é adicionar uma flag

isJumping no herói para verificar se ele está pulando

void Heroine::handleInput(Input input){ if (input == PRESS_B){ if (!isJumping_){ isJumping_ = true; yVelocity_ = JUMP_VELOCITY; setGraphics(IMAGE_JUMP); } } }

(5)

Situação

• Agora queremos que o herói se abaixe.

• Quando o jogador apertar “direcional para baixo” o

herói se abaixa enquanto no chão

• Ao soltar o botão o player volta a ficar de pé

void Heroine::handleInput(Input input){ if (input == PRESS_B){

// Jump if not jumping... }

else if (input == PRESS_DOWN){ if (!isJumping_){

setGraphics(IMAGE_DUCK); }

}

else if (input == RELEASE_DOWN){ setGraphics(IMAGE_STAND);

(6)

Situação

• Com esse código

o jogador pode

– Apertar para baixo para abaixar

– Apertar B para pular da posição abaixada

– Soltar para baixo enquanto no ar

• O herói vai mudar para a animação de “ficar em pé”

no meio do pulo

(7)

Situação

• Hora de adicionar uma outra flag:

void Heroine::handleInput(Input input){ if (input == PRESS_B){

if (!isJumping_ && !isDucking_){ // Jump...

} }

else if (input == PRESS_DOWN){ if (!isJumping_){

isDucking_ = true;

setGraphics(IMAGE_DUCK); }

}

else if (input == RELEASE_DOWN){ if (isDucking_){

isDucking_ = false;

setGraphics(IMAGE_STAND); }

(8)

Situação

• Seria legal se o herói fizesse um “dive attack” quando

o jogador aperta para baixo no meio de um pulo!

(9)

Situação

void Heroine::handleInput(Input input){ if (input == PRESS_B){

if (!isJumping_ && !isDucking_){ // Jump...

} }

else if (input == PRESS_DOWN){ if (!isJumping_){ isDucking_ = true; setGraphics(IMAGE_DUCK); } else { isJumping_ = false; setGraphics(IMAGE_DIVE); } }

else if (input == RELEASE_DOWN){ if (isDucking_){ // Stand... } }

Hora de achar

bug de novo

=/

(10)

Situação

• Embora não dê para pular enquanto pulando…

• … Dá pra pular enquanto “mergulhando”

• Vamos adicionar outro campo/flag?

• Ou que tal… Admitir que tem algo claramente

estranho em nossa abordagem?

• A gente nem colocou o “caminhar” ainda…

• Mas do jeito que as coisas tão indo…

(11)

FSM – Máquinas de Estados Finitas

• Meio frustrado, você tira tudo da mesa, deixa só uma

caneta e papel e começa a desenhar um diagrama…

(12)

FSM – Máquinas de Estados Finitas

• A essência das FSM são:

– Conjunto fixo de estados (standing, jumping, ducking,

diving…)

– A máquina pode estar apenas em um estado por vez

• Não queremos que nosso herói pule e antes ao mesmo tempo

• Um dos motivos para escolhermos usar uma FSM

– Entrada ou eventos são enviados à máquina

• No nosso caso apertar e soltar botões.

– Cada estado tem um conjunto de transições

• Associada com uma entrada/evento

(13)

FSM – Máquinas de Estados Finitas

• Quando uma entrada chega, se ela corresponder a

alguma transição a máquina muda para o estado

apontado.

• Exemplo:

– Apertar para baixo enquanto de pé faz a transição para o

estado abaixado (ducking)

– Apertar para baixo enquanto pulando faz a transição para

o estado “diving”

– E se chegar uma entrada que não tenha transição para o

estado?

(14)

FSM – Máquinas de Estados Finitas

• Resumindo:

–Estados

–Entradas

–Transições

(15)

Enums e Switches

• Um dos problemas no nosso exemplo até então é

que algumas combinações daqueles campos

booleanos não são válidos:

– isJumping e isDucking nunca deveriam ser ambos true

• Ter várias flags, sendo que apenas uma pode ser true

por vez pode sugerir que:

(16)

Enums e Switches

• No nosso caso, o Enum vai ser exatamente o

conjunto de estados

• Ao invés de um monte de flags, a classe Herói terá um campo

“State state”

enum State { STATE_STANDING, STATE_JUMPING, STATE_DUCKING, STATE_DIVING };

(17)

Enums e Switches

• Mudamos também a ordem das condições

– No código anterior verificamos o input e depois o estado

– Agora a verificação do estado fica antes

(18)

void Heroine::handleInput(Input input){ switch (state_){ case STATE_STANDING: if (input == PRESS_B){ state_ = STATE_JUMPING; yVelocity_ = JUMP_VELOCITY; setGraphics(IMAGE_JUMP); }

else if (input == PRESS_DOWN){ state_ = STATE_DUCKING; setGraphics(IMAGE_DUCK); } break; case STATE_JUMPING: if (input == PRESS_DOWN){ state_ = STATE_DIVING; setGraphics(IMAGE_DIVE); } break; case STATE_DUCKING: if (input == RELEASE_DOWN){ state_ = STATE_STANDING; setGraphics(IMAGE_STAND); } break; }

Melhorou?

(19)

Situação

void Heroine::handleInput(Input input){ if (input == PRESS_B){

if (!isJumping_ && !isDucking_){ // Jump...

} }

else if (input == PRESS_DOWN){ if (!isJumping_){ isDucking_ = true; setGraphics(IMAGE_DUCK); } else { isJumping_ = false; setGraphics(IMAGE_DIVE); } }

else if (input == RELEASE_DOWN){ if (isDucking_){ // Stand... } }

O código anterior

era assim

(20)

Enums e Switches

• Parece trivial, mas temos algumas melhorias em

relação ao código anterior

– O estado encontra-se apenas em um campo único

– Bugs reduzem?

• Código para gerenciar um estado está mais coeso

• Esta é uma das maneiras mais simples de se

implementar uma máquina de estados. Ok para

alguns usos. Porém…

(21)

Enums e Switches

• … Seu problema pode exigir um pouco mais do que

essa solução pode oferecer.

• Exemplo:

– Resolvemos adicionar uma funcionalidade que permite o

herói ficar abaixado por um tempo para carregar e liberar

um ataque especial.

(22)

Enums e Switches

• Então… enquanto abaixado é preciso manter e

atualizar o tempo de carga.

• Precisamos de um atributo em Herói que armazene

por quanto tempo foi carregado

• Assumindo que já há um método update em Herói

void Heroine::update(){ if (state_ == STATE_DUCKING){ chargeTime_++; if (chargeTime_ > MAX_CHARGE){ superBomb(); } }

(23)

Enums e Switches

• Precisamos também resetar o contador quando ele

começa a abaixar… Assim, modificamos handleInput:

void Heroine::handleInput(Input input){ switch (state_){ case STATE_STANDING: if (input == PRESS_DOWN){ state_ = STATE_DUCKING; chargeTime_ = 0; setGraphics(IMAGE_DUCK); }

// Handle other inputs... break;

// Other states... }

(24)

Enums e Switches

• Resumindo, para adicionar “charge attack” tivemos

que modificar dois métodos e adicionar um atributo

“chargeTime” em Herói.

• Apesar de só ser significativo para o estado abaixado

(“ducking”).

(25)
(26)

Padrão State

• Atingimos um ponto no nosso exemplo que

uma solução mais orientada a objetos

encaixaria melhor.

– Algumas vezes porém, tudo que precisamos é

apenas um “If”

• Como podemos deixar a solução mais POO?

• Sugestões?

(27)

Padrão State

• Que tal criamos uma interface para state?

• Cada pedaço de comportamento que é

dependente de estado vira um método na

interface.

public interface IHeroineState {

void handleInput(Heroine heroine, Input input); void update(Heroine heroine);

(28)

Padrão State

• Para cada estado, nós definimos uma classe

que implementa a interface.

• Os métodos definem o comportamento do

herói quando naquele estado

• Trocando em miúdos, pegamos cada case do

switch e movemos para sua própria classe

(29)

Padrão

State

class DuckingState extends public HeroineState {

public int chargeTime = 0;

public DuckingState(){}

public void handleInput(Heroine heroine, Input input) {

if (input == RELEASE_DOWN){

// Change to standing state...

heroine.setGraphics(IMAGE_STAND);

}

}

public void update(Heroine heroine) {

chargeTime++;

if (chargeTime > MAX_CHARGE){

heroine.superBomb();

(30)

Padrão State

• Delegue para o estado

• Perdemos aquele switch gigantesco

• Apenas informamos o estado inicial

class Heroine {

private HeroineState state;

public void handleInput(Input input){ state.handleInput(this, input); }

public void update(){ state.update(this); }

// Other methods... };

(31)

Padrão State

• Ráa! Perceberam que passei por cima de um detalhe

aqui, né?

• Para mudar os estados nos precisamos atribuir um

novo estado no atributo “state”.

• Mas de onde vêm esses objetos?

– Com enuns era simples: são tipos primitivos

• Mas agora nossos estados são classes. Precisamos de

um objeto para atribuir.

– Static States

(32)

Padrão State – Static states

• Se o objeto de estado não tem quaisquer atributos, ele

só precisa reimplementar os métodos para serem

chamados através de polimorfismo.

– Nesse caso, não há razões para termos mais de uma instância

– Afinal, todas as instâncias vão ser iguais mesmo…

• Nesse caso, você só precisa criar uma única instância

estática

– Mesmo se você tiver várias máquinas rodando ao mesmo

tempo, elas podem chamar a mesma instância (já que não há

nada “machine-specific”)

(33)

Padrão State – Static states

• Onde colocar a instância estática você escolhe.

• Ache um lugar que faça sentido na sua implementação.

• Qual outro padrão está em aplicação aqui?

• Quais são as vantagens e desvantagens de não colocar State ao

public class HeroineState implements IHeroineState { static StandingState standing = StandingState(); static DuckingState ducking = DuckingState(); static JumpingState jumping = JumpingState(); static DivingState diving = DivingState();

public HeroineState(){}

public void handleInput(Heroine heroine, Input input){} public void update(Heroine heroine){}

(34)

Padrão State – Static states

• Cada um desses atributos estáticos é uma instância

do estado que o jogo usa.

• Para fazer o herói pular, o estado “standing” poderia

ter algo assim:

• Nesse momento geralmente perguntam algo... O que?

• Irei explicar mais adiante...

public void handleInput(Heroine heroine, Input input) {

if (input == PRESS_B){

heroine.setState(HeroineState.jumping); heroine.setGraphics(IMAGE_JUMP);

} }

(35)

Padrão State – Instantiated states

• Porém... As vezes utilizar estados estáticos não dá liga...

• No nosso caso, por exemplo, temos um problema com o

estado “ducking”.

– Ele tem um atributo “chargeTime”, que é específico para o herói que

acontece de estar no estado “ducking”

• Coincidentemente pode acontecer de funcionar se só há um

herói.

– Mas e se adicionarmos multiplayer? Dois na mesma tela

– E se a IA quiser utilizar a máquina também?

(36)

Padrão State – Instantiated states

• No nosso exemplos, nós poderíamos ter que criar um objeto de

estado quando a transição fosse feita:

• Assim, cada FSM tenha sua própria instância do estado.

• Quando o estado é mais “stateful”, esta é a maneira.

• Quando o estado é mais “stateless”, prefere-se economizar

memória e ciclos de CPU para alocar objetos a cada mudança de

// Em StandingState...

public void handleInput(Heroine heroine, Input input) {

if (input == PRESS_DOWN){

heroine.setState(new DuckingState()); // ...

} }

(37)

Padrão State – “Eventos de Borda”

• O objetivo do padrão State é encapsular todo o comportamento e dados

para um estado em sua respectiva classe.

• Isso foi alcançado com certo sucesso, mas ainda falta alguns detalhes...

• Quando movemos a entrada de usuário e a atualização do tempo de

carregamento para DuckingState, teve um pedaço de código que ficou de

fora...

– No estado “standing”, quando o herói começa o ducking, ele faz alguma inicialização

// Em StandingState...

public void handleInput(Heroine heroine, Input input) {

if (input == PRESS_DOWN){ // Change state...

chargeTime_ = 0;

setGraphics(IMAGE_DUCK); }

(38)

Padrão State – “Eventos de Borda”

• O DuckingState é quem deve tomar conta de resetar esse

tempo e tocar a animação

– Afinal de contas é ele quem tem o atributo.

• E se os estados tivessem um “ação de entrada”?

public class DuckingState extends HeroineState {

public void enter(Heroine heroine){

chargeTime_ = 0;

heroine.setGraphics(IMAGE_DUCK);

}

// Other code...

};

(39)

Padrão State – “Eventos de Borda”

• De volta na classe do Herói, poderíamos realizar essa

chamada na mudança do estado.

• No código de StandingState faríamos apenas a chamada

//public void setState(HeroineState state){

public void changeState(HeroineState state){

this.state = state;

this.state.enter(this);

}

public void handleInput(Heroine heroine, Input input) {

if (input == PRESS_DOWN){

heroine.changeState(new DuckingState()); }

(40)

Padrão State – “Eventos de Borda”

• Agora sim a ação de “ducking” está realmente

encapsulada.

• Algumas vantagens sobre essa ação de entrada é:

– Abstrai o tipo de estado que você está utilizando. Irá

funcionar tanto para estáticos quanto para instanciados.

– Irá ser chamado independente do estado do qual partiu

– Evita duplicação de código por fornecer um lugar único de

(41)

Padrão State – “Eventos de Borda”

• Evidentemente podemos estender o que vimos para

suportar uma ação de saída.

– Assim que estivermos saíndo de um estado, o método é

chamado para só então mudar para o novo estado.

public void changeState(HeroineState state){

this.state.exit(this);

this.state = state;

this.state.enter(this);

}

(42)

Padrão State

• Resumindo...

– Permite que um objeto altere seu comportamento

de acordo com o estado interno que se encontra

em um momento dado.

– Maneira bem mais limpa de um objeto mudar seu

comportamento em tempo de execução sem

utilizar uma grande quantidade de condicionais

em grande bloco monolítico de código.

(43)

Padrão State – Qual a pegadinha?

• Tudo que discutimos é verdade e é útil para muitos

problemas, mas...

• “Sua maior virtude é também sua maior falha”

• Máquinas de estado ajuda a desembaraçar o código cabeludo

por exigir uma estrutura bem específica. Tudo que você tem é

um conjunto fixo de estados, um único “estado atual” e

(44)

Padrão State – Qual a pegadinha?

• Em alguns cenários você pode bater de cara nessas

limitações

• Algumas dessas barreiras podem ser transpostas

usando técnicas já conhecidas.

– Concurrent State Machines

– Hierarquical State Machines

– Pushdown Automata

Para mais detalhes:

http://gameprogrammingpatterns.com/state.html#concurrent-state-machines

(45)

Padrão State – Usos

• Apesar de existirem tais extensões, elas ainda

possuem limitações.

• Em jogos é utilizado geralmente para IA

– Embora alguns assuntos como “behavior trees” e

“planning systems” são a tendência.

• Também em jogos são usados para gerenciamento

de entrada de usuário, navegação entre telas de

(46)

Padrão State – Usos

• Programas de desenho usam o padrão para execucar

operações específicas baseado no estado (tool)

selecionada.

• The Flyweight explica como e onde objetos State

podem ser compartilhados.

(47)
(48)

Padrão State – Segundo GOF

• State – “Permite que um objeto altere seu

comportamento quando seu estado interno muda. O

objeto parecerá ter mudado sua classe”

(49)

Participantes

• Contexto

– Define a interface de interesse para os clientes.

– Mantém instância para uma subclasse ConcreteState

que define o estado atual

• State

– Define uma interface para encapsular o comportamento

associado com um estado particular de Contexto

• ConcreteState subclasses

– Cada subclasse implementa um comportamento

associado com um estado de Contexto.

(50)

Colaborações

• Contexto delega requisições para o ConcreteState atual

• Um Contexto pode passar ele mesmo como argumento

para o objeto de estado recebendo a requisição. Permite

o estado acessar o context se necessário.

• Contexto é a interface primária para os clientes. Clientes

podem configurar um context com objetos de estados.

Uma vez que o contexto é configurado, seus clientes não

terão de lidar com os objetos de estado diretamente.

• Tanto Contexto quanto as subclasses ConcreteState

podem decidir qual estado sucede outro e sob quais

circunstâncias.

(51)

Consequências

• O Padrão State localiza comportamento

relative a estados e particiona tais

comportamentos em diferentes estados.

• Deixa transições explícitas

Referências

Documentos relacionados

Para um homem “negar a si mesmo” totalmente, deverá renunciar completamente sua própria vontade. A atitude do servo verdadeiro é, “Para mim, o viver

Nesse estudo comparativo do índice de importância entre dois mangues em cidades diferentes, pudemos concluir que a presença de mais Laguncularia racemosa no manguezal de

Local de realização da avaliação: Centro de Aperfeiçoamento dos Profissionais da Educação - EAPE , endereço : SGAS 907 - Brasília/DF. Estamos à disposição

Código Descrição Atributo Saldo Anterior D/C Débito Crédito Saldo Final D/C. Este demonstrativo apresenta os dados consolidados da(s)

Principais mudanças na PNAB 2017  Estratégia Saúde da Família/Equipe de Atenção Básica  Agentes Comunitários de Saúde  Integração da AB e Vigilância 

햲 Conecte o dLAN 500 AVsmart+ através do cabo de rede fornecido a uma ligação de rede do seu computador ligado ou a um outro dispositivo de rede.. 햳 Encaixe o dLAN 500 AVsmart+

Depois de manusear o medicamento veterinário e antes de comer, beber ou fumar, mudar o vestuário e lavar as zonas expostas com água e sabão. Nocivo para os peixes, não

Entre janeiro e junho, movimento de vendas caiu 11,1% aponta a Associação Comercial de São Paulo; segundo a Serasa Experian retração nas vendas foi de 8,3% em igual período..