jueves, 24 de julio de 2014

La tienda de animales II

¿Qué tipo de animales quieres vender? 

El patrón Abstract Factory

Hola de nuevo a todos y todas. En esta segunda entrada sobre los patrones GOF seguiremos con la parte del sistema que se encargará de crear tiendas específicas que se diferencien por el tipo de animales que pueden vender. En nuestro caso podremos crear tiendas de animales domésticos y salvajes.

El diagrama siguiente muestra cómo queda el patrón Abstract Factory adaptado a nuestro caso.

Recordemos que tenemos por un lado las clases PetShopFactory como clase abstracta que es implementada por SavagePetShopFactory y HomePetShopFactory que se encargarán de crear los distintos tipos de animales según el tipo de tienda.

Por otro lado están las familias de animales agrupados por Reptil, Bird, Canine y Feline. Dependiendo del tipo de tienda se creará uno de los posibles animales que pertenezcan a cada familia de animales.

Por ejemplo, si se crea una tienda SavagePetShopFactory el animal de la familia Feline será Lion.

Implementación

La primera clase a mostrar será PetShopFactory que sólo contendrá los métodos abstractos para crear las familias de animales.

     public abstract class PetShopFactory
   {
        public abstract Reptil CreateReptil();
        public abstract Bird CreateBird();
        public abstract Canine CreateCanine();
        public abstract Feline CreateFeline();
    }


A continuación las clases que heredan de esta e implementan los métodos y que serán los que realmente serán llamados.

      public class SavagePetShopFactory : PetShopFactory
    {
        public override Reptil CreateReptil()
        {
            return new Aligator();
        }

        public override Bird CreateBird()
        {
            return new Eagle();
        }

        public override Canine CreateCanine()
        {
            return new Dingo();
        }

        public override Feline CreateFeline()
        {
            return new Lion();
        }
    }


    public class HomePetShopFactory : PetShopFactory
    {
        public override Reptil CreateReptil()
        {
            return new Chamaleon();
        }

        public override Bird CreateBird()
        {
            return new Parrot();
        }

        public override Canine CreateCanine()
        {
            return new Dog();
        }

        public override Feline CreateFeline()
        {
            return new Cat();
        }
    }


Se puede ver cómo los métodos devuelven objetos de las clases abstractas de las distintas familias, pero realmente lo que se ha creado son objetos que heredan de estas clases. Por ejemplo, al llamar al método CreateReptil en la clase HomePetShopFactory se está creando un Chamaleon y en la clase SavagePetShopFactory se está creando un Aligator, pero en ambas se devuelve un Reptil.

El principio de la programación orientada a objetos sobre el que se basa esto es aquel que dice que cuando una clase hereda de otra, allí donde haya un padre siempre se podrá sustituir por un hijo. Es decir, aunque devolvamos una clase padre abstracta como es Reptil, podremos utilizar una clase hija como es Chamaleon.

Pero tenemos algo que no está muy bien. Cada una de las clases abstractas de las familias representan realmente a Animales. Por lo que tendrán características que se repetirán como el precio, el continente, la comida, el nombre o el entorno en el que viven. Sería mucho mucho mejor crear otra clase llamada Animal de la que heredaran las clases abstractas de las familias y que almacenara todas estas características comunes.

De esta forma el nuevo diagrama quedaría así.


Además, se han añadido tres enumerados para representar los tipos de Continent, Enviroment y Food para asegurarnos que el conjunto de los valores posibles esté restringido y evitar errores en su uso que podría afectar a la lógica del sistema que dependa de los valores.

La implementación de esta parte del sistema sería la siguiente.

      public enum Environment
    {
        Aquatic,
        Terrestrial,
        Amphibian,
        Aerial,
    }

    public enum Food
    {
        Carnivore,
        Herbivore,
    }

    public enum Continent
    {
        Africa,
        America,
        Europe,
        Asia,
        Oceania,
    }

    public abstract class Animal
    {
        protected string _name;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        protected Environment _environment;

        public Environment Environment
        {
            get { return _environment; }
            set { _environment = value; }
        }
        protected Food _food;

        public Food Food
        {
            get { return _food; }
            set { _food = value; }
        }
        protected Continent _continent;

        public Continent Continent
        {
            get { return _continent; }
            set { _continent = value; }
        }

        private float _price;

        public float Price
        {
            get { return _price; }
            set { _price = value; }
        }

        public Animal() { }

        public Animal(string name, Environment environment, 
                     Food food, Continent continent, float price)
        {
            this._name = name;
            this._environment = environment;
            this._food = food;
            this._continent = continent;
            this._price = price;
        }

        public string Show()
        {
            string values = GetType().Name + " >> ";

      PropertyInfo[] fields = GetType().BaseType.GetProperties();

            foreach (PropertyInfo item in fields)
            {
                values += item.Name + ": " + 
                          item.GetValue(this, null) + " | ";
            }
            return values;
        }
    }

Esta clase tiene el método Show que nos vendrá muy bien luego para ver los valores que almacenan los objetos a la hora de realizar las pruebas.

Las clases de las familias que heredan de Animal sólo tienen el constructor porque todo lo común se ha sacado hacia la clase Animal. De esta forma todo queda mucho más limpio.

      public abstract class Feline : Animal
    {
        public Feline()
        {
            this.Price = 40.0f;
        }
    }

    public abstract class Canine : Animal
    {       
        public Canine()
        {
            this.Price = 30.0f;
        }
    }

    public abstract class Reptil: Animal
    {       
        public Reptil()
        {
            this.Price = 10.0f;
        }
    }

    public abstract class Bird: Animal
    {       
        public Bird()
        {
            this.Price = 20.0f;
        }
    }

Las clases finales de los animales sólo tienen un constructor en el que asignar los valores de sus propiedades de clase.

    public class Aligator : Reptil
    {
        public Aligator()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Amphibian;
            this.Food = Food.Carnivore;
            this.Continent = Continent.America;
        }
    }

    public class Chamaleon : Reptil
    {
        public Chamaleon()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Terrestrial;
            this.Food = Food.Carnivore;
            this.Continent = Continent.America;
        }
    }

    public class Eagle : Bird
    {
        public Eagle()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Aerial;
            this.Food = Food.Carnivore;
            this.Continent = Continent.Europe;
        }
    }

    public class Parrot : Bird
    {
        public Parrot()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Aerial;
            this.Food = Food.Herbivore;
            this.Continent = Continent.Asia;
        }
    }

    public class Dingo : Canine
    {
        public Dingo()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Terrestrial;
            this.Food = Food.Carnivore;
            this.Continent = Continent.Oceania;
        }
    }

    public class Dog : Canine
    {
        public Dog()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Terrestrial;
            this.Food = Food.Carnivore;
            this.Continent = Continent.Europe;
        }
    }

    public class Lion : Feline
    {
        public Lion()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Terrestrial;
            this.Food = Food.Carnivore;
            this.Continent = Continent.Africa;
        }
    }

    public class Cat : Feline
    {
        public Cat()
        {
            this.Name = GetType().Name;
            this.Environment = Environment.Terrestrial;
            this.Food = Food.Carnivore;
            this.Continent = Continent.Europe;
        }
    }

Y hasta aquí la entrada de hoy. En la próxima entrega veremos cómo podemos mejorar un poco más lo que hemos visto hoy añadiendo el patrón Prototype y mostraremos la parte del sistema correspondiente al patrón Builder relacionada con los Packs.

Hasta pronto.




jueves, 17 de julio de 2014

La tienda de animales I

Implementación de una tienda de animales utilizando los patrones de diseño GOF.


Si buscamos por Internet información sobre los patrones de diseño GOF existen multitud de ejemplos de su implementación. No voy a descubrir nada nuevo. Pero lo que voy a presentar es una implementación de varios patrones y mostrar cómo pueden funcionar juntos.

En este caso sólo haré uso de los patrones Creacionales para no complicar demasiado las cosas. Estos son los que se dedican a aspectos de creación de objetos, por decirlo llanamente. Su nombres son:  Abstract Factory, Builder,  Factory Method, Prototype, Singleton.

En dofactory hay una referencia muy buena de todos ellos por si queréis ampliar más.

Bueno vamos al lio.

Definición del sistema

El proyecto será implementar una tienda de animales. El sistema puede crear y gestionar dos tipos de tienda. Una se dedica a vender animales salvajes y otra animales domésticos. Aunque ahora ya no esté muy claro qué es un animal doméstico.

Los dos tipos de tienda pueden vender distintas categorias de animales: Reptil, Pájaro, Canes y Felinos.
Además, en determinadas épocas del año ofrece unos packs descuento a sus clientes con animales distintos.
Por último, para gestionar los datos de sus clientes dispone de la posibilidad de almacenar sus datos.

Diagrama de clases

A continuación aparece un diagrama de clases con las clases del sistema y su relación. Aparecen organizadas según el patrón de diseño que representan.


Justificación de los patrones elegidos y clases implicadas

  • Singleton: Este patrón se utiliza cuando sólo queremos tener una instancia de una clase. Es decir, queremos que se cree un sólo objeto.
    • PetShop: Es la clase principal que representa la tienda.

  • Abstract factory: Ya que queremos poder crear tiendas de animales domésticos o salvajes, se ha utilizado este patrón porque permite crear famillias de objetos y manejarlos de una forma uniforme. 
    • PetShopFactory: clase abstracta que contiene los métodos de creación de las familias de animales.
    • SavagePetShopFactory: implementa a PetShopFactory y creará las familias de animales salvajes.
    • HomePetShopFactory: implementa a PetShopFactory y creará las familias de animales domésticos.
    • Reptil, Bird, Canine, Feline: Son las clases que representan las familias de animales.
    • Aligator, Chamaleon, Eagle, Parrot, Dingo, Dog, Lion, Cat: Los animales que se pueden crear en cada familia.


  • Builder: Este patrón es ideal para crear objetos que tienen las mismas partes, pero su contenido o proceso de creación son diferentes. En el sistema formará el subsistema de los packs de temporada.
    • PackDirector: Es la clase que gestiona la creación de los packs.

    • PackBuilder: Es una clase abstracta que contiene los métodos necessarios para crear las partes de los packs.

    • ChristmasPack, EasterPack: Son las clases que implementan a PackPuilder y realizan la creación de todas las partes de cada pack siguiendo las reglas de creación apropiadas. Por ejemplo, en el primer pack sólo se admiten animales carnívoros.

    • Pack: Es la clase que representa a un pack genérico con la lista de sus partes.


  • Factory Method: Este patrón permite crear objetos compuestos por distintas clases que sólo se puede saber su tipo en tiempo de ejecución. En nuestro caso nos servirá para encargarse de los contactos de los clientes y proveedores de la tienda.
    • Contact: Es la clase abstracta que representa a un contacto de la tienda. Cada contacto puede tener información especifica.

    • Customer, Suplier: Son las clases que implementan a Contact y representan los dos tipos de contactos que se pueden tener.

    • Data: cada contacto puede almacenar información específica dependiendo de su tipo.

    • CompanyData, DiscountData: Esta información sólo la podrán tener los Suplier.

    • PersonalData, SalesData: Esta información sólo la podrán tener los Customer.  
  

Implementación

Lo primero que vamos a hacer es la clase principal PetShop y que debemos diseñar usando el patrón singleton.

Hay que colocar una propiedad estática que sea privada del mismo tipo de la clase y que será la única instancia de la clase.

private  static PetShop _petShop = null;


El constructor debe ser privado para que no se puedan crear instancias de esta clase desde fuera.

private PetShop() {}

Y tendremos un método que se encargará de crear una única instancia de la clase
        
public static PetShop Create()
{
     if (_petShop == null)
      _petShop = new PetShop();
 

  return _petShop;
}


Todo junto queda así:


public class PetShop
{
     private  static PetShop _petShop = null;
       
     public static PetShop Create()
     {
         if (_petShop == null)
             _petShop = new PetShop();

         return _petShop;
     }

     private PetShop()
     {
     } 


        
Hasta aquí la primera parte. Sigueme y podrás ver cómo se implementa el resto del sistema.

Hasta pronto.