State: diferenças entre revisões

Origem: Wikipédia, a enciclopédia livre.
Conteúdo apagado Conteúdo adicionado
KLBot2 (discussão | contribs)
m Bot: A migrar 13 interwikis, agora providenciados por Wikidata em d:Q230866
Linha 55: Linha 55:
== Colaborações ==
== Colaborações ==


O padrão de State pode usar o padrão [[Singleton (padrão de design) | Singleton]] quando necesita a existe de apenas uma instância de cada estado. Se pode utilizar quando se compartem os objetos como Flyweight existindo uma única instância de cada Estado e esta é compartilhada com mais de um objeto.
O padrão de State pode usar o padrão [[Singleton (padrão de design) | Singleton]] quando necessita a existência de apenas uma instância de cada estado. Se pode utilizar quando se compartem os objetos como Flyweight existindo uma única instância de cada Estado e esta é compartilhada com mais de um objeto.


== Como funciona ==
== Como funciona ==

Revisão das 21h39min de 13 de abril de 2013

Diagrama UML Pattern Design State
Diagrama UML Pattern Design State

State é um padrão de projeto de software usado quando o comportamento de um objeto muda, dependendo do seu estado.

Introdução

Em certas ocasiões, quando o contexto em que está a desenvolver requer um objeto que possue comportamentos diferentes dependendo de qual estado se encontra, é difícil manejar a mudança de comportamento e os estados desse objeto, tudo dentro do mesmo bloco de código. O padrão State propõe uma solução para esta complicação, criando basicamente, um objeto para cada estado possível do objeto que o chama.

Objetivo

Permite que um objeto altere seu comportamento de acordo com o estado interno que se encontra em um momento dado.

Motivação

O padrão State é motivado por aqueles objetos que, em seu estado atual, varia o seu comportamento devido as diferentes mensagens que possa receber. Como exemplo, tomamos uma classe Livro, um objeto desta classe terá respostas diferentes, dependendo da seu estado(Disponível ou Emprestado). Por exemplo invocando o método reservar de um objeto da classe Livro seu comportamento será diferente, se o Livro está no estado Disponível ou no estado Emprestado.

Diagrama de Classes

O seguinte diagrama é uma representação da estrutura e relações das classes que servem de modelo para os objetos.

Diagrama UML, ejemplo Padrão State
Diagrama UML, ejemplo Padrão State

Diagrama de Estados

No seguinte diagrama representamos os possíveis estados ou situações em que um Livro pode se encontrar no decorrer da execução em nosso sistema.

Diagrama de Estados em UML, ejemplo Padrão State
Diagrama de Estados em UML, ejemplo Padrão State

Problema

Há uma extrema complexidade no código quando tentamos gerênciar comportamentos diferentes, dependendo de um número de estados diferentes. Também manter o código torna-se difícil, e mesmo em alguns casos, podem apontar a uma inconsistência de estados atuais na forma de implementação dos diferentes estados no código (por exemplo, com variáveis ​​para cada estado).

Considerações

Devemos analisar a complexidade em comparação com outras soluções.

Solução

Se implementa uma classe para cada estado diferente do objeto e o desenvolvimento de cada método para cada estado em particular. O objeto da classe a que pertencem esses estados resolvem os diferentes comportamentos, dependendo de sua condição, com as instâncias das classes de estado. Assim, sempre está presente em um objeto o seu estado atual e se comunica com ele a resolvendo suas responsabilidades.

A idéia principal do padrão State é a introdução de um classe abstrata EstadoLivro que representa os estados e uma interface para todas as classes que representam os próprios estados. Por exemplo, as classes Disponível e Prestado implementam responsabilidades especiais para os estados Disponível e Prestado respectivamente do objeto Livro. A classe Livro mantém uma instância de alguma subclasse de EstadoLivro com o atributo estado que representa o estado actual do Livro. Na implementação dos métodos de Livro haverá chamadas a esses objetos que serão representados pelo atributo estado para a execução das responsabilidades, dependendo de qual estado se encontre en esse momento, enviará essas chamadas para um objeto ou outro das subclasses de EstadoLivro.

Estrutura UML

Os participantes

  1. Contexto: Este integrante define a interface com o cliente. Mantém uma instância de Estado Concreto que define seu estado atual.
  2. Estado: Define uma interface para encapsular as responsabilidades associadas a um estado particular de contexto.
  3. Subclasse EstadoConcreto: Cada uma dessas subclasses implementa o comportamento ou responsabilidade de Contexto.

O Contexto delega o estado específico para o objeto EstadoConcreto atual. Um objeto de Contexto pode passar-se como parâmetro a um objeto Estado. Assim, a classe Estado pode acessar o contexto, se fosse necessário. Contexto é a interface principal para o cliente. O cliente pode configurar um contexto com objetos Estado. Uma vez feito isso, os clientes não têm de lidar com objetos de tipo Estado diretamente. Tanto o objeto de Contexto como os objetos de EstadoConcreto podem decidir a mudança de estado.

Colaborações

O padrão de State pode usar o padrão Singleton quando necessita a existência de apenas uma instância de cada estado. Se pode utilizar quando se compartem os objetos como Flyweight existindo uma única instância de cada Estado e esta é compartilhada com mais de um objeto.

Como funciona

A classe Contexto envia mensagens para os objetos de EstadoConcreto dentro de seu código para dar a estes a responsabilidade que deve ser cumplida pelo objeto Contexto. Então, o objeto Contexto vai mudando as responsabilidades de acordo com o estado em que se encontra, devido a que também muda de objeto EstadoConcreto ao fazer a mudança de estado.

Quando usá-lo?

Está recomendado quando um determinado objeto tem estados e responsabilidades diferentes, dependendo de qual estado você está em determinado momento. Também pode ser usada para simplificar os casos em que há código complicado e extenso de decisão que depende do estado do objeto

Vantagens e desvantagens

São as seguintes vantagens:

  • É fácil de localizar as responsabilidades de estados específicos, devido a que os encontram nas classes que correspondem a cada estado. Isto proporciona uma maior clareza no desenvolvimento e na manutenção subsequente. Esta facilidade é fornecida pelo fato de que diferentes estados são representados por um único atributo (estado) e não envolvidos em diferentes variáveis ​​e grandes condicionais.
  • Faz as mudanças de estado explícitas, posto que em otro tipo de implantação os estados são alterados, modificando os valores em variáveis, enquanto aqui fazer-se representar cada estado.
  • Os objetos Estado podem ser compartilhados se eles não contêm variáveis ​​de instância, isto pode ser alcançado se o estado está totalmente codificado representando seu tipo. Quando isso é feito, os estados são flyweights sem estado intrínseco.
  • Facilita a expansão de estados.
  • Permite a um objeto alterar de classe em tempo de execução dado que ao modificar suas responsabilidades pela de outro objeto de outra classe, a herança e responsabilidades do primeiro mudaram pelas do segundo.

Desvantagem:

  • Aumenta o número de subclasses.

Implementação (Java)

public class Main {

  public static void main(String argv[]) {

    Livro l1 = new Livro("Design Patterns");
    Livro l2 = new Livro("Java Programming Language");

    l1.solicitar();    // Disponivel -> Prestado
    l1.solicitar();    // Ops, o livro já está prestado
    l1.devolver();     // Prestado -> Disponible

    l2.devolver();     // nada, o livro já está disponível
  }

}
 /** 
 * Livro define o contexto para este exemplo simples de padrao estado. 
 * Um Livro puede estar em dos estados: Disponível y Prestado, de tal modo que
 * se escolhemos por representar o estado com um atributo, os métodos da 
 * clase Livro acabariam por convertir-se em condicionais sobre esse estado 
 */

public class Livro {

  // O construtor da classe, además de inicializar o título do
  // livro, define o estado inicial (Disponível). Como neste caso
  // os estados de livros têm o seu próprio estado, usamos um singleton.

  public Livro(String titulo) {
    _titulo = titulo;
    _estado = Disponivel.instancia();
  }

  public String toString() { return (_titulo + " (" + _estado + ")" ); }

  // Este método modifica o estado do livro. Problema: o método deve 
  // ser acessado a partir de uma classe externa (EstadoLibro), o que exclui 
  // a visibilidade private e protect. public é demasiado geral pois 
  // *todas* as classes pode acessar o método... Neste caso, sugere-se a 
  // visibilidade de package, com Livro e os seus estados no mesmo package...

  
  void estabelecerEstado(EstadoLibro estado) {
    System.out.println("Transitando de " + _estado + " a " + estado);
    _estado = estado;
  }

  // Os métodos de dependentes do estado delegam o comportamento
  // definido para cada estado. Uma vez que vamos a responsabilizar aos
  // estados de realizar as transições, passamos o livro ao estado
  // para que possa, se lhe interessa, chamar estabelecerEstado

  public void devolver() { _estado.devolver(this); }
  
  public boolean solicitar() { return _estado.solicitar(this); }

  //-------- privadas ---------

  private EstadoLibro _estado;    // implementa associação com o estado
  private String _titulo;
}
/**
 * Esta é a classe abstrata que define as operações específicas do Estado
 * del estado. Os métodos podem ser declarados abstratos, de modo que
 * as classes derivadas têm que implementar-los forzosamente, o podem
 * ter uma implementação por defeito, definida neste nível
 */

public abstract class EstadoLivro {

  // Os métodos devolver e solicitar são abstratos (devem ser implementados
  // pelos estados concretos) e sao tomados como argumento livro, se
  // desejamos aceder posteriormente aos atributos e métodos do mesmo

  abstract public void devolver(Livro livro);
  abstract public boolean solicitar(Livro livro);

  // Além disso, adicionamos um método com um string que identifica o status
  // do livro -- a definição estabelece um valor por defeito que será
  // usado se as sub-clases nao redefinem

  public String toString() { return "Desconhecido"; }

}
/**
 * Um dos estados concretos do livro. A classe Disponivel faz a
 * transição Disponivel -> Prestado ao chamar a solicitar. Ignora 
 * as devolucioes (nao se contemplam várias cópias do mesmo livro)
 */

public class Disponivel extends EstadoLibro {

  // Uma vez que em este exemplo os estados dos livros nao vao a conter
  // atributos dependentes do contexto, fazemos que Disponivel seja um
  // Singleton

  protected Disponivel() {}

  public static Disponivel instancia() { 
    if (_instancia == null)
      _instancia = new Disponivel();

    return _instancia; 
  }

  // Métodos específicos deste estado concreto. Solicitar modifica o estado
  // do livro, enquanto que devolver simplesmente o ignora

  public boolean solicitar(livro livro) {
    System.out.println("Atendendo pedido do livro " + livro);
    livro.estabelecerEstado(Prestado.instancia());
    return true;
  }

  public void devolver(livro livro) {
    System.out.println("Oba... já tenho o livro " + livro);
  }

  // Redefine o nome do estado...

  public String toString() { return "Disponivel"; }

  //--------- privadas ------------

  // Instancia do Singleton Disponivel

  private static Disponible _instancia;
}
/**
 * Um dos estados concretos do livro. A classe Prestado faz a
 * transição Prestado ->  ao chamar a devolver. Ignora 
 * os pedidos (não se contemplam reservas)
 */

public class Prestado extends EstadoLivro {

  // Dado que neste exemplo os estados dos livros não vão a conter
  // atributos dependentes do contexto, fazemos que Prestado seja un
  // Singleton

  protected Prestado() {}

  public static Prestado instancia() { 
    if (_instancia == null)
      _instancia = new Prestado();

    return _instancia; 
  }

  // Métodos específicos deste estado concreto. Devolver faz a transicão
  // a Disponible, enquanto que solicitar rejeita

  public boolean solicitar(livro livro) {
    System.out.println("El livro " + livro + " não está disponível");
    return false;
  }

  public void devolver(livro livro) {
    System.out.println("Ok. O livro " + livro + " foi devolvido");
    livro.estabelecerEstado(Disponivel.instancia());
  }

  // Redefine o nome do estado

  public String toString() { return "Prestado"; } 

  //--------- privadas ------------

  // Instancia do Singleton Prestado

  private static Prestado _instancia;
}

Conclusões

O padrão não indica exatamente donde definir as transições de um estado para outro. Há duas maneiras de contornar isso: Uma delas é definir essas transições dentro da classe Contexto, a outra é definir essas transições nas subclasses de Estado. É mais conveniente utilizar a primeira solução quando o critério a ser aplicado é fixo, ou seja, não mudará. O segundo é conveniente quando este critério é dinâmico, a desvantagem aqui é apresentada na dependência de código entre as subclasses.

Precisamos também avaliar a implementação ao criar instancias de estado concreto diferentes ou usar a mesma instância compartilhada. Isto irá depender se a mudança de estado é menos frequente ou mais frequente, respectivamente.