Design Patterns - Prototype

Propósito

El Prototype permite la creación de nuevos objetos clonando un objeto existente, conocido como el prototipo, evitando la necesidad de conocer los detalles de cómo se crean estos objetos.

Tenemos un objeto con algunos estados posibles, creamos prototipos de cada uno y los clonamos, evitando tener que pasar muchos parámetros, saber qué valores van en cada uno, o sea, facilitando la creación.

Problema

La necesidad de duplicar objetos existentes, con comportamiento o estado similar, o cuando la creación directa es costosa (en términos de recursos o tiempo).

Solución

La solución que propone el Prototype es:

  • Clonación de objetos: En lugar de crear un objeto desde cero, se clona uno existente que sirve como prototipo.
  • Interfaz común de clonación: Esto permite que los objetos se clonen sin acoplar el código al tipo concreto del objeto.

Con esta solución, un cliente le pide al prototipo que se clone.

Estructura

prototype

Participantes

  • Prototype: Interfaz para clonarse.
  • ConcretePrototype: Implementa una operación para clonarse.
  • Client: Crea un nuevo objeto pidiéndole al prototipo que se clone.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • Las clases a instanciar se especifican en tiempo de ejecución.
  • Las instancias de una clase puedan tener uno de entre unos pocos estados, ya que puede ser mejor tener sus prototipos y clonarlos que crear cada instancia manualmente.
  • Los costos de crear una instancia son más altos que los de clonar.
  • Se requieren objetos similares

Ventajas

Eficiencia en la creación de objetos: Clonar generalmente es más eficiente que crear un objeto nuevo.

Oculta la complejidad de creación: El cliente no necesita saber cómo se construye el objeto.

Agregar y eliminar objetos en tiempo de ejecución: Instalando o eliminando prototipos en tiempo de ejecución, algo que da flexibilidad.

Desventajas

Clonaciones complejas: Objetos complejos, objetos con otros objetos adentro (clonación superficial vs profunda), o con referencias circulares, pueden traer problemas.

Acoplamiento con clases concretas: El patrón requiere que las clases concretas y cada subclase implementen la operación de clonación, algo que puede ser difícil si las clases ya existen.

Ejemplo: Sistema de gráficos

Estamos desarrollando un sistema de gráficos y necesitamos crear múltiples instancias de formas geométricas configurables, como círculos o rectángulos.

Problema

Los clientes deben poder crear de forma simple y eficiente figuras geométricas, como círculos o rectángulos, sin necesidad de saber cómo se construyen estos objetos.

Debemos tener en cuenta que las formas, en un sistema gráfico, suelen tener configuraciones y estados internos que pueden ser complejos y costosos de replicar.

Solución planteada

Implementamos un Prototype, porque permite crear nuevas instancias de las formas de manera eficiente, clonando un objeto existente en lugar de construir uno nuevo.

Si más adelante se necesitan nuevas formas o variantes, es fácil agregar nuevas clases que implementen el prototipo sin alterar el resto del sistema.

Creamos la interfaz Shape que contiene la operación de clonación. 

Circle y Rectangle son los productos concretos, implementan la interfaz Shape, saben cómo clonarse.

GraphicTool es la clase que usa los prototipos y puede crear nuevas formas usando el método de clonación.

prototype

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos la interfaz prototipo de la forma:


    interface Shape {
      Shape clone();
    }
                

Implementamos un prototipo concreto para círculo y rectángulo:


    class Circle implements Shape {
      private int radius;
  
      public Circle(int radius) {
          this.radius = radius;
      }
  
      @Override
      public Circle clone() {
          return new Circle(this.radius);
      }
  
      // Métodos adicionales…
    }
                

    class Rectangle implements Shape {
      private int width;
      private int height;
  
      public Rectangle(int width, int height) {
          this.width = width;
          this.height = height;
      }
  
      @Override
      public Rectangle clone() {
          return new Rectangle(this.width, this.height);
      }
  
      // Métodos adicionales…
    }
                 

El código cliente, usa los prototipos para crear formas:


    class GraphicTool {
      private Shape shape;
  
      public GraphicTool(Shape shape) {
          this.shape = shape;
      }
  
      public Shape createShape() {
          return shape.clone();
      }
    }                  
                

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Prototype, ConcretePrototype, Client:

  • Shape (Prototype): Interfaz que actúa como prototipo, representa la capacidad de clonar objetos.
  • Circle, Rectangle (ConcretePrototype): Implementan Shape, el prototipo, cada una de estas clases sabe cómo clonarse a sí misma.
  • GraphicTool (Client): Es la clase que usa los prototipos. Crea nuevas formas usando el método de clonación definido en Shape.

Conclusiones

Necesitábamos crear nuevas instancias de objetos complejos de manera eficiente, para esto usamos un Prototype. En lugar de crear un objeto de cero cada vez, creamos un prototipo y lo clonamos, lo cual es sumamente útil cuando la creación de un objeto es una operación costosa.

Además, este enfoque mantiene nuestro código flexible y fácil de extender a nuevas formas geométricas.

Patrones relacionados

  • Factory Method

Suele ser usado con Prototype para crear objetos.

  • Abstract Factory

Pueden usarse juntos, una fábrica abstracta puede almacenar un conjunto de prototipos a partir de los cuales clonar y devolver objetos producto.

  • Composite y Decorator

Los diseños que usan patrones Composite y Decorator suelen beneficiarse también del Prototype.