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.