Patrones de creación (I): Factory Patterns


Habíamos quedado en que los patrones creacionales o de creación eran aquellos en los que se delegaba la instanciación de un objeto en otro en lugar de recurrir a un simple new(). La pregunta que nos hacemos es: ¿por qué hacer esto? ¿Qué interés práctico puede existir en crear una clase cuya función sea instanciar otras clases pudiendo dejarle el trabajo a la clase original?

Bien, esta forma de trabajar puede ser útil en algunos escenarios, pero el principal suele involucrar el no saber qué objeto vamos a instanciar hasta el momento de la ejecución. Valiéndonos del polimorfismo podremos utilizar una interfaz para alojar una referencia a un objeto que será instanciado por un tercero en lugar de dejar que sea el propio constructor del objeto el que proporcione la instancia. Hablando en plata, pasaremos de esto:


	MotorDiesel motor = new MotorDiesel();

A esto otro:


	IMotor iMotor = MotorFactory.CreateInstance(tipoMotor);

Por tanto, nuestro objetivo principal será la encapsulación de la creación de objetos. Volveremos al ejemplo que hemos usado en otras ocasiones: vehículos con un motor que está representado mediante una interfaz que será implementada por las clases MotorDiesel y MotorGasolina. La interfaz expondrá las siguientes propiedades y métodos:


namespace Patterns.Factory.SimpleFactory.Interfaces
{
    public interface IMotor
    {
        public int Estabilidad { get; set; }
        public decimal ParMotor { get; set; }
        public int Potencia { get; set; }
        public decimal Rendimiento { get; set; }
        public int VelocidadNominal { get; set; }

        string ConsumirCombustible();
        string InyectarCombustible();
        string RealizarEscape();
        string RealizarExpansion();
    }
}

A continuación codificaremos las dos clases que implementan la interfaz.

En primer lugar implementaremos el motor Diesel, cuyos métodos devolverán cadenas de texto con sus respectivos mensajes. Además implementará un método privado RealizarCombustion() para simbolizar el tratamiento del combustible.


    public class MotorDiesel : IMotor
    {
        #region IMotor Members

        int Estabilidad { get; set; }
        decimal ParMotor { get; set; }
        int Potencia { get; set; }
        decimal Rendimiento { get; set; }
        int VelocidadNominal { get; set; }

        public string ConsumirCombustible()
        {
            return RealizarCombustion();
        }

        public string InyectarCombustible(int cantidad)
        {
            return string.Format("MotorDiesel: Inyectados {0} ml. de Gasoil.", cantidad);
        }

        public string RealizarEscape()
        {
            return "MotorDiesel: Realizado escape de gases";
        }

        public string RealizarExpansion()
        {
            return "MotorDiesel: Realizada expansion";
        }

        #endregion

        private string RealizarCombustion()
        {
            return "MotorDiesel: Realizada combustion del Gasoil";
        }
    }

Realizamos lo propio con el motor de gasolina, salvando el hecho que, en lugar de realizar una combustión, realizará una explosión. Por lo tanto, el nombre del método privado será RealizarExplosion():


    public class MotorGasolina : IMotor
    {
        #region IMotor Members

        public int Estabilidad { get; set; }
        public decimal ParMotor { get; set; }
        public int Potencia { get; set; }
        public decimal Rendimiento { get; set; }
        public int VelocidadNominal { get; set; }

        public string ConsumirCombustible()
        {
            return RealizarExplosion();
        }

        public string InyectarCombustible(int cantidad)
        {
            return string.Format("MotorGasolina: Inyectados {0} ml. de Gasolina.", cantidad);
        }

        public string RealizarEscape()
        {
            return "MotorGasolina: Realizado escape de gases";
        }

        public string RealizarExpansion()
        {
            return "MotorGasolina: Realizada expansion";
        }

        #endregion

        private string RealizarExplosion()
        {
            return "MotorGasolina: Realizada explosion de la Gasolina";
        }
    }

Finalmente, crearemos una clase MotorFactory que exhiba un método público CreateInstance(nombreClase) para instanciar, por ejemplo, un motor Diesel.


    public class MotorFactory
    {
        public IMotor CreateInstance(string tipoMotor)
        {
            IMotor resultado;

            switch (tipoMotor)
            {
                case "MotorDiesel":
                    resultado = new MotorDiesel() { Estabilidad = 100, Potencia = 40, Rendimiento = 800, VelocidadNominal = 0 };
                    break;
                default:
                    resultado = null;
                    break;
            }

            return resultado;
        }
    }

Lo que estamos haciendo aquí no es otra cosa que delegar en MotorFactory la tarea de instanciar el motor. Ya tenemos el motor Diesel. Nos falta ahora el Gasolina. Para ello deberemos modificar la clase MotorFactory y añadir un nuevo case a la sentencia switch:


    public class MotorFactory
    {
        public IMotor CreateInstance(string tipoMotor)
        {
            IMotor resultado;

            switch (tipoMotor)
            {
                case "MotorDiesel":
                    resultado = new MotorDiesel();
                    break;
                case "MotorGasolina":
                    resultado = new MotorGasolina();
                    break;
                default:
                    resultado = null;
                    break;
            }

            return resultado;
        }
    }

El diagrama de clases de esta primera factoría sería el siguiente:

Si quisiéramos obtener un motor diesel, por lo tanto, bastaría con el siguiente código:


        static void Main(string[] args)
        {
            MotorFactory factory = new MotorFactory();

            IMotor motorDiesel = factory.CreateInstance("MotorDiesel");

            if (motorDiesel == null)
                return;

            Console.WriteLine(motorDiesel.InyectarCombustible(20));
            Console.WriteLine(motorDiesel.ConsumirCombustible());
            Console.WriteLine(motorDiesel.RealizarExpansion());
            Console.WriteLine(motorDiesel.RealizarEscape());
            Console.ReadLine();
        }

La ejecución daría origen al siguiente resultado:

Principios SOLID

Lo que acabamos de hacer es delegar la decisión de instanciar un motor u otro hasta el momento en el que la variable tipoMotor llega a nuestro método CreateInstance, lo cual flexibiliza el código permitiendo que no nos atemos a un tipo de motor concreto en tiempo de compilación. Sin embargo, al aparecer un nuevo tipo de motor hemos tenido que modificar una clase para incluir el nuevo elemento a instanciar.

Si recordamos el artículo sobre la inyección de dependencias, comentamos por encima los Principios
SOLID
propuestos por Robert C. Martin, que eran los siguientes:

  • Responsabilidad única: un objeto sólo debe tener una única responsabilidad.
  • Abierto/cerrado: una clase debe estar abierta para su extensión, pero cerrada para su modificación.
  • Principio de sustitución de Liskov: una clase padre siempre debe poder ser sustituida por una clase hija sin alterar el comportamiento del programa.
  • Segregación de la Interfaz: es preferible contar con muchas interfaces específicas que con una de propósito general.
  • Inversión de dependencia: se debe depender de abstracciones, no de concreciones.

Si cada vez que aparezca un nuevo motor tenemos que modificar nuestra factoría para darle cabida en nuestro proceso de instanciado, estaremos violando directamente el principio abierto/cerrado, ya que las clases deben extenderse, no modificarse.

Por lo tanto, lo que estamos afirmando es que es necesario encontrar un modo de generalizar nuestra factoría de tal modo que no sea necesario modificarla en el caso de que un nuevo tipo se añada al ensamblado. El problema, a simple vista, parece irresoluble por métodos ajenos a la brujería. Sin embargo, recordemos que la razón de ser de los patrones de diseño era precisamente esta: proporcionar soluciones generales a problemas concretos. Y este problema concreto tiene una solución más sencilla de lo que parece: bucear en las tripas del ensamblado y generar un listado de pares clave-valor con los tipos que se ajusten a lo que necesitamos. Entraremos, por lo tanto, en el campo de Reflection.

Reflection al rescate

Ya hemos usado Reflection en otras ocasiones, pero no le hemos dado un uso realmente práctico. Lo hemos utilizado para extraer los métodos y propiedades de una clase, pero sin un motivo real por el que deberíamos hacer eso. Aquí tenemos uno muy bueno.

Configurando la factoría mediante un fichero XML

Un primer acercamiento puede ser el siguiente: crear una sección en nuestro fichero de configuración (o cualquier otro fichero XML, base de datos…) que contenga el nombre de las clases que nuestra factoría puede generar. El constructor de la factoría iterará sobre estos elementos y creará un diccionario con pares clave-valor cuya clave será el nombre del tipo indicado en el fichero y cuyo valor correspondiente será el tipo en sí. Por tanto, añadiremos una nueva sección a nuestro fichero app.config, del siguiente modo:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <section name="AllowedMotorTypes" type="System.Configuration.NameValueSectionHandler" />
    </configSections>
  
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  
    <AllowedMotorTypes>
      <add key="Patterns.Factory.SimpleFactory.Motores.MotorDiesel" value="true" />
      <add key="Patterns.Factory.SimpleFactory.Motores.MotorGasolina" value="true" />
    </AllowedMotorTypes>

</configuration>

A continuación añadiremos una referencia a la biblioteca System.Configuration, y haremos uso del siguiente código para lograr nuestro objetivo: instanciaremos un objeto de la clase NameValueCollection que simbolizará la sección que hemos creado, y usaremos una sentencia LINQ para iterar sobre todas las claves de la sección. Sobre esta consulta usaremos el método de extensión ToDictionary(campoClave, campoValor) en los que indicaremos el contenido de key para la clave y, a través de Reflection, recuperaremos el tipo correspondiente a esa clave para almacenarlo como valor.


        private void ConfiguracionDesdeXml()
        {
            // Extraemos los pares clave-valor del fichero de configuracion, concretamente de
            // la seccion AllowedMotorTypes
            NameValueCollection settings = ConfigurationManager.GetSection("AllowedMotorTypes") as NameValueCollection;

            if (settings != null)
            {
                // Instanciamos el diccionario<nombreTipo, tipo>
                tiposInstanciables = (from clave in settings.AllKeys            // Recorremos todas las claves
                                      where bool.Parse(settings[clave])         // ...en las que value sea "true"
                                      select clave).ToDictionary(key => key,    // La clave del diccionario sera "key"
                                                                 key => Assembly.GetExecutingAssembly().GetType(key)); // El valor sera el tipo
            }

        }

Configurando la factoría mediante las clases del ensamblado

Una segunda posibilidad consiste en recorrer todas las clases existentes en el ensamblado, comprobar si implementan la interfaz IMotor y, en caso afirmativo, añadirla a un diccionario en el que se relacione el nombre de la clase con el tipo que representa. Una vez que se invoque el método CreateInstance, se consultará el diccionario usando el parámetro del método como clave y se instanciará el tipo asociado mediante la clase Activator.

Resumiendo, el proceso será el siguiente:

  • Al instanciar la factoría, se recorren todas las clases del ensamblado.
    • Por cada clase del ensamblado
      • Si la clase implementa la interfaz que nuestra factoría se encarga de instanciar, se añade al diccionario(nombreClase, tipo)
  • Al invocar el método CreateInstance(nombreClase), se consulta el diccionario y se recupera el tipo asociado, instanciándolo mediante Activator.

        private void ConfiguracionAutomatica()
        {
            // Usamos LINQ para obtener un diccionario (nombreClase, tipoClase) a partir de todos
            // aquellos tipos del ensamblado actual que implementen la interfaz IMotor.
            tiposInstanciables = (from tipo in Assembly.GetExecutingAssembly().GetTypes()
                                  where tipo.GetInterface(typeof(IMotor).ToString()) != null
                                  select tipo).ToDictionary(t => t.ToString(),  // Clave: nombre del tipo
                                                            t => t);            // Valor: tipo

        }

Como vemos, el proceso de recorrer el ensamblado no es más que una forma de configurar la factoría. Este proceso no tiene por qué ser como el que aquí se expone: puede recorrerse un fichero XML, una base de datos o cualquier otra forma de proporcionar a nuestra factoría un método de saber qué clases puede instanciar.

El siguiente paso será obtener el tipo a partir de la cadena de texto que recibirá nuestro método CreateInstance(). Esto será tan sencillo como consultar el diccionario y devolver el resultado.


        private Type ObtenerTipo(string nombreTipo)
        {
            if (tiposInstanciables == null)
                return null;
            else
                return (tiposInstanciables.ContainsKey(nombreTipo) ? tiposInstanciables[nombreTipo] : null);
        }

Por último, codificaremos el método CreateInstance(), que obtendrá el tipo del diccionario a partir de la cadena de texto e instanciará el objeto a partir de su tipo. Existen varias formas de realizar esta operación. La primera de ellas será mediante el método Activator.CreateInstance(tipo). Esta invocación equivale al constructor por defecto de la clase:


        public IMotor CreateInstance(string tipoMotor)
        {
            // Usamos Activator.CreateInstance(tipo) para instanciar el objeto de forma dinamica
            IMotor resultado = (IMotor)Activator.CreateInstance(ObtenerTipo(tipoMotor));

            return resultado;
        }

Un segundo método sería a través del método tipo.GetConstructor(arrayConTiposDeLosParámetros).Invoke(arrayConLosParámetros). En el supuesto de que el constructor no posea parámetros (constructor por defecto), le pasaríamos el valor Type.EmptyTypes a GetConstructor y null al método Invoke.


        // Otra forma de instanciar el objeto podria haber sido obteniendo el constructor por
        // defecto e invocarlo mediante el metodo Invoke
        IMotor resultado = (IMotor)ObtenerTipo(tipoMotor).GetConstructor(Type.EmptyTypes).Invoke(null);

En caso de que el constructor que queremos invocar requiriese por ejemplo un valor entero, le pasaríamos un array con un único elemento con el tipo entero a GetConstructor y un array con un único elemento entero al método Invoke.


        // Si tuviesemos un constructor del estilo a MotorFactory(int dato), esta ultima invocacion
        // equivaldria a  "new MotorFactory(34)":
        IMotor resultado = (IMotor)ObtenerTipo(tipoMotor).GetConstructor(new[] {typeof(int) }).Invoke(new object[] {34} );

Patrón Factory Method

Hasta ahora hemos explicado el concepto de factoría: una clase especializada en crear objetos. A partir de aquí, podemos especializar aún más estas factorías para que generen tipos concretos. El patrón Factory Method es similar a lo que hemos visto hasta ahora, con una pequeña variación: el método CreateInstance() ya no generará una instancia, sino que o bien se convertirá en abstracto o bien pertenecerá a una interfaz y dejará a las clases que la implementen la tarea de codificar su comportamiento. Por lo tanto, nuestra clase MotorFactory ya no podrá utilizarse directamente para generar objetos, sino que habrá que crear una nueva clase que herede de MotorFactory que implemente el método CreateInstance() o bien transformar MotorFactory en una interfaz IMotorFactory y dejar a las clases que la implementen el trabajo de instanciar las clases.

¿Qué conseguimos con esto? Básicamente, especializar las factorías. En lugar de tener una única factoría que centralice todo el proceso de creación de objetos, tendremos varias clases factoría que heredan de MotorFactory (o implementan IMotorFactory) que estarán especializadas en crear variantes concretas.

En nuestro ejemplo sólo exponemos el caso de la instanciación de dos clases: MotorDiesel y MotorGasolina. Además, las diferencias entre ambas clases son mínimas, por lo que una factoría simple nos bastaría para nuestro diseño. Pero, ¿qué ocurriría si estas clases se especializaran cada vez más y más? ¿Y si se añadiesen muchos más tipos de motores? Quizás la carga funcional sobre nuestra factoría sería demasiada, por lo que sería aconsejable pasar al siguiente paso: utilizar Factory Method para especializar las factorías.

Vamos a realizar, por lo tanto, el siguiente proceso:

1) Eliminar la clase MotorFactory y sustituirla por la interfaz IMotorFactory. Esta nueva interfaz expondrá el método CreateInstance(), que deberá ser implementado por otras clases.

2) Crear dos nuevas clases, MotorDieselFactory y MotorGasolinaFactory, que implementen la interfaz IMotorFactory y su método CreateInstance(), especializando el proceso de instanciado. El nombre del patrón Factory Method viene precisamente de aquí.

Fijémonos en que el método CreateInstance sigue devolviendo una interfaz IMotor. Podríamos pensar en que sería mejor que MotorGasolinaFactory.CreateInstance() devolviera una instancia de MotorGasolina, ¿verdad? Bien, en realidad lo hará, pero recordemos que MotorGasolina implementa la interfaz IMotor, por lo que puede usar esta referencia para devolver una instancia de MotorGasolina tal y como lo ha venido haciendo hasta ahora. Dejemos el nivel de abstracción lo más alto posible: la concreción no es buen amigo del diseñador.

Por tanto, el código de la interfaz IMotorFactory se simplificará hasta el punto de exponer únicamente la firma del método CreateInstance():


    public interface IMotorFactory
    {
        IMotor CreateInstance();
    }

Configurando la factoría por defecto

Anteriormente vimos cómo podíamos configurar qué clases estaban disponibles para su instanciado: mediante el recorrido de todas las clases del ensamblado que implementaban la interfaz IMotor o mediante su inclusión en el fichero de configuración .config.

Sin embargo, en lugar de usar un fichero .config para estas tareas, es más aconsejable usar para ello un fichero .settings. Este fichero consiste en una especie de diccionario al que se puede acceder mediante la clase Settings (o Properties.Settings, dependiendo del Framework). Para añadir un fichero de este tipo, agregaremos un nuevo elemento a nuestro proyecto de tipo Settings File.

A continuación, añadiremos un nuevo par clave-valor. El valor consistirá en el nombre de la clase de la factoría que queremos utilizar. Por supuesto, podríamos utilizar varios pares clave-valor (en la práctica es lo que se hace) dependiendo del contexto. Esto nos puede servir, entre otras cosas, para instanciar distintos elementos dependiendo de si estamos desarrollando en producción, pruebas unitarias, en desarrollo…

Hecho esto, creamos un método ObtenerFactoria() que devuelva una interfaz IMotorFactory que alojará la instancia de la factoría concreta que queremos utilizar y que vendrá determinada por el contenido del valor que hemos guardado en la configuración.


        private static IMotorFactory ObtenerFactoria()
        {
            // Obtenemos el nombre de la clase de la configuracion (Settings)
            string nombreFactoria = Settings.Default.MotorFactory;

            // Mediante Reflection, obtenemos el ensamblado e instanciamos la factoria a
            // partir del nombre de la clase (esto llamará a su constructor por defecto)
            Assembly ensamblado = Assembly.GetExecutingAssembly();
            IMotorFactory factoria = (IMotorFactory)ensamblado.CreateInstance(nombreFactoria);

            // Devolvemos la instancia de la factoría
            return factoria;
        }

Codificando las factorías concretas

Lo siguiente será configurar el código de nuestras factorías individuales: MotorDieselFactory y MotorGasolinaFactory. En estas clases se codificará el comportamiento del método CreateInstance(), realizando las operaciones que consideremos oportunas para cada tipo de motor, como asignarle valores a sus propiedades o realizar tareas fijas de inicialización. Por ejemplo:


    public class MotorGasolinaFactory : IMotorFactory
    {
        #region IMotorFactory Members

        public IMotor CreateInstance()
        {
            IMotor motorGasolina = new MotorGasolina()
            {
                Estabilidad = 100,
                ParMotor = 40,
                Potencia = 1200,
                Rendimiento = 420,
                VelocidadNominal = 47
            };

            return motorGasolina;
        }

        #endregion
    }
    public class MotorDieselFactory : IMotorFactory
    {
        #region IMotorFactory Members

        public IMotor CreateInstance()
        {
            IMotor motorDiesel = new MotorDiesel()
            {
                Estabilidad = 60,
                ParMotor = 90,
                Potencia = 700,
                Rendimiento = 220,
                VelocidadNominal = 80
            };

            return motorDiesel;
        }

        #endregion
    }

El diagrama de clases asociado a nuestro modelo tendría, por tanto, el siguiente aspecto:

Utilizando Factory Method

Finalmente, haremos uso de Factory Method mediante los siguientes pasos:

  • Usamos una interfaz de la factoría (genérica) para alojar una instancia de la factoría concreta, que será generada mediante el método ObtenerFactoria() accediendo a la configuración para decidir qué factoría debe instanciar.
  • Usamos una interfaz del motor (genérica) para alojar una instancia de un motor concreto, que será generado mediante el método CreateInstance() de la factoría generada previamente.
  • Hacemos uso de los métodos del objeto a través de su interfaz.

        static void Main(string[] args)
        {
            // Usamos un método genérico para instanciar la factoría por defecto.
            // La factoría estará definida en la configuración
            IMotorFactory factoria = ObtenerFactoria();

            // Instanciamos un motor a través de la factoría.
            // Fijémonos que únicamente tratamos con interfaces. En ningún momento
            // concretamos la clase con la que estamos trabajando
            IMotor motor = factoria.CreateInstance();

            // Finalmente, hacemos uso del motor a través de los métodos de la
            // interfaz IMotor.
            Console.WriteLine(motor.InyectarCombustible(20));
            Console.WriteLine(motor.ConsumirCombustible());
            Console.WriteLine(motor.RealizarExpansion());
            Console.WriteLine(motor.RealizarEscape());
            Console.ReadLine();

        }

Ejecutando nuestro código, vemos el resultado:

Patrón Abstract Factory (Factoría Abstracta)

La factoría abstracta va un paso más allá y extiende la funcionalidad de Factory Method mediante la posibilidad de instanciar , en vez de un objeto, un conjunto de objetos relacionados o de la misma familia. Hasta ahora disponíamos de un método CreateInstance() que generaba una instancia de un motor. La factoría abstracta contendrá, en lugar de un único método, una colección de métodos que al ser invocados instanciarán un objeto de una familia determinada. Este patrón suele ser corriente, por ejemplo, en las conexiones a base de datos, donde una misma factoría es capaz de crear tanto una conexión (DbConnection) como una órden Sql (SqlCommand) para distintas bases de datos. Por ejemplo:


            // Creamos la factoría concreta a través de un parámetro
            DbProviderFactory factoryOracle = DbProviderFactories.GetFactory("Oracle.DataAccess.Client");

            // Instanciamos dos objetos distintos, pero de la misma familia (Oracle)
            DbConnection conexionOracle = factoryOracle.CreateConnection();
            DbDataAdapter adapterOracle = factoryOracle.CreateDataAdapter();


            // Creamos la factoría concreta a través de un parámetro
            DbProviderFactory factorySql = DbProviderFactories.GetFactory("System.Data.SqlClient");

            // Instanciamos dos objetos distintos, pero de la misma familia (SQL Server)
            DbConnection conexionSql = factorySql.CreateConnection();
            DbDataAdapter adapterSql = factorySql.CreateDataAdapter();

Como observamos, las interfaces DbConnection y DbDataAdapter efectúan las mismas operaciones sobre una base de datos Oracle y SQL Server. Las factorías proporcionan estos elementos, junto a algunos más. Por lo tanto, la diferencia fundamental entre Factory Method y Abstract Factory radica en que el segundo patrón se centra en familias completas de objetos en lugar de hacerlo con una única clase.

Usando este tipo de factorías es posible ampliar la funcionalidad de nuestras clases respetando el Principio Abierto/Cerrado. El nombre de la clase de la nueva factoría (en el ejemplo anterior Oracle.DataAccess.Client y System.Data.SqlClient) permitirá a la factoría abstracta instanciar nuevas factorías a medida que las nuevas familias de objetos se vayan añadiendo.

¿Cuándo utilizar este patron? Ejemplos reales

Ya hemos visto un ejemplo real de factoría abstracta: la clase DbProviderFactory, que proporciona factorías para diversas familias de bases de datos y permiten crear desde conexiones hasta adaptadores de datos (DataAdapter).

Este patrón se utiliza también a la hora de definir elementos de interfaces gráficas, tales como botones, paneles, etc. Dependiendo del motor gráfico que se vaya utilizar. Por ejemplo, se utilizaría una factoría para elementos gráficos de Gtk, otra para elementos gráficos de KDE, otra para elementos gráficos de xfce…

Si se cuenta con varios entornos (producción, desarrollo, pruebas) también puede ser útil para, mediante el fichero de configuración, instanciar determinados objetos dependiendo de éste (por ejemplo, para realizar el mocking de las pruebas unitarias).

Fuentes:

Anuncios

6 comments

  1. He entendido perfectamente estos patrones. Los ejemplos que has utilizado y explicaciones dadas me parecen muy fáciles de entender. Voy a leer los siguientes post’s.

    1. Me alegro de que te haya servido. Ahora estoy un poco corto de tiempo y probablemente no actualice con la frecuencia que quisiera, pero tengo intención de, con el tiempo, cubrir la práctica totalidad de los patrones GoF.

      Un saludo y ¡gracias por tus comentarios!

  2. Buenas Tardes.

    Una consulta, si yo quisiera agregar métodos específicos a clases concretas, es decir, que sólo MotorDiesel tenga un método HolaMundo() se podría realizar?… porque veo que las clases concretas sólo deben tener los métodos que implementa su interfaz.

    Gracias.

    1. Por supuesto. El problema de utilizar esta técnica es que no podrías utilizar una referencia a una interfaz para acceder a ese método específico. Es decir, si añades un método HolaMundo() a MotorDiesel, la única forma que tendrás de ejecutar el método HolaMundo() es a través de una referencia a MotorDiesel. Si la interfaz que implementa MotorDiesel no expone ese método, tampoco servirá para invocar su ejecución.

      Un saludo.

  3. Hola, el caso para creación me viene genial. Pero además necesito acceder de igual modo a las subclases sin conocer cuál debería ser.
    ¿Tendría que crear un nuevo método y reutilizar la lógica del switch o setting file como en el método CreateInstance()?

    muchas gracias

  4. Hola, muchas gracias por compartir su conocimiento con el mundo, muy bien explicado, fácil de entender. Sólo tengo una observación en el diagrama de clases la relación entre la clase MotorGasolinaFactory se encuentra incorrecta debería ser contra la clase MotorGasolina No MotorDiesel.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s