Documentos de Académico
Documentos de Profesional
Documentos de Cultura
02 Patrones de Diseño
02 Patrones de Diseño
E INYECCIÓN
DE DEPENDENCIAS
• Los conceptos de Inversión de Control e Inyección de Dependencias no son nuevos, sino que se remontan
hasta finales de la década de los 80. Sin embargo, estos conceptos han comenzado a popularizarse debido
a la estrecha relación que mantienen con la aparición de Frameworks como Spring en Java o Unity en .NET.
• Inversión de Control
• El concepto de Inversión de Control fue acuñado originalmente por Martin Fowler, diseñador del patrón
MVVM (Model View View-Model). Fowler definió el concepto de forma informal denominándolo como
el Principio de Hollywood, en el que, tras una audición, se le decía al actor la famosa frase de No nos
llames, nosotros te llamaremos.
• El principio establece una diferenciación entre el concepto de biblioteca y framework, definiendo el
primero como un simple conjunto de clases, métodos y funciones que son invocadas por el flujo del
programa y que posteriormente devuelven el control a éste (control normal) y el segundo como un diseño
más abstracto y elaborado que se encargará, en algún momento, de invocar el código que el programador
se encargue de codificar (inversión de control).
• El ejemplo expuesto por Fowler no puede ser más sencillo: un control normal sería un simple programa
secuencial de consola en el que el programa va solicitando datos al usuario y realizando operaciones
(cálculos, visualización por pantalla) al recibir las respuestas. Un programa que aplica una inversión de
control, sin embargo, se podría representar como una ventana compuesta por cajas de texto, etiquetas y
botones. El Framework, en este caso, expondría un bucle de espera que detectaría la emisión de eventos,
como la pulsación de un botón, momento en el cual se ejecutaría el código de usuario. El Framework
invocará nuestro código en lugar de realizar la operación contraria.
• El Principio de Inversión de Dependencias
• El segundo concepto se lo debemos a otro de los gurús de la ingeniería del Software,
Robert C. Martin, creador del desarrollo ágil y de los principios básicos de la programación orientada
a objetos, denominados SOLID.
• Dos de estos principios, de hecho, sirvieron como base para otro de ellos: el concepto de Inversión
de Dependencias:
• Principio Abierto/Cerrado: una entidad software debe ser abierta para su extensión, pero cerrada
para su modificación.
• Principio de Sustitución de Liskov: un objeto siempre debe poder ser reemplazado por una instancia
de una clase derivada sin alterar el funcionamiento del programa, es decir, todos los métodos de una
clase padre deben estar presentes en una clase hija, y esta debe poder asumir el papel de su padre.
• Partiendo de estos principios, Martin escribió un artículo en el que desarrollaría el concepto
de Principio de Inversión de Dependencias. En este artículo establece, a grandes rasgos, que uno de
los grandes problemas del software es el acoplamiento, es decir, la dependencia de clases entre sí.
• El concepto de inversión proviene, por lo tanto, por el giro de 180 grados que se produce respecto al
paradigma habitual en el que los módulos superiores tienden a construirse sobre los inferiores (por
ejemplo, una clase de interfaz de usuario invocando un método de una clase de negocio) y en el que
las abstracciones tienden a depender de los detalles (por ejemplo, una implementación de una
interfaz).
• Inyección de Dependencias
• Llegamos así al tercer concepto en discordia, relacionado con los dos conceptos anteriores: la
inyección de dependencias. Este concepto se basa en hacer que una clase A inyecte objetos en una
clase B en lugar de dejar que sea la propia clase B la que se encargue de crear el objeto (éste último
caso se suele realizar mediante un simple new()).
• La forma más común de realizar la inyección de dependencias es a través de lo que se conoce
como Contenedor DI, que se encarga de realizar la inyección en objetos simples (POJOs o POCOs). Es
probable que, después de esta escueta explicación, haya quien se plantee la pregunta: ¿qué tiene que
ver la inyección de dependencias con el rollo que nos has soltado en los dos apartados anteriores?
Principalmente se basa en que la inyección de dependencias se suele realizar a través de algún tipo de
Framework (Spring, Unity, Castle Windsor…), por lo que se realizará mediante una inversión de
control, haciendo que sea el Framework (concretamente el contendor DI) el que invoque nuestro
código.
• El siguiente ejemplo ilustrará el concepto de inyección de dependencias. Imaginemos que tenemos el
siguiente código, que ilustra el funcionamiento “habitual” de una clase que depende de otra:
Como podemos ver, existe una clase “Vehiculo” que contiene un objeto de la clase “Motor”. La clase
public class Motor
{ “Vehiculo” quiere obtener las revoluciones del motor, por lo que invoca el método GetRevoluciones del
public void Acelerar() objeto Motor y devuelve su resultado. Este caso se corresponde con una dependencia (el módulo
{ superior -vehículo- depende del módulo inferior -motor-).
// ...
} Como primer paso para desacoplar el motor del vehículo podríamos hacer que la clase “Vehiculo” deje
public int GetRevoluciones()
de encargarse de instanciar el objeto “Motor”, pasándoselo como parámetro al constructor. De este
{ modo, la clase “Vehiculo” quedaría de la siguiente manera:
int currentRPM = 0;
public class Vehiculo
// ... {
private Motor m;
return currentRPM;
} public Vehiculo(Motor motorVehiculo)
} {
// El módulo superior ya no instancia directamente el objeto Motor,
public class Vehiculo // sino que éste es pasado como parámetro en el constructor
{ m = motorVehiculo;
private Motor m; }
public Vehiculo() public int GetRevolucionesMotor()
{ {
m = new Motor(); return m.GetRevoluciones();
} }
}
public int GetRevolucionesMotor()
{ Simple, ¿verdad? El constructor del vehículo se encarga de
return m.GetRevoluciones();
}
inyectar la dependencia dentro del objeto, evitando que esta
} responsabilidad recaiga sobre la propia clase. De este modo,
estamos desacoplando ambos objetos. Si hacemos uso de
interfaces, el acoplamiento será incluso menor.
public class MotorDiesel : IMotor
{
public interface IMotor public void Acelerar()
{ {
// Métodos comunes a todos los motores RealizarAdmision();
void Acelerar(); RealizarCompresion();
int GetRevoluciones(); RealizarCombustion(); // Propia del motor diesel
} RealizarEscape();
}
public class MotorGasolina : IMotor
{ public int GetRevoluciones()
public void Acelerar() {
{ int currentRPM = 0;
RealizarAdmision();
RealizarCompresion(); return currentRPM;
RealizarExplosion(); // Propia del motor de gasolina }
RealizarEscape();
} private void RealizarAdmision() { /* ... */ }
private void RealizarCompresion() { /* ... */ }
public int GetRevoluciones() private void RealizarCombustion() { /* ... */ }
{ private void RealizarEscape() { /* ... */ }
int currentRPM = 0; }
// ... public class Vehiculo
{
return currentRPM; private IMotor m;
}
public Vehiculo(IMotor motorVehiculo)
private void RealizarAdmision() { /* ... */ } {
private void RealizarCompresion() { /* ... */ } // El módulo superior ya no instancia directamente el objeto
private void RealizarExplosion() { /* ... */ } Motor,
private void RealizarEscape() { /* ... */ } // sino que éste es pasado como parámetro en el constructor
m = motorVehiculo;
} }
public int GetRevolucionesMotor()
{
return m.GetRevoluciones();
}
}
• Como vemos, el objeto public class Vehiculo
{
Vehiculo ya ni siquiera está private IMotor m;
acoplado a un objeto de la // Se añade una propiedad para poder acceder al motor
public IMotor M
clase Motor, sino que bastará {
un objeto que implemente la get { return m; }
set { m = value; }
interfaz IMotor, }
como MotorGasolina y MotorDi public Vehiculo(IMotor motorVehiculo)
esel. A continuación podremos {
// El módulo superior ya no instancia directamente el objeto Motor,
modificar el vehículo // sino que éste es pasado como parámetro en el constructor
m = motorVehiculo;
añadiéndole una propiedad }
Finalmente,
{
private IGame game;
public Table(IGame game)
{
this.game = game;
}
codificaremos la
clase Table que contendrá
public string GameStatus()
{
una interfaz IGame que
return game.result();
}
public void AddPlayer()
{
game.addPlayer();
}
será inyectada a través de
su constructor.
public void RemovePlayer()
{
game.removePlayer();
}
public void Play()
{
game.play();
}
}
• Registro de tipos Lo primero que deberemos hacer será instanciar un
contenedor de Unity y registrar los tipos que queramos que resuelva
por nosotros. Como primer ejemplo, registraremos la
interfaz IGame con la clase TrivialPursuit. Esto significará que, cuando
resolvamos las dependencias, Unity instanciará un objeto de la
clase TrivialPursuit en el momento en el que resolvamos un objeto
que posea una dependencia de tipo IGame.
static void Main(string[] args)
{
// Declaramos un contenedor Unity
var unityContainer = new UnityContainer();
// Registramos IGame para que cuando se detecte la dependencia
// proporcione una instancia de TrivialPursuit
unityContainer.RegisterType<IGame, TrivialPursuit>();
}
• Resolución de tipos Por tanto, cada vez que le pidamos a Unity que
resuelva una interfaz IGame, el Framework buscará si tiene registrado
el tipo y, en caso afirmativo, proporcionará una instancia de la clase
que se haya mapeado (en este caso, TrivialPursuit:
Si le indicamos a Unity que resuelva una instancia de la clase Table, lo que lograremos será que se inyecten
todas aquellas dependencias que Unity tiene registradas, es decir, nos proporcionará una instancia de la
clase Table inyectando un objeto de la clase TrivialPursuit en el momento en el que se encuentre con una
interfaz IGame:
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();
}
}
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";
}
}
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;
}
}
• 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();
}
• 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.
• 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 keypara 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.
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.
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).
PATRON BUILDER
• Objetivo: “Separar la construcción de un objeto complejo de su
representación, de modo que el mismo proceso de construcción
pueda crear representaciones diferentes.”
• Hablando en plata, el patrón Builder es un patrón creacional cuyo objetivo es instanciar objetos
complejos que generalmente están compuestos por varios elementos y que admiten diversas
configuraciones. Cuando hablamos de “construcción” nos referimos al proceso, mientras que
cuando hablamos de “representación” nos estaremos refiriendo a los datos que componen el
objeto. Se encargará, por tanto, de encapsular todo el proceso de generación de modo que
únicamente necesite los detalles necesarios para “personalizar” el objeto, devolviendo como
resultado una instancia del objeto complejo que deseamos construir. Es un patrón fuertemente
ligado a otro, el patrón estructural Composite.
Vehiculo v = new Vehiculo();
v.NumPuertas = 5; Como vemos, la lógica de nuestro programa construye un
v.Matricula = "1034 CAA"; vehículo compuesto de muchas partes (de hecho, podríamos
v.Faros = new Faro[4];
v.Faros[0] = new Faro(TipoFaro.Xenon); seguir añadiendo detalles) de forma secuencial. El
v.Faros[1] = new Faro(TipoFaro.Xenon); patrón Builder se encargará de encapsular todo este proceso
v.Faros[2] = new Faro(TipoFaro.Lexus);
v.Faros[3] = new Faro(TipoFaro.Lexus); haciendo que, mediante la obtención de estos detalles, el
v.Color = "Rojo"; proceso de generación de este objeto sea transparente para
v.Motor = new Motor(TipoMotor.Gasolina);
v.Motor.Capacidad = 2200; quien lo solicita.
v.Motor.Valvulas = 12;
DirectorConstruccion director = new DirectorConstruccion (new ConstructorFordFiestaSportEdition());
Vehiculo v = director.ConstruirVehiculo();
• El resultado a nivel computacional será más o menos el mismo, ya
que el proceso de “ensamblado” debe realizarse de todos modos. La
diferencia radica en que la lógica del programa no necesita
saber cómo ensamblar el objeto, sino únicamente aquellas piezas que
quiere que éste disponga. Si vas a comprar un vehículo, no le indicarás
al vendedor el proceso de fabricación de éste paso a paso: él
únicamente debe proporcionarte un vehículo que se ajuste a las
características que tú le solicitas, dejándole a él los detalles de cómo
el vehículo es construido.
• Elementos del patrón
• Utilizaremos el ejemplo de los vehículos para entender cómo funciona este patrón.
Comenzaremos por el final: el producto que queremos obtener. Se trata del objeto complejo que
queremos obtener, que suponemos que estará compuesto por un buen número de elementos.
Imaginemos que queremos obtener un vehículo y que su estructura es la siguiente:
// Tipo de rueda: diámetro, llanta y neumático
public class Rueda // Motor diesel, que implementará la interfaz IMotor
{ public class MotorDiesel : IMotor
public int Diametro { get; set; } {
public string Llanta { get; set; } #region IMotor Members
public string Neumatico { get; set; }
public string ConsumirCombustible()
}
{
// Tipo de carrocería
return RealizarCombustion();
public class Carroceria
}
{
public bool HabitaculoReforzado { get; set; }
public string InyectarCombustible(int cantidad)
public string Material { get; set; }
{
public string TipoCarroceria { get; set; }
return string.Format("MotorDiesel: Inyectados {0} ml. de Gasoil.", cantidad);
}
}
// Interfaz que expone las propiedades del motor
public interface IMotor public string RealizarEscape()
{ {
string ConsumirCombustible(); return "MotorDiesel: Realizado escape de gases";
string InyectarCombustible(int cantidad); }
string RealizarEscape();
string RealizarExpansion(); public string RealizarExpansion()
} {
return "MotorDiesel: Realizada expansion";
}
#endregion
private string RealizarCombustion()
{
return "MotorDiesel: Realizada combustion del Gasoil";
}
}
• Finalmente, la clase que simboliza el vehículo estará formada por los elementos anteriores más algún atributo más. El
producto, como podemos imaginar, será el elemento final una vez haya sido correctamente configurado. Marquémonos
como objetivo la construcción del siguiente vehículo: Audi A3 Sportback. Este vehículo constará de llantas de aluminio de
17cm y neumáticos Michelín, color plata cromado, cierre centralizado y dirección asistida, con una carrocería reforzada de
fibra de carbono. Ya tenemos el objetivo. Ahora necesitamos un constructor (builder) que lo construya por nosotros.
public class Vehiculo
{
public bool CierreCentralizado { get; set; }
public string Color { get; set; }
public bool DireccionAsistida { get; set; }
public string Marca { get; set; }
public string Modelo { get; set; }
public IMotor Motor { get; set; }
public Carroceria TipoCarroceria { get; set; }
public Rueda TipoRuedas { get; set; }
public string GetPrestaciones()
{
StringBuilder sb = new StringBuilder();
string nl = Environment.NewLine;
sb.Append("El presente vehiculo es un ").Append(Marca).Append(" ").Append(Modelo);
sb.Append(" estilo ").Append(TipoCarroceria.TipoCarroceria).Append(nl);
sb.Append("Color: ").Append(Color).Append(nl);
sb.Append(DireccionAsistida ? "Con " : "Sin ").Append("direccion asistida").Append(nl);
sb.Append(CierreCentralizado ? "Con " : "Sin ").Append("cierre centralizado").Append(nl);
sb.Append("Carroceria de ").Append(TipoCarroceria.Material);
sb.Append(TipoCarroceria.HabitaculoReforzado ? " con " : " sin ").Append("el habitaculo reforzado").Append(nl);
sb.Append("Ruedas con llantas ").Append(TipoRuedas.Llanta).Append(" de ").Append(TipoRuedas.Diametro).Append(" cm").Append(nl);
sb.Append("Neumaticos ").Append(TipoRuedas.Neumatico);
sb.Append("Respuesta del motor: ").Append(Motor.InyectarCombustible(100));
return sb.ToString();
}
}
• La clase Builder y los constructores concretos
• La clase constructora será la encargada de construir nuestro objeto. Sin embargo, esta clase no
debe preocuparse de cómo debe construir el producto, sino únicamente de qué partes han de
construirse. Al igual que en una fábrica de ensamblaje, el peón debe centrarse en una tarea
concreta de construcción, dejando el proceso del ensamblaje ordenado para otros
trabajadores.
• Sin embargo, antes de ponernos a codificar el contenido de nuestro constructor, es preciso
pensar un poco en la (más que probable) posibilidad de que otro cliente decida, en algún
momento dado, un vehículo diferente. Por ello, definiremos una interfaz a la que todo
constructor deberá ceñirse para asegurar la compatibilidad del proceso. Todos los constructores
de coches deberán saber añadir ruedas, carrocería o motor, por lo que crearemos una
interfaz IVehiculoBuilder que implemente todas estas operaciones. Recordemos que las
operaciones deben ceñirse al proceso de construir las partes, nunca de cómo combinarlas (ni
en qué orden.
• Otra posibilidad, en lugar de utilizar una interfaz, será la de hacer uso de una clase que defina la
funcionalidad común y declare como abstractos los métodos que deberá implementar el
constructor concreto para cada coche en particular. Ciñámonos a esta segunda opción y
creemos la clase VehiculoBuilder, que será como sigue:
• La interfaz tendrá un aspecto como el siguiente:
public class A3SportbackBuilder : VehiculoBuilder
public abstract class VehiculoBuilder {
{
public override void DefinirVehiculo()
// Declaramos la referencia del producto a
{
construir
v = new Vehiculo();
protected Vehiculo v; v.Marca = "Audi";
v.Modelo = "A3 Sportback";
// Declaramos el método que recuperará el objeto }
Estos patrones están pensados más en como crear clases nuevas sin
modificar las existentes O/C
PATRÓN ADAPTER (WRAPPER)
• Objetivo: Convertir la interfaz de una clase en otra interfaz que el cliente
espera. Adapter consigue que trabajen juntas clases que de otro modo no
podrían.
• El patrón Adapter nos abre el camino hacia el segundo grupo de patrones
propuestos por el Gang of Four: los patrones estructurales. Si bien los
patrones de creación definían la forma en la que los objetos son
instanciados, los patrones estructurales se basan en la forma en la que un
conjunto de clases se relacionan entre sí para proporcionar una funcionalidad
compleja, proporcionando una estructura para conseguir lograr ese objetivo.
• La filosofía de Adapter, al igual que vimos con Prototype, es tan simple como
autoexplicativa: establecer una capa intermedia que permita comunicarse a
dos clases que de otro modo no podrían hacerlo, realizando
una adaptación de la interfaz de la clase que proporciona el servicio a la que
la solicita.
• Representándolo de una forma visual, imaginemos que nuestro objeto
pertenece a la clase Taladro y requiere hacer uso del método Flujo110V() de
una clase que implemente la interfaz IEnchufeIngles. Por tanto, nuestro
taladro está preparado para hacer uso de esa interfaz y espera recibir como
valor de retorno un flujo con una diferencia de potencial de 110V
• Sin embargo, resulta que la clase de la que disponemos en el otro
extremo no implementa esta interfaz, sino otra
llamada IEnchufeEuropeo() que dispone de un método capaz de
devolver a nuestro taladro un flujo de 220V, cuyo nombre
es Flujo220V(). La funcionalidad es parecida, pero no la esperada, y la
interfaz que el taladro espera utilizar no es compatible con el
elemento del subsistema. Nos encontramos, por tanto, con un claro
problema de compatibilidad.
• Para que nuestro taladro pueda comunicarse con IEnchufeEuropeo, será
necesario modificar uno de los dos extremos para que la interfaz de
comunicación coincida, pero si hiciéramos esto, nuevamente estaríamos
rompiendo el principio abierto/cerrado del que hablamos con anterioridad,
amén de que cambiando esta interfaz haríamos que de repente dejasen de
compilar todas aquellas clases que la estuvieran utilizando hasta el momento.
• La solución adecuada consistirá en adaptar los requerimientos de la interfaz de
la clase cliente (taladro) a los servicios que la clase servidora (sistema eléctrico
europeo) puede ofrecer. Hablando en plata, y tal como indica el propio nombre
del patrón, necesitaremos un adaptador que haga coincidir la entrada de un
elemento con la salida del otro. Este será el objetivo del patrón Adapter
• Por lo tanto, cuando nos encontremos con un problema similar a este,
lo más adecuado será codificar una nueva clase que implemente la
interfaz original (IEnchufeIngles) y que posea una propiedad cuyo tipo
será el de la clase que se pretende adaptar. En este caso, nuestra
clase adaptador contendrá una referencia a una instancia de una clase
que implemente IEnchufeEuropeo. De este modo, nuestro taladro
hará una llamada a la clase que espera (IEnchufeIngles.Flujo110V()) y
de forma interna se invocará el método Flujo220V() mediante la
instancia de EnchufeEuropeo.
• Codificando un Adaptador
• Un adaptador no sólo se encarga de transformar una interfaz en otra:
también puede realizar otro tipo de operaciones, principalmente de
transformación. En el ejemplo de los enchufes, además de transformar
una interfaz en otra (los polos del enchufe), nuestro adaptador podía
también realizar otras operaciones, como por ejemplo transformar la
diferencia de potencial del flujo de salida de 220 a 110, ya que de lo
contrario correríamos el riesgo de quemar nuestro dispositivo.
• Comencemos proporcionando las
interfaces IEnchufeIngles e IEnchufeEuropeo y dos clases que las
implementen:
public interface IEnchufeIngles
{
// Devuelve un array de enteros con un voltaje de unos 110V
int[] Flujo110V();
// Devuelve el numero de bornes del enchufe
int getNumeroBornes(); public class EnchufeBritanico : IEnchufeIngles
} {
#region IEnchufeIngles Members
// Devuelve un array con voltajes en distintos momentos
public int[] Flujo110V()
{
int[] arrayFlujo = new int[100];
Random r = new Random();
for (int i = 0; i < arrayFlujo.Length; i++)
{
// Calculamos la fluctuacion del voltaje
int fluctuacion = r.Next(100) > 50 ? 1 : -1; // Positiva o negativa
fluctuacion = fluctuacion * (r.Next(7) + 1); // El valor fluctuara entre -7 y +7
// Valor total entre 103 y 117V
arrayFlujo[i] = fluctuacion + 110;
}
return arrayFlujo;
}
// Devuelve el numero de bornes del enchufe
public int getNumeroBornes()
{
return 3;
}
#endregion
}
public interface IEnchufeEuropeo
{
// Devuelve un array de enteros con un voltaje de unos 220V
int[] Flujo220V();
// Devuelve el numero de bornes del enchufe
int getNumeroBornes(); public class EnchufeEspanol : IEnchufeEuropeo
} {
#region IEnchufeEuropeo Members
// Devuelve un array con voltajes en distintos momentos
public int[] Flujo220V()
{
int[] arrayFlujo = new int[100];
Random r = new Random();
for (int i = 0; i < arrayFlujo.Length; i++)
{
// Calculamos la fluctuacion del voltaje
int fluctuacion = r.Next(100) > 50 ? 1 : -1; // Positiva o negativa
fluctuacion = fluctuacion * (r.Next(10) + 1); // El valor fluctuara entre -0 y +10
// Valor total entre 210 y 230V
arrayFlujo[i] = fluctuacion + 220;
}
return arrayFlujo;
}
// Devuelve el numero de bornes del enchufe
public int getNumeroBornes()
{
return 2;
}
#endregion
Ahora codificaremos nuestra clase Taladro, que posee un
enchufe inglés (es decir, posee una referencia a una
interfaz IEnchufeIngles) para recibir la alimentación:
El cliente, por lo tanto, tendría que realizar las siguientes acciones si quisiera realizar un cambio de marcha: