Prototype (padrón de deseño)

Prototype é un padrón de deseño creacional que ten como finalidade crear novos obxectos duplicándoos, clonando unha instancia creada previamente. Este padrón especifica a clase de obxectos a crear mediante a clonación dun prototipo que é unha instancia xa creada. A clase dos obxectos que servirán de prototipo deberá incluír na súa interface a maneira de solicitar unha copia, que será desenvolvida máis tarde polas clases concretas de prototipos. Cada obxecto é, en si mesmo, unha fábrica especializada en construír obxectos iguais a el mesmo.

Motivación editar

Este padrón resulta útil en escenarios onde é preciso abstraer a lóxica que decide que tipos de obxectos utilizará unha aplicación, da lóxica que logo usarán eses obxectos na súa execución. Os motivos desta separación poden ser variados, por exemplo, pode ser que a aplicación deba basearse nalgunha configuración ou parámetro en tempo de execución para decidir o tipo de obxectos que se debe crear. Nese caso, a aplicación necesitará crear novos obxectos a partir de modelos. Estes modelos, ou prototipos, son clonados e o novo obxecto será unha copia exacta dos mesmos, co mesmo estado.

Aplicabilidade editar

Cando un sistema debe ser independente da forma na que os seus produtos son creados e as clases a instanciar son especificadas en tempo de execución ou cando só existe un número pequeno de combinacións diferentes de estado para as instancias dunha clase. No caso, por exemplo, dun editor gráfico, podemos crear rectángulos, círculos etc... como copias de prototipos. Estes obxectos gráficos pertencerán a unha xerarquía na que as súas clases derivadas porán en funcionamento o mecanismo de clonación.

Estrutura editar

Na seguinte imaxe podemos ver a estrutura do padrón, no exemplo citado no punto anterior:

 

Participantes editar

Cliente: Encárgase de crear un novo obxecto solicitándolle ao prototipo que se clone.

Prototipo Concreto: Posúe unhas características concretas que serán reproducidas para novos obxectos e pon en funcionamento unha operación para clonarse.

Prototipo: Declara unha interface para clonarse, á cal accede o cliente.

Colaboracións editar

O cliente solicita ao prototipo que se clone.

Consecuencias editar

Aplicar o padrón prototipo permite ocultar as clases produto (prototipos concretos) do cliente e tamén permite que o cliente traballe con clases dependentes da aplicación sen cambios.

Ademais, é posible engadir e eliminar produtos en tempo de execución ao invocar a operación clonar.

Este padrón permite a especificación de novos obxectos mediante o cambio dos seus valores ou mediante a variación da súa estrutura.

Permite unha configuración dinámica da aplicación, reducindo o numero de subclases.

Desvantaxes editar

A xerarquía de prototipos debe ofrecer a posibilidade de clonar un elemento e esta operación pode non ser sinxela de incluír. Se a clonación se produce frecuentemente, o custo pode ser importante. Ademais as clases encargadas de crear os obxectos novos deben coñecer as clases concretas dos obxectos que van instanciar.

Outros detalles editar

Unha interface declara un conxunto de funcións, pero sen introducilas. En Java existe unha interface chamada Cloneable.

public interface Cloneable { 
}

Unha clase que introduce esta interface é a clase Object que pode facer unha copia membro a membro das instancias de dita clase. Nos exemplos de inclusión de clonación profunda e a clonación superficial se usará esta interface.

Clonación profunda e clonación superficial editar

Entre as diferentes modalidades polas que se pode optar á hora de introducir a clonación dun obxecto prototipo, merece a pena destacar dúas maneiras de realizar a clonación: superficial ou profunda.

Na primeira delas, un cambio sobre o obxecto asociado cun clon afecta ao obxecto orixinal, porque os obxectos relacionados son os mesmos (e dicir, a clonación replica só o propio obxecto e o seu estado, non as súas asociacións con terceiros obxectos), mentres que na clonación profunda clónanse os obxectos e tamén os seus obxectos relacionados.

 

Negociador de produtos editar

Unha modificación ou derivación deste padrón é o Negociador de Produtos (Product Trader), que se centra no tratamento dos prototipos cando varios clientes traballan sobre eles. Este padrón incorpora un xestor, xeralmente utilizando o patron singleton, que actúa sobre un conxunto de prototipos fronte aos clientes.

Inclusión editar

Esta sería a inclusión en Java do exemplo proposto:

/**
 * A clase cliente constrúe un obxecto e realiza operacións
 * sen coñecer de forma explícita de que clase é o obxecto (só coñece
 * que descende do prototipo Figura)
 */
public class Cliente {

  // O construtor do cliente establece que prototipo vai usarse
  // nas clonacións

  public Cliente(Figura proto) {
    establecerPrototipo(proto);
  }

  // Método que permite cambiar o prototipo. Adicionalmente interroga
  // ao prototipo acerca da clase á que pertence

  public void establecerPrototipo(Figura proto) {
    System.out.println("Utilizando un prototipo de "
                           + proto.obterClase()
                           + " para construír obxectos");
    _proto = proto;
  }

  // Este método constrúe unha réplica do prototipo, visualiza o seu
  // estado, altérao e visualiza novamente o seu estado. En ningún
  // momento resulta necesario referirnos á clase concreta do obxecto.
  // O prototipo tras a invocación permanece sen alteracións.

  public void construir() {
    Figura p = _proto.clonar();

    System.out.println("[Antes] " + p.info() );
    p.aumentarTamaño();
    System.out.println("[Despois] " + p.info() );
    System.out.println("[Proto] " + _proto.info() );
  }

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

  private Figura _proto;
}
/**
 * Esta é a clase abstracta que representa a interface dos
 * obxectos que incorporan a operación de clonación fundamental
 * para o prototipado.
 */
public abstract class Figura {

  // O construtor do prototipo almacena nun atributo privado
  // un nome para o obxecto. Dito nome pode ser accesible
  // desde as subclases utilizando obterNome()

  public Figura(String nome)  {
      _nome = nome;
  }

  protected String obterNome() {
      return _nome;
  }

  // Este método abstracto permite obter unha réplica de calquera
  // obxecto subclase de Prototipo

  public abstract Figura clonar();

  // Algúns métodos que deben ser definidos ou poden redefinirse
  // nas clases derivadas

  public abstract void aumentarTamaño();

  public abstract String obterEstado();

  public String obterClase() {
      return "Figura";
  }

  // Este método fai uso dos métodos redefinidos polas subclases

  public String info() {
    return ("Obxecto " + _nome
                      + " (" + obterClase() + ")"
                      + " Estado: " + obterEstado() ); }

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

  private String _nome;
}
/**
 * A clase Rectangulo estende ao prototipo. O seu estado está constituído
 * polo alto e o ancho do rectángulo, así como o ángulo de rotación
 */
public class Rectangulo extends Figura {

  // O construtor do rectángulo á parte de inicializar a parte de figura
  //inicializa ancho e alto e establece un ángulo de rotación por defecto

  public Rectangulo(String nome, double ancho, double alto) {
    super(nome);
    _ancho = ancho;
    _alto = alto;
    establecerAngulo(0.0);
  }

  // Redefinición dos métodos proporcionados pola interface Figura

  public void aumentarTamaño() {
    _ancho = _ancho * 2;
    _alto = _alto * 2;
  }

  public String obterEstado() {
    return ("Ancho=" + _ancho + ", Alto=" + _alto + ", Angulo=" + _angulo );
  }

  public String obterClase() {
      return "Rectangulo";
  }

  // Este é un método adicional que está presente só nas instancias de
  // Rectángulo e que permite modificar o estado deste (ángulo)

  void establecerAngulo(double angulo) {
      _angulo = angulo;
  }

  // clonación de rectángulos. A dificultade adicional é a necesidade de crear
  // en primeiro lugar un rectángulo e logo modificar o estado no inicializado

  public Figura clonar() {
    Rectangulo r = new Rectangulo(obterNome(), _ancho, _alto);
    r.establecerAngulo(_angulo);
    return r;
  }

  //------- privadas ----------
  private double _ancho, _alto, _angulo;
}
/**
 * A clase Circulo estende ao prototipo. O seu estado está constituído
 * polo radio do círculo
 */
public class Circulo extends Figura {

  // O construtor do círculo á parte de inicializar a parte
  // do prototipo (super), inicializa o radio

  public Circulo(String nome, double radio) {
    super(nome);
    _radio = radio;
  }

  // Redefinición dos métodos proporcionados pola interface Prototipo

  public void aumentarTamaño() {
      _radio = 2.0 * _radio;
  }

  public String obterEstado() {
      return ("Radio=" + _radio);
  }

  public String obterClase() {
      return "Circulo";
  }

  // A clonación consiste en crear un obxecto con igual nome
  // e igual radio e devolvelo

  public Figura clonar() {
    return (new Circulo(obterNome(), _radio));
  }

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

  private double _radio;
}

Exemplo de inclusión de clonación en profundidade

public class Celula implements Cloneable{

    Nucleo nucleo = new Nucleo();

    public Celula(){

    }

    public Object clon(){

        Nucleo clon = (Nucleo)super.clone();

        clon.nucleo = new Nucleo(); //nova referencia

        return clon;

    }
}

Exemplo de inclusión de clonación superficial:

public class Celula implements Cloneable {

    Nucleo nucleo = new Nucleo();

    public Celula() {}

    public Object clone() {

        return super.clone();

    }
}

public class Nucleo implements Cloneable {

    AcidoNucleico acidoNucleico = new AcidoNucleico();

    public Nucleo() {}

    // código da clase Nucleo
}