viernes, 1 de agosto de 2014

La tienda de animales III

¿Llegan las ofertas en Packs? 

Patrones Prototype y Builder

Hola de nuevo a todos y todas. En esta tercera entrada sobre los patrones GOF seguiremos con el patrón Prototype y el patrón Builder

Si hacemos un poco de memoria en el sistema teníamos distintos tipos de animales organizados por familias que heredaban de una clase abstracta base llamada Animal. Todos los animales compartían los mismos atributos por lo que si utilizásemos el patrón Prototype se encapsularía su copia de tal forma que se ganaría en seguridad y se evitarían posibles olvidos a la hora de copiar los datos de los animales.

Por otro lado, una de las funcionalidades que se habían previsto era la de poder crear los packs de animales según ofertas de temporada. Estos packs tenían en común su estructura: un animal, un accesorio, su comida y el precio del pack. El patrón Builder sería perfecto ya que nos permitiría separar la estructura de su implementación específica según cada Pack. 

Por ejemplo, si quisiéramos crear un ChristmasPack este tendría un animal Carnivore, un bozal como complemento ( por si al bicho le da por morder ), un paquete de la comida preferida del animal y su precio.

El diagrama de clases del patrón Prototype queda dentro del Abstract Factory de la segunda entrada.


El diagrama del patrón Builder es este.



Implementación

Empezaremos con el patrón Prototype que tiene una estructura muy sencilla. En la clase abstracta Animal se coloca un método abstracto llamado Clone que será implementado por cada una de las clases que heredan de ella. De esta forma cada clase hija podrá decidir cómo clonarse incluyendo lo que quieran en el nuevo objeto. También podría darse el caso que para clonarse se tuvieran que realizar cálculos previos específicos en cada clase, aunque esto se saldría un poco de la filosofía del patrón sería perfectamente posible.

Lo bueno de estar utilizando C# en .Net, entre otras ventajas, es que podemos hacer uso de funciones específicas del entorno para realizar la copia de los valores. Usaremos el método MemberwiseClone que simplemente lo que hace es realizar una copia de los miembros o propiedades del objeto que realiza la llamada. ¡ Vaya un Prototype genérico dentro de .Net !.

El código que resulta de todo esto es el siguiente:

public abstract class Animal
{
   ...
  // prototype
  public abstract Animal Clone();
  ...
}

public abstract class Canine : Animal
{
  ...
  public override Animal Clone()
  {
     return (Animal)this.MemberwiseClone();
  }
  ...
}

public class Dog : Canine
{
  public override Animal Clone()
  {
    return (Animal)this.MemberwiseClone();
  }
}


En el mundo Python existe una norma que dice que "lo simple es bello", pues en este caso el patrón Prototype lo cumple con creces. Existen pocos patrones tan simples como este, quizás Singleton sea el otro.  Pues bien por lo que se refiere al patrón Prototype ya queda poco más que decir, usadlo siempre que lo necesitéis porque es simple y os puede servir más adelante para introducir nuevas funcionalidades a la hora de copiar objetos.

Ahora seguimos con el grueso de esta entrada. El patrón Builder.  Según la definición que se da en dofactory.com este patrón se utiliza cuando se quiere separar la implementación de un objeto completo de su representación, de esta forma se pueden crear diferentes representaciones con el mismo proceso.

Vamos a empezar explicando las clases de abajo a arriba. En primer lugar la clase Pack es la que contrendrá lo que nos interesa realmente, las partes del pack: el animal, su complemento, la comida y el precio. Estos datos se introducirán en una lista con el método público Add.

public class Pack
{
  List _parts = new List();

  public void Add(string part)
  {
    _parts.Add(part);
  }
}


La siguiente es la clase abstracta PackBuilder que tendrá los métodos abstractos para formar las distintas partes del pack. Estos métodos serán llamados más tarde por PackDirector para ir conformando los packs en su totalidad. Su código es muy simple y claro, un método por cada parte y el cómo se realice esto queda delegado a las clases hijas.

public abstract class PackBuilder
{
  protected float _price;

  public float Price
  {
    get { return _price; }
  }

  public PackBuilder(List _stockAnimals){}
  public abstract void AddAnimal();
  public abstract void AddComplement();
  public abstract void AddFood();
  public abstract void AddPrice();
  public abstract Pack GetPack();
}


Ahora vamos con las clases ChristmasPack y EasterPack que implementarán la clase abstracta con sus métodos y cada una podrá realizar dentro de estos lo que les sea propio. 
Por ejemplo, la primera de ellas sólo admite animales carnívoros y la segunda animales herbívoros. Esta es una diferencia muy simple, pero en un caso real podría complicarse hasta límites insospechados. Otra parte importante es que tienen una instancia de la clase Pack como propiedad. 

public class ChristmasPack : PackBuilder
{
  Pack _pack = new Pack();
  Animal _animal = null;

  public ChristmasPack(List _stockAnimals) :  base(_stockAnimals)
  {
  Console.WriteLine("> " +  System.Reflection.MethodBase.GetCurrentMethod().ReflectedType.FullName);

  if (_stockAnimals != null)
  {
    foreach (Animal item in _stockAnimals)
    {
     // only this kind of animal
     if (item.Food.Equals(Food.Carnivore))
     {
       _animal = item.Clone();
      _pack.Add(item.GetType().BaseType.Name + ": " + item.GetType().Name);
       break;
     }
    }

    if (_animal == null)
     Console.WriteLine("\tNo " + Food.Carnivore + " in stock");
   }
  }

  public override void AddAnimal()
  {
   if (_animal != null)
   {
    _pack.Add(_animal.GetType().BaseType.Name + ": " + _animal.GetType().Name);
     _price += _animal.Price;
   }
  }

  public override void AddComplement()
  {
    if (_animal != null)
    {
      _pack.Add("Complement: buzzle");
      _price += 1f;
    }
  }

  public override void AddFood()
  {
    if (_animal != null)
    {
      _pack.Add("Food: " + _animal.Food);
      _price += 2f;
    }
  }

  public override void AddPrice()
  {
    if (_animal != null)
    {
      _pack.Add("Price: " + _price);
    }
  }

  public override Pack GetPack()
  {
    return _pack;
  }
}


Por último, hablaremos de la clase PackDirector que nos permitirá generalizar la creación de los packs y no tener que llamar a todos los métodos de las partes desde el cliente. De esta forma sólo tendremos que hacer uso de una llamada y el proceso de creación y sus partes quedarán ocultos. En un futuro podríamos necesitar quitar o añadir partes y esto podría provocar que en el código del cliente tuviéramos que realizar cambios, de esta forma esto se evita y sólo habría que modificar el código de las otras clases dejando el sistema cliente aislado de los cambios.

public class PackDirector
{
  public void Construct(PackBuilder packBuilder)
   {

     packBuilder.AddAnimal();
     packBuilder.AddComplement();
     packBuilder.AddFood();
     packBuilder.AddPrice();
   }
}


Para finalizar os dejaré un ejemplo de cómo se podría hacer uso de este patrón ya que algunos de vosotros me habéis pedido ejemplos de de uso en el cliente de los anteriores patrones. Procuraré actualizar lo antes posible las entradas I y II con su utilización. 

// Builder
PackBuilder packChristmasBuilder = new ChristmasPack(petShop.StockAnimals);
PackDirector packDire = new PackDirector();
packDire.Construct(packChristmasBuilder);
Pack packChristmas = packChristmasBuilder.GetPack();
Console.WriteLine("\t" + packChristmas.ShowPack());


Básicamente para utilizar este patrón se crea una variable de la clase abstracta PackBuilder, pero llamando al constructor del tipo del pack que queramos, en este caso ChristmasPack. Después a PackDirector se le pasa el PackBuilder creado. Para terminar se llama a su método GetPack  que devuelve el Pack ya creado. Simple y efectivo. 

Se puede ver ahora de forma clara como los cambios en el proceso nunca afectarán al cliente ya que todo el proceso de creación queda oculto al cliente.

Y esto es todo por lo que respecta a estos dos patrones. En la próxima entrada hablaremos del patrón Factory Method para gestionar los datos de los clientes y los proveedores.


Hasta pronto.