tag:blogger.com,1999:blog-27282557543286060122024-03-19T03:56:20.503+01:00.NET Design Code TipsPrincipios de diseño, patrones de diseño, patrones de aplicación y otras hierbasUnknownnoreply@blogger.comBlogger20125tag:blogger.com,1999:blog-2728255754328606012.post-23534408044574982252014-05-05T17:55:00.000+02:002014-05-05T17:56:40.795+02:00Patrones estructurales: Facade<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20estructurales" style="font-weight: normal;"><span style="font-size: small;">Patrones estructurales</span></a></h2>
<div>
<h2>
Facade</h2>
<div>
<i>"Proporciona una interfaz unificada para las interfaces de un subsistema. Facade define una interfaz de alto nivel que hace que el subsistema sea más fácil de usar".</i><br />
<i><br /></i>El patrón Facade es probablemente uno de los más usados. En mi opinión, que sea tan ampliamente usado, se debe al gran valor que aporta frente a un bajo coste de implementación.<br />
<br />
<a name='more'></a><br /><br />
En palabras llanas, el patrón Facade permite al cliente de un sistema complejo usarlo de manera más simple.<br />
<br />
Veamos un ejemplo que nos ayudará a clarificar más el concepto del patrón Facade.<br />
<br />
<h4>
El problema</h4>
<div>
Aprovechando que en la reciente conferencia <a href="http://www.buildwindows.com/" rel="nofollow" target="_blank">Build 2014</a> Microsoft hizo público el <a href="http://roslyn.codeplex.com/" rel="nofollow" target="_blank">proyecto "Roslyn"</a> (un proyecto open-source de compilador escrito en C# y VB incluido en la iniciativa <a href="http://www.dotnetfoundation.org/" rel="nofollow" target="_blank">.NET Foundation</a>) usaremos como ejemplo de sistema complejo con el que interactuar un compilador. </div>
</div>
</div>
<div>
<br /></div>
<div>
Un compilador es un sistema que tiene objetos como símbolos, analizadores léxicos, analizadores sintácticos, analizadores semánticos, generadores de código, optimizadores de código, etc...</div>
<div>
<br /></div>
<div>
Un sin fin de objetos que interactúan unos con otros para convertir nuestro código fuente en código binario. </div>
<div>
<br /></div>
<div>
Para algunos programas clientes muy específicos todos estos objetos tienen sentido y son de mucha utilidad. Sin embargo, la mayoría de nosotros sólo queremos compilar nuestro código fuente nada más. Y si tenemos que tratar con toda esa complejidad para compilar nuestros programas mucho me temo que para muchos de nosotros será misión imposible crear una build.</div>
<div>
<br /></div>
<div>
Bien, en este punto tenemos un sistema de compilación muy complejo y la necesidad de poder usarlo de forma más simple, al menos para las operaciones más habituales. Aquí es donde el patrón Facade cobra todo su sentido. </div>
<div>
El patrón Facade nos ofrece una interfaz de alto nivel para facilitarnos el uso del sistema complejo. Por ejemplo una forma simple de compilar nuestro código a través de una línea de comando:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1Gp9BqONI_d7DLbnnSOXxA1b93Q6HtC8FaTTGNj6fyIp1MSzYQ8iO38CwIOI5AUNDkDin_fZLSMrCdp_cmm962L18C3toT-1kNKFjJvMqr_NVs_oizGfNTWk4SKCPPPpIn3Hse6-Exno0/s1600/Facade0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1Gp9BqONI_d7DLbnnSOXxA1b93Q6HtC8FaTTGNj6fyIp1MSzYQ8iO38CwIOI5AUNDkDin_fZLSMrCdp_cmm962L18C3toT-1kNKFjJvMqr_NVs_oizGfNTWk4SKCPPPpIn3Hse6-Exno0/s1600/Facade0.png" height="206" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
O de forma aún más simple a través de Visual Studio:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL1UnSgYnlyaYjYNy9qqy6Nq-mgohRBm45MM5sCnf1rehNOLjmZ-mppz2i6k4pRaQJz_d9sjg4ZYwETbGquAUk8ZoWlFYIkNB4jzlxoRjr9HqDMfqUYDJqwAbn2ekOCDBdAuaU7BifCT7a/s1600/Facade1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL1UnSgYnlyaYjYNy9qqy6Nq-mgohRBm45MM5sCnf1rehNOLjmZ-mppz2i6k4pRaQJz_d9sjg4ZYwETbGquAUk8ZoWlFYIkNB4jzlxoRjr9HqDMfqUYDJqwAbn2ekOCDBdAuaU7BifCT7a/s1600/Facade1.png" height="205" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Bien a través la línea de comandos o de Visual Studio usamos Facade que nos simplifica el uso de sistema, en este caso un compilador.</div>
<div class="separator" style="clear: both; text-align: left;">
Roslyn implementa el método estático Run en la clase Csc. El método Run recibe los argumentos de la línea de comandos y "pone en marcha la maquinaria" que compila el código fuente.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Veamos un gráfico muy simplificado que explica cómo funcionaría el patrón Facade en un compilador:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcW8JiF2tqCJC-J83tIWI2uAf5Zap4v9vmTX-sQSFJ4bS9nO3KRYrudkNj6LQZuL-wJFsc0_MqeT-m0i5cXfpn0uy0c4QRBkMI8XuvEyHsd4V0htHWwj2DSofWJkRabm8ZxAKy0a5RXt5O/s1600/Facade3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgcW8JiF2tqCJC-J83tIWI2uAf5Zap4v9vmTX-sQSFJ4bS9nO3KRYrudkNj6LQZuL-wJFsc0_MqeT-m0i5cXfpn0uy0c4QRBkMI8XuvEyHsd4V0htHWwj2DSofWJkRabm8ZxAKy0a5RXt5O/s1600/Facade3.png" height="266" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Los clientes que sólo necesitan un uso "simplificado" del compilador usan los métodos de la clase Compiler (Csc de Roslyn). Pero esto no quiere decir que no podamos usar el resto de clases del sistema. Un cliente específico que lo necesite puede usar igualmente objetos de tipo CodeGenerator, Scanner, Parser, etc.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
De forma genérica esta sería la representación del patrón Facade:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxXN3Lio_-i_JAQ_b-i2qgOX9n3r4fTewhl7KylT6Fo-ZZyzfr3ENLqdUdh1GMqNxZssoWy6gAzwHGQ9WAsxiYn_o2eZNoN5F2LSCqMUM2atsjJhQwFiWnCLYM3U1QX6dModf0z3cStQmd/s1600/Facade2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxXN3Lio_-i_JAQ_b-i2qgOX9n3r4fTewhl7KylT6Fo-ZZyzfr3ENLqdUdh1GMqNxZssoWy6gAzwHGQ9WAsxiYn_o2eZNoN5F2LSCqMUM2atsjJhQwFiWnCLYM3U1QX6dModf0z3cStQmd/s1600/Facade2.png" height="152" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Estos son los participantes del patrón:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li><b>Facade (clase Csc).</b> Conoce las clases y subsistemas responsables de responder a una petición de un cliente y delega esa petición a las clases o subsistemas responsables de resolverla.</li>
<li><b>Subsystem Classes (CodeGenerator, Scanner, Parser, etc).</b> Implementan la funcionalidad del subsistema. No tienen ningún conocimiento sobre el Facade, es decir, no tienen ninguna referencia a él.</li>
</ul>
<h4>
Ventajas del uso del patrón Facade</h4>
<div>
<ul>
<li>Abstrae al cliente de los componentes del subsistema reduciendo el número de objetos con los que el cliente tiene que tratar y haciendo que el subsistema sea más fácil de usar.</li>
<li>Promueve un bajo acoplamiento entre el cliente y el subsistema. Es habitual que los componentes de un subsistema estén fuertemente acoplados. Mantener un acoplamiento bajo entre el cliente y el subsistema permite hacer cambios en el subsistema sin afectar al cliente. Pensemos en un patrón Facade aplicado a la capa de acceso a datos de nuestra aplicación. Esto nos permitiría reducir al mínimo los efectos que pudiera tener un cambio en la capa de acceso a datos sobre la capa de negocio.</li>
<li>El patrón Facade no impide que se pueda acceder a los componentes del subsistema si es necesario. El cliente puede elegir entre el uso normal del subsistema o el uso fácil a través del Facade.</li>
</ul>
<h4>
A tener en cuenta a la hora de implementar el patrón Facade</h4>
<div>
Aunque creo que el concepto del patrón Facade es bastante simple, hay que tener una serie de consideraciones a la hora de implementarlo. </div>
<div>
<br /></div>
<div>
<b>Reducir el acoplamiento Cliente-Subsistema</b>. Podemos reducir aún más el acoplamiento entre el cliente y el subsistema si empleamos como Facade una clase abstracta con subclases concretas para distintas implementaciones del subsistema. </div>
<div>
<br /></div>
<div>
Volviendo al ejemplo del Facade para la capa de acceso a datos. Podríamos tener la clase abstracta DataAccessFacade como Facade y una subclase SqlDataAccessFacade para una capa de acceso a datos que usa SqlServer, o XmlDataAccessFacade para una capa de acceso a datos que usa archivos Xml para guardar datos. En ambos casos el cliente usaría el subsistema de acceso a datos a través de la clase abstracta DataAccessFacade.<br />
<b><br /></b></div>
<div>
<b>Clases del subsistema públicas o privadas.</b> En cierto sentido un subsistema es análogo a una clase. Ambos exponen una interfaz pública y ambos encapsulan funcionalidad y estado.<br />
<br />
La parte pública de un subsistema son las clases a las que todos los clientes tienen acceso, mientras que la parte privada es sólo de uso del propio subsistema. Las clases Facade son evidentemente públicas, pero no son las únicas. Clases como CodeGenerator, Scanner, Parser en nuestro ejemplo son también clases públicas ya que un cliente puede necesitar manejar esos objetos de bajo nivel el lugar del Facade.</div>
<div>
<br /></div>
<div>
A la hora de implementar un Facade en un subsistema debemos elegir detenidamente que clases formarán parte de la interfaz pública del subsistema además de las clases Facade. </div>
<div>
<br /></div>
</div>
<h4>
</h4>
<h4>
Implementación </h4>
<div>
Ahora que hemos visto que es un patrón Facade vamos a hacer ver un ejemplo más práctico de este patrón en funcionamiento.</div>
<div>
<br /></div>
<div>
Supongamos que en nuestra empresa "ACME Domótica" hemos creado un sistema de software capaz de manejar diferentes aparatos de casa como un proyector, la pantalla del proyector, la luz, el reproductor de DVD, el sistema de sonido del salón, etc.<br />
Además hemos desarrollado una aplicación móvil que permite al usuario reproducir una película desde su teléfono.</div>
<div>
<br /></div>
<div>
Este sería un esbozo del código de nuestro sistema de domótica:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div>
<pre class="brush:csharp;">public class LightController
{
public void Dim(int percentage)
{
// Establece la intensidad de la luz
// a un porcentaje determinado.
}
}
public class ScreenController
{
public void Down()
{
// Baja la pantalla del proyector.
}
}
public class ProjectorController
{
public void On()
{
//Enciende el proyector
}
public void Input(string inputSource)
{
// Establece el origen de la señal
// a reproducir
}
public void WideScreenMode()
{
// Establece el formato panorámico.
}
}
public class AmplifierController
{
public void SetVolume(int p)
{
// Establece el volumen
}
public void SetSurroundSound()
{
// Establece el sonido envolvente
}
public void Input(string inputSource)
{
// Establece el origen de la señal
}
public void On()
{
// Enciende el amplificador
}
}
public class DvdController
{
public void On()
{
// Enciende el Dvd
}
public void Play(object movie)
{
//Inicia la película
}
}
</pre>
<br /></div>
<div>
Y este sería el código del cliente que usa el sistema de domótica desde el móvil.<br />
<br /></div>
<div>
<pre class="brush:csharp;">class Program
{
static void Main(string[] args)
{
object movie = null;
// Aquí obtendriamos el stream de la película.
PlayMovie(movie);
}
private static void PlayMovie(object movie)
{
var lights = new LightController();
// Establece la intensidad de la luz al 10%
lights.Dim(10);
var screen = new ScreenController();
// Baja la pantalla del proyector
screen.Down();
var proyector = new ProjectorController();
// Enciende el proyector, pone el origen de la señal en DVD
// y establece el modo panorámico para la película.
proyector.On();
proyector.Input("Dvd");
proyector.WideScreenMode();
var amp = new AmplifierController();
// Enciende el sistema de sonido, pone el origen de la señal en DVD
// enciende el sonido envolvente y pone el volumen al 5
amp.On();
amp.Input("Dvd");
amp.SetSurroundSound();
amp.SetVolume(5);
var dvd = new DvdController();
// Enciende el reproductor de DVD e inicia la película.
dvd.On();
dvd.Play(movie);
}
}
</pre>
<br />
Como podemos ver el cliente móvil necesita interactuar con cinco componentes distintos del sistema de domótica sólo para reproducir una película. Además hay un acoplamiento muy fuerte entre el cliente y el sistema de domótica.<br />
<br />
Vamos a seguir avanzando para ver las consecuencias.<br />
Ahora nuestra empresa "ACME Domótica" ha desarrollado un dispositivo capaz de subir y bajar las persianas. Como consecuencia nos piden que modifiquemos el sistema de domótica para que gestione también las persianas.<br />
<br />
A primera vista lo que debemos hacer es crear la clase BlindController con un método Up y un método Down que nos permita subir y bajar las persianas. Algo así:<br />
<br />
<pre class="brush:csharp;">public class BlindController
{
public void Up()
{
// Sube las persianas
}
public void Down()
{
// Baja las persianas.
}
}
</pre>
</div>
<div>
<br />
Esta clase BlindController nos soluciona el problema de manejar las ventanas.<br />
Sin embargo tenemos un problema añadido. Debido a que nuestra aplicación cliente para móvil está muy acoplada al sistema de domótica, si queremos bajar las persianas al reproducir una película deberemos también modificar la aplicación móvil. Quedaría de la siguiente forma:<br />
<br />
<pre class="brush:csharp;">class Program
{
static void Main(string[] args)
{
object movie = null;
// Aquí obtendriamos el stream de la película.
PlayMovie(movie);
}
private static void PlayMovie(object movie)
{
var lights = new LightController();
// Establece la intensidad de la luz al 10%
lights.Dim(10);
var blinds = new BlindController();
// Baja las persianas.
blinds.Down();
var screen = new ScreenController();
// Baja la pantalla del proyector
screen.Down();
var projector = new ProjectorController();
// Enciende el proyector, pone el origen de la señal en DVD
// y establece el modo panorámico para la pelicula.
projector.On();
projector.Input("Dvd");
projector.WideScreenMode();
var amp = new AmplifierController();
// Enciende el sistema de sonido, pone el origen de la señal en DVD
// enciende el sonido envolvente y pone el volumen al 5
amp.On();
amp.Input("Dvd");
amp.SetSurroundSound();
amp.SetVolume(5);
var dvd = new DvdController();
// Enciende el reproductor de DVD e inicia la película.
dvd.On();
dvd.Play(movie);
}
}
</pre>
<br /></div>
<div>
Para solamente reproducir una película necesitamos manejar demasiados componentes y el cliente está acoplado al sistema de domótica de tal forma que un cambio en el sistema nos ha obligado a cambiar también el cliente.<br />
<br />
<b>¿Cómo nos podría ayudar el patrón Facade en este caso?</b><br />
Veamos cómo sería la implementación original (sin el controlador de persianas) usando un Facade para acceder al sistema de domótica.<br />
Éste sería el sistema de domótica<br />
<br />
<pre class="brush:csharp;">public class LightController
{
public void Dim(int percentace)
{
// Establece la intensidad de la luz
// a un porcentaje determinado.
}
}
public class ScreenController
{
public void Down()
{
// Baja la pantalla del proyector.
}
}
public class ProjectorController
{
public void On()
{
//Enciende el proyector
}
public void Input(string inputSource)
{
// Establece el origen de la señal
// a reproducir
}
public void WideScreenMode()
{
// Establece el formato panorámico.
}
}
public class AmplifierController
{
public void SetVolume(int p)
{
// Establece el volumen
}
public void SetSurroundSound()
{
// Establece el sonido envolvente
}
public void Input(string inputSource)
{
// Establece el origen de la señal
}
public void On()
{
// Enciende el amplificador
}
}
public class DvdController
{
public void On()
{
// Enciende el Dvd
}
public void Play(object movie)
{
//Inicia la película
}
}
public class DomoticFacade
{
public void WatchMovie(object movie)
{
var lights = new LightController();
// Establece la intensidad de la luz al 10%
lights.Dim(10);
var screen = new ScreenController();
// Baja la pantalla del proyector
screen.Down();
var projector = new ProjectorController();
// Enciende el proyector, pone el origen de la señal en DVD
// y establece el modo panorámico para la película.
projector.On();
projector.Input("Dvd");
projector.WideScreenMode();
var amp = new AmplifierController();
// Enciende el sistema de sonido, pone el origen de la señal en DVD
// enciende el sonido envolvente y pone el volumen al 5
amp.On();
amp.Input("Dvd");
amp.SetSurroundSound();
amp.SetVolume(5);
var dvd = new DvdController();
// Enciende el reproductor de DVD e inicia la película.
dvd.On();
dvd.Play(movie);
}
}
</pre>
</div>
<div>
<br />
Y este el cliente:<br />
<br />
<pre class="brush:csharp;">class Program
{
static void Main(string[] args)
{
object movie = null;
// Aquí obtendriamos el stream de la película.
var domoticFacade = new DomoticFacade();
domoticFacade.WatchMovie(movie);
}
}
</pre>
<br />
<b>¿Qué ha cambiado?
</b><br />
Ahora el sistema de domótica implementa una interfaz de alto nivel, la clase DomoticFacade, que aísla al cliente de los componentes concretos del sistema. Hay un acoplamiento bajo entre el sistema de domótica y el cliente. Además para la clase cliente es mucho más simple usar el sistema de domótica.<br />
<br />
Ahora los cambios necesarios para manejar las persianas sólo son necesarios en el sistema de domótica y no en el cliente. Agregamos la clase BlindController y modificamos el método WatchMovie de Facade. Quedaría así:<br />
<br />
<br />
<pre class="brush:csharp;">public class BlindController
{
public void Up()
{
// Sube las persianas
}
public void Down()
{
// Baja las persianas.
}
}
public class DomoticFacade
{
public void WatchMovie(object movie)
{
var lights = new LightController();
// Establece la intensidad de la luz al 10%
lights.Dim(10);
var blinds = new BlindController();
// Baja las persianas.
blinds.Down();
var screen = new ScreenController();
// Baja la pantalla del proyector
screen.Down();
var projector = new ProjectorController();
// Enciende el proyector, pone el origen de la señal en DVD
// y establece el modo panorámico para la pelicula.
projector.On();
projector.Input("Dvd");
projector.WideScreenMode();
var amp = new AmplifierController();
// Enciende el sistema de sonido, pone el origen de la señal en DVD
// enciende el sonido envolvente y pone el volumen al 5
amp.On();
amp.Input("Dvd");
amp.SetSurroundSound();
amp.SetVolume(5);
var dvd = new DvdController();
// Enciende el reproductor de DVD e inicia la pelicula.
dvd.On();
dvd.Play(movie);
}
}
</pre>
<br />
No es necesario modificar nada en el cliente por que el Facade mantiene un acoplamiento bajo entre el sistema de domótica y el cliente móvil.<br />
<br />
Hasta aquí el patrón Facade. Continuaremos con el patrón Flyweight en el próximo post.<br />
<br />
<br /></div>
Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-2728255754328606012.post-42300457381536102032014-04-01T16:32:00.000+02:002014-04-02T12:08:32.433+02:00Patrones estructurales: Decorator<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20estructurales" style="font-weight: normal;"><span style="font-size: small;">Patrones estructurales</span></a></h2>
<div>
<h2>
Decorator</h2>
<div>
<i>"Agrega responsabilidades adicionales a un objeto de forma dinámica. Los decoradores proveen una alternativa más flexible que la herencia para extender la funcionalidad de un objeto".</i><br />
<br />
Para entender cómo y cuándo aplicar este patrón vamos a empezar por ver un ejemplo de aplicación a desarrollar para entender el problema al que nos enfrentamos.<br />
<br />
<br />
<a name='more'></a>Desde el fabricante de coches ACME nos solicitan desarrollar una aplicación para que los clientes que visitan su web puedan calcular el precio de un coche en base a un modelo seleccionado y una serie de extras que el cliente puede elegir para el coche. Esto no es más que la típica calculadora de precios que podemos ver en la mayoría de webs de fabricantes de coches.<br />
<br />
Para ver un ejemplo simple vamos a ver unas especificaciones del cliente muy simples.<br />
<br />
<ul>
<li>Existen dos modelos de coches para elegir: el Sport y el Family</li>
<li>Cada modelo puede ser motor Diesel o Gasolina</li>
<li>El cliente también puede agregar las siguientes características si lo desea: Cambio automático, Turbo, Frenos ABS, Elevalunas eléctricos, Navegador y Techo solar.</li>
</ul>
<div>
<h4>
</h4>
<h4>
El problema</h4>
<br />
Una primera aproximación a la solución del problema podría estar basada en calcular el precio en una clase específica fuera de la jerarquía de la clase coche. Podríamos crear la clase CarPriceCalculator que debería tener las propiedades para las opciones que el cliente puede elegir. Así en CarPriceCalculator tendríamos la propiedad Turbo por ejemplo que estableceríamos a true si el cliente selecciona la opción turbo. Algo como lo siguiente:<br />
<br />
<pre class="brush:csharp;">public class CarPriceCalculator
{
private Car car;
public CarPriceCalculator(Car carModel)
{
this.car = carModel;
}
public bool Automatic { get; set; }
public bool Turbo { get; set; }
public bool AbsBrakes { get; set; }
public bool PowerWindows { get; set; }
public bool Diesel { get; set; }
public bool Navigator { get; set; }
public bool Sunroof { get; set; }
public double GetPrice()
{
var price = car.Price;
if (Automatic)
price += 2000D;
if (Turbo)
price += 1300D;
//...
//...
// Aquí serguirá una enorme estructura de
// sentencias if
return price;
}
}
</pre>
<br />
Esta solución presenta algunos problemas, pero el más evidente es que viola el <a href="http://designcodetips.blogspot.com.es/2013/10/the-openclose-principle-principio.html" target="_blank">principio Open / Close</a>. Si agregamos una nueva característica tendríamos que modificar la clase CarPriceCalculator.<br />
<br />
Podríamos pensar en una solución basada en la herencia. Cada tipo de coche Sport y Family heredaría de la clase Car y establece su precio. Esto será un problema por la rigidez que conlleva el uso de herencia, ya que también cada coche podría ser con o sin turbo y tendríamos clases del tipo Sport, SportTurbo, Family, FamilyTurbo, si continuamos agregando características a la jerarquía de clases podríamos acabar con algo como esto:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggJ-pUAjNZ7TuKidXMOlDiGhECyw53XpvWWgGXsyeP1HrzF9vjZEY_Jd-dSDgJzq9MqC4Lom5BkynzW7qPYfkvvYCQ0qmhWMNY6Reh5YtemIoe1CiX1nauLhH0A4OUad_pXeuVBCOyQefA/s1600/Decorator.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggJ-pUAjNZ7TuKidXMOlDiGhECyw53XpvWWgGXsyeP1HrzF9vjZEY_Jd-dSDgJzq9MqC4Lom5BkynzW7qPYfkvvYCQ0qmhWMNY6Reh5YtemIoe1CiX1nauLhH0A4OUad_pXeuVBCOyQefA/s1600/Decorator.png" height="162" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Y esto para tan tres características (Diesel, Turbo y Frenos ABS). ¿Habéis visto cuantos parámetros tienen las calculadoras de precios de coches? ¡Tienen una cantidad inmensa de opciones! </div>
<div class="separator" style="clear: both; text-align: left;">
Lo que está claro es que por este camino no vamos a acabar bien. La cantidad de clases que deberíamos implementar va a crecer exponencialmente según agregamos opciones al sistema. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
El error fundamental de este planteamiento para calcular el precio del coche es que se basa en la herencia. Mediante herencia estamos agregando responsabilidades (cambio en el precio) a las clases, pero estas responsabilidades se agregan de forma estática, en la definición del modelo. Lo ideal sería que pudiésemos agregar responsabilidades a las clases Sport o Family (que definen los modelos de coches) de forma dinámica, según el cliente selecciona características extra del coche.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h4 style="clear: both; text-align: left;">
La solución</h4>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
El patrón Decorator como hemos visto en la definición nos permite agregar de forma dinámica responsabilidades a un objeto, así que de entrada parece una base factible para solucionar nuestro problema.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Vamos a ver el diagrama y las clases que intervienen en el patrón Decorator.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-zDTUQH6iXQMVCVlUSvLKzRekJHqIs208MHL9L79Eevvvj1wBVrEmoUFY_IEKATlSdeChfGZn9VnTgBr0iY1Jv2ukG0HZuuDSe3nlH8hhDPaQOPMWk6navOP8qXmJom4a05y9BLYDbCEx/s1600/DecoratorUml.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-zDTUQH6iXQMVCVlUSvLKzRekJHqIs208MHL9L79Eevvvj1wBVrEmoUFY_IEKATlSdeChfGZn9VnTgBr0iY1Jv2ukG0HZuuDSe3nlH8hhDPaQOPMWk6navOP8qXmJom4a05y9BLYDbCEx/s1600/DecoratorUml.png" height="175" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li><b>Component (Car).</b> Define la interfaz de los objetos a los que podemos agregar responsabilidades dinámicamente.</li>
<li><b>ConcreteComponent (Sport, Family).</b> Define objetos concretos a los que se les agregarán responsabilidades.</li>
<li><b>Decorator.</b> Mantiene una referencia a Component. Y define una interfaz que se ajusta a la de Component.</li>
<li><b>ConcreteDecorator (Turbo, Frenos Abs, Diesel, etc.).</b> Agregan responsabilidades la clase Component.</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: left;">
Ahora veremos una solución para nuestra calculadora de precios basada en el patrón Decorator y después iremos comentando algunas cosas sobre la implementación.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<br /></div>
<pre class="brush:csharp;">/// <summary>
/// Define la interfaz de los modelos de los coches.
/// Si es necesario se puede implementar como una
/// clase abstracta
/// </summary>
public interface ICar
{
double GetPrice();
}
/// <summary>
/// Define un coche del modelo Sport
/// </summary>
public class Sport : ICar
{
public double GetPrice()
{
// Devuelve el precio de un coche
// del modelo sport básico
return 19500D;
}
}
/// <summary>
/// Define un coche del
/// modelo Family
/// </summary>
public class Family : ICar
{
public double GetPrice()
{
// Devuelve el precio de un coche
// del modelo family básico
return 14800D;
}
}
/// <summary>
/// Clase base para los decoradores.
/// Mantiene una referencia a ICar (Component)
/// y define una interfaz igual a Componenet
/// </summary>
public abstract class Decorator : ICar
{
protected ICar carModel;
protected Decorator(ICar carModel)
{
this.carModel = carModel;
}
public abstract double GetPrice();
}
/// <summary>
/// Define la responsabilida
/// para coches con frenos Abs.
/// La responsabilidad
/// en este caso es alterar el precio de la
/// referencia ICar que mantiene su clase base.
/// </summary>
public class AbsBrakes : Decorator
{
public AbsBrakes(ICar car)
: base(car)
{
}
public override double GetPrice()
{
// Obtiene el precio de la referencia
// del coche de la clase base y suma el
// precio extra de los frenos Abs.
return this.carModel.GetPrice() + 800D;
}
}
/// <summary>
/// Define la responsabilida
/// para coches con cambio automático.
/// La responsabilidad
/// en este caso es alterar el precio de la
/// referencia ICar que mantiene su clase base.
/// </summary>
public class Automatic : Decorator
{
public Automatic(ICar car)
: base(car)
{
}
public override double GetPrice()
{
// Obtiene el precio de la referencia
// del coche de la clase base y suma el
// precio extra del cambio automático.
return this.carModel.GetPrice() + 2100D;
}
}
/// <summary>
/// Define la responsabilida
/// para coches diesel. La responsabilidad
/// en este caso es alterar el precio de la
/// referencia ICar que mantiene su clase base.
/// </summary>
public class Diesel : Decorator
{
public Diesel(ICar car)
: base(car)
{
}
public override double GetPrice()
{
// Obtiene el precio de la referencia
// del coche de la clase base y suma el
// precio extra del motor diesel.
return this.carModel.GetPrice() + 1800D;
}
}
/// <summary>
/// Define la responsabilida
/// para coches con Turbo.
/// La responsabilidad
/// en este caso es alterar el precio de la
/// referencia ICar que mantiene su clase base.
/// </summary>
public class Turbo : Decorator
{
public Turbo(ICar car)
: base(car)
{
}
public override double GetPrice()
{
// Obtiene el precio de la referencia
// del coche de la clase base y suma el
// precio extra del turbo.
return this.carModel.GetPrice() + 1100D;
}
}
class Program
{
static void Main(string[] args)
{
// Precio de un modelo sport básico
ICar car = new Sport();
Console.WriteLine(string.Format("Precio del modelo sport básico: {0}", GetPriceOfAnyCar(car)));
// Precio de un modelo family decorado con Diesel y Turbo
ICar family = new Family();
ICar diesel = new Diesel(family);
ICar turbo = new Turbo(diesel);
Console.WriteLine(string.Format("Precio del modelo family, diesel, turbo: {0}", GetPriceOfAnyCar(turbo)));
// Precio de un modelo sport decorado con Turbo y Abs
ICar sport = new Sport();
ICar spTurbo = new Turbo(sport);
ICar spAbs = new AbsBrakes(spTurbo);
Console.WriteLine(string.Format("Precio del modelo sport, turbo con Abs: {0}", GetPriceOfAnyCar(spAbs)));
Console.ReadLine();
}
static double GetPriceOfAnyCar(ICar car)
{
return car.GetPrice();
}
}
</pre>
</div>
<br />
Veamos algunas cosas interesantes a tener en cuenta de este código:<br />
<br />
<ul>
<li>Los decoradores son del mismo tipo (ICar) que los objetos a los que decoran. Esto es fundamental porque nos permite usar del mismo modo un coche básico y uno decorado. He agregado el método GetPriceOfAnyCar que muestra esto. </li>
<li>Los decoradores normalmente reenvían las llamadas del cliente a la referencia que mantienen del componente (ICar) y pueden hacer operaciones adicionales antes o después de reenviar la llamada. En nuestro caso después de reenviar la llamada al método GetPrice alteran el precio.</li>
<li>El patrón Decorator modifican los objetos Component (ICar) desde fuera (veremos otros patrones que modifican los objetos desde dentro). Realmente crean un envoltorio (wrapper) del objeto original. Lo interesante es que el envoltorio (Turbo, Diesel, etc) es del mismo tipo que el objeto original, permitiéndonos aplicar un número ilimitado de envoltorios. </li>
</ul>
<div>
Una descripción del patrón Decorator que creo que es muy gráfica es que conceptualmente podemos verlo como que en tiempo de ejecución se parece a las capas de una cebolla envolviendo al objeto Component (ICar). El siguiente gráfico muestra esto para el ejemplo del modelo Sport con Turbo y Abs:</div>
</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2YNeclNc_1054M1C7DaFByDMm-pcHhRJoPimWsKBE5Yz5r7-TTNuiBWsYAjz0Y3khp2yVdadnKCPu2_u_BhvB86y0JORytjWYyWuC6Fx7GwwAAYU3NrYyhSDlUPLlmEVUb0NL3y-10jPe/s1600/DecoratorOnion.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2YNeclNc_1054M1C7DaFByDMm-pcHhRJoPimWsKBE5Yz5r7-TTNuiBWsYAjz0Y3khp2yVdadnKCPu2_u_BhvB86y0JORytjWYyWuC6Fx7GwwAAYU3NrYyhSDlUPLlmEVUb0NL3y-10jPe/s1600/DecoratorOnion.png" height="221" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<h4 style="clear: both; text-align: left;">
Ventaja e inconvenientes del uso del patrón Decorator</h4>
<div>
<br /></div>
<div>
La aplicación del patrón Decorator nos ofrece las siguientes ventajas:</div>
<div>
<ul>
<li>Promueve la composición de objetos y la delegación frente a la herencia. Como ya hemos mencionado en post anteriores esto se traduce en diseños más flexibles.</li>
<li>Mayor flexibilidad que la herencia estática. Con decoradores podemos agregar responsabilidades a los objetos en tiempo de ejecución. Por el contrario, la herencia requiere crear nuevas clases para agregar una responsabilidad. Además como hemos visto si creamos varias responsabilidades para un mismo objeto podemos aplicar varias de esas responsabilidades a la vez.</li>
<li>Evita "responsabilidades pre-cargadas" en las clases de la parte alta de la jerarquía. En nuestro ejemplo evita tener por ejemplo en la interfaz ICar propiedades del tipo HasTurbo, IsDiesel, etc. para poder calcular el precio posteriormente. Por el contrario, los decoradores permiten agregar esas responsabilidades en tiempo de ejecución justo cuando las necesitamos.</li>
</ul>
<div>
Además de ventajas, el uso de decoradores tiene ciertos riesgos o limitaciones que también deberemos tener en cuenta:</div>
</div>
<div>
<ul>
<li>Los decoradores (Turbo, Diesel, etc) y los componentes (Sport, Family) no son idénticos. Como ya hemos visto los decoradores crean un wrapper transparente que nos permiten usarlos como si fueran componentes, pero realmente no son del mismo. Esto es muy importante tenerlo en cuenta por que no podremos crear operaciones basadas en la identidad de los objetos si usamos decoradores. En el ejemplo del código Sport, Turbo y Abs hemos creado la variable spAbs de tipo AbsBreaks. Pero aunque la usamos como si fuera un modelo Sport la operación spAbs as Sport devolverá null porque AbsBread y Sport no son idénticos.</li>
<li>Muchos objetos pequeños. Los diseños basados en decoradores suelen acabar en muchos objetos pequeños todos del mismo tipo y con muy pocas diferencias entre ellos. Aunque teniendo un poco de experiencia es muy fácil configurarlos, para desarrolladores más inexpertos puede ser complejo entenderlo y depurarlo.</li>
</ul>
<div>
<br /></div>
</div>
<h4>
Consideraciones a tener en cuenta al implementar el patrón Decorator.</h4>
<div>
<br /></div>
<div>
Hay unas cuantas cosas que deberíamos tener en cuenta a la hora de implementar una solución basada en el patrón Decorator. </div>
<div>
<br /></div>
<div>
<b>No siempre es necesario implementar la clase Decorator.</b> Como hemos visto esta clase se encarga de mantener una referencia a la clase Component. Sobre todo en los casos en que vamos a implementar el patrón en una jerarquía heredada (no creada nueva) es posible que sea más fácil de implementar si damos la responsabilidad de mantener la referencia al Component en los decoradores concretos que crear una clase Decorator.</div>
<div>
<br /></div>
<div>
<b>Mantener la clase Component lo más ligera posible.</b> Teniendo en cuenta que tanto los ConcreteComponents (Sport, Family) como los decoradores heredan de la misma clase común (Component) debemos tratar de tener esa clase común lo más ligera posible. Lo ideal es que su responsabilidad se exclusivamente definir la interfaz y no almacenar datos. Si la clase Component es muy pesada hará que los decoradores (que heredan de ella) sean también muy pesados y eso puede limitar el número de decoradores que podríamos usar.</div>
<div>
<br /></div>
<div>
<b>Cambiar "la piel" o cambiar "las tripas".</b> Relacionado con el punto anterior. Hemos visto que los decoradores cambian "la piel" de un objeto para alterar su comportamiento. Es decir, cambian el comportamiento de un objeto desde fuera. Por otro lado acabamos de ver que una clase Component muy pesada creará decoradores muy pesados y puede limitar su uso. </div>
<div>
En los casos en los que la clase Component sea intrínsecamente pesada y no podamos cambiar eso una mejor opción sería aplicar una solución basada en el patrón State (veremos este patrón en un futuro post). </div>
<div>
El patrón State cambia el comportamiento de un objeto desde dentro y no se verá afectado por que la clase Component sea muy pesada.</div>
<div>
Por consiguiente, si la clase Component es ligera elegiremos la opción del patrón Decorator, por el contrario si no podemos "adelgazar" a la clase Component elegiremos el patrón State.</div>
<div>
<br /></div>
<blockquote class="tr_bq">
La elección entre Decorator y State puede venir marcada por el tamaño intrínseco de la clase Component (Car)</blockquote>
<div>
<br /></div>
<div>
<h4>
Usos conocidos del patrón Decorator</h4>
</div>
<div>
Son muchos los entornos gráficos que hacen uso del patrón Decorator para añadir adornos a los objetos; como los tiradores del objeto seleccionado en editores gráficos.</div>
<div>
<br /></div>
<div>
Pero el uso de Decorator no se restringe a la interfaz gráfica. Como hemos visto en nuestro ejemplo nosotros lo hemos usado para añadir responsabilidades de cálculo de precio de forma dinámica. </div>
<div>
<br /></div>
En .NET una implementación típica de Decorator son las clases<br />
<div>
<ul>
<li>System.IO.Stream</li>
<ul>
<li>System.IO.BufferedStream</li>
<li>System.IO.FileStream</li>
<li>System.IO.MemoryStream</li>
<li>System.Net.Sockets.NetworkStream</li>
<li>System.Security.Cryptography.CryptoStream</li>
</ul>
</ul>
<div>
Todas ellas son de tipo Stream y pueden recibir un Stream en su constructor.</div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Hasta aquí el patrón Decorator. El próximo post será sobre Facade.</div>
<div>
<br /></div>
<div>
<br /></div>
</div>
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-2728255754328606012.post-21558678340401774462014-03-17T15:29:00.002+01:002014-03-18T12:24:14.644+01:00Patrones estructurales: Composite<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20estructurales" style="font-weight: normal;"><span style="font-size: small;">Patrones estructurales</span></a></h2>
<div>
<h2>
Composite</h2>
<div>
<i>"Compone objetos en estructuras de tipo árbol para representar jerarquías del tipo parte-todo. Permite que el cliente trate de forma uniforme tanto un objeto como una composición de objetos".</i><br />
<i><br /></i>Son muchas las situaciones en las que nos encontramos ante estructuras de datos de tipo árbol como se explica en el enunciado del patrón Composite. Un problema típico con este tipo de estructuras es que debemos tratar de forma diferente a los nodos hoja (los nodos que están al final de la rama, sin hijos) de los nodos que no lo son.<br />
<br />
<a name='more'></a><br />
<br />
<h4>
El problema</h4>
</div>
</div>
<div>
Vamos a volver al ejemplo del editor gráfico 3DWorlds del <a href="http://designcodetips.blogspot.com.es/2014/03/patrones-estructurales-bridge.html" target="_blank">post sobre el patrón Bridge</a>. Este editor gráfico nos permite dibujar figuras 3D proyectándolas con diferentes perspectivas. </div>
<div>
<br /></div>
<div>
Bien, una funcionalidad típica en este tipo de software es la agrupación de elementos. Nuestros usuarios quieren poder agrupar varias figuras y además quieren tratar ese grupo como si fuera un elemento más del dibujo. Quieren poder mover ese grupo, cortar, pegar, cambiar el tamaño (resize), moverlo adelante y atrás en el eje Z, agruparlo a otras figuras o grupos, etc. En definitiva, queremos tratar una agrupación de figuras igual que si fuera una figura cualquiera.</div>
<div>
<br /></div>
<div>
Dejando a un lado las funcionalidades que queremos aplicar y centrándonos en la parte estructural del problema, lo que básicamente tenemos es que cada elemento en nuestro editor gráfico puede ser una figura o bien un grupo de elementos. Cada grupo puede a su vez estar compuesto por un numero indeterminado de figuras, de otros grupos de elemento, o ambos.<br />
<br />
De forma gráfica podría representarse así:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWqsi7gCBHNyuV8tJB3G1bNlnRexIolipdWH-B4sFId7q-itN6JOVOm_nebEw_tHwiLaEu-VM2KC-DqLgUIN4VEhWEh893pooyy_QRIIgPjrSLOQ3s3SsclVeZPE_egGC-NSzCZ3eHUbWT/s1600/CompositeTree.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWqsi7gCBHNyuV8tJB3G1bNlnRexIolipdWH-B4sFId7q-itN6JOVOm_nebEw_tHwiLaEu-VM2KC-DqLgUIN4VEhWEh893pooyy_QRIIgPjrSLOQ3s3SsclVeZPE_egGC-NSzCZ3eHUbWT/s1600/CompositeTree.png" height="320" width="245" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<br />
Este árbol representa un gráfico con tres cubos y dos pirámides, pero algunas de las figuras están agrupadas.<br />
<br />
En este punto la idea original era mostrar un código que soportara esta estructura sin aplicar ningún patrón (o más bien aplicando de forma intensiva como dirían mis compañeros <a href="http://es.linkedin.com/pub/lucio-horcajada-gonzalez/33/255/a50" target="_blank">Lucio </a>e <a href="http://es.linkedin.com/pub/iv%C3%A1n-morales-mu%C3%B1oz/55/a59/397" target="_blank">Iván </a>el patrón "i-efe" :o) ). Pero sinceramente, después de ponerme un par de veces, es un código tan "feo", lleno de sentencias "if", frágil, etc. que si me lo permitís ni siquiera voy a ponerlo.<br />
<br />
Así que vamos a ver directamente la solución que propone el patrón Composite tanto para soportar la estructura del árbol como para permitir que el cliente trate a todos los nodos de la misma manera independientemente de que sean figuras o grupos. Esto va a facilitar mucho la tarea al cliente. Después aplicaremos el patrón para resolver el problema anterior y veremos como quedaría la implementación.<br />
<br />
<h4>
La solución</h4>
</div>
<div>
Veamos el diagrama UML del patrón Composite y vamos a analizarlo:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDwmN-uLKj1i707Sx5lltCq7_mD6IaOe7YXD3ULx3Aj7bLEhJdwW_sMi-kT_JqTLdfYnF4Y0iU0UI4oSR41AgrJCa_VpYhYfslmNI66cNUcjXLENQRkP0-yLuAOPliqJn92-JBCDOrTVP7/s1600/CompositeUML.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDwmN-uLKj1i707Sx5lltCq7_mD6IaOe7YXD3ULx3Aj7bLEhJdwW_sMi-kT_JqTLdfYnF4Y0iU0UI4oSR41AgrJCa_VpYhYfslmNI66cNUcjXLENQRkP0-yLuAOPliqJn92-JBCDOrTVP7/s1600/CompositeUML.png" height="181" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Participantes en el patrón:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li><b>Component (crearemos la clase Glyph): </b></li>
<ul>
<li>Define una interfaz común para todos los nodos del árbol, tanto para grupos como para figuras.</li>
<li>Define una interfaz para acceder y gestionar los elementos hijos.</li>
</ul>
<li><b>Leaf (Pyramid, Cube): </b>Representan objetos hoja del árbol. Son objetos que no tienen hijos.</li>
<li><b>Composite (Grupo, crearemos la clase GlyphGroup): </b></li>
<ul>
<li>Define el comportamiento para objetos que tienen hijos.</li>
<li>Almacenan hijos de tipo Component.</li>
<li>Implementan las operaciones relacionadas con la gestión de hijos de la clase Component.</li>
</ul>
<li><b>Client: </b>Manipula los objetos de la composición a través de la clase Component.</li>
</ul>
<div>
Lo primero que debemos observar es que el patrón Composite define una interfaz común para todos los elementos de la composición. Esta interfaz (la clase Component) permite al cliente tratar a todos los elementos del árbol de forma uniforme. En nuestro ejemplo de la aplicación 3DWorlds que describíamos al principio de post no tenemos aún esta clase. Crearemos la clase Glyph (Glifo) hará el papel de la clase Component y representa cualquier cosa que pueda pintarse en nuestro editor gráfico (Pirámide, Cubo o Grupo).</div>
<div>
<br /></div>
<div>
Vamos a ver como implementaríamos estos conceptos para resolver nuestro problema. Partiremos de la implementación que ya teníamos del <a href="http://designcodetips.blogspot.com.es/2014/03/patrones-estructurales-bridge.html" target="_blank">post del patrón Birdge</a> y haremos los cambios oportunos para permitir agrupar elementos.</div>
<pre class="brush:csharp;">/// <summary>
/// Clase que representa cualquier cosa que pueda dibujarse
/// en el editor gráfico.
/// </summary>
public abstract class Glyph
{
/// <summary>
/// Agrega un hijo.
/// Por defecto no hace nada, la clase GlyphGroup
/// sobreescribirá este método con lógica.
/// Los objetos hoja no lo sobreescriben, una hoja
/// no puede agregar hijos.
/// </summary>
/// <param name="child" />
public virtual void Add(Glyph child) { }
/// <summary>
/// Elimina un hijo.
/// Por defecto no hace nada, la clase GlyphGroup
/// sobreescribirá este método con lógica.
/// Los objetos hoja no lo sobreescriben, una hoja
/// no puede eliminar hijos.
/// </summary>
/// <param name="child" />
public virtual void Remove(Glyph child) { }
/// <summary>
/// Obtiene los hijos.
/// Este método es una variación de la definición original
/// del método que sería Glyph GetChild(int index)
/// En nuestro caso esta variante puede que sea más util,
/// aunque si es necesario podemos implementar las dos.
/// La implementación de este método devuelve un enumerable vacío.
/// La clase GlypGroup lo sobreescribirá para devolver sus hijos.
/// </summary>
/// <returns></returns>
public virtual IEnumerable<Glyph> GetChildren() { return Enumerable.Empty<Glyph>(); }
/// <summary>
/// Metodo abstracto que implmentaran las clases hijas.
/// En nuestro caso esta es la operación específica de
/// negocio, las anteriores son exclusivamente para
/// gestionar los hijos del composite.
/// </summary>
public abstract void Draw();
/// <summary>
/// Método que permite mover un glifo basándose
/// en desplazamientos para los ejes X, Y, Z
/// </summary>
/// <param name="offsetX" />
/// <param name="offsetY" />
/// <param name="offsetZ" />
public abstract void Move(double offsetX, double offsetY, double offsetZ);
}
/// <summary>
/// Clase abstracta define la interfaz de figuras que podemos dibujar
/// Tambien define métodos de dibujo de alto nivel
/// </summary>
public abstract class Figure : Glyph
{
private IImplementor internalPerspectiveImplementor;
protected Figure(IImplementor perspectiveImplementor)
{
internalPerspectiveImplementor = perspectiveImplementor;
}
/// <summary>
/// Metodo de alto nivel para dibujar un rectangulo
/// </summary>
/// <param name="rectangle" />
protected void DrawRectangle(Rectangle3D rectangle)
{
internalPerspectiveImplementor.DrawLine(rectangle.Point1, rectangle.Point2);
internalPerspectiveImplementor.DrawLine(rectangle.Point2, rectangle.Point3);
internalPerspectiveImplementor.DrawLine(rectangle.Point3, rectangle.Point4);
internalPerspectiveImplementor.DrawLine(rectangle.Point4, rectangle.Point1);
}
/// <summary>
/// Metodo de alto nivel para dibujar un triangulo a partir de 3 puntos
/// </summary>
protected void DrawTriangle(Point3D point1, Point3D point2, Point3D point3)
{
internalPerspectiveImplementor.DrawLine(point1, point2);
internalPerspectiveImplementor.DrawLine(point2, point3);
internalPerspectiveImplementor.DrawLine(point3, point1);
}
}
/// <summary>
/// Composite. Clase que agrupa objetos de tipo Glyph
/// y que el cliente puede manejar igual que si fuera
/// un Glyph simple.
/// </summary>
public class GlyphGroup : Glyph
{
private List<Glyph> internalChildren;
public GlyphGroup()
{
internalChildren = new List<Glyph>();
}
public override void Add(Glyph child)
{
// Por simplicidad omitimos comprobaciones
// previas a agregar el glifo. Como que no
// exista ya en el listado.
internalChildren.Add(child);
}
public override void Remove(Glyph child)
{
internalChildren.Remove(child);
}
public override IEnumerable<Glyph> GetChildren()
{
return internalChildren;
}
/// <summary>
/// GlyphGroup no tiene ninguna representación propia,
/// el método draw simplemente llama al método Draw
/// de todos sus hijos.
/// </summary>
public override void Draw()
{
Console.WriteLine("Inicio de grupo");
foreach (var glyph in internalChildren)
{
glyph.Draw();
}
Console.WriteLine("Fin de grupo");
}
public override void Move(double offsetX, double offsetY, double offsetZ)
{
foreach (var glyph in internalChildren)
{
glyph.Move(offsetX, offsetY, offsetZ);
}
}
}
/// <summary>
/// Estructura que representa un triangulo con
/// puntos 3D
/// </summary>
public struct Rectangle3D
{
public Point3D Point1 { get; set; }
public Point3D Point2 { get; set; }
public Point3D Point3 { get; set; }
public Point3D Point4 { get; set; }
public void Move(double offsetX, double offsetY, double offsetZ)
{
// Asignamos un nuevo Point3D a las propiedades por que Point3D es un objeto
// por valor no por referencia.
// Si hicieramos Point1.Offset(offsetX, offsetY, offsetZ) no modificaría la
// localización del punto por que la propiedad devuelve una copia del punto
// ver: http://msdn.microsoft.com/en-us/library/system.windows.media.media3d.point3d.offset.aspx
Point1 = new Point3D(Point1.X + offsetX, Point1.Y + offsetY, Point1.Z + offsetZ);
Point2 = new Point3D(Point2.X + offsetX, Point2.Y + offsetY, Point2.Z + offsetZ);
Point3 = new Point3D(Point3.X + offsetX, Point3.Y + offsetY, Point3.Z + offsetZ);
Point4 = new Point3D(Point4.X + offsetX, Point4.Y + offsetY, Point4.Z + offsetZ);
}
}
public interface IImplementor
{
void DrawLine(Point3D point1, Point3D point2);
void DrawPoint(Point3D point);
}
public class OnePointPerspective : IImplementor
{
object internalDrawingContext;
/// <summary>
/// Crea una instancia de un implementor para
/// perspectivas con un punto de fuga
/// </summary>
/// <param name="drawingContext" />
/// Contexto de dibujo. Aquí es object como solo a
/// modo de referencia. Podria ser del tipo
/// System.Windows.Media.DrawingContext usado en WPF
///
public OnePointPerspective(object drawingContext)
{
internalDrawingContext = drawingContext;
}
public void DrawLine(System.Windows.Media.Media3D.Point3D point1, System.Windows.Media.Media3D.Point3D point2)
{
// Realizamos las transformaciones que puedan ser
// necesarias en los puntos para la perspectiva
// con un punto de fuga
Point3D convertedPoint1 = ConvertPoint(point1);
Point3D convertedPoint2 = ConvertPoint(point2);
// Dibujamos la línea.
// Sería algo así como la siguiente linea
//internalDrawingContext.DrawLine(convertedPoint1, convertedPoint2);
// Para nuestro ejemplo sacaremos a la consola las coordenadas del punto
Console.WriteLine(string.Format("Linea desde(X,Y,Z): {0},{1},{2} hasta: {3},{4},{5}",
point1.X, point1.Y, point1.Z, point2.X, point2.Y, point2.Z));
}
public void DrawPoint(System.Windows.Media.Media3D.Point3D point)
{
// Realizamos las transformaciones que puedan ser
// necesarias en el punto para la perspectiva
// con un punto de fuga
Point3D convertedPoint = ConvertPoint(point);
// Dibujamos el punto.
// Sería algo así como la siguiente linea
//internalDrawingContext.DrawPoint(convertedPoint);
// Para nuestro ejemplo sacaremos a la consola las coordenadas del punto
Console.WriteLine(string.Format("Punto en (X,Y,Z): {0},{1},{2}",
point.X, point.Y, point.Z));
}
private Point3D ConvertPoint(Point3D point)
{
Point3D convertedPoint = new Point3D();
// Aqui realizamos las trasnformaciones necesarias
// sobre convertedPoint
// para aplicar la perspectiva de un punto de fuga
// al punto.
convertedPoint = point;
return convertedPoint;
}
}
/// <summary>
/// Pirámide con base rectangular
/// </summary>
public class Pyramid : Figure
{
private Rectangle3D internalBase;
private Point3D internalVertex;
/// <summary>
/// Crea una instancia de una piramide con base rectangular
/// </summary>
/// <param name="perspectiveImplementor" />
/// Perpectiva
///
/// <param name="base" />Base de la pirámide
/// <param name="vertex" />Vértice
public Pyramid(IImplementor perspectiveImplementor, Rectangle3D @base, Point3D vertex)
: base(perspectiveImplementor)
{
internalBase = @base;
internalVertex = vertex;
}
public override void Draw()
{
Console.WriteLine("Inicio de pirámide");
// Dibujamos la base
DrawRectangle(internalBase);
// Dibujamos las caras triangulares de la pirámide.
// Cada cara es un triangulo formado por dos puntos consecutivos de
// la base y el vértice
DrawTriangle(internalBase.Point1, internalBase.Point2, internalVertex);
DrawTriangle(internalBase.Point2, internalBase.Point3, internalVertex);
DrawTriangle(internalBase.Point3, internalBase.Point4, internalVertex);
DrawTriangle(internalBase.Point4, internalBase.Point1, internalVertex);
Console.WriteLine("Fin de pirámide");
}
public override void Move(double offsetX, double offsetY, double offsetZ)
{
// Aquí iría la lógica de borrado del la figura
// de la posición actual. La omitimos por simplicidad
// Calculamos las nuevas coordenadas de la pirámide
internalBase.Move(offsetX, offsetY, offsetZ);
internalVertex.Offset(offsetX, offsetY, offsetZ);
}
}
class Program
{
static void Main(string[] args)
{
object drawingContext = new object();
// ******************************************
// Dibujamos tres piramides con una perspectiva
// de un punto de fuga
// ******************************************
var perspective1Point = new OnePointPerspective(drawingContext);
// Pirámide 1
// Creamos la base de una piramide
var @base = new Rectangle3D
{
Point1 = new Point3D { X = 100, Y = 100, Z = 0 },
Point2 = new Point3D { X = 100, Y = 100, Z = 100 },
Point3 = new Point3D { X = 0, Y = 100, Z = 100 },
Point4 = new Point3D { X = 100, Y = 0, Z = 0 },
};
var vertex = new Point3D { X = 100, Y = 200, Z = 50 };
var pyramid = new Pyramid(perspective1Point, @base, vertex);
// Pirámide 2
// Creamos la base de una piramide
var @base2 = new Rectangle3D
{
Point1 = new Point3D { X = 100, Y = 100, Z = 0 },
Point2 = new Point3D { X = 100, Y = 100, Z = 100 },
Point3 = new Point3D { X = 0, Y = 100, Z = 100 },
Point4 = new Point3D { X = 100, Y = 0, Z = 0 },
};
var vertex2 = new Point3D { X = 100, Y = 200, Z = 50 };
var pyramid2 = new Pyramid(perspective1Point, @base2, vertex);
// Pirámide 1
// Creamos la base de una piramide
var @base3 = new Rectangle3D
{
Point1 = new Point3D { X = 100, Y = 100, Z = 0 },
Point2 = new Point3D { X = 100, Y = 100, Z = 100 },
Point3 = new Point3D { X = 0, Y = 100, Z = 100 },
Point4 = new Point3D { X = 100, Y = 0, Z = 0 },
};
var vertex3 = new Point3D { X = 100, Y = 200, Z = 50 };
var pyramid3 = new Pyramid(perspective1Point, @base3, vertex);
// Agrupamos las pirámides 1 y 2
var group = new GlyphGroup();
group.Add(pyramid);
group.Add(pyramid2);
// Dibujamos el grupo y la pirámide 3
group.Draw();
pyramid3.Draw();
// Movemos el grupo
group.Move(-5000, 1000, 8000);
group.Draw();
// Movemos la pirámide 3
pyramid3.Move(-300, 500, 679);
pyramid3.Draw();
Console.ReadLine();
}
}
</pre>
<br />
<div>
Está es la salida en la consola del código:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigiLmY9_sT7Khq0vGqwXVncsi-52nDyCiKe0bbpGnNGS8l2_UBJzUhvM0iU0Whb1BaVLY6sjtS4koYBVVcmEEP05vmOR39xyy2oZksME5ZKnDY04PJTxixGkza6hmB8n3bjTciLFWZuhSD/s1600/CompositeResult.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigiLmY9_sT7Khq0vGqwXVncsi-52nDyCiKe0bbpGnNGS8l2_UBJzUhvM0iU0Whb1BaVLY6sjtS4koYBVVcmEEP05vmOR39xyy2oZksME5ZKnDY04PJTxixGkza6hmB8n3bjTciLFWZuhSD/s1600/CompositeResult.png" height="400" width="320" /></a></div>
<br />
<br />
Aunque sólo sea un ejemplo muy simple, hay varias ventajas interesantes en las que debemos fijarnos.<br />
<br />
<ul>
<li>Mediante la clase GlyphGroup hemos conseguido poder estructurar una jerarquía de tipo arbol de objetos de tipo Glyph. Podemos agregar objetos básicos Figure (en nuestro ejemplo dos objetos Pyramid) a un GlyphGroup y el cliente trata al grupo como si fuera un objeto básico más. Para dibujarlo el cliente sólo debe llamar al método Draw del grupo o al método Move si quiere moverlo. En el ejemplo no lo hemos hecho, pero también podemos agregar grupos como hijos de otro grupo ya que GlypGroup es también de tipo Glyph. Esto nos da infinitas opciones de agrupación.</li>
<li>El cliente no necesita distinguir si está tratando con un grupo o con una figura. Esto hace que el código del cliente sea mucho más simple que una alternativa basada en interminables sentencias if o switch. Como consecuencia si agregamos nuevos tipos de grupos o nuevas figuras el cliente no necesita modificarse.</li>
</ul>
<div>
Claro que casi nada en la vida es blanco o negro, y el patrón Composite igualmente tiene ciertas limitaciones que debemos conocer:</div>
<div>
<ul>
<li>Ya hemos dicho que con Composite tenemos prácticamente infinitas opciones de composición de objetos. Esto sin embargo puede ser una desventaja en contextos donde existen limitaciones en los tipos o en la forma en que los objetos pueden componerse. Supongamos que por especificación no pudiésemos agrupar pirámides con cubos.</li>
<li>Otro inconveniente del patrón son los métodos de gestión de hijos (de esto hablaremos un poco más adelante). En principio no en una herencia una clase no debería implementar método o propiedades que no sean útiles para todos sus hijos. Sin embargo la clase Glyph implementa métodos como Add para agregar un hijo que es completamente innecesario en las hojas del arbol ya que las hojas no tienen hijos.</li>
</ul>
<div>
<br /></div>
</div>
<h4>
Consideraciones al implementar el patrón Composite</h4>
<div>
Existen una serie de consideraciones que debemos tener en cuenta a la hora de implementar un patrón Composite. Vamos a ver algunas de ellas:</div>
<div>
<br /></div>
<div>
<b>Referencias explícitas al padre</b></div>
<div>
En la implementación que hemos visto podemos navegar desde los nodos padre a los nodos hijos, pero no al contrario. Los nodos no tienen una referencia al padre. </div>
<div>
Tener una referencia al padre simplifica enormemente tanto poder moverse hacia arriba en el árbol de nodos como la operación de eliminar nodos. Además nos ayuda a soportar en nuestro árbol de nodos un patrón Cadena de responsabilidad (Chain of Resposability que veremos en un post en la serie de patrones de comportamiento).</div>
<div>
<br /></div>
<div>
La clase más habitual donde mantener una referencia al padre es en la clase Component (Glyph), ya que tanto los nodos Composite como las hojas heredarán la referencia y las operaciones para manejarla.</div>
<div>
<br /></div>
<div>
Es fundamental que se manejamos una referencia al padre nos ocupemos de que siempre se cumpla el invariante de que todos los hijos de un nodo (GlypGroup) tienen como padre al nodo (GlypGroup) que los tiene a ellos como hijos. La mejor forma de conseguir esto es que sea el propio padre quien asegure este invariante en las operaciones para gestionar hijos. Los métodos Add y Remove de GlypGroup los podríamos implementar de la siguiente forma:</div>
<div>
<br /></div>
<pre class="brush:csharp;">public override void Add(Glyph child)
{
// Aseguramos el invariante
// De esta formar todos los hijos del
// GlypGroup tendrán al GlypGroup como padre.
child.Parent = this;
internalChildren.Add(child);
}
public override void Remove(Glyph child)
{
// Eliminamos el padre del glifo a eliminar
child.Parent = null;
internalChildren.Remove(child);
}
</pre>
<div>
<br />
<b>Maximizar la interfaz de Component (Glyph)</b><br />
Ya sabemos que uno de los objetivos de patrón Composite es conseguir que el cliente maneje una estructura de tipo árbol sin que tenga de distinguir entre nodo "rama" y nodos hoja. La mejor forma de conseguir esto ya que el cliente trata con la interfaz de la clase Glyph es tratar de que la interfaz de la clase Glyph defina el mayor número posible de operaciones comunes de los nodos hoja y los nodos "rama".</div>
<div>
<br /></div>
<div>
Como ya he mencionado anteriormente esto puede suponer un problema en el sentido en que definimos en la clase Glyph métodos como Add o Remove que en principio no son relevantes para las clases hoja (Pyramide). Desde un punto de vista estrictamente conceptual este problema desaparece si consideramos a los objetos hojas como un tipo de Component (GlyphGroup) que no devuelve hijos.<br />
<br />
<u>Balance entre transparencia y seguridad</u><br />
<u><br /></u>
<br />
<blockquote class="tr_bq">
Al implementar el patrón Composite deberemos hacer un balance entre una implementación transparente y una implementación segura.</blockquote>
<br />
A pesar de lo que acabamos de ver cabe preguntarse si es más correcto tener las operaciones de gestión de hijos en la clase Composite (Glyph) o en la clase Component (GlyphGroup). Realmente es aceptable tanto una opción como la otra. Es una decisión que tendremos que tomar nosotros para cada contexto donde apliquemos el patrón Composite y en el fondo no se trata más que hacer un balance entre transparencia y seguridad teniendo en cuenta lo siguiente:<br />
<br />
<ul>
<li>Definir las operaciones de gestión de hijos (Add, Remove, GetChildre, etc) en la clase Composite (Glyph) nos aporta transparencia. Nos permite desde el cliente podemos tratar a todos los nodos de la misma forma como vemos en el ejemplo de nuestra implementación. A cambio perdemos seguridad, porque permitimos que el cliente pueda intentar hacer cosas sin sentido como intentar agregar un hijo a una hoja (pirámide).</li>
<li>Definir las operaciones de gestión de hijos en la clase Component (GlypGroup) nos aporta mayor seguridad. Cualquier intento por parte del cliente de hacer algo inapropiado como agregar un hijo a una hoja dará una excepción en tiempo de compilación.. Por otra parte, con esta aproximación perderemos transparencia ya que Composite (Glyph) y Component (GlyphGroup) tienen interfaces diferentes y obligamos al cliente a tratarlas de forma distinta.</li>
</ul>
<br />
En nuestra implementación nos hemos decantado por la transparencia frente a la seguridad. Para nuestro caso si el cliente intenta agregar un hijo a una pirámide no se producirá ejecutará ninguna operación y para nuestro diseñador 3D no será nada crítico.<br />
<br />
Con la información vista hasta ahora si necesitas creo que no debería haber ningún problema para implementar un patrón Composite donde prime la seguridad frente a la transparencia.<br />
<br />
<h4>
Usos conocidos</h4>
El patrón Composite es ampliamente usado y podemos encontrar ejemplos de su implementación en prácticamente cualquier framework. En .NET podemos verlo en clases como las siguientes:<br />
<br />
<ul>
<li>System.Windows.Forms.Control y sus clases derivadas. </li>
<li>System.Xml.XmlNode y derivadas.</li>
<li>System.Web.UI.Control</li>
</ul>
<br />
<br />
Hasta aquí el patrón Composite. Ojalá la espera haya valido la pena. En el próximo post hablaremos sobre otro patrón estructural Decorator.</div>
</div>
<div>
<br /></div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-2728255754328606012.post-11713686914770802772014-03-04T15:48:00.000+01:002014-03-17T15:33:54.745+01:00Patrones estructurales: Bridge<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20estructurales" style="font-weight: normal;"><span style="font-size: small;">Patrones estructurales</span></a></h2>
<div>
<h2>
Bridge</h2>
<div>
<i>"Desacopla una abstracción de su implementación de forma que ambas pueden variar de manera independiente".</i><br />
<i><br /></i>
Como ya hemos hecho en otras ocasiones vamos a estudiar detenidamente la definición de este patrón, tratar de entender qué dice y después veremos cómo y dónde aplicarlo.<br />
<br />
Cuando tenemos una abstracción que puede tener diferentes implementaciones normalmente usamos herencia para resolver la situación. Es decir, una clase abstracta define la interfaz de la abstracción y las clases hijas concretas implementan cada una hace su propia implementación.<br />
<br />
<a name='more'></a><br />
<br />
Bien, y ¿qué problema hay en esto? La verdad, es que es lo que hemos visto y hemos hecho tantas y tantas vences ¿no?<br />
<br />
No sé si he dicho esto antes o no, si no lo he dicho disculpadme, debí haberlo dicho en el primer post en mayúsculas:<br />
<br />
<blockquote class="tr_bq">
El problema de la herencia es que crea diseños en general bastante rígidos y difícil de modificar. Siempre que sea posible debemos decantarnos por diseños basados en la composición de objetos frente a los basados en la herencia por que serán diseños más flexibles</blockquote>
<br />
Pues eso, justo es ese el problema de que las diferentes implementaciones de una abstracción hereden de la abstracción. La rigidez.<br />
<br />
La herencia crea un enlace permanente entre la abstracción y las implementaciones de esa abstracción que hace que sea difícil modificar, extender y reutilizar la abstracción y las implementaciones de manera independiente una de otras.<br />
<br />
A mí la siguiente cuestión que me viene a la cabeza de inmediato es ¿y por qué iba a querer yo reutilizar o modificar o extender la abstracción de forma independiente a su implementación o viceversa? al fin y al cabo si hago un cambio en una clase padre modifico las clases hijas que heredan de ella y eso es todo ¿no?<br />
<br />
En realidad el problema puede ir un poco más allá. Vamos a verlo con un ejemplo:<br />
<br />
<h4>
El problema</h4>
Supongamos que vamos a desarrollar un editor gráfico 3D lo llamaremos 3DWorlds. Por simplicidad nuestro editor gráfico es capaz de representar cubos y conos en 3D. Hemos modelado una clase abstracta Figure con un método abstracto Draw y de esta clase heredan Cube y Pyramid.<br />
<br />
Este sería el diagrama UML por el momento:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV7lIjo4aa0LQPFkfFbyzzib3i4-fIW49PmueC3d53L7b02Q0H03_W6oIkONMnVDmhLDvGamwkcbb5lYNi7UAeLHXhSFoaWi7di3HRdQChFJiLVwEjvfJxwqyMmC1sP4LetwpmpruTRcMB/s1600/BridgeDiagram1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgV7lIjo4aa0LQPFkfFbyzzib3i4-fIW49PmueC3d53L7b02Q0H03_W6oIkONMnVDmhLDvGamwkcbb5lYNi7UAeLHXhSFoaWi7di3HRdQChFJiLVwEjvfJxwqyMmC1sP4LetwpmpruTRcMB/s1600/BridgeDiagram1.png" height="302" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
No voy a mostrar el código de este diagrama porque por el momento es muy simple. </div>
<div class="separator" style="clear: both; text-align: left;">
Hasta aquí no vemos un gran problema, aunque las implementaciones (Pyramid y Cube) y la abstracción (Figure) están fuertemente acopladas, este diagrama parece bastante manejable si hay cambios.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Como ya he dicho el problema puede ir un poco más allá. Nuestro editor 3DWorlds es un software serio y soporta diferentes perspectivas a la hora de proyectar los polígonos (para quien como yo no saben muy bien cómo funciona esto de las perspectivas gráficas <a href="http://en.wikipedia.org/wiki/Perspective_(graphical)" rel="nofollow" target="_blank">mirar este artículo de la Wikipedia</a>). Las perspectivas que vamos a soportar son las que tienen en cuenta uno, dos y tres puntos de fuga.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Así que nuestro nuevo diagrama UML con soporte para tres perspectivas diferentes es el siguiente:</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-O5am73RPc30pEBn0jP6_mXrp45N5sH7g9EC6MBo1GIocZJ0LiYAnK68x6BdTDnwh82mchlUrHaRJUZ6m-YiN4B31pjbMi0bBD2FMBKj4c4osxGbQC0PIFAdEsuI4pM2_qwLbHwSfGGFS/s1600/BridgeDiagram2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-O5am73RPc30pEBn0jP6_mXrp45N5sH7g9EC6MBo1GIocZJ0LiYAnK68x6BdTDnwh82mchlUrHaRJUZ6m-YiN4B31pjbMi0bBD2FMBKj4c4osxGbQC0PIFAdEsuI4pM2_qwLbHwSfGGFS/s1600/BridgeDiagram2.png" height="137" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Opps! Esto se complica por momentos. Al añadir al modelo implementaciones para tres tipos de perspectivas distintas se han disparado el número de clases que necesitamos. Pero además por cada perspectiva que añadimos a 3DWorlds debemos agregar una implementación para Cube y otra para Pyramid. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>¡¡¡ Y esto para sólo dos figuras y tres perspectivas !!! </b>Pensemos que pasará cuando agreguemos todas las figuras geométricas y perspectivas que 3DWorlds debería tener. La jerarquía de clases combinando figuras y perspectivas será inmanejable.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
La idea importante de esto es que la herencia impide que la abstracción y la implementación evolucionen por separado y rápidamente provocan esta explosión de clases.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<h4 style="clear: both; text-align: left;">
La solución</h4>
<div class="separator" style="clear: both; text-align: left;">
El patrón Bridge propone una solución a este problema que como hemos dicho se base en desacoplar la abstracción (Figure) de la implementación (OnePointPerspective, TwoPointPerspective, ThreePointPerspective, etc). </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Antes de continuar veamos cómo es el diagrama UML del patrón Bridge:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqtObAIfPyXtUZ72xqXgzlqfYZ1a8fthuOsK89TliHvRh9XkNQivyepGYBZQpUdgC_QWlJP_ew5cqQLrb9pjF77GjIMdaxDTBEkfckc8kTgZQLJPvsP87eh0B3HEyk42Lxh3AqF5kYEkFF/s1600/BridgeUml.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqtObAIfPyXtUZ72xqXgzlqfYZ1a8fthuOsK89TliHvRh9XkNQivyepGYBZQpUdgC_QWlJP_ew5cqQLrb9pjF77GjIMdaxDTBEkfckc8kTgZQLJPvsP87eh0B3HEyk42Lxh3AqF5kYEkFF/s1600/BridgeUml.png" height="161" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Estos son los participantes del patrón:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li><b>Abstraction (Figure).</b> Define la interfaz de la abstracción y mantiene una referencia al objeto Implementor.</li>
<li><b>RedefinedAbstraction (Cube, Pyramid).</b> Extienden la interfaz definida por la abstracción.</li>
<li><b>Implementor. </b>Define la interfaz de las implementaciones concretas. Esta interfaz no tiene por qué coincidir con la de la abstracción. Lo típico es que la interfaz de <b>Implementor </b>defina operaciones primitivas (DrawLine, DrawPoint, DrawEllipse, etc) y la abstracción defina operaciones de más alto nivel (DrawRectangle, DrawTriangle, etc.) basadas en las operaciones primitivas del <b>Implementor.</b></li>
<li><b>ConcreteImplementor (OnePointPerspective, etc.) </b>Hace una implementación concreta de la interfaz de Implementor</li>
</ul>
<div>
Vemos cómo sería un extracto de la implementación para nuestro ejemplo:</div>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<pre class="brush:csharp;"> /// <summary>
/// Interfaz para las clases que contienen la implementacion
/// concreta de cada perspectiva.
/// Define metodos de bajo nivel para dibujar.
/// </summary>
public interface IImplementor
{
void DrawLine(Point3D point1, Point3D point2);
void DrawPoint(Point3D point);
}
public class OnePointPerspective : IImplementor
{
object internalDrawingContext;
/// <summary>
/// Crea una instancia de un implementor para
/// perspectivas con un punto de fuga
/// </summary>
/// <param name="drawingContext" />
/// Contexto de dibujo. Aquí es object como solo a
/// modo de referencia. Podria ser del tipo
/// System.Windows.Media.DrawingContext usado en WPF
///
public OnePointPerspective(object drawingContext)
{
internalDrawingContext = drawingContext;
}
public void DrawLine(System.Windows.Media.Media3D.Point3D point1, System.Windows.Media.Media3D.Point3D point2)
{
// Realizamos las transformaciones que puedan ser
// necesarias en los puntos para la perspectiva
// con un punto de fuga
Point3D convertedPoint1 = ConvertPoint(point1);
Point3D convertedPoint2 = ConvertPoint(point2);
// Dibujamos la línea.
// Sería algo así como la siguiente linea
//internalDrawingContext.DrawLine(convertedPoint1, convertedPoint2);
}
public void DrawPoint(System.Windows.Media.Media3D.Point3D point)
{
// Realizamos las transformaciones que puedan ser
// necesarias en el punto para la perspectiva
// con un punto de fuga
Point3D convertedPoint = ConvertPoint(point);
// Dibujamos el punto.
// Sería algo así como la siguiente linea
//internalDrawingContext.DrawPoint(convertedPoint);
}
private Point3D ConvertPoint(Point3D point)
{
Point3D convertedPoint = new Point3D();
// Aqui realizamos las trasnformaciones necesarias
// sobre convertedPoint
// para aplicar la perspectiva de un punto de fuga
// al punto.
return convertedPoint;
}
}
public class TwoPointPerspective : IImplementor
{
object internalDrawingContext;
/// <summary>
/// Crea una instancia de un implementor para
/// perspectivas con dos puntos de fuga
/// </summary>
/// <param name="drawingContext" />
/// Contexto de dibujo. Aquí es object como solo a
/// modo de referencia. Podria ser del tipo
/// System.Windows.Media.DrawingContext usado en WPF
///
public TwoPointPerspective(object drawingContext)
{
internalDrawingContext = drawingContext;
}
public void DrawLine(System.Windows.Media.Media3D.Point3D point1, System.Windows.Media.Media3D.Point3D point2)
{
// Realizamos las transformaciones que puedan ser
// necesarias en los puntos para la perspectiva
// con dos punto de fuga
Point3D convertedPoint1 = ConvertPoint(point1);
Point3D convertedPoint2 = ConvertPoint(point2);
// Dibujamos la línea.
// Sería algo así como la siguiente linea
//internalDrawingContext.DrawLine(convertedPoint1, convertedPoint2);
}
public void DrawPoint(System.Windows.Media.Media3D.Point3D point)
{
// Realizamos las transformaciones que puedan ser
// necesarias en el punto para la perspectiva
// con dos punto de fuga
Point3D convertedPoint = ConvertPoint(point);
// Dibujamos el punto.
// Sería algo así como la siguiente linea
//internalDrawingContext.DrawPoint(convertedPoint);
}
private Point3D ConvertPoint(Point3D point)
{
Point3D convertedPoint = new Point3D();
// Aqui realizamos las trasnformaciones necesarias
// sobre convertedPoint
// para aplicar la perspectiva de dos puntos de fuga
// al punto.
return convertedPoint;
}
}
/// <summary>
/// Estructura que representa un triangulo con
/// puntos 3D
/// </summary>
public struct Rectangle3D
{
public Point3D Point1 { get; set; }
public Point3D Point2 { get; set; }
public Point3D Point3 { get; set; }
public Point3D Point4 { get; set; }
}
/// <summary>
/// Clase abstracta define la interfaz de figuras que podemos dibujar
/// Tambien define métodos de dibujo de alto nivel
/// </summary>
public abstract class Figure
{
private IImplementor internalPerspectiveImplementor;
protected Figure(IImplementor perspectiveImplementor)
{
internalPerspectiveImplementor = perspectiveImplementor;
}
/// <summary>
/// Metodo de alto nivel para dibujar un rectangulo
/// </summary>
/// <param name="rectangle" />
protected void DrawRectangle(Rectangle3D rectangle)
{
internalPerspectiveImplementor.DrawLine(rectangle.Point1, rectangle.Point2);
internalPerspectiveImplementor.DrawLine(rectangle.Point2, rectangle.Point3);
internalPerspectiveImplementor.DrawLine(rectangle.Point3, rectangle.Point4);
internalPerspectiveImplementor.DrawLine(rectangle.Point4, rectangle.Point1);
}
/// <summary>
/// Metodo de alto nivel para dibujar un triangulo a partir de 3 puntos
/// </summary>
protected void DrawTriangle(Point3D point1, Point3D point2, Point3D point3)
{
internalPerspectiveImplementor.DrawLine(point1, point2);
internalPerspectiveImplementor.DrawLine(point2, point3);
internalPerspectiveImplementor.DrawLine(point3, point1);
}
/// <summary>
/// Metodo abstracto que implmentaran las clases hijas.
/// </summary>
public abstract void Draw();
}
/// <summary>
/// Pirámide con base rectangular
/// </summary>
public class Pyramid : Figure
{
private Rectangle3D internalBase;
private Point3D internalVertex;
/// <summary>
/// Crea una instancia de una piramide con base rectangular
/// </summary>
/// <param name="perspectiveImplementor" />
/// Perpectiva
///
/// <param name="base" />Base de la pirámide
/// <param name="vertex" />Vértice
public Pyramid(IImplementor perspectiveImplementor, Rectangle3D @base, Point3D vertex)
: base(perspectiveImplementor)
{
internalBase = @base;
internalVertex = vertex;
}
public override void Draw()
{
// Dibujamos la base
DrawRectangle(internalBase);
// Dibujamos las caras triangulares de la pirámide.
// Cada cara es un triangulo formado por dos puntos consecutivos de
// la base y el vértice
DrawTriangle(internalBase.Point1, internalBase.Point2, internalVertex);
DrawTriangle(internalBase.Point2, internalBase.Point3, internalVertex);
DrawTriangle(internalBase.Point3, internalBase.Point4, internalVertex);
DrawTriangle(internalBase.Point4, internalBase.Point1, internalVertex);
}
}
/// <summary>
/// Cubo
/// </summary>
public class Cube : Figure
{
private Rectangle3D internalFace1;
private Rectangle3D internalFace2;
private Rectangle3D internalFace3;
private Rectangle3D internalFace4;
private Rectangle3D internalFace5;
private Rectangle3D internalFace6;
/// <summary>
/// Crea una instancia de un cubo a partir de sus 6 caras
/// </summary>
/// <param name="perpectiveImplementor" />
public Cube(IImplementor perpectiveImplementor,
Rectangle3D face1,
Rectangle3D face2,
Rectangle3D face3,
Rectangle3D face4,
Rectangle3D face5,
Rectangle3D face6)
: base(perpectiveImplementor)
{
internalFace1 = face1;
internalFace2 = face2;
internalFace3 = face3;
internalFace4 = face4;
internalFace5 = face5;
internalFace6 = face6;
}
public override void Draw()
{
// Dibujamos las seis caras del cubo
DrawRectangle(internalFace1);
DrawRectangle(internalFace2);
DrawRectangle(internalFace3);
DrawRectangle(internalFace4);
DrawRectangle(internalFace5);
DrawRectangle(internalFace6);
}
}
</pre>
<br /></div>
</div>
Lo primero es lo primero. Yo no tengo mucha idea de diseño 3D, así que tanto la implementación de perspectivas como todo lo relacionado con el dibujo 3D puede que esté basado en conceptos no realistas. De cualquier forma, el fin del ejemplo no es mostrar cómo crear un editor 3D propiamente dicho sino que sirve solamente como vehículo para explicar el patrón Bridge.<br />
<br />
Lo que podemos comprobar es que ahora la implementación de cómo se pintan las figuras (Implementor concretos) está completamente desacoplada de las propias figuras. Esto nos permite por ejemplo agregar nuevas figuras (Cilindro, Cono, Octaedro, etc.) sin tener que agregar nuevos Implementors. O por otro lado agregar nuevas perspectivas sin tener que modificar nuestras figuras. Es decir, como dice la propia definición del patrón Bridge tanto la definición de la interfaz como la implementación pueden evolucionar de forma independiente.<br />
<br />
Otra cosa interesante del Bridge es que oculta al cliente los detalles de cómo se dibujan las figuras. El cliente sólo tiene que invocar el método Draw de la figura.<br />
<br />
Es usual encontrar el patrón Bridge en framework de UI. Típicamente los framework multi-plataforma usan alguna variante del patrón Bridge para la UI. Por ejemplo crean la abstracción "Window" y una implementación distinta para cada plataforma soportada (winPhoneWindow, androidWindow, etc) que realizan la implementación concreta en cada plataforma para dibujar una ventana.<br />
<br />
<h4>
Consideraciones antes de implementar el patrón Bridge</h4>
Veamos algunas cosas a tener en cuenta a la hora de implementar el patrón Bridge.<br />
<br />
<ul>
<li>Evidentemente aplicar el patrón Bridge no tendría ningún sentido si no hubiera varias implementaciones. Es decir si nuestra aplicación 3DWorlds solo puede dibujar con una perspectiva no hay razón para implementar este patrón.</li>
<li>Determinar cómo se crean las implementaciones. Hay que decidir la forma en que se van a instanciar las perspectivas. Es posible que queramos asegurarnos que las diferentes figuras de un dibujo se pinten todas con la misma perspectiva. En ese caso quizá queramos instanciar la perspectiva como un Singleton.</li>
<li>Es posible reutilizar las implementaciones. En muchos casos las implementaciones pueden ser reutilizadas. En nuestro caso la implementación es el responsable último de realizar el dibujo, podríamos reutilizar la implementación para dibujar un fondo de la imagen a crear un dibujo nuevo.</li>
</ul>
<div>
Pues hasta aquí el patrón Bridge. El próximo post será sobre un patrón ampliamente utilizado. El patrón <a href="http://designcodetips.blogspot.com.es/2014/03/patrones-estructurales-composite.html" target="">Composite</a></div>
Unknownnoreply@blogger.com11tag:blogger.com,1999:blog-2728255754328606012.post-9331685678707817762014-02-20T09:30:00.001+01:002014-02-20T09:34:47.765+01:00Patrones estructurales: Adapter<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20estructurales" style="font-weight: normal;"><span style="font-size: small;">Patrones estructurales</span></a></h2>
<div>
<h2>
Adapter</h2>
<div>
<i>"Cambia la interfaz de una clase en otra interfaz que espera el cliente. El Adapter permite trabajar juntas a clases que de otra forma no podrían porque tienen interfaces incompatibles".</i><br />
<br />
La definición del patrón Adapter dice que cambia la interfaz de una clase por otra interfaz para que podamos utilizarla desde un cliente. Hay que entender que la clase a la que queremos cambiar la interfaz tiene la funcionalidad que necesitamos pero no cumple la interfaz que requiere nuestra clase cliente.<br />
<br />
<a name='more'></a><br />
<br />
A primera vista, cambiar la interfaz de una clase no parece que sea un gran problema ¿no? Si necesitamos que la clase cumpla una interfaz determinada; cambiamos la clase y todo solucionado. El problema viene cuando la clase que no cumple la interfaz que necesitamos no podemos cambiarla porque no es nuestra y no tenemos su código fuente, o simplemente cambiar su interfaz no es viable.<br />
<br />
<h3>
El problema</h3>
Veamos un ejemplo de cuándo puede ser necesario utilizar un patrón Adapter.<br />
<br />
Supongamos que hemos desarrollado una aplicación ERP (Enterprise Resource Planning). Este ERP nos permite entre otras cosas gestionar acciones comerciales dirigidas a clientes. Una parte importante de nuestro ERP es el módulo de informes. Este módulo genera informes muy potentes que permiten tomar mejores decisiones en diferentes ámbitos de la gestión de la empresa. Uno de estos informes se llama "Clientes dormidos" son clientes que llevan más de tres meses sin realizar una compra y es necesario llevar alguna acción comercial para animarles a volver a comprar.<br />
Veamos cómo sería de forma simplificada el código que genera este informe:<br />
<br />
<pre class="brush:csharp;">public class Client
{
public DateTime? LastPurchaseDate { get; set; }
}
public class ERP
{
/// <summary>
/// Clientes almacenados en el Erp.
/// Simula los clientes de la base de datos por ejemplo.
/// </summary>
private List<Client> storedClients = null;
public IEnumerable<Client> GetClientsLastPurchaseBeforeReport(int days)
{
return storedClients.Where(c => c.LastPurchaseDate != null && c.LastPurchaseDate < DateTime.Now.AddDays(days * -1));
}
}
public class ReportsManager
{
ERP internalErp;
public ReportsManager(ERP erp)
{
internalErp = erp;
}
public IEnumerable<Client>GetSleepingClients()
{
// Pedimos al Erp los clientes que no han comprado en los
// últimos 90 dias.
return internalErp.GetClientsLastPurchaseBeforeReport(90);
}
}
</pre>
<br />
<br />
Una vez vista la situación hasta aquí vayamos un paso más allá.<br />
<br />
A nuestra empresa le está costando mucho vender el ERP. La mayoría de los clientes con los que contactamos ya tienen un ERP y convencerlos para cambiar a uno distinto es bastante complicado. Aun así, a los clientes en general les gusta mucho el módulo de informes ya que los informes de los ERP de la competencia son mucho más limitados.<br />
<br />
Ante esta situación nuestra empresa decide, para facilitar introducirse en el mercado, comercializar el módulo de informes como una aplicación independiente que debe permitir conectarse con los ERP de la competencia y generar nuestros "fabulosos" informes sobre los datos de ERP de terceros. ¡Acaba de nacer la aplicación EnterpriseMagicReports! :o)<br />
<br />
Lo primero que haremos será eliminar las referencias que hay en el módulo de informes a nuestro ERP refactorizando el código anterior. Definiremos una interfaz de proveedor de datos de clientes (IClientsDataProvider) para conseguir este primer paso.<br />
<br />
<pre class="brush:csharp;">public class Client
{
public DateTime? LastPurchaseDate { get; set; }
}
public interface IClientsDataProvider
{
IEnumerable<Client> GetClientsLastPurchaseBeforeReport(int days);
}
public class ERP : IClientsDataProvider
{
/// <summary>
/// Clientes almacenados en el Erp.
/// Simula los clientes de la base de datos por ejemplo.
/// </summary>
private List<Client> storedClients = null;
public IEnumerable<Client> GetClientsLastPurchaseBeforeReport(int days)
{
return storedClients.Where(c => c.LastPurchaseDate != null && c.LastPurchaseDate < DateTime.Now.AddDays(days * -1));
}
}
public class ReportsManager
{
IClientsDataProvider internalClientDataProcider;
public ReportsManager(IClientsDataProvider clientDataProvider)
{
internalClientDataProcider = clientDataProvider;
}
public IEnumerable<Client> GetSleepingClients()
{
// Pedimos al Erp los clientes que no han comprado en los
// últimos 90 dias.
return internalClientDataProcider.GetClientsLastPurchaseBeforeReport(90);
}
}
</pre>
<br />
Bien, ahora nuestro ERP sigue funcionando y generando informes pero el módulo de informes ya no tiene dependencias con nuestro ERP.<br />
<br />
Nuestro problema ahora es que cada uno de los ERPs de la competencia tiene su propia interfaz para poder obtener datos de los clientes. Y excepto el nuestro, ninguno cumple con la interfaz IClientsDataProvider que espera nuestra nueva aplicación EnterpriseMagicReports.<br />
<br />
Es decir, estamos en una situación tal que así:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYPJbVqBuYYVGcxU2zlvXncEZtSf6tQgOvuR9Z4XPAzQFOQPj3uPtjbzh8kqnyEwLc8oXBz8_XZ_4-6v45Gr_FqLwNIyD991AzLf0VGqW81yGK3f0VVawkGngPYaUpSWpiPY9drLSIAGSc/s1600/ERPAdapter.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYPJbVqBuYYVGcxU2zlvXncEZtSf6tQgOvuR9Z4XPAzQFOQPj3uPtjbzh8kqnyEwLc8oXBz8_XZ_4-6v45Gr_FqLwNIyD991AzLf0VGqW81yGK3f0VVawkGngPYaUpSWpiPY9drLSIAGSc/s1600/ERPAdapter.png" height="198" width="320" /></a></div>
<br />
El único ERP que cumple la interfaz que espera es "Nuestro ERP". Los ERP de la competencia cada uno expone su propia interfaz. Me refiero a interfaz en un sentido amplio, podría ser una API o incluso la propia base de datos del ERP de la competencia si no expone una API de forma explícita.</div>
<div>
<br />
Si volvemos a repasar la definición que vimos al principio del patrón Adapter, <i>"Cambia la interfaz de una clase en otra interfaz que espera el cliente. El Adapter permite trabajar juntas a clases que de otra forma no podrían porque tienen interfaces incompatibles"</i>, vemos que es esa justamente la situación en la que nos encontramos. Así que vamos a aplicar el patrón Adapter para llegar a la siguiente situación que resuelve nuestro problema:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidJNDMn6BToHSLeihM2-lHaR0yrbKgG1Ujiy30Uii8R3Q11VmaJjnwBV4emqH8Wvc0U8on45JnQbMeOMRrjpw2UXL_Gd1vIlgUT1ujDax7OiwRvfcn-JqDUnv8i6ju8hbceW-1ti1zBu-y/s1600/ERPAdapter1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidJNDMn6BToHSLeihM2-lHaR0yrbKgG1Ujiy30Uii8R3Q11VmaJjnwBV4emqH8Wvc0U8on45JnQbMeOMRrjpw2UXL_Gd1vIlgUT1ujDax7OiwRvfcn-JqDUnv8i6ju8hbceW-1ti1zBu-y/s1600/ERPAdapter1.png" height="171" width="400" /></a></div>
<br />
Es decir, vamos a "interponer" un objeto adapter entre nuestra aplicación EnterpriseMagicReport y los ERP de la competencia; que por un lado exponga la interfaz IClientsDataProvider que necesita EnterpriseMagicReport y por otro se comunique con el ERP de la competencia. De esta forma conseguimos mantener a nuestra aplicación EnterpriseMagicReport independiente del ERP al que está "atacando".<br />
<br />
<h3>
Dos formas de implementar Adapter</h3>
El patrón Adapter puede implementarse básicamente de dos formas distintas:<br />
<h4>
</h4>
<h4>
Implementación del patrón Adapter mediante composición de objetos</h4>
<br />
Este es el diagrama UML del patrón Adapter para implementarlo mediante la composición de objetos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdBmuVm0_z0zQhTz5FJzVZP9uQd3hjs4SrvT8xD8uxOOz0-lvcJsvnYIT5zizxJ13JDXc8zQAVxhKY7sbQprn8eXpwH4v4cW1GA08_UKJljMdp9K5jJtAFGOW0MAJNMyJ5t7ebWJkSCDmV/s1600/AdapterObject.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgdBmuVm0_z0zQhTz5FJzVZP9uQd3hjs4SrvT8xD8uxOOz0-lvcJsvnYIT5zizxJ13JDXc8zQAVxhKY7sbQprn8eXpwH4v4cW1GA08_UKJljMdp9K5jJtAFGOW0MAJNMyJ5t7ebWJkSCDmV/s1600/AdapterObject.png" height="146" width="400" /></a></div>
<br />
Los participantes en este patrón son:<br />
<ul>
<li>Client (EnterpriseMagicReport)</li>
<li>Target (IClientsDataProvider) Define la interfaz que usa el cliente.</li>
<li>Adaptee (ERPs de la competencia) Definen una interfaz que necesita ser adaptada.</li>
<li>Adapter (Competencia ERP Adapters) Adapta la interfaz de Adaptee a la de Target.</li>
</ul>
<br />
La siguiente es la implementación de EnterpriseMagicReports incluyendo un adapter para un ERP de la competencia.<br />
<br />
<pre class="brush:csharp;">public class Client
{
public DateTime? LastPurchaseDate { get; set; }
}
public interface IClientsDataProvider
{
IEnumerable<Client> GetClientsLastPurchaseBeforeReport(int days);
}
public class ERP : IClientsDataProvider
{
/// <summary>
/// Clientes almacenados en el Erp.
/// Simula los clientes de la base de datos por ejemplo.
/// </summary>
private Lis<Client> storedClients = null;
public IEnumerable<Client> GetClientsLastPurchaseBeforeReport(int days)
{
return storedClients.Where(c => c.LastPurchaseDate < DateTime.Now.AddDays(days * -1));
}
}
public class ReportsManager
{
IClientsDataProvider internalClientDataProcider;
public ReportsManager(IClientsDataProvider clientDataProvider)
{
internalClientDataProcider = clientDataProvider;
}
public IEnumerable<Client> GetSleepingClients()
{
// Pedimos al Erp los clientes que no han comprado en los
// últimos 90 dias.
return internalClientDataProcider.GetClientsLastPurchaseBeforeReport(90);
}
}
/// <summary>
/// Adapter para el ERP de la marca ACME
/// Como el ERP de ACME no ofrece un API
/// El adapter ataca a la base de datos.
/// </summary>
public class AcmeErpClientsDataAdapter: IClientsDataProvider
{
SqlConnection acmeErpDatabaseConnection;
public AcmeErpClientsDataAdapter(SqlConnection dataBaseConnection)
{
// Guardamos la conexión a la base de datos del ERP de ACME
acmeErpDatabaseConnection = dataBaseConnection;
}
public IEnumerable<Client> GetClientsLastPurchaseBeforeReport(int days)
{
// Conectamos a la base de datos del ERP de ACME y
// Obtenemos los clientes filtrados.
using (acmeErpDatabaseConnection)
{
acmeErpDatabaseConnection.Open();
string commandText = "SELECT ... FROM Clients WHERE....";
var command = new SqlCommand(commandText);
using (SqlDataReader reader = command.ExecuteReader())
{
foreach (var acmeClient in reader)
{
var client = new Client();
// Rellenamos las propiedades del cliente
// desde el reader
yield return client;
}
}
acmeErpDatabaseConnection.Close();
}
}
}
class Program
{
static void Main(string[] args)
{
var clientDataProvider = GetDataProvider();
var reportManager = new ReportsManager(clientDataProvider);
var sleepingClients = reportManager.GetSleepingClients();
foreach (var client in sleepingClients)
{
// Escribimos los datos del cliente
Console.WriteLine("Cliente...");
}
}
private static IClientsDataProvider GetDataProvider()
{
// Lógica para decidir qué proveedor de datos
// devolvemos. Nuesto ERP o el adapter para el
// ERP de ACME
if (true)
{
SqlConnection acmeDbConnection = new SqlConnection();
// Configuramos la conexión.
return new AcmeErpClientsDataAdapter(acmeDbConnection);
}
return new ERP();
}
}
</pre>
<br /></div>
<div>
Como podemos comprobar ahora hemos conseguido que nuestra aplicación de informes ataque tanto a nuestro propio ERP como al ERP del fabricante Acme, ya que hemos implementado un adapter que adapta la interfaz de ERP de Acme (en este caso la propia base de datos) a la interfaz IClientsDataProvider que es la interfaz que espera nuestra aplicación de informes.<br />
<h4>
</h4>
<h4>
</h4>
<h4>
Implementación del patrón Adapter mediante la herencia de clases.</h4>
</div>
<div>
<br />
Otra forma de implementar el patrón Adapter es mediante la herencia. Este es el diagrama UML para este caso:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoetHkgbFK26je_yQOIRnfsoUrEL0vURoELLojaoKRf7_YDu4gGE68a2aBROugPoy4A_3wfJJifLDbX9wdzr40Bu8tTQWt2vqntBoGAN9NiZafSEKjlYNXtAmLX61COg7mwWqmQZSunOaT/s1600/AdapterUML2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgoetHkgbFK26je_yQOIRnfsoUrEL0vURoELLojaoKRf7_YDu4gGE68a2aBROugPoy4A_3wfJJifLDbX9wdzr40Bu8tTQWt2vqntBoGAN9NiZafSEKjlYNXtAmLX61COg7mwWqmQZSunOaT/s1600/AdapterUML2.png" height="135" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Como podemos ver esta implementación se basa en la herencia múltiple, característica que no es soportada por todos los lenguajes de programación debido a los problemas de ambigüedad que conlleva su implementación. Entre los lenguajes que podríamos considerar más conocidos soportan herencia múltiple C++, Perl y Pyton. Estoy seguro que me olvidaré alguno. Si alguien quiere, que corrija. C# o VB.NET por ejemplo sólo soportan herencia simple.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Para los lenguajes que no soportan herencia múltiple una alternativa para implementar mediante herencia sería convertir la clase Target en una interfaz; y en vez de que nuestro adapter reciba el objeto a adaptar (AcmeErpClientDataProvider) por el constructor heredar de él.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Así sería la implementación de nuestro adapter AcmeErpClientDataProvider si optamos por la opción de implementarlo mediante herencia en lugar de mediante composición de objetos:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<pre class="brush:csharp;">/// <summary>
/// Clase cliente de la API del ERP de ACME
/// </summary>
public class AcmeClient
{
public DateTime? LastPurchaseDate { get; set; }
}
/// <summary>
/// API del ERP de ACME
/// </summary>
public class AcmeErpClientApi
{
public IEnumerable<acmeclient> GetClientsNotPurchaseFrom(DateTime fromDate)
{
IEnumerable<acmeclient> result = null;
// Lógica para cargar el listado de clientes que no compran desde fromDate
return result;
}
}
public class Client
{
public DateTime? LastPurchaseDate { get; set; }
}
public interface IClientsDataProvider
{
IEnumerable<client> GetClientsLastPurchaseBeforeReport(int days);
}
public class ReportsManager
{
IClientsDataProvider internalClientDataProcider;
public ReportsManager(IClientsDataProvider clientDataProvider)
{
internalClientDataProcider = clientDataProvider;
}
public IEnumerable<client> GetSleepingClients()
{
// Pedimos al Erp los clientes que no han comprado en los
// últimos 90 dias.
return internalClientDataProcider.GetClientsLastPurchaseBeforeReport(90);
}
}
/// <summary>
/// Adapter para el ERP de la marca ACME
/// Como el ERP de ACME no ofrece un API
/// El adapter ataca a la base de datos.
/// </summary>
public class AcmeErpClientsDataAdapter : AcmeErpClientApi, IClientsDataProvider
{
public IEnumerable<client> GetClientsLastPurchaseBeforeReport(int days)
{
var dateFrom = DateTime.Now.AddDays(days * -1);
var acmeClients= base.GetClientsNotPurchaseFrom(dateFrom);
foreach (var acmeClient in acmeClients)
{
var client = new Client();
// Transformar AcmeClient en Client
yield return client;
}
}
}
class Program
{
static void Main(string[] args)
{
var clientDataProvider = GetDataProvider();
var reportManager = new ReportsManager(clientDataProvider);
var sleepingClients = reportManager.GetSleepingClients();
foreach (var client in sleepingClients)
{
// Escribimos los datos del cliente
Console.WriteLine("Cliente...");
}
}
private static IClientsDataProvider GetDataProvider()
{
// Lógica para decidir qué proveedor de datos
// devolvemos. Nuesto ERP o el adapter para el
// ERP de ACME
if (true)
{
SqlConnection acmeDbConnection = new SqlConnection();
// Configuramos la conexión.
return new AcmeErpClientsDataAdapter(acmeDbConnection);
}
return new ERP();
}
}
</client></client></client></acmeclient></acmeclient></pre>
<div>
<br />
En este caso, como ya dije antes, en lugar de realizar composición de objetos heredamos de la clase que pretendemos adaptar (AcmeErpClientApi) e implementamos la interfaz Target (IClientsDataProvider). La implementación de los métodos de la interfaz acaban llamando a los métodos de la clase base, la clase que queremos adaptar.<br />
<br />
Hay que tener en cuenta que no siempre es posible heredar de la clase que queremos adaptar, por lo que no siempre es posible implementa el patrón Adapter mediante la herencia.<br />
<br />
Desde mi punto de vista la elección principal debe ser implementar un Adapter mediante la composición de objetos ya que resulta en un diseño más flexible que con la herencia.<br />
<br />
<h4>
¿Cuánto debe adaptar un Adapter?</h4>
<div>
El hecho de implementar un Adapter no quiere decir de debemos adaptar toda la interfaz del objeto a adaptar. Es decir, en nuestro último ejemplo la clase AcmeErpClientApi podría tener diez métodos, pero para nuestro propósito sólo necesitamos adaptar uno; no es necesario adaptar todo el objeto. La cantidad de trabajo que adaptaremos lo definirá normalmente la clase Target (en nuestro caso la interfaz IClientsDataProvider).</div>
<div>
<br /></div>
<div>
Hay un sin fin de variaciones en cuanto a qué cantidad de la clase AcmeErpClientApi podemos adaptar. De hecho en una implementación de Adapter basada en la composición de objetos podríamos recibir en el constructor del adapter varios objetos a adaptar para cumplir la interfaz IClientsDataProvider. </div>
<div>
<br />
Incluso podemos también crear diferentes Adapters para el mismo objeto de forma que cada adapter representa un aspecto diferente del objeto a adaptar. Por ejemplo, para el ERP de la marca Acme podríamos crear el adapter DemographicsAcmeErpAdapter que cumple la interfaz IDemographicsDataProvider con métodos para crear informes sobre la edad y el sexo de nuestros clientes.</div>
<div>
<br /></div>
<div>
Vamos a dejar aquí el patrón Adapter. Hay algunos matices y ejemplos más sobre los que podríamos seguir incidiendo, pero creo que con lo visto hasta ahora es suficiente para entender el patrón, implementarlo y reconocer los contextos donde puede ser útil.</div>
<div>
<br /></div>
<div>
En el próximo post veremos otro patrón estructural: Bridge.</div>
<br /></div>
<div>
</div>
</div>
Unknownnoreply@blogger.com6tag:blogger.com,1999:blog-2728255754328606012.post-13877416173917427262014-02-07T11:55:00.000+01:002014-03-19T10:38:59.673+01:00Patrones de creación: Singleton<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20creaci%C3%B3n" style="font-size: medium; font-weight: normal;" target="_blank">Patrones de creación</a></h2>
<h2>
Singleton</h2>
<div>
"Asegura que una clase tiene una sola instancia, y provee un punto de acceso global a esta instancia."</div>
<div>
<br />
El patrón Singleton es probablemente el más sencillo de los patrones definidos por GoF. Debido a esto es frecuentemente sobre utilizado y a veces de forma incorrecta. Esto no quiere decir que el Singleton sea un patrón malo que no deba utilizarse, sino que, debe usarse en su justa medida y en el contexto correcto.<br />
<br />
<a name='more'></a><br />
<br />
<br />
Este es el diagrama UML del patrón Singleton:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_BDDRubwFTy3YgbjEPu9OrFYcIo3IQeI8dFomsqXwOxM91YeJv92XBHLUlYxphsH0mRlUUmeR1KbjqyGtenV4tyGRChp6Bsy9xWd1tppdZPQzLioP9wJxaTPXosCUPuY_TdzrSFcqrjJR/s1600/Singleton.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_BDDRubwFTy3YgbjEPu9OrFYcIo3IQeI8dFomsqXwOxM91YeJv92XBHLUlYxphsH0mRlUUmeR1KbjqyGtenV4tyGRChp6Bsy9xWd1tppdZPQzLioP9wJxaTPXosCUPuY_TdzrSFcqrjJR/s1600/Singleton.png" height="143" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div>
<b>Participantes:</b><br />
<br />
<ul>
<li><b>Singleton. </b>Defina el método Instance que permite a los clientes acceder a su única instancia. Instance es un método estático. Igualmente se puede implementar con una propiedad estática.</li>
<li>Puede ser el responsable de crear su propia instancia única.</li>
</ul>
<div>
El patrón Singleton garantiza que sólo exista una instancia de una clase. Si queremos una sola instancia de una clase, no podemos esperar que los clientes la instancien una sola vez.<br />
Debe ser el Singleton el responsable de mantener una sola instancia, liberando a los clientes de la clase de esa responsabilidad.<br />
Aunque a primera vista parece que podamos conseguir lo mismo con una clase con miembros estáticos, esta no sería una buena idea. Si usáramos miembros estáticos no se podrían extender mediante herencia y perderíamos la capacidad de polimorfismo que nos ofrecen los lenguajes orientados a objetos. </div>
</div>
<br />
<h4>
Implementaciones</h4>
Sea cual sea la implementación de un Singleton, la mayoría siguen las siguientes pautas básicas:<br />
<br />
<ul>
<li>Se oculta el constructor de la clase Singleton para que los clientes no puedan crear instancias. Es decir se declara el constructor como private o protected.</li>
<li>Se crea un campo privado que mantiene la referencia a la instancia única que se crea.</li>
<li>Se crea un método o propiedad pública estática que permite a los clientes acceder a la instancia única del Singleton.</li>
</ul>
<div>
Veamos un ejemplo. Supongamos que en nuestra aplicación queremos ofrecer un acceso fácil a la configuración de algunos parámetros. Por ejemplo el número de items por página que se mostraran en los listados. En usuario puede cambiar el número de items por página que quiere ver, pero debemos asegurarnos que en todas las funcionalidades de nuestra aplicación ese número es el mismo. Para esto implementaremos un patrón Singleton que representa la configuración y del que podemos obtener el número de items por página a mostrar.</div>
<div>
<br />
<h4>
Implementación básica</h4>
</div>
<div>
Una primera implementación básica sería la siguiente:</div>
<div>
<br /></div>
<pre class="brush:csharp;">/// <summary>
/// Clase del Singleton
/// </summary>
public class Configuration
{
private static Configuration instance;
private Configuration()
{
// Se inicializa el valor a 10 por defecto.
ItemsPerPage = 10;
}
/// <summary>
/// Propiedad que permite el acceso a la instancia
/// unica de Singleton
/// </summary>
public static Configuration Instance
{
get
{
// Instanciación tardía de la unica instancia
// del Singleton. Se pospone la instanciación
// hasta la primera vez que se solicita.
if (instance == null)
instance = new Configuration();
return instance;
}
}
public int ItemsPerPage { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Items por página: {0}", Configuration.Instance.ItemsPerPage);
Configuration.Instance.ItemsPerPage = 30;
Console.WriteLine("Items por página: {0}", Configuration.Instance.ItemsPerPage);
Console.ReadLine();
}
}
</pre>
<br />
Cómo podemos comprobar en el código anterior la clase Configuration cumple las dos premisas del Singleton:<br />
<br />
<ol>
<li>Es responsable de mantener la instancia única del Singleton (una instancia de sí misma).</li>
<li>Provee a los clientes una forma de acceder a la instancia única del Singleton. En este caso se hace a través de la propiedad estática Instance.</li>
</ol>
<div>
Es importante entender bien que aunque la propiedad que permite acceder al Singleton (Instance) es estática, el Singleton no es estático sino una instancia (objeto).<br />
Otro punto interesante de esta implementación es la instanciación tardía de la instancia única del Singleton. Esto es importante porque aunque en este ejemplo el Singleton es muy "ligero", es posible que sea una clase pesada y no es necesario instanciarla si no se usa.</div>
<div>
<h4>
</h4>
<h4>
</h4>
<h4>
Implementación sin la propiedad Instance </h4>
</div>
Cuando los clientes acceden a un Singleton habitualmente lo hacen a través de una propiedad/método (en el ejemplo anterior la propiedad Instance). Sin embargo, podemos implementar un Singleton mediante propiedades y métodos estáticos que delegan internamente en la instancia única del Singleton. El siguiente es una refactorización del ejemplo anterior que evita el uso de la propiedad Instance:<br />
<br />
<pre class="brush:csharp;">public class Configuration
{
private static Configuration instance;
private int itemsPerPage;
private Configuration()
{
// Se inicializa el valor a 10 por defecto.
// IMPORTANTE: Establecer el campo privado.
// ItemsPerPage = 10; provocaría StackOverflow.
itemsPerPage = 10;
}
public static int ItemsPerPage
{
get
{
return GetInstance().itemsPerPage;
}
set
{
GetInstance().itemsPerPage = value;
}
}
private static Configuration GetInstance()
{
if (instance == null)
instance = new Configuration();
return instance;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Items por página: {0}", Configuration.ItemsPerPage);
Configuration.ItemsPerPage = 30;
Console.WriteLine("Items por página: {0}", Configuration.ItemsPerPage);
Console.ReadLine();
}
}
</pre>
<br />
<div>
Esta implementación como hemos dicho usa una propiedad estática (ItemsPerPage) que gestiona internamente la instancia única del Singleton. Esto hace que los clientes accedan a las propiedades y métodos del Singleton sin tener que escribir continuamente Instance. Pero aun así, esto sigue siendo un Singleton. Seguimos manteniendo una instancia única y proveemos un acceso a ella (a través de los métodos/propiedades estáticos).<br />
Un problema añadido es que en este caso es difícil extender el Singleton mediante la herencia ya que la interfaz del Singleton está compuesta por una serie de método y propiedades estáticas.<br />
<br />
Personalmente prefiero una implementación donde aparezca la propiedad/método (Instance en el ejemplo) para acceder a la instancia única aunque no sea tan cómodo para los clientes del Singleton. Por un lado permite más fácilmente extender el Singleton mediante herencia; y por otro, los clientes al acceder a la instancia única a través de una propiedad o un método (Instance en el primer ejemplo) tienen más claro con qué tipo de objeto están tratando. </div>
<br />
<h4>
Singleton en entornos multihilo (multi-thread)</h4>
Antes de entrar en materia de Singleton con multihilo quiero dar las gracias a Kyril Jardinico porque esta sección del post es sugerencia suya. Y ya que estamos, por ser tan fiel lector del blog y porque siempre hace comentarios sobre los post que me animan a seguir. Gracias Kyril.<br />
<br />
Ante todo decir que explicar la programación en entornos multihilo evidentemente no entra dentro de las pretensiones de este post y probablemente tampoco sea yo la persona más indicada para explicar este tema en profundidad. Así que veremos lo justo para entender el problema desde el punto de vista del patrón Singleton.<br />
<br />
Para que podamos entender el problema partiremos de la primera implementación que vimos del Singleton que repetiré a continuación:<br />
<pre class="brush:csharp;">/// <summary>
/// Clase del Singleton
/// </summary>
public class Configuration
{
private static Configuration instance;
private Configuration()
{
// Se inicializa el valor a 10 por defecto.
ItemsPerPage = 10;
}
/// <summary>
/// Propiedad que permite el acceso a la instancia
/// unica de Singleton
/// </summary>
public static Configuration Instance
{
get
{
// Instanciación tardía de la unica instancia
// del Singleton. Se pospone la instanciación
// hasta la primera vez que se solicita.
if (instance == null)
instance = new Configuration();
return instance;
}
}
public int ItemsPerPage { get; set; }
}
</pre>
<br />
Si estamos en un entorno de un sólo hilo (single-thread) esta implementación no presenta ningún problema. Sin embargo en entornos multihilo nuestro problema llega cuando dos hilos ejecutan a la vez la línea <b>if (instance == null)</b> del método get de Instance. En ese momento ambos hilos evaluará la condición como true y cada hilo creará una instancia distinta del Singleton, y esa no es evidentemente la idea que queremos.<br />
Veamos un diagrama que nos permite entender mejor el problema:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVJziyNPcsqUjfjZbd6DWNT8y8Unh3yk3X8VPlqXYKYTIscxBT0YnLI9lJiHUVTmIUenniRkQaUapzvXBLVUE507cvpMzui2R5Ex8dKIz-WG5sfpVBijhSAcs2wFi9GRt5-d7bZPeqST7t/s1600/SingletonMultithread.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVJziyNPcsqUjfjZbd6DWNT8y8Unh3yk3X8VPlqXYKYTIscxBT0YnLI9lJiHUVTmIUenniRkQaUapzvXBLVUE507cvpMzui2R5Ex8dKIz-WG5sfpVBijhSAcs2wFi9GRt5-d7bZPeqST7t/s1600/SingletonMultithread.png" height="202" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
El diagrama anterior muestra cómo en un entorno multihilo la primera implementación de Singleton que hemos visto puede no ser suficiente, ya que bajo algunas circunstancias es posible que se creen varias instancias del Singleton. Los errores de este tipo son realmente muy difíciles de detectar y de depurar ya que no son deterministas, no se producirán siempre y será muy difícil recrear un contexto donde reproducirlos. Para evitar este problema debemos hacer una implementación que tenga en cuenta los entornos multihilo.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
No obstante, antes de implementar un Singleton que funcione en entornos multihilo debemos preguntarnos si el problema que acabamos de ver es realmente un problema para nosotros o no. Es decir, ¿es un problema tener varias instancias de un Singleton? Pues eso dependerá del tipo de Singleton que tenemos:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>Si el Singleton no mantiene el estado (stateless), es decir, si la propiedad ItemsPerPage no cambia nunca esto no es un problema grave. En el peor de los casos tendríamos varias instancias del Singleton pero todas con el mismo valor, lo que desde un punto de vista práctico es como tener una.</li>
<li>Si el Singleton debe mantener el estado. Es decir, si los valores de las propiedades del Singleton cambian como es el caso de ItemsPerPage en nuestro ejemplo; no podemos permitirnos tener una instancia del Singleton que diga que debemos mostrar 10 items por página y otra instancia que diga 25 items por página. En este caso si hay un contexto multihilo debemos hacer una implementación del Singleton que evite este problema.</li>
</ul>
<div>
Bien, veamos una primera aproximación a una implementación del Singleton que funcione en un entorno multihilo:</div>
<div>
<br />
<pre class="brush:csharp;">public class Configuration
{
private static Configuration instance;
private static readonly object objectlock = new object();
private Configuration()
{
// Se inicializa el valor a 10 por defecto.
ItemsPerPage = 10;
}
public static Configuration Instance
{
get
{
lock (objectlock)
{
if (instance == null)
instance = new Configuration();
return instance;
}
}
}
public int ItemsPerPage { get; set; }
}
</pre>
</div>
<div>
<br />
El ejemplo anterior soportará entornos multihilo ya que hace un lock siempre que se va a acceder a la instancia única del Singleton.<br />
<br />
La sentencia lock asegura que un bloque de código se ejecutará sin interrupciones. Es decir, si un hilo comienza a ejecutar el bloque nos aseguramos que acabará de ejecutarlo antes de permitir entrar a otro hilo en el bloque. El objeto que usamos como parámetro en la sentencia lock determina la granularidad del bloqueo que se realiza. Para nuestro ejemplo como pretendemos bloquear una región estática debemos pasar como parámetro de la sentencia lock un objeto estático y único. Por eso el objeto <b>objectlock </b>se declara estático y se instancia en el mismo momento de declararlo.</div>
<br />
Esta implementación no obstante tiene un grave problema de rendimiento ya que bloquear una región de código tiene un alto coste de recursos y en nuestro caso estamos bloqueando la región de acceso a la instancia del Singleton siempre; incluso aunque el Singleton ya esté instanciado.<br />
<br />
<h4>
Double-Check Locking</h4>
Para evitar este problema de rendimiento usaremos una técnica que realiza el bloqueo con una doble comprobación del null de la instancia única del Singleton. Veamos cómo es esto:<br />
<br />
<div>
<pre class="brush:csharp;">public class Configuration
{
private static volatile Configuration instance;
private static readonly object objectlock = new object();
private Configuration()
{
// Se inicializa el valor a 10 por defecto.
ItemsPerPage = 10;
}
public static Configuration Instance
{
get
{
if (instance == null)
{
lock (objectlock)
{
if (instance == null)
instance = new Configuration();
}
}
return instance;
}
}
public int ItemsPerPage { get; set; }
}
</pre>
</div>
<br />
Con esta implementación sólo se entra en el bloque lock cuando aún no se ha creado la instancia única del Singleton, con lo que es mucho más eficiente que la implementación anterior.<br />
<br />
Creo que esta implementación no funciona correctamente en Java por la forma que la máquina virtual de Java trata la memoria, pero esto sólo lo cuento "de oidas" porque no tengo los conocimientos de Java suficientes para asegurar esto. <br />
<br />
Una cosa a tener en cuenta es que ahora a la declaración de la instancia se le ha incluido la cláusula volatile. Esta cláusula evita optimizaciones del compilador al tratar la instancia. Su explicación se sale del ambito de este post, pero nos vale con quedarnos que agregando la cláusula volatile a la declaración de la instancia nos aseguramos de que la cpu no va a cachear el valor null del Singleton y en cuanto se instancia ese valor pasará a la memoria. Esto evitará posibles comportamientos no deseados en entornos multihilo.<br />
<br />
Por último indicar que todos estos problemas del Singleton en entornos multihilos se producen por la instanciación tardía de la instancia única del Singleton. Si tenemos un Singleton poco pesado una posible solución al problema con el entorno multihilo sería la siguiente:<br />
<br />
<div>
<pre class="brush:csharp;">public class Configuration
{
private static Configuration instance = new Configuration();
private Configuration()
{
// Se inicializa el valor a 10 por defecto.
ItemsPerPage = 10;
}
public static Configuration Instance
{
get
{
return instance;
}
}
public int ItemsPerPage { get; set; }
}
</pre>
</div>
<br />
En este caso no se realiza la instanciación tardía de la instancia única del Singleton, sino que se crea la instancia inmediatamente por lo que no tenemos ningún riesgo de que se instancien más de un Singleton en entornos multihilo.<br />
<br />
<div class="correction">
<div class="correction-title">
Ampliación del 19 de Marzo de 2014
</div>
<div class="correction-body">
<a href="http://www.blogger.com/profile/16786942978117932468" rel="nofollow" style="background-color: white; color: #33aaff; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 12px; font-weight: bold; line-height: 16.799999237060547px;" target="_blank">Son Gokū</a> en un comentario sugiere el uso de <a href="http://msdn.microsoft.com/es-es/library/dd642331(v=vs.110).aspx" target="_blank">Lazy</a> para implementar un Singleton. Nos ha aportado un <a href="http://geekswithblogs.net/BlackRabbitCoder/archive/2010/05/19/c-system.lazylttgt-and-the-singleton-design-pattern.aspx" target="_blank">enlace muy interesante</a> a este respecto. Os animo a que echéis un vistazo. Es importante tener en cuenta que Lazy sólo puede usarse a partir de la versión 4 del framework .NET</div>
</div>
</div>
<br />
Con el patrón Singleton hemos acabado la serie <span style="font-size: small;"><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20creaci%C3%B3n" target="_blank">Patrones de creación</a>.<br />
En el próximo post empezaremos con los patrones estructurales.<br />
<br />Unknownnoreply@blogger.com7tag:blogger.com,1999:blog-2728255754328606012.post-21057220756780482412014-01-29T11:54:00.002+01:002014-01-29T23:43:44.588+01:00Patrones de creación: Prototype<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20creaci%C3%B3n" style="font-size: medium; font-weight: normal;" target="_blank">Patrones de creación</a></h2>
<h2>
Prototype</h2>
<div>
"Especifica los tipos de objetos a crear usando una instancia como prototipo y crea nuevos objetos copiando ese prototipo"<br />
<br />
El patrón Prototype es ampliamente conocido, el framework .NET lo implementa mediante la interfaz ICloneable que declara el método Clone(), sin embargo no siempre es comprendido en profundidad.<br />
<br />
Prototype es un patrón de creación y se basa en crear objetos mediante la clonación.<br />
<br />
<a name='more'></a><br />
<br />
<div>
<ul>
</ul>
<div>
El siguiente es el diagrama UML del patrón Prototype:</div>
</div>
<br />
<br /></div>
<div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicEBY4_IjI9TP8C86MVUWBcduQAGEF5GMWjRvsO3MinytvaffKqyB2UUfFlaYHrtnS7aeQyUnplSgRsxBkeHZIF-WPB__xeUri1UWEYmqSb-qzpfypprPLOxllHFHeF1ZAXB6gubAyy88e/s1600/Prototype.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicEBY4_IjI9TP8C86MVUWBcduQAGEF5GMWjRvsO3MinytvaffKqyB2UUfFlaYHrtnS7aeQyUnplSgRsxBkeHZIF-WPB__xeUri1UWEYmqSb-qzpfypprPLOxllHFHeF1ZAXB6gubAyy88e/s1600/Prototype.png" height="149" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
Estos son los objetos participantes en el patrón Prototype:<br />
<br />
<ul>
<li><b>Prototype.</b> Declara una interfaz para clonarse a sí mismo. En .NET este objeto es la interfaz ICloneable y el método es Clone()</li>
<li><b>ConcretePrototype</b>. Implementa un método para clonarse a sí mismo.</li>
<li><b>Cliente</b>. Crea un objeto llamando al método Clone() del objeto Prototype.</li>
</ul>
<div>
<div>
<br />
<h4>
Uso de Prototype para otros fines</h4>
La mayoría de nosotros hemos visto y usado el método <b>Clone </b>de <b>ICloneable</b>. Pero en muchas ocasiones no con el fin de instanciar objetos de la aplicación sino más bien para obtener una copia de el estado de un objeto en un momento determinado.<br />
<br />
Por ejemplo, si estamos haciendo una funcionalidad que modifica los datos de una factura podríamos crear un clon de la factura justo antes de que el usuario la modifique para conservar una instancia con los valores originales y así poder restaurar los valores originales si el usuario cancela los cambios. O saber exactamente (por comparación con el clon) que propiedades exactamente ha cambiado el usuario.<br />
<br />
<h4>
Prototype como patrón de creación</h4>
Una vez que hemos visto que el método <b>Clone </b>de <b>Prototype </b>puede usarse con otros fines, me gustaría centrarme en la finalidad principal o la más interesante desde el punto de vista del diseño de software. Esta funcionalidad como ya hemos visto es la instanciación de nuevos objetos a partir de un prototipo.<br />
<br />
En una aplicación existen dos formas habituales de parametrizar la creación de nuevos objetos:<br />
<ul>
<li>Creando subclases (<b>ConcreteCreator</b>) de la clase responsable de crear los objetos (<b>Creator</b>); este es el caso cuando usamos el patrón <a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-factory-method.html" target="_blank">Factory Method</a>. El inconveniente de esta forma de crear objetos nuevos (<b>Product</b>) es que tenemos que crear nuevas subclases (<b>ConcreteCreator</b>) para cada tipo de objeto nuevo que queremos crear (<b>Product</b>). Esto provoca la proliferación de clases <b>ConcreteCreator </b>aunque sean con diferencias mínimas entre ellas.</li>
<li>Los patrones de creación <a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a>, <a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-builder.html" target="_blank">Builder </a>y <b>Prototype </b>se basan en la composición de objetos. Un aspecto clave de estos tres patrones es que crean un "objeto factoría" responsable de crear objetos concretos. <a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a> usa la clase <b>AbstractFactory</b> para crear objetos concretos de una familia; <a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-builder.html" target="_blank">Builder</a> usa la clase <b>Director </b>para crear objetos complejos paso a paso y siguiendo un protocolo de construcción y <b>Prototype </b>usa su factoría para crear objetos clonándolos a partir de un prototipo. En este caso el objeto factoría y el objeto prototipo es el mismo ya que el prototipo es el responsable de crear el nuevo objeto.</li>
</ul>
<div>
Usar el patrón <b>Prototype </b>permite reducir el número de clases necesarias para crear un objeto y además sólo necesitamos implementar el método <b>Clone</b></div>
<br />
Cuando creamos un objeto mediante la clonación de un prototipo es posible que el estado interno del prototipo no sea correcto para la instancia concreta que estamos creando y tendremos que establecer el estado interno para la instancia que hemos creado.<br />
<br />
Dado que no podemos pasar parámetros al método <b>Clone </b>para establecer el estado interno porque cada prototipo tiene sus propios parámetros y perderíamos la interfaz uniforme para clonar; si queremos establecer el estado interno de una instancia clonada tenemos dos opciones principalmente:<br />
<br />
<ol>
<li>Establecer el estado interno asignando propiedades del objeto clonado (si es posible).</li>
<li>Crear un método de inicialización, por ejemplo Initialize, en cada prototipo donde pasamos los parámetros necesarios a cada prototipo para establecer su estado interno.</li>
</ol>
</div>
<div>
<br /></div>
<div>
Normalmente vamos a usar Prototype en los casos en que:</div>
<div>
<ul>
<li>Sólo sabemos los objetos concretos que vamos a instanciar en tiempo de ejecución.</li>
<li>Si queremos evitar construir una jerarquía de clases factoría paralela a la jerarquia de objetos a construir. (<b>ConcreteCreator - Product</b>)</li>
<li>Si la instanciación de un objeto es costosa. Por ejemplo, si hay que leer sus datos de una base de datos o servicio web es mejor instanciar una vez un prototipo, y después crear el resto de instancias clonando ese prototipo.</li>
<li>Si el número de objetos a crear es pequeño, o es un objeto con uno o unos cuantos estados internos diferentes. En este caso puede ser más conveniente instalar una serie de prototipos y crear los objetos clonando esos prototipos.</li>
</ul>
</div>
</div>
<br />
<h4>
Copia superficial o copia profunda.</h4>
</div>
<div>
Sin duda la parte más difícil a la hora de implementar el patrón Prototype es justamente implementar el método <b>Clone </b>correctamente, especialmente si los objetos que queremos clonar tienen referencias circulares. </div>
<div>
<br /></div>
<div>
Hoy en día la mayoría de frameworks existentes tienen algún tipo de soporte para implementar la clonación, pero eso no nos evita el problema de decidir si necesitamos una copia superficial o una copia profunda. Es decir: nos bastará sólo con copiar las propiedades por valor y compartir las propiedades por referencia entre el clon y el original; o necesitaremos copiar las propiedades por valor y a su vez clonar también las propiedades por referencia.</div>
<div>
<br /></div>
<div>
La copia superficial es simple y la mayor parte de las veces suficiente. <b>.NET</b> provee el método <b>MemberwiseClone()</b> de la clase <b>Object </b>para crear una copia superficial de un objeto. </div>
<div>
<br /></div>
<div>
Copiar objetos con estructuras complejas suele requerir hacer copias profundas. Es decir, si nuestro objeto original A hace referencia a otro objeto B también necesitamos clonar el objeto B. De esta forma el objeto original y el clonado serán independientes.</div>
<div>
<br />
<blockquote class="tr_bq">
Al implementar el método Clone, la decisión que debemos tomar es si nos vale una copia superficial o necesitamos una copia profunda</blockquote>
<br /></div>
<div>
Por lo tanto a la hora de clonar un objeto la decisión fundamental será que partes del objeto serán clonadas y que partes serán compartidas entre el original y el clon.<br />
<br />
El siguiente código crea una copia superficial de un objeto tipo Person:</div>
<div>
<br />
<pre class="brush:csharp;">public class Address
{
public string City { get; set; }
public string Street { get; set; }
public int Number { get; set; }
public string Zip { get; set; }
}
public class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
public override string ToString()
{
return string.Format("{0},{1} años. Vive en {2}, calle {3}", Name, Age, Address.City, Address.Street);
}
public object Clone()
{
return this.MemberwiseClone();
}
}
class Program
{
static void Main(string[] args)
{
var personPrototype = new Person
{
Name = "Miguel A. González",
Age = 41,
Address = new Address
{
City = "Madrid",
Street = "C/ Mayor",
Number = 45,
Zip = "28075"
}
};
clonedPerson.Age = 40;
Person clonedPerson = (Person)personPrototype.Clone();
clonedPerson.Address.City = "Barcelona";
System.Console.WriteLine(personPrototype);
System.Console.WriteLine(clonedPerson);
System.Console.ReadLine();
}
}
</pre>
<div>
<br />
El resultado lo podemos ver en la siguiente imagen.
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggn3TmGqKFs8-4NeDDIz1FgdnWhUP_VsFiLWcEavd2VhD4GN4eocyoXJoMzJiALSjKbmihyPfg7-IYAg9jbZ4KNnHP02sfH4z74MW28ymXjQxlnKVDfKjRS8gryKheqOOmtrq-LithU1yC/s1600/Prototype1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEggn3TmGqKFs8-4NeDDIz1FgdnWhUP_VsFiLWcEavd2VhD4GN4eocyoXJoMzJiALSjKbmihyPfg7-IYAg9jbZ4KNnHP02sfH4z74MW28ymXjQxlnKVDfKjRS8gryKheqOOmtrq-LithU1yC/s1600/Prototype1.png" height="326" width="640" /></a></div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiY6rSGH6vaLBGrMTH3mNd9sqXYMZS7iDNE8cdyUa90DH3IuMb_xpnMJXbYlhPymaEMVkwRoiLkb6mGDsraIODUiOphIyI23a6mplRqXAPCipYYeZuG-3xInqOY6kpjxq86ygZJQfTzwjFJ/s1600/Prototype1.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><br /></a>
<br />
<div class="separator" style="clear: both; text-align: left;">
Como podemos ver, tanto <b>personPrototype </b>como <b>clonedPerson </b>tienen la misma dirección. Esto es debido a que en el método <b>Clone()</b> de <b>Person </b>hacemos una copia superficial.
</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
En una copia superficial se copian las propiedades por valor y se comparten las propiedades por referencia. Por eso al cambiar la propiedad <b>Address.City</b> del clon cambia igualmente la ciudad del original (<b>Address </b>se comparte entre original y clon).<br />
<br />
<div>
Ahora haremos lo mismo pero creando una copia profunda de <b>Person</b>:</div>
<pre class="brush:csharp;">public class Address : ICloneable
{
public string City { get; set; }
public string Street { get; set; }
public int Number { get; set; }
public string Zip { get; set; }
public object Clone()
{
return new Address
{
City = this.City,
Street = this.Street,
Number = this.Number,
Zip = this.Zip
};
}
}
public class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
public override string ToString()
{
return string.Format("{0},{1} años. Vive en {2}, calle {3}", Name, Age, Address.City, Address.Street);
}
public object Clone()
{
return new Person
{
Name = this.Name,
Age = this.Age,
Address = (Address)this.Address.Clone()
};
}
}
class Program
{
static void Main(string[] args)
{
var personPrototype = new Person
{
Name = "Miguel A. González",
Age = 41,
Address = new Address
{
City = "Madrid",
Street = "C/ Mayor",
Number = 45,
Zip = "28075"
}
};
Person clonedPerson = (Person)personPrototype.Clone();
clonedPerson.Age = 40;
clonedPerson.Address.City = "Barcelona";
System.Console.WriteLine(personPrototype);
System.Console.WriteLine(clonedPerson);
System.Console.ReadLine();
}
}
</pre>
<br />
Este código produce la siguiente salida en la consola:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3vPO0CD1HwOsc1bZa2a2N-qpekVBxxXvdmjoVs_5NxO2RjbTzdNXrHKgYVyihx_AQGCmuyvVsj9uhx4ltqEcPjw1WWJAxPrEJ-rnw2FjSG27Ervo5Kq_fUzPoooxK_roHczeU4q0pV_MJ/s1600/Prototype2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3vPO0CD1HwOsc1bZa2a2N-qpekVBxxXvdmjoVs_5NxO2RjbTzdNXrHKgYVyihx_AQGCmuyvVsj9uhx4ltqEcPjw1WWJAxPrEJ-rnw2FjSG27Ervo5Kq_fUzPoooxK_roHczeU4q0pV_MJ/s1600/Prototype2.png" height="328" width="640" /></a></div>
<br /></div>
<div>
Como vemos ahora el objeto original y le objeto clonado son instancias completamente distintas, no comparten el objeto <b>Address</b>.<br />
<br />
<h4>
Una práctica común al clonar en .NET</h4>
En <b>.NET</b> podemos crear una copia profunda de un objeto serializando el original en memoria y de-serializándolo posteriormente sobre el clon. La única restricción que este método implica es que el objeto original como los objetos a los que se hace referencia deben ser serializables. El siguiente código muestra un ejemplo en el que es posible aplicar esta técnica y otro en el que no lo es.</div>
<div>
<br />
<pre class="brush:csharp;">[Serializable]
public class Address
{
public string City { get; set; }
public string Street { get; set; }
public int Number { get; set; }
public string Zip { get; set; }
}
[Serializable]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
public override string ToString()
{
return string.Format("{0},{1} años. Vive en {2}, calle {3}", Name, Age, Address.City, Address.Street);
}
}
class Program
{
static void Main(string[] args)
{
var personPrototype = new Person
{
Name = "Miguel A. González",
Age = 41,
Address = new Address
{
City = "Madrid",
Street = "C/ Mayor",
Number = 45,
Zip = "28075"
}
};
Person clonedPerson = null;
using (var memoryStream = new System.IO.MemoryStream())
{
var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
binaryFormatter.Serialize(memoryStream, personPrototype);
memoryStream.Position = 0;
clonedPerson = (Person)binaryFormatter.Deserialize(memoryStream);
}
clonedPerson.Age = 40;
clonedPerson.Address.City = "Barcelona";
System.Console.WriteLine(personPrototype);
System.Console.WriteLine(clonedPerson);
System.Console.ReadLine();
}
}
</pre>
</div>
<div>
<br />
Si ejecutamos este código veremos que el objeto original (<b>personPrototype</b>) y el objeto clonado (<b>clonedPerson</b>) evolucionan de forma independiente, cada uno tiene sus propiedades no compartidas, sus direcciones son distintas. Para esta técnica hay que reseñar el atributo<b> [Serializable]</b> que hemos aplicado tanto a la clase <b>Person </b>como a la clase <b>Address </b>para que puedan serializarse.<br />
<br />
<h4>
PrototypeManager </h4>
Cuando decidimos usar <b>Prototype </b>como método de creación de instancias en nuestra aplicación, o en una parte de ella, y el número de prototipos no es fijo sino que pueden variar de forma dinámica (se pueden crear y destruir prototipos en tiempo de ejecución); la forma habitual de gestionar los prototipos es mediante un <b>PropotypeManager</b>.<br />
<br />
La clase <b>PrototypeManager </b>tiene la responsabilidad de mantener un registro de los prototipos activos en la aplicación. <b>PrototypeManager </b>permite a los clientes registrar y recuperar prototipos mediante una clave, eliminar un prototipo del registro, navegar por los prototipos, obtener un inventario de los prototipos activos y cualquier otra funcionalidad adicional que nuestra aplicación requiera.<br />
<br />
Veamos cómo podría ser una implementación básica de un <b>PrototypeManager</b>:<br />
<br />
<br /></div>
<div>
<pre class="brush:csharp;">public class Address : ICloneable
{
public string City { get; set; }
public string Street { get; set; }
public int Number { get; set; }
public string Zip { get; set; }
public object Clone()
{
return new Address
{
City = this.City,
Street = this.Street,
Number = this.Number,
Zip = this.Zip
};
}
}
public class Person : ICloneable
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
public override string ToString()
{
return string.Format("{0},{1} años. Vive en {2}, calle {3}", Name, Age, Address.City, Address.Street);
}
public object Clone()
{
return new Person
{
Name = this.Name,
Age = this.Age,
Address = (Address)this.Address.Clone()
};
}
}
public class PrototypeManager
{
private Dictionary<string, ICloneable> registry;
public PrototypeManager()
{
registry = new Dictionary<string, ICloneable>();
}
public void RegisterPrototype(string key, ICloneable prototype)
{
registry.Add(key, prototype);
}
public void UnregisterPrototype(string key)
{
if (registry.ContainsKey(key))
registry.Remove(key);
}
public ICloneable GetPrototype(string key)
{
if (registry.ContainsKey(key))
return registry[key];
return null;
}
}
class Program
{
static void Main(string[] args)
{
var prototypeManaer = new PrototypeManager();
RegisterPrototypes(prototypeManaer);
// Obtenemos los prototipos, los clonamos y asignamos las propieades necesarias.
var prototype1 = prototypeManaer.GetPrototype("madrid-middle-aged");
var person1 = (Person)prototype1.Clone();
person1.Name = "Miguel Angel";
person1.Address.Street = "C/ Mayor";
person1.Address.Number = 45;
var prototype2 = prototypeManaer.GetPrototype("barcelona-Teenager");
var person2 = (Person)prototype2.Clone();
person2.Name = "Alba";
person2.Address.Street = "C/ Rambla";
person2.Address.Number = 45;
Console.WriteLine(person1);
Console.WriteLine(person2);
Console.ReadLine();
}
private static void RegisterPrototypes(PrototypeManager prototypeManaer)
{
var madridMiddleAgePrototype = new Person
{
Age = 40,
Address = new Address
{
City = "Madrid",
Zip = "28075"
}
};
var barcelonaTeenAgerPrototype = new Person
{
Age = 16,
Address = new Address
{
City = "Barcelona",
Zip = "08075"
}
};
prototypeManaer.RegisterPrototype("madrid-middle-aged", madridMiddleAgePrototype);
prototypeManaer.RegisterPrototype("barcelona-Teenager", barcelonaTeenAgerPrototype);
}
}
</pre>
</div>
<div>
<br />
Hasta aquí el patrón <b>Prototype</b>. Como ya he mencionado es un patrón conocido pero a veces más por el método <b>Clone </b>que permite hacer una copia de un objeto que como patrón de creación. Espero que este post haya ayudado a comprenderlo en mayor profundidad.<br />
<br />
En el próximo post veremos el patrón Singleton.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-2728255754328606012.post-34024979216546189682014-01-20T11:21:00.001+01:002014-01-20T15:06:29.944+01:00Patrones de creación: Factory Method<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20creaci%C3%B3n" style="font-size: medium; font-weight: normal;" target="_blank">Patrones de creación</a></h2>
<h2>
Factory Method</h2>
<div>
"Define una interfaz para crear un objeto, pero deja que sean las subclases las que deciden que objeto instanciar. Factory Method permite a la clase diferir la instanciación del objeto a las subclases".<br />
<br />
En el post sobre <a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a> ya vimos, aunque de pasada, el patrón <b>Factory Method</b>. Abstract Factory se centra en crear familias de objetos y cada uno de los objetos de la familia se crean habitualmente mediante Factory Method.<br />
<br />
<a name='more'></a><br />
Es importante entender y distinguir entre Abstract Factory y Factory Method.<br />
<ul>
<li><b>Abstract Factoy:</b> Crea familias de productos.</li>
<li><b>Factory Method:</b> Crea los objetos concretos.</li>
</ul>
<div>
Aunque es habitual que <b>Abstract Factory</b> implemente sus métodos mediante <b>Factory Method</b>. <b>Factory Method</b> no tiene por qué aparecer solamente como métodos de <b>Abstract Factory</b>, veremos infinidad de veces <b>Factory Method</b> fuera de una factoría abstracta.<br />
<br /></div>
<div>
<h3>
</h3>
<h3>
El problema</h3>
</div>
<div>
Veamos el problema que intenta resolver el patrón <b>Factory Method</b> y cómo lo resuelve. Vamos a basarnos en el ejemplo que ofrecen <b>GoF </b>en su libro Design Patterns: Elements of Reusable Object-Oriented Software.</div>
<div>
<br /></div>
<div>
Supongamos que vamos a crear un framework de aplicaciones que muestran documentos al usuario. Es decir, si un desarrollador quiere crear una aplicación de edición de textos usaría nuestro framework que le facilitará la tarea.</div>
<div>
<br /></div>
<div>
Nuestro framework tendrá dos entidades clave, la entidad <b>Application </b>y la entidad <b>Document</b>. Ambas entidades son abstractas y los desarrolladores deberán heredar de estas clases para implementar sus aplicaciones. Para crear una aplicación de dibujo, por ejemplo, el desarrollador que use nuestro framework creará las clases <b>DrawingApplication </b>y <b>DrawingDocument</b>.</div>
<div>
<br /></div>
<div>
La clase <b>DrawingApplication </b>es la responsable de gestionar los documentos <b>DrawingDocumet</b>, por ejemplo creará un documento cuando el usuario seleccione Nuevo o Abrir desde el menú de la aplicación.</div>
<br />
Veamos el diagrama UML del patrón<b> FactoryMethod</b> antes de seguir:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUIeE5oWO2ZPzoe5doX8rl-Mii38dBseRhkqQQqobmYwdyqSaVBTM5jxNGnzcBLipgH6yG2ywDXX2R6TGEzMTo81iJAcZrdZtbkVyX-nKGQo8BghCeQuR8eBH5xJvLZH3EKYGbR0H_4ann/s1600/FactoryMethodUml.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgUIeE5oWO2ZPzoe5doX8rl-Mii38dBseRhkqQQqobmYwdyqSaVBTM5jxNGnzcBLipgH6yG2ywDXX2R6TGEzMTo81iJAcZrdZtbkVyX-nKGQo8BghCeQuR8eBH5xJvLZH3EKYGbR0H_4ann/s1600/FactoryMethodUml.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
El siguiente es el diagrama de la adaptación del patrón <b>Factory Method</b> a nuestro problema concreto:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZwcePUuak6IvmcddJ15CZTL3NUE_vJG84p3U2GHUUFHz2cNwWfLXZoMTO52FY5lmPcZh8FSbNI_z65rQd8MotL_AXH0r863qzDEbqXddekWC1kZdqaSsXUsOo24eCL6ga_BabtDxupbTd/s1600/FactoryMethod.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZwcePUuak6IvmcddJ15CZTL3NUE_vJG84p3U2GHUUFHz2cNwWfLXZoMTO52FY5lmPcZh8FSbNI_z65rQd8MotL_AXH0r863qzDEbqXddekWC1kZdqaSsXUsOo24eCL6ga_BabtDxupbTd/s1600/FactoryMethod.png" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Ya que el tipo de documento (<b>DrawingDocument</b>) es específico de cada tipo de aplicación (<b>DrawingApplication</b>), cuando desarrollemos nuestro framework al implementar la clase <b>Application </b>no sabemos qué clase concreta de <b>Document </b>debemos crear. La clase <b>Application </b>sólo sabe cuándo debe crear un objeto <b>Document</b>, pero no que tipo de documento crear. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Este es el problema que nos encontramos al desarrollar nuestro framework. En nuestra clase <b>Application </b>debemos crear un objeto de tipo <b>Document </b>pero no podemos saber cuál.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
El patrón <b>Factory Method</b> ofrece una solución a este problema. Encapsula la responsabilidad de la creación del documento concreto, en nuestro caso en el método abstracto <b>CreateDocument </b>de la clase <b>Application</b>, y mueve esa responsabilidad fuera de nuestro framework a la clase <b>DrawingApplication</b>.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Veamos cómo sería el código del framework y del cliente:</div>
<pre class="brush:csharp;">namespace Framework
{
public class Document
{
public virtual void Open()
{
// Lógica para abrir el documento.
// Este método puede ser abstracto o
// como en este caso virtual e
// implementar una lógica razonable
// por defecto del tipo
// System.Windows.Forms.OpenFileDialog();
}
}
/// <summary>
/// Clase abstracta para crear aplicaciones concretas.
/// </summary>
public abstract class Application
{
/// <summary>
/// Colección de documentos en la aplicación.
/// </summary>
private List<Document> documetList;
public Application()
{
documetnList = new List<Document>();
}
/// <summary>
/// Método abstracto para crear un documento específico (Factory Method).
/// </summary>
/// <returns></returns>
protected abstract Document CreateDocument();
/// <summary>
/// Crea un nuevo documeto,
/// lo agrega a la lista de documentos de la aplicación y lo muestra.
/// </summary>
public void NewDocument()
{
Document newDocument = CreateDocument();
documetList.Add(newDocument);
newDocument.Open();
}
public void OpenDocument()
{
// Lógica para abrir un documento.
}
}
}
namespace Client
{
public class DrawingDocument : Framework.Document
{
public override void Open()
{
// Podemos sobrescribir este método y
// crear una lógica específica
// para abrir un DrawingDocument
// o no sobrescribirlo y usar la lógica
// por defecto de la clase base.
}
}
public class DrawingApplication : Framework.Application
{
protected override Framework.Document CreateDocument()
{
// Creamos y devolvemos un documento
// específico de nuestra aplicación
// cliente del framework
return new DrawingDocument();
}
}
}
</pre>
<br />
<div>
El patrón <b>Factory Method</b> establece puntos de extensión de nuestro framework, el hecho de crear un objeto mediante <b>Factory Method</b> en lugar de crearlo directamente nos da más flexibilidad y heredando podemos crear versiones extendidas de las clases del framework.<br />
<br />
Por ejemplo, en la clase <b>Document </b>del framework podríamos crear un <b>Factory Method</b> llamado <b>CreateFileDialog </b>que nos permite crear un cuadro de dialogo para abrir un archivo con una implementación por defecto (método virtual en C#) que muestra el cuadro de dialogo <b>OpenFileDialog </b>de Windows. La clase <b>DrawingDocument </b>puede sobrescribir este método para crear un cuadro de dialogo específico que por ejemplo muestre una vista previa del documento.<br />
<br />
Como ya hemos mencionado varias veces en esta serie de posts los patrones de diseño no son más que una guia para resolver un problema y a la hora de implementarlos tendremos que adaptarlos a nuestro problema concreto. En el caso anterior <b>CreateFileDialog </b>no es abstracto sino que ofrece una implementación razonable por defecto. Igualmente esto se ajusta al patrón<b> Factory Method</b>, ya que delega en las subclases la creación del cuadro de dialogo aunque ofrezca una implementación por defecto razonable.<br />
<br />
Quiero destacar aquí el hecho de que Factory Method delega en la subclase la creación del objeto concreto, ya que es posible que veáis implementaciones de métodos para crear objetos pero en lugar de delegar en subclases la creación es el propio método quien crea el objeto concreto. Algo tal que así:<br />
<br />
<pre class="brush:csharp;">public class PlanTextDocument
{
public PlanTextDocument CreateDocument()
{
return new PlanTextDocument();
}
}
</pre>
<br />
Esta implementación del método CreateDocument no correspondería con el patrón Template Method ya que no permite delegar la creación del documento a una subclase. Esta implementación corresponde a un método de creación y la podréis ver definida como Creation Method o Factory Function. En nuestro ejemplo el método CreateDocument de la clase DrawingApplication es un Creation Method. Gracias a mi amigo<a href="http://lnkd.in/bjVPm2a" target="_blank"> Iván Morales</a> por su sugerencia de aclarar esto.<br />
<br />
<h4>
La trampa del constructor de C#</h4>
Creo que lo visto hasta ahora es suficiente para entender el patrón <b>Factory Method</b>. Sin embargo, antes de acabar este post me gustaría comentar una "trampa" en la que podemos caer al implementar este patrón en C#.<br />
<br />
Supongamos que estamos creando un framework para que lo usen otros desarrolladores. Este framework tiene una clase <b>UserNameWriter </b>que es responsable de escribir en la consola el nombre de un usuario. La implementación de <b>UserNameWriter </b>es esta:<br />
<br />
<pre class="brush:csharp;">namespace Framework
{
public abstract class UserNameWriter
{
public UserNameWriter()
{
var userName = GetUserName();
Console.WriteLine(userName);
}
protected abstract string GetUserName();
}
}
</pre>
<br />
Como se puede ver claramente <b>UserNameWriter </b>escribe el nombre del usuario cuando se instancia.<br />
Ahora nos pondremos en la piel de un desarrollador que usará nuestro framework. Implementaremos la clase <b>GuestNameWriter </b>que escribirá en la consola el nombre de un usuario con rol de invitado en mayúsculas. Este sería el código:<br />
<pre class="brush:csharp;">namespace Client
{
public class GuestNameWriter : UserNameWriter
{
protected override string GetUserName
{
return "GUEST";
}
}
class Program
{
static void Main(string[] args)
{
var userWriter = new GuestWriter();
}
}
}
</pre>
<br />
Evidentemente, este código el ejecutarlo escribirá en la consola el string "GUEST".<br />
<br /></div>
Crearemos ahora la clase <b>LoggedUserNameWriter</b>, esta clase como se puede suponer mostrará en la consola el nombre de un usuario conectado. Este es el código:<br />
<pre class="brush:csharp;">namespace Client
{
public class LoggedUserNameWriter : UserNameWriter
{
private string userName;
public LoggedUserNameWriter(string loggedUserName)
{
userName = loggedUserName;
}
protected override string GetUserName
{
return userName.ToUpperInvariant();
}
}
class Program
{
static void Main(string[] args)
{
var userWriter = new LoggedUserNameWriter("Miguel A. González");
}
}
}
</pre>
<br />
<span style="color: red;">¡Este código sorprendentemente al ejecutarse lanzará una excepción del tipo <b>NullReferenceException</b>! </span>Pero, ¿qué hemos hecho mal al implementar <b>LoggedUserNameWriter</b>? la respuesta es simple: <b>NADA</b>.<br />
<br />
No hay nada mal en la implementación de <b>LoggedUserNameWriter</b>. El problema está en la implementación de la clase <b>UserNameWriter </b>del framework, pero nosotros como "clientes" del framework vamos a obtener una excepción que nos va a dar más de un quebradero de cabeza depurándola.<br />
<br />
Para explicar qué ha pasado realmente empezaremos por ver cómo funciona la instanciación de objetos. La instanciación de un objeto, aunque la escribimos con una sola instrucción (new), en tiempo de ejecución tiene un proceso complejo, no se trata simplemente de crear un objeto sin más.<br />
<br />
En C#, en tiempo de ejecución, cuando instanciamos un objeto se siguen los siguientes pasos:<br />
<ol>
<li>Se instancian los campos privados estáticos de la clase hija.</li>
<li>Se llama al constructor estático de la clase hija.</li>
<li>Se instancian los campos privados estáticos de la clase padre.</li>
<li>Se llama al constructor estático de la clase padre.</li>
<li>Se instancian los campos privados de instancia de la clase padre.</li>
<li>Se llama al constructor de instancia de la clase padre.</li>
<li>Se instancian los campos privados de instancia de la clase hija.</li>
<li>Se llama al constructor de instancia de la clase hija.</li>
</ol>
<br />
Ahora vamos a ver cómo se han aplicado estos pasos al instanciar nuestra clase <b>LoggedUserNameWriter</b>:<br />
<ol>
<li>Se instancian los campos privados estáticos de la clase <b>LoggedUserNameWriter </b>(en este caso no tiene).</li>
<li>Se llama al constructor estático de la clase <b>LoggedUserNameWriter </b>(en este caso no tiene).</li>
<li>Se instancian los campos privados estáticos de la clase <b>UserNameWriter </b>(en este caso no tiene).</li>
<li>Se llama al constructor estático de la clase <b>UserNameWriter </b>(en este caso no tiene).</li>
<li>Se instancian los campos privados de instancia de la clase <b>UserNameWriter </b>(en este caso no tiene).</li>
<li>Se llama al constructor de instancia de la clase <b>UserNameWriter </b>. Aquí, en este caso, pasa lo siguiente:</li>
<ol>
<li><b>UserNameWriter </b>llama al método <b>GetUserName </b>abstracto que implementa la clase <b>LoggedUserNameWriter</b>.</li>
<li><span style="color: red;">¡<b>LoggedUserNameWriter </b>lanza una excepción por que <b>userName </b>aún no se ha instanciado, tiene valor null!</span> Evidentemente tampoco se le ha asignado el valor "Miguel A. González" porque todavía no se ha llamado al constructor de la clase LoggedUserNameWriter.</li>
</ol>
<li>Se instancian los campos privados de instancia de la clase hija (No se llega a realizar porque se ha lanzado una excepción en el paso 6).</li>
<li>Se llama al constructor de instancia de la clase hija (No se llega a realizar porque se ha lanzado una excepción en el paso 6).</li>
</ol>
<div>
En resumen, la llamada al método abstracto <b>GetUseName()</b> en el constructor de la clase <b>UserNameWriter </b>provoca que se llame a la implementación el método en <b>LoggedUserNameWriter </b>incluso antes de que se haya ejecutado el constructor de <b>LoggedUserNameWriter</b>. Esto dependiendo de la implementación de la clase hija puede provocar una excepción como la que hemos visto.<br />
<br />
Al heredar de <b>UserNameWriter </b>no tenemos por qué saber cómo está implementada esta clase, y no deberíamos obtener una excepción en nuestra clase hija si está bien escrita.</div>
<div>
<br /></div>
<div>
Como desarrolladores de la clase base debemos evitar a toda costa llamar a un método abstracto o virtual desde el constructor de nuestra clase para evitar esta trampa del constructor.</div>
<div>
<br /></div>
<div>
Una mejor implementación del patrón<b> Factory Method</b> para evitar esta trampa en C# sería la siguiente:</div>
<div>
<br />
<pre class="brush:csharp;">namespace Framework
{
public abstract class UserNameWriter
{
public UserNameWriter()
{
}
protected abstract string GetUserName();
public void WriteUserName()
{
var userName = GetUserName();
Console.WriteLine(userName);
}
}
}
namespace Client
{
public class LoggedUserNameWriter : UserNameWriter
{
private string userName;
public LoggedUserNameWriter(string loggedUserName)
{
userName = loggedUserName;
}
protected override string GetUserName
{
return userName.ToUpperInvariant();
}
}
class Program
{
static void Main(string[] args)
{
var userWriter = new LoggedUserNameWriter("Miguel A. González");
userWriter.WriteUserName();
}
}
}
</pre>
<br />
Con esta implementación de <b>UserNameWriter </b>nos aseguramos que las llamadas al método abstracto se hacen siempre después de que haya terminado completamente el proceso de instanciación.<br />
<br />
Ahora si, termina este post sobre el patrón <b>Factory Method</b>. En el próximo post veremos un nuevo patrón de creación: <b>Prototype</b></div>
<br /></div>
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-2728255754328606012.post-54992543070007292672014-01-08T16:07:00.001+01:002014-01-17T08:24:15.509+01:00Patrones de creación: Builder<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20creaci%C3%B3n" style="font-size: medium; font-weight: normal;" target="_blank">Patrones de creación</a><br />
<h2>
Builder</h2>
<div>
"Separa la construcción de un objeto complejo de su representación, de forma que el mismo proceso de construcción puede crear representaciones distintas"<br />
<br />
Como en la mayoría de patrones de diseño entender la definición a primera vista es difícil. Intentaré explicar exactamente qué quiere decir esta definición del patrón Builder.<br />
La idea fundamental es separar la lógica de construcción de los objetos resultantes, de esta forma podemos reutilizar la misma lógica de construcción pero obtener distintos objetos complejos.<br />
<br />
<a name='more'></a>Vamos a definir un ejemplo del problema que el patrón Builder intenta solucionar e iremos viendo cómo solucionarlo.<br />
<br />
<h3>
El problema</h3>
<div>
Vamos a desarrollar un módulo de una aplicación de gestión. El módulo debe crear a partir de un listado de productos una serie de documentos distintos. </div>
<div>
<ul>
<li>Un archivo Excel con un listado de precios de los productos para los comerciales.</li>
<li>Un archivo en formato Word con información técnica de los productos para enviar a los responsables técnicos del cliente.</li>
<li>Un archivo en formato Pdf con información comercial para enviar a los responsables de compras de los clientes</li>
</ul>
<div>
Estos son sólo tres ejemplos, pero es fácil entender que la lista de posibles "exportaciones" de estos datos es muy amplia, y además se irá ampliando en el tiempo con distintas versiones etc.</div>
</div>
<div>
<br /></div>
<div>
Si miramos el problema desde un nivel de abstracción un poco superior al detalle de cada documento a generar nuestro problema se reduce a regenerar distintas representaciones de la misma información. El proceso de construcción es prácticamente igual en todas las forma de representar los datos. El resultado final, sin embargo, es muy distinto para cada documento que tenemos que exportar.</div>
<div>
<br /></div>
<div>
El siguiente es una mala implementación en pseudocódigo que nos podríamos plantear:</div>
<div>
<br /></div>
<pre class="brush:csharp;">class Program
{
static void Main(string[] args)
{
// Obtenemos la lista de productos
var productList = DataSource.GetProductList();
// Decidimos que documento debemos generar
switch (documentType)
{
case "PriceList":
ExportPriceList(productList);
break;
case "TechnicalInfo":
ExportTechnicalInfo(productList);
break;
case "CommercialInfo":
ExportCommercialInfo(productList);
break;
}
}
private static void ExportPriceList(List<product> productList)
{
// Creamos un documento Excel e insertamos una fila
// por cada producto con información sobre precio e impuestos.
CreateExcelDocumet();
foreach (var product in productList)
{
CreateExcelRow();
WriteProductIdCell(product);
WriteProductNameCell(product);
WriteProductPriceCell(product);
WriteProductTaxes(product);
}
SaveExcelDocument();
}
private static void ExportTechnicalInfo(List<product> productList)
{
// Creamos un documento Word e insertamos una página
// por cada producto con información técnica sobre el producto.
CreateWordDocumet();
foreach (var product in productList)
{
CreateWordPage();
WriteProductIdCell(product);
WriteProductNameCell(product);
DrawProductImage(product);
WriteProductTechnicalDetails(product);
}
SaveWordDocument();
}
private static void ExportCommercialInfo(List<product> productList)
{
// Creamos un documento Pdf e insertamos una página
// por cada producto con información sobre las ventajas
// comerciales de nuestros productos.
CreatePdfDocumet();
foreach (var product in productList)
{
CreatePdfPage();
WriteProductIdCell(product);
WriteProductNameCell(product);
DrawProductImage(product);
WriteProductCommercialAdvantages(product);
}
SavePdfDocument();
}
}</pre>
<div>
<br />
El problema con el código anterior es que el número de posibles formatos que debemos crear está abierto y debemos buscar una forma fácil de agregar nuevas conversiones.<br />
<br />
<h3>
Builder</h3>
El patrón builder propone una solución a este problema que se basa en separar la forma de construir estos objetos complejos de los objetos que resultan después de la construcción. Dicho de otra forma, propone crear un objeto que indique qué partes debemos construir y otro que construya esas partes como mejor crea de acuerdo con el resultado que se quiere obtener.<br />
<br />
Veamos el diagrama UML del patrón Builder y los objetos que participan en el:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8P7q_emre4_AMnlypZEhu5ExM6kWmVXZyn1XUV4xwmizitEKab51OyD31hchn1Ei95an66M3c7ntXTSvbsIor7NHaOr28SrMOWRO3q3GaFG2hBKUlreYAWWqhvhy4cRQ_EtsY0aZIXjUz/s1600/Builder.png" imageanchor="1" style="clear: left; margin-bottom: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8P7q_emre4_AMnlypZEhu5ExM6kWmVXZyn1XUV4xwmizitEKab51OyD31hchn1Ei95an66M3c7ntXTSvbsIor7NHaOr28SrMOWRO3q3GaFG2hBKUlreYAWWqhvhy4cRQ_EtsY0aZIXjUz/s400/Builder.png" height="130" width="400" /></a></div>
<span id="goog_682777134"></span><span id="goog_682777135"></span><br />
Estos son los objetos participantes en el patrón Builder:<br />
<ul>
<li><b>Builder</b>: Clase abstracta que define las partes a crear del objeto producto. Esta clase abstracta podemos sustituirla por una interfaz.</li>
<li><b>ConcreteBuilder</b>: Implementa la interfaz de Builder para crear y ensamblar las partes de Product. Provee una interfaz para obtener el producto (GetResult())</li>
<li><b>Director</b>: Construye un producto usando la interfaz Builder</li>
<li><b>Product</b>: Representa el objeto complejo que se construye.</li>
</ul>
<blockquote class="tr_bq">
El patrón Builder propone crear un objeto que indique qué partes debemos construir y otro
que construya esas partes como mejor crea de acuerdo con el resultado
que se quiere obtener.</blockquote>
<br />
Será más fácil entender sobre nuestro ejemplo. Este es el diagrama UML de nuestra adaptación del patrón builder que aplicaremos en nuestro ejemplo:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHJLqa0nwhMWQf3T9XoeV_F7eZ2-E90J4nCgol5Pqf4IQWDyYT1RNBlxbqmGGDci_5eV-t5peW2euYzXAWAWojDejnhY7wboadrshtkPzLptMWcziVRalNgSyImwOiLypwi4_upsh5BWs3/s1600/Builder.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiHJLqa0nwhMWQf3T9XoeV_F7eZ2-E90J4nCgol5Pqf4IQWDyYT1RNBlxbqmGGDci_5eV-t5peW2euYzXAWAWojDejnhY7wboadrshtkPzLptMWcziVRalNgSyImwOiLypwi4_upsh5BWs3/s1600/Builder.png" /></a></div>
<br />
<br />
Estos son nuestros objetos del Builder:<br />
<ul>
<li><b>Builder:</b> Clase abstracta donde definimos todas las partes que podemos crear del producto. En nuestro ejemplo la implementaremos como una clase abstracta con métodos virtuales con implementación vacía. Esto nos permitirá implementar en cada ConcreteBuilder implementar sólo los métodos que necesitemos. </li>
<li><b>PriceListBuilder, TechnicalCatalogBuilder, CommercialCatalogBuilder:</b> Son los ConcreteBuilder para cada tipo de documento que queremos exportar. Cada uno sobrescribe los métodos virtuales de Builder que necesita.</li>
<li><b>ProductReader (Director):</b> Es la clase responsable de leer el listado de productos y llamar a método Build de cada parte a construir.</li>
<li><b>Excel, Word, Pdf:</b> Instancias concretas de cada producto que podemos construir. Archivos de distintos formatos.</li>
</ul>
Veamos la implementación en pseudocódigo ya que la implementación concreta de los ConcreteBuilder no es interesante para explicar el patrón:<br />
<pre class="brush:csharp;">public abstract class Builder
{
public virtual void BuildNewProduct(Product product) { }
public virtual void BuildProductId() { }
public virtual void BuildProductName() { }
public virtual void BuildProductPrice() { }
public virtual void BuildProductTaxes() { }
public virtual void BuildProductTechicalInfo() { }
public virtual void BuildProductCommercialInfo() { }
public abstract object GetResult();
}
public class PriceListBuilder : Builder
{
private dynamic currentSheet;
private Microsoft.Office.Interop.Excel.Application app;
private int currentRow;
private Product currentProduct;
public PriceListBuilder()
{
// Creamos una aplicación Excel y obtenemos la hoja actual.
app = new Microsoft.Office.Interop.Excel.Application();
app.Workbooks.Add();
app.Workbooks[1].Sheets.Add();
currentSheet = app.Workbooks[1].Sheets[1];
currentRow++;
WriteHeader();
}
private void WriteHeader()
{
//Escribimos las cabeceras de las columnas.
}
public override void BuildNewProduct(Product product)
{
// Asignamos el product actual.
currentProduct = product;
// Nos movemos a la siguiente fila.
currentRow++;
}
public override void BuildProductId()
{
currentSheet.Cells[currentRow, 1] = currentProduct.Id;
}
public override void BuildProductName()
{
currentSheet.Cells[currentRow, 2] = currentProduct.Name;
}
public override void BuildProductPrice()
{
currentSheet.Cells[currentRow, 3] = currentProduct.Price;
}
public override void BuildProductTaxes()
{
currentSheet.Cells[currentRow, 4] = currentProduct.Taxes;
}
public override object GetResult()
{
return app;
}
}
public class TechnicalCatalogBuilder : Builder
{
/*
* Esta clase crea un documento word con información técnica
* de los productos. Por lo tanto no sobreescribe los métodos que
* se refieren a información sobre precios, impuestos, etc.
*
*
*/
public TechnicalCatalogBuilder()
{
// Creamos documento Word
}
public override void BuildNewProduct(Product product)
{
// Agregamos una nueva página al documento Word.
}
public override void BuildProductId()
{
// Agregamos un párrafo con el identificador del producto.
}
public override void BuildProductName()
{
// Agregamos un párrafo con el nombre del producto.
}
public override void BuildProductTechicalInfo()
{
// Agregamos un párrafo con la información técnica del producto.
}
public override object GetResult()
{
// Devolvemos el documento Word.
return wordDocument;
}
}
public class CommercialCatalogBuilder : Builder
{
/*
* Esta clase crea un documento pdf con información comercial
* de los productos. Por lo tanto no sobreescribe los métodos que
* se refieren a información técnica, etc.
*
*
*/
public CommercialCatalogBuilder()
{
// Creamos documento Pdf
}
public override void BuildNewProduct(Product product)
{
// Agregamos una nueva página al documento Pdf.
}
public override void BuildProductId()
{
// Agregamos un párrafo con el identificador del producto.
}
public override void BuildProductName()
{
// Agregamos un párrafo con el nombre del producto.
}
public override void BuildProductCommercialInfo()
{
// Agregamos un párrafo con la información commercial del producto.
}
public override object GetResult()
{
// Devolvemos el documento Pdf.
return pdfDocument;
}
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public double Taxes { get; set; }
}
public class ProductReader
{
private IEnumerable<product> products;
private Builder builder;
public ProductReader(IEnumerable<product> productList, Builder concreteBuilder)
{
products = productList;
builder = concreteBuilder;
}
public object Construct()
{
foreach (var product in products)
{
builder.BuildNewProduct(product);
builder.BuildProductId();
builder.BuildProductName();
builder.BuildProductPrice();
builder.BuildProductTaxes();
builder.BuildProductTechicalInfo();
builder.BuildProductCommercialInfo();
}
return builder.GetResult();
}
}
class Program
{
static void Main(string[] args)
{
IEnumerable<product> productList = ProductProvider.GetProducts();
Builder concreteBuilder = GetConcreteBuilder();
ProductReader director = new ProductReader(productList, concreteBuilder);
var resultado = director.Construct();
// Manejar el resultado como necesitemos,
// Guardamos a disco, enviar por email, etc.
}
private static Builder GetConcreteBuilder()
{
// Devolvemos el builder concreto que necesitamos
// en base a los criterios que requiera nuestra
// aplicación. Por ejemplo selección del usuario.
// La lógica de cómo obtener el builder concreto
// dependerá de cada aplicación.
return new PriceListBuilder();
}
}
</pre>
<br />
En el ejemplo hemos decidido crear archivos de diferentes formatos, pero podría haber sido diferentes versiones de un archivo, etc. <br />
Como podemos observar hemos encapsulado la lógica de construcción en el método <b>Construct()</b> de la clase <b>ProductReader </b>y la forma concreta en que se ensamblan todas las piezas de cada producto (Excel, word, Pdf) a los <b>ConcreteBuilder</b>. Con esto conseguimos reutilizar la lógica de construcción del objeto complejo y para crear un nuevo tipo de "exportación" solamente tenemos que crear un nuevo <b>ConcreteBuilder.</b><br />
<h3>
Consecuencias de usar el patrón Builder</h3>
Estas son las ventajas e inconvenientes principales de usar el patrón <b>Builder </b>en la creación de objetos complejos:<br />
<h4>
Ventajas </h4>
<ul>
<li>Nos permite cambiar la representación interna de los productos. El director (<b>ProductReader</b>) construye el objeto a través de una clase abstracta por lo que está aislado de la forma en que se ensambla el objeto. Ya que la clase <b>Builder </b>oculta al director la representación del objeto (la forma en que se ensambla), todo lo que tenemos que hacer para cambiar la representación del producto es crear un nuevo <b>ConcreteBuilder</b>. Por ejemplo una clase AcmeErpInteropBuilder que creará un archivo de productos para importar en el ERP de la empresa Acme.</li>
<li>Aísla el código de la construcción (el código de <b>ProductReader</b>) del código de la representación (el código de cada <b>ConcreteBuilder</b>). El cliente no necesita saber nada sobre las clases que definen la estructura de los archivos que se generan ya que las clases ConcreteBuilder no aparecen en la interfaz pública del patrón.</li>
<li>Tenemos mucho control sobre el proceso de construcción del objeto. El patrón Builder crea los objetos complejos paso a paso bajo la "supervisión" del director. Esto nos da mucho control sobre el proceso de construcción en aspectos como los pasos necesarios para la construcción del objeto complejo así como el orden de esos pasos.</li>
</ul>
<h4>
Inconvenientes</h4>
<ul>
<li>La interfaz de la clase <b>Builder</b> debe ser lo suficientemente genérica para permitir la construcción de cualquier tipo de objetos por parte de los <b>ConcreteBuilder</b>. Esto hace que tengamos que tener en la interfaz de <b>Builder </b>métodos que no usarán todos con <b>ConcreteBuilder</b>.</li>
<li>El objeto complejo creado a veces se devuelve poco tipado. Como en nuestro caso cada builder concreto devuelve objetos diferentes la el tipado de la interfaz del método <b>Construct()</b> es débil con respecto al tipo devuelto, en nuestro caso <b>object</b>.</li>
</ul>
Al igual que el patrón <a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a>, el patrón Builder se usa en la construcción de objetos complejos. La principal diferencia es que el patrón Builder se centra en la creación paso a paso del objeto, mientras que el patrón <a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a> se centra en crear familias de objetos (simples o complejos). El patrón Builder crea objetos mediante una serie de pasos de los que el último es obtener el objeto creado. Desde el punto de vista de <a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a> el objeto se crea inmediatamente, en un solo paso.</div>
<br />
<h3>
Usos conocidos</h3>
<div>
Podemos ver una variante del patrón Builder en .NET Framework en objetos como <b>DbConnectionStringBuilder, SqlConnectionStringBuilder, OracleConnectionStringBuilder,</b> etc. que mediante el establecimiento de propiedades permiten crear cadenas de conexión sintácticamente correctas.<br />
<br />
Hasta aquí el patrón Builder. En el próximo post hablaremos de otro patrón de creación. Factory Method<br />
<br />
Espero vuestros comentarios.</div>
<br /></div>
Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-2728255754328606012.post-21489850028572641862013-12-16T09:02:00.000+01:002014-01-10T14:43:04.262+01:00Patrones de creación: Abstract Factory<h2>
<span style="font-size: small; font-weight: normal;">Este post corresponde a la serie <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">Patrones de diseño</a> - </span><a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20creaci%C3%B3n" style="font-size: medium; font-weight: normal;" target="_blank">Patrones de creación</a></h2>
<h2>
Abstract Factory</h2>
<div>
Oficialmente el patrón Abstract Factory:<i> "Provee una interfaz para crear familias de objetos dependientes o relacionados sin conocer sus clases concretas".</i></div>
<div>
<i><br /></i></div>
<div>
Como ya mencioné en el <a href="http://designcodetips.blogspot.com.es/2013/11/design-patters-object-oriented-programming.html" target="_blank">post de introducción</a> de la <a href="http://designcodetips.blogspot.com.es/search/label/Patrones%20de%20dise%C3%B1o" target="_blank">serie "Patrones de diseño"</a> un patrón es una guía para solucionar un problema. Así que lo primero será definir el problema para el que aplicar este patrón será útil.</div>
<div>
<br /></div>
<div>
Vamos a empezar por analizar la definición del patrón. Lo primero que me pregunto es ¿por qué querríamos crear una familia de objetos? Y después, ¿cómo es posible que no conozca sus clases concretas? Si voy a crear una familia de objetos necesitaré conocer que tipos de objetos voy a crear ¿no? Vamos a aclarar estas dudas planteando el problema que queremos resolver.<br />
<br />
<a name='more'></a><br />
<br /></div>
<h3>
El problema</h3>
<div>
Estamos implementando una aplicación para una tienda de venta de equipos informáticos. La tienda configura presupuestos para dos tipos de ordenadores. La opción A es un equipo de bajo precio y la opción B es un equipo de alto rendimiento. Vamos a crear un módulo que muestre los componentes de estos equipos. </div>
<div>
<br /></div>
<div>
Una primera aproximación podría ser la siguiente:</div>
<div>
<pre class="brush:csharp;">public class Celeron
{
public Celeron()
{
Name = "Intel Celeron 2.60GHz";
}
public string Name { get; set; }
}
public class Xeon
{
public Xeon()
{
Name = "Intel Xeon Phi 5110P";
}
public string Name { get; set; }
}
public class LowCostRam
{
public LowCostRam()
{
GB = 4;
}
public int GB { get; set; }
}
public class HighPerformanceRam
{
public HighPerformanceRam()
{
GB = 32;
}
public int GB { get; set; }
}
class Program
{
static void Main(string[] args)
{
string computerType = args.GetUpperBound(0) >= 0 ? args[0] : "lowcost";
if (computerType == "lowcost")
{
Console.WriteLine("LowCost Computer Components");
Console.WriteLine(string.Format("CPU: {0}", new Celeron().Name));
Console.WriteLine(string.Format("RAM: {0}GB", new LowCostRam().GB));
}
else
{
Console.WriteLine("High Performance Computer Components");
Console.WriteLine(string.Format("CPU: {0}", new Xeon().Name));
Console.WriteLine(string.Format("RAM: {0}GB", new HighPerformanceRam().GB));
}
Console.ReadLine();
}
}
</pre>
<br />
A pesar de ser un ejemplo muy sencillo podemos ver algunos problemas en este código.<br />
<br />
<ul>
<li>De entrada este código viola el <a href="http://designcodetips.blogspot.com.es/2013/10/dependency-inversion-principle.html" target="_blank">Principio de Inversión de Dependencias (DIP)</a>. El método Main que muestra los componentes de un ordenador tiene dependencias de tipos concretos de componentes.</li>
</ul>
<br />
<br />
<ul>
<li>Además de esto, a nivel de lógica de negocio, es fácil equivocarse y mezclar componentes de ordenadores distintos. Es decir, por error podríamos mostrar un ordenador con CPU Xeon y Ram 4GB. Cosa que por nuestra lógica de negocio no deberíamos hacer. </li>
</ul>
<br />
Este segundo punto contesta a una de las preguntas que nos hacíamos al principio del post. ¿Por qué querríamos crear una familia de objetos relacionados? La respuesta obvia es que si creamos los objetos (en este caso los componentes del ordenador) en familias en lugar de crearlos de forma aislada nos aseguramos que siempre creamos objetos que están relacionados entre sí, disminuyendo la probabilidad de equivocarnos. En nuestro caso lo más evidente será crear la familia de componentes "LowCost" y la familia de componentes "HighPerformance". </div>
<br />
La respuesta a la pregunta sobre ¿cómo crear una familia de objetos sin conocer los tipos concretos de los que se trata? como habréis adivinado es mediante una factoría abstracta (Abstract Factory). Así que vamos a ver el patrón y luego volveremos al ejemplo para aplicarlo.<br />
<div>
<br />
<h3>
Abstract Factory </h3>
</div>
<div>
La siguiente es la estructura UML del patrón Abstract Factory</div>
<div>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBlLWqYmNVRcJj1tDRg2jkinHrCi1Zpsk6-d2JS1b8H1c1_Hlq7qobAVVTi9WVOZo1rFt6pVFaCMs7wllmnw9qYsfhghYdpiw_7rtZLYJ58pCcVGjjMg27feQzZfWdti5yNzmeS3hRDpPd/s1600/AbstractFactory.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhBlLWqYmNVRcJj1tDRg2jkinHrCi1Zpsk6-d2JS1b8H1c1_Hlq7qobAVVTi9WVOZo1rFt6pVFaCMs7wllmnw9qYsfhghYdpiw_7rtZLYJ58pCcVGjjMg27feQzZfWdti5yNzmeS3hRDpPd/s1600/AbstractFactory.png" /></a></div>
<br />
Estos son los participantes en este patrón:<br />
<br />
<ul>
<li><b>AbstractFactory </b>provee una interfaz con operaciones para crear productos abstractos. En nuestro ejemplo crearemos ComputerComponentFactory con las operaciones CreateCpu y CreateRam. Podríamos usar interfaces en lugar de clases abstractas puras.</li>
<li><b>ConcreteFactory </b>implementa las operaciones para crear familias de objetos concretos. En nuestro ejemplo implementaremos LowCostFactory e HighPerformanceFactory.</li>
<li><b>AbstractProduct </b>declara una interfaz (o clase base) para los productos. En nuestro ejemplo declararemos las clases base Cpu y Ram para los productos. Podríamos usar interfaces en lugar de clases abstractas puras.</li>
<li><b>ConcreteProducts </b>definen los productos que se crearán en las factorías concretas. Nuestra LowCostFatory devolverá un Celeron al llamar al método CreateCpu. Heredan de AbstractProduct</li>
<li><b>Client </b>usa la factoría. Sólo usa la interfaz declarada por AbstractFactory y los tipos AbstractProduct.</li>
</ul>
<div>
El siguiente diagrama es el del patrón Abstract Factory como lo implementaremos en nuestro ejemplo:</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCQlPGHbcXjvJixLKxMycC8mJ1sR2sDcyZQeI1MGmFKFqnoQnYSybcnLislWknf20khNf33A_qCHglvQNJoon1tFXG040F-ezbiaEuKzJqcLN4Hm-l8ttmbaTEGV74_hwdaYTsOH0LdWGz/s1600/AFComputerStore.png" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCQlPGHbcXjvJixLKxMycC8mJ1sR2sDcyZQeI1MGmFKFqnoQnYSybcnLislWknf20khNf33A_qCHglvQNJoon1tFXG040F-ezbiaEuKzJqcLN4Hm-l8ttmbaTEGV74_hwdaYTsOH0LdWGz/s1600/AFComputerStore.png" /></a></div>
<br />
El ejemplo anterior refactorizado para usar Abstract Factory quedaría más o menos así:<br />
<pre class="brush:csharp;">public abstract class Cpu
{
public string Name { get; set; }
}
public class Celeron : Cpu
{
public Celeron()
{
Name = "Intel Celeron 2.60GHz";
}
}
public class Xeon : Cpu
{
public Xeon()
{
Name = "Intel Xeon Phi 5110P";
}
}
public abstract class Ram
{
public int GB { get; set; }
}
public class LowCostRam : Ram
{
public LowCostRam()
{
GB = 4;
}
}
public class HighPerformanceRam : Ram
{
public HighPerformanceRam()
{
GB = 32;
}
}
public abstract class ComputerComponentFactory
{
public abstract Cpu CreateCpu();
public abstract Ram CreateRam();
}
public class LowCostFactory : ComputerComponentFactory
{
public override Cpu CreateCpu()
{
return new Celeron();
}
public override Ram CreateRam()
{
return new LowCostRam();
}
}
public class HighPerformanceFactory : ComputerComponentFactory
{
public override Cpu CreateCpu()
{
return new Xeon();
}
public override Ram CreateRam()
{
return new HighPerformanceRam();
}
}
class Program
{
static void Main(string[] args)
{
string computerType = args.GetUpperBound(0) >= 0 ? args[0] : "lowcost";
ComputerComponentFactory factory = CreateFactory(computerType);
Console.WriteLine("Computer Components");
Console.WriteLine(string.Format("CPU: {0}", factory.CreateCpu().Name));
Console.WriteLine(string.Format("RAM: {0}GB", factory.CreateRam().GB));
Console.ReadLine();
}
private static ComputerComponentFactory CreateFactory(string computerType)
{
if (computerType == "lowcost")
return new LowCostFactory();
else
return new HighPerformanceFactory();
}
}
</pre>
<br />
Ahora obtenemos la factoría concreta a través del método <i>CreateFactory</i>. El cliente de la factoría (nuestro método Main) no trata directamente con las factorías concretas, sino que lo hace a través de la factoría abstracta <i>ComputerComponentFactory. </i>De la misma forma, también trata con las clases abstractas de productos (Cpu y Ram) en lugar de hacerlo con los productos concretos.<br />
<br />
Es importante entender que lo fundamental a la hora de aplicar este patrón es que la factoría abstracta en ningún caso es responsable de crear productos concretos, sino que como hemos visto ya esa responsabilidad la delega en las clases hijas (factorías concretas).<br />
<br />
Analicemos como ejemplo el código siguiente:
<br />
<pre class="brush:csharp;">public class ComputerComponentFactory
{
private string ComputerType;
public ComputerComponentFactory(string computerType)
{
ComputerType = computerType;
}
public Cpu CreateCpu()
{
if (ComputerType == "lowcost")
return new Celeron();
else
return new Xeon();
}
public Ram CreateRam()
{
if (ComputerType == "lowcost")
return new LowCostRam();
else
return new HighPerformanceRam();
}
}
</pre>
<br />
Esta implementación de <i>ComputerComponentFactory </i>a pesar de que los métodos <i>CreateCpu </i>y <i>CreateRam </i>crean los productos correctos basándose en el parámetro <i>computerType </i>que recibe por el constructor no podemos considerarla una factoría abstracta ya que asume la responsabilidad de crear los objetos concretos en lugar de delegar esa responsabilidad en las subclases. A esta clase podríamos llamarla Factoría o Factoría Simple, pero no es en ningún caso Factoría Abstracta.<br />
<h3>
</h3>
<h3>
Consecuencias de usar Abstract Factory</h3>
<div>
El patrón Abstract Factory tiene las siguientes ventajas e inconvenientes:<br />
<h4>
Ventajas</h4>
</div>
<div>
<ul>
<li><u>Aísla las clases concretas.</u> La factoría abstracta nos ayuda a controlar los objetos concretos que crea la aplicación, ya que el cliente manipula esos objetos siempre a través de la interfaz abstracta de los objetos.</li>
<li><u>Facilita el intercambio de familias de productos.</u> Como hemos visto en los ejemplos la factoría concreta que crea los productos sólo aparece una vez en la aplicación, en el lugar en el que se instancia. Esto hace que sea fácil cambiar la factoría concreta que usa la aplicación. En el ejemplo anterior vemos que es muy fácil cambiar de la factoría "low cost" a la factoría "high performance" sin afectar al resto de la aplicación.</li>
<li><u>Promueve la consistencia entre productos.</u> Cuando los objetos de una familia se diseñan para trabajar juntos es importante asegurarnos que la aplicación no mezcla objetos de distinta familia. A esto nos ayuda el patrón Abstract Factory.</li>
</ul>
<h4>
Inconvenientes</h4>
<ul>
<li><u>Es difícil agregar nuevos productos.</u> Este quizá sea el inconveniente más importante de Abstract Factory y hay que tenerlo muy en cuenta a la hora de valorar si es conveniente o no el uso del patrón. Si queremos añadir nuevos productos a la familia tendremos que reescribir la factoría abstracta y todas las factorías concretas que hayamos creado. En circunstancias donde preveamos que los cambios en las familias de objetos serán frecuentes y las factorías concretas numerosas deberíamos tratar de evitar el uso de factorías abstractas.</li>
</ul>
</div>
<h3>
Una variante</h3>
<div>
Cómo ya he comentado en un <a href="http://designcodetips.blogspot.com.es/2013/11/design-patters-object-oriented-programming.html" target="_blank">post anterior</a>, los patrones de diseño no son una solución directamente aplicable sino más bien una guía para solucionar un problema recurrente. Basándonos en esa guía podemos proponer soluciones concretar que aplican el patrón con más o menos adaptaciones. A continuación vamos a ver cómo aplicamos una adaptación del patrón Abstract Factory para solucionar un problema concreto.<br />
<br />
Supongamos que vamos a desarrollar una aplicación web MVC. La aplicación consta de 2 páginas, la página principal y la página privada. Además hay dos tipos de usuarios en esta aplicación, el invitado y el administrador. De forma que nuestra aplicación mostrará distintas páginas en función del tipo de usuario según la siguiente tabla.<br />
<br />
<table>
<tbody>
<tr>
<th>Usuario</th>
<th>Acción</th>
<th>Página a mostrar</th>
</tr>
<tr>
<td>Invitado</td>
<td>Home</td>
<td>GuestHomeView</td>
</tr>
<tr>
<td>Invitado</td>
<td>Private</td>
<td>NotAvailable</td>
</tr>
<tr>
<td>Administrador</td>
<td>Home</td>
<td>AdminHomeView</td>
</tr>
<tr>
<td>Administrador</td>
<td>Private</td>
<td>AdminPrivateView</td>
</tr>
</tbody></table>
<br />
Quien no conozca el patrón MVC que no se asuste, lo que vamos a hacer es algo muy simple. </div>
<div>
Solo decir que en este patrón la <b>V</b> es de View, y aunque no es así, para nuestro ejemplo diremos que es el "archivo Html" que vamos a mostrar al usuario. <b>C</b> es de Controller y esta clase debe encargarse fundamentalmente de atender las peticiones del usuario y decidir que Vista se muestra al usuario.<br />
<br />
En base al usuario y a través de una Abstract Factory vamos a obtener no una familia de objetos en el sentido estricto sino el nombre de las vistas que debemos renderizar para un usuario concreto. Es decir, nuestra factoría abstracta tendrá métodos del tipo <b><span style="color: blue;">string </span>GetHomeViewName()</b> que devuelve el nombre de la vista Home para el usuario actual.<br />
<br /></div>
<div>
Veamos el código:<br />
<pre class="brush:csharp;">/// <summary>
/// factoría abstracta de vistas
/// </summary>
public abstract class ViewsFactory
{
public abstract string GetHomeViewName();
public abstract string GetPrivateViewName();
}
/// <summary>
/// Factoría concreta de las vistas del administrador.
/// </summary>
public class AdminViewsFactory : ViewsFactory
{
public override string GetHomeViewName()
{
return "AdminHomeView";
}
public override string GetPrivateViewName()
{
return "AdminPrivateView";
}
}
/// <summary>
/// Factoría concreta de las vistas del invitado
/// </summary>
public class UserViewFactory : ViewsFactory
{
public override string GetHomeViewName()
{
return "UserHomeView";
}
public override string GetPrivateViewName()
{
// El invitado no tiene acceso a la vista privada
// en su lugar se muestra una vista que indica que no
// tiene el acceso permitido.
return "NotAllowedView";
}
}
/// <summary>
/// Singleton para instanciar y acceder a la Abstract Factory
/// Veremos el patrón Singleton en un próximo post.
/// </summary>
public class GlobalFactories
{
private static GlobalFactories _instance;
public static GlobalFactories Instance
{
get { return _instance ?? (_instance = new GlobalFactories()); }
}
private GlobalFactories()
{
}
public ViewsFactory ViewsFactory { get; set; }
}
/// <summary>
/// Clase que configura la vista en
/// el inicio de la aplicación
/// </summary>
public static class FactoriesConfig
{
/// <summary>
/// Este método configura la ViewsFactory.
/// Se llama al iniciar la aplicación
/// </summary>
public static void ConfigFactory()
{
string userName;
//
// Aquí va la lógica que determine
// el usuario actual y asigne UserName
//
userName = "Admin";
if (userName == "Admin")
GlobalFactories.Instance.ViewsFactory = new AdminViewsFactory();
else
GlobalFactories.Instance.ViewsFactory = new UserViewFactory();
}
}
/// <summary>
/// Controller de la página principal.
/// Decide qué vista renderizar cuando el
/// usuario acceda a http://www.midemoabstractfactory.com
/// </summary>
public class HomeController : Controller
{
public ActionResult Index()
{
return View(GlobalFactories.Instance.ViewsFactory.GetHomeViewName());
}
}
/// <summary>
/// Controller de la página privada.
/// Decide qué vista renderizar cuando el
/// usuario acceda a http://www.midemoabstractfactory.com/private
/// </summary>
public class PrivateController : Controller
{
public ActionResult Index()
{
return View(GlobalFactories.Instance.ViewsFactory.GetPrivateViewName());
}
}
</pre>
<br /></div>
<div>
La solución que el código anterior aplica al problema de qué vista renderizar en cada momento está claramente inspirada en el patrón Abstract Factory, aunque tiene pequeñas adaptaciones para ajustar la solución al problema concreto.<br />
<br />
<h3>
Usos conocidos</h3>
</div>
<div>
El uso de factorías abstractas es muy habitual y podemos ver implementaciones de este patrón fácilmente. Como ejemplo veamos la forma en que ADO.NET conecta e interactúa con una base de datos:</div>
<div>
<ul>
<li><u>DbProviderFactory (AbstractFactory): </u>Define la interfaz para crear los objetos que conectan e interactuan con una base de datos.</li>
<li><u>SqlClientFactory (ConcreteFactory): </u>Implementan los métodos que crean los objetos concretos para interactuar con SQL Server. Hay diversas factorías concretas para diversas bases de datos: OracleClientFactory, OleDbFactory, OdbcFactory, etc. Cualquiera puede crear una factoría para su propia base de datos.</li>
<li><u>DbConnection, DbCommand, etc (Abstract Product):</u> Productos abstractos. Definen la interfaz para cada tipo de producto.</li>
<li><u>SqlConnection, SqlCommand, etc (Concrete Products):</u> Productos concretos para cada sistema de base de datos. Igualmente existen OracleConnection, etc.</li>
</ul>
<div>
En ADO.NET usar un gestor de base de datos u otro se reduce a simplemente instanciar una u otra factoría concreta.</div>
</div>
<div>
<br /></div>
<h3>
Patrones relacionados</h3>
<div>
Como ya hemos mencionado los patrones no suelen aparecer de forma aislada. Es decir, habitualmente están relacionados unos con otros. En el caso de Abstract Factory se suele implementar con el patrón Factory Method, también pueden ir junto a un patrón Prototype para la instanciación de los productos concretos. Las factorías concretas suelen ser Singleton. </div>
<div>
Todos estos patrones relacionados iremos viéndolos en detalle en sus respectivos post.</div>
<div>
<br /></div>
<div>
Hasta aquí Abstract Factory. Hay muchas cosas más que decir de este patrón, pero creo que con lo visto hasta ahora nos podemos hacer una buena idea de cómo funciona este patrón.</div>
<div>
<br /></div>
<div>
El próximo post seguiremos con otro patrón de creación: <a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-builder.html" target="_blank">Builder</a>.</div>
<div>
<br /></div>
Unknownnoreply@blogger.com7tag:blogger.com,1999:blog-2728255754328606012.post-16723325733329753492013-11-28T10:30:00.002+01:002014-03-17T15:33:31.114+01:00Introducción a los patrones de diseñoRealizar diseños orientados a objetos es difícil, y realizar diseños orientados a objetos reutilizables es más difícil aún. Tienes que encontrar los objetos correctos, encapsularlos en clases con la granularidad correcta, definir las interfaces, la herencia y las relaciones entre los objetos.<br />
<br />
Una de las cosas que los desarrolladores debemos aprender cuanto antes es que no debemos afrontar la resolución de los problemas siempre desde cero. En lugar de eso debemos buscar soluciones que hayan funcionado a nosotros mismos o a otros en el pasado.<br />
<br />
<a name='more'></a><br />
<br />
Los patrones son justo eso. Una guía de soluciones probadas a problemas recurrentes en el diseño de software. Pero atención a que digo una guía, no la solución. De hecho es casi imposible que un patrón se aplique tal cual, lo normal es que requiera de diferentes adaptaciones al problema concreto que queremos solucionar. Por lo tanto debemos tomar los patrones de diseño como una guía, como una fuente de inspiración para encontrar la solución a nuestros problemas concretos. Tratar de usar los patrones como si fueran dogmas es probablemente peor que no usarlos en absoluto. Sólo conseguiremos crear código complejo y difícil de mantener. Tampoco debemos olvidar que con o sin patrones nuestros diseños deberían aplicar los <a href="http://designcodetips.blogspot.com.es/search/label/Principios%20de%20dise%C3%B1o" target="_blank">principios de diseño que hemos visto en post anteriores</a>.<br />
<br />
Por mi experiencia creo que es muy difícil encontrar, cuando diseñamos, un problema "original". Me explico. Cuando afrontamos un problema de diseño, ese problema en concreto, es posible que sea único. Que nadie antes haya resuelto un problema como el nuestro, con todos sus detalles. Y eso será así siempre que miremos nuestro problema con un nivel de abstracción de grano muy fino, con mucho detalle. Sin embargo, si lo enfocamos con un nivel de abstracción mayor la cosa es diferente, no encontraremos una solución concreta a nuestro problema pero sí una guía de cómo otros han solucionado problemas similares.<br />
<br />
Veamos un ejemplo de lo que hablo:<br />
Supongamos que tenemos que diseñar un módulo que gestiona nuestros productos y sus categorías como un árbol. Cada categoría puede contener otras categorías o directamente productos, aunque la máxima profundidad de subcategorías es 3. Además necesitamos que seleccionando un nodo de ese árbol podamos aplicar un incremento en el precio de los productos que forman parte de las ramas de ese nodo. Y también debemos poder mover nodos de una rama a otra. Como ejemplo no pondré más funcionalidad a aplicar sobre el árbol, pero imaginemos que es mucha más.<br />
<br />
No será fácil encontrar una solución ya creada y probada para resolver este problema en concreto, pero si lo miramos desde un nivel de abstracción un poco superior (de grano más grueso). Podríamos definir nuestro problema así:<br />
<br />
Queremos gestionar entidades en forma de árbol, de manera que cada una puede tener una serie de hijos y estos hijos a su vez otros hijos etc. Este árbol tendrá alguna limitación a la hora de componerlo. Además debemos poder aplicar mucha funcionalidad distinta a los nodos del árbol.<br />
<br />
Bien, desde este nivel de abstracción el problema se ve distinto. Como veremos en futuros post un patrón Composite nos serviría de guía para crear el árbol, y un patrón Visitor nos permitiría agregar funcionalidad al árbol pero separada del árbol para mantenerlo lo más "limpio" posible.<br />
<br />
Vuelvo a recalcar que estos patrones no nos darán una solución para aplicar directamente, sino más bien una guía para solucionar nuestro problema. Los patrones suelen tener un nivel de abstracción superior al problema que queremos solucionar, por lo que tendremos que hacer modificaciones hasta que se adapten a nuestro problema concreto.<br />
<blockquote class="tr_bq">
Los patrones no son, en sí mismos, una solución a los problemas concretos sino una guía para solucionarlos.</blockquote>
<br />
Otra consecuencia importante cuando usamos soluciones basadas en patrones es que cuando hablamos con compañeros de trabajo u otros colegas y les decimos que al final hemos solucionado el problema anterior con un patrón Composite y un Visitor para la funcionalidad, inmediatamente se hacen una buena idea de cómo se ha solucionado el problema sin tener que dar un sin fin de detalles. Es decir, el uso de patrones mejora la comunicación con tu propio equipo.<br />
<br />
Bueno, una vez hecha esta introducción de los patrones de diseño vamos a enumerar qué patrones vamos a ver en la serie que ahora empieza y cómo voy a enfocarlos.<br />
<br />
Los patrones que veremos se basan en los patrones de diseño expuestos en el libro "Design Patterns - Elements of Reusable Object-Oriented Software" de los conocidos como "<a href="http://es.wikipedia.org/wiki/Gang_of_Four_(dise%C3%B1o)" target="_blank">Gang of Four</a>".<br />
<br />
Los patrones que vamos a ver los dividiremos en tres categorías:<br />
<br />
<ul>
<li><b>Patrones de creación.</b> Estos patrones nos ayudan en el proceso de instanciación de objetos. Los patrones de creación de clases se basan en la herencia para decidir que objeto se crea, mientras que los patrones de creación de objetos delegan la instanciación en otro objeto.</li>
</ul>
<ol><ul>
<li><u><a href="http://designcodetips.blogspot.com.es/2013/12/patrones-de-creacion-abstract-factory.html" target="_blank">Abstract Factory</a>.</u> Provee una interfaz para crear familias de objetos relacionados sin especificar sus clases concretas.</li>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-builder.html" target="_blank">Builder</a>.</u> Separa la construcción de un objeto complejo de su representación.</li>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-factory-method.html" target="_blank">Factory Method</a>.</u> Define una interfaz para crear un objeto, pero delega en las subclases la decisión de qué clase instanciar.</li>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/01/patrones-de-creacion-prototype.html" target="_blank">Prototype</a>.</u> Especifica los tipos de objetos a crear usando un objeto prototipo y crea nuevos objetos clonando al prototipo.</li>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/02/patrones-de-creacion-singleton.html">Singleton</a>.</u> Asegura que sólo existe una instancia de una determinada clase y provee un acceso global a esa instancia.</li>
</ul>
</ol>
<ul>
<li><b style="font-weight: bold;">Patrones estructurales.</b> Tienen que ver con cómo las clases y los objetos se componen para crear estructuras más grandes y nueva funcionalidad. Agregan una gran flexibilidad frente a estructuras estáticas por su capacidad para cambiar la composición en tiempo de ejecución.</li>
</ul>
<ol><ul>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/02/patrones-estructurales-adapter.html">Adapter</a>.</u> Convierte la interfaz de una clase en otra interfaz que el cliente espera.</li>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/03/patrones-estructurales-bridge.html">Bridge</a>.</u> Desacopla una abstracción de su implementación y ambas pueden evolucionar por separado.</li>
<li><u><a href="http://designcodetips.blogspot.com.es/2014/03/patrones-estructurales-composite.html" target="_blank">Composite</a>.</u> Compone objetos en estructuras de árbol para representar jerarquías del tipo parte-todo.</li>
<li><u>Decorator.</u> Agrega responsabilidades a un objeto de forma dinámica.</li>
<li><u>Facade.</u> Provee una interfaz que unifica un conjunto de interfaces de un subsistema.</li>
<li><u>Flyweight.</u> Permite soportar un gran número de objetos compartiendo su parte común.</li>
<li><u>Proxy.</u> Provee un sustituto de un objeto para controlar el acceso al objeto sustituido.</li>
</ul>
</ol>
<ul>
<li><b style="font-weight: bold;">Patrones de comportamiento.</b> Tienen que ver con algoritmos, asignación de responsabilidades y comunicación entre objetos.</li>
</ul>
<ol><ul>
<li><u>Chain of responsability.</u> Evita el acoplamiento entre el emisor y el receptor de una solicitud.</li>
<li><u>Command.</u> Encapsula una petición en un objeto.</li>
<li><u>Interpreter.</u> Dado un lenguaje, define una representación para su gramática y un intérprete que usa esa representación.</li>
<li><u>Iterator.</u> Provee una forma de acceder a los elementos de un agregado de forma secuencial sin exponer su representación interna.</li>
<li><u>Mediator.</u> Define un objeto que encapsula cómo un conjunto de objetos interactuan.</li>
<li><u>Memento.</u> Captura y almacena el estado interno de un objeto, sin violar la encapsulación, para poder restaurar ese estado interno de nuevo más adelante.</li>
<li><u>Observer.</u> Define una dependencia de uno a muchos entre objetos de forma que cuando el objeto observado cambia su estado todos los demás dependientes son notificados.</li>
<li><u>State.</u> Permite a un objeto cambiar su comportamiento cuando cambia su estado interno. </li>
<li><u>Strategy.</u> Define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables.</li>
<li><u>Template method.</u> Define el esqueleto de un algoritmo en una operación y delega en las subclases para implementar algunos de los pasos.</li>
<li><u>Visitor.</u> Representa una operación que se aplica a los elementos de una estructura de objetos. Permite agregar operaciones sin cambiar los objetos a los que se les aplica.</li>
</ul>
</ol>
<div>
En los post de la serie veremos en detalle cada uno de estos patrones. Para estudiarlos seguiremos la siguiente estructura:</div>
<div>
<ol>
<li><b>El problema.</b> No olvidemos que un patrón es una guía para solucionar un problema. Lo primero que haremos será identificar y estudiar el problema que pretendemos resolver.</li>
<li><b>El patrón.</b> Estudiaremos el patrón original ofrecido por GoF.</li>
<li><b>El patrón en .NET</b> cada plataforma y cada lenguaje tiene sus propios modismos, su propia forma de implementar los conceptos. En esta sección veremos cómo implementar el patrón con C#.</li>
<li><b>Consecuencias.</b> Aquí veremos las consecuencias de aplicar el patrón en un sistema. </li>
<li><b>Relaciones con otros patrones </b>habitualmente un problema no se soluciona aplicando un patrón aislado, sino que se usan una combinación de ellos. En esta sección veremos para cada patrón las formas habituales de relacionarse con otros.</li>
</ol>
<div>
Aquí termina la introducción a los patrones de diseño, a partir del próximo post entramos de lleno en materia.</div>
</div>
Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-2728255754328606012.post-1628250746685058642013-11-19T16:04:00.001+01:002013-11-19T16:06:04.937+01:00Principios DRY, YAGNI y KISSEn este post vamos a ver otros principios de diseño a tener en cuenta.<br />
Que los ponga todos en el mismo post no quiere decir que sean menos relevantes que los principios explicados hasta el momento.<br />
<br />
<a name='more'></a><br /><br />
<h2>
Principio DRY (Don't Repeat Yourself)</h2>
Este principio fue formulado por Andy Hunt y Dave Thomas en <a href="http://pragprog.com/the-pragmatic-programmer" rel="nofollow" target="_blank">"The Pragmatic Programmer"</a>. La interpretación popular de este principio es algo así como:
<br />
<div>
<i>"No repitas código, si hay código duplicado refactorizas y reutilizas el código en lugar de duplicarlo". </i></div>
<div>
Todos estaremos de acuerdo que el código duplicado es un problema. Cuando necesitamos hacer cambios tendremos que cambiar en varios sitios con el riesgo de olvidar hacer esos cambios en algún sitio y dejar el código inconsistente. Incluso aunque no olvidemos ningún sitio donde debemos hacer los cambios el propio hecho de tener que cambiar en varios sitios nos obliga a hacer un esfuerzo extra de mantenimiento.<br />
<br />
Sin embargo los propios autores se quejan de la interpretación anterior del principio por considerarla superflua. El principio <b>DRY </b>tiene unas pretensiones mucho más amplias. Su formulación original es "Cada pieza de conocimiento dentro de un sistema debe tener una representación única, clara y acreditada" (pongo aquí la versión original por si alguien sugiere una mejor traducción -<em style="background-color: white;">Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.-</em>)<br />
<br />
Es importante destacar que el principio <b>DRY </b>habla de pieza de conocimiento. No se refiere solamente a código. Una pieza de conocimiento puede ir desde el código, un esquema de base de datos, el conocimiento de un experto, requisitos, planes de pruebas, planes de compilación, hasta la propia documentación, etc.<br />
<br />
El problema es cómo crear una representación única de ese conocimiento. Si se trata de código parece obvio que como hemos mencionado antes se trata de refactorizar y reutilizar. Pero, ¿cómo se maneja desde la perspectiva del principio <b>DRY </b>conceptos como esquemas de base de datos, planes de compilación o incluso el conocimiento determinado de un experto? Andy Hunt y Dave Thomas nos sugieren para esto el uso de técnicas como generadores de código, sistemas de compilación automáticos, uso de lenguajes de script - (como plantillas T4), etc.<br />
<br />
Como ejemplo, las plantillas de proyectos o las plantillas T4 para la creación de objetos POCO con Entity Framework que ofrece Visual Studio las podemos considerar como la representación única del conocimiento de un experto (en este caso de Microsoft) que generan muchas líneas de código que muchos de los desarrolladores que las usamos probablemente no entenderíamos pero que usamos habitualmente. Piezas de conocimiento únicas, claras y acreditadas.<br />
<br />
En resumen, el principio <b>DRY </b>nos dice que una pieza de código en un sistema nunca debería estar duplicada. Y por pieza de código entendemos un concepto que va mucho más allá que el código.<br />
<br /></div>
<h2>
Principio YAGNI (You Ain't Gonna Need It)</h2>
<div>
<b>YAGNI </b>es un principio de Extreme Programming (XP) que establece que no se debe implementar funcionalidad hasta que sea necesario. En palabras de <a href="http://en.wikipedia.org/wiki/Ron_Jeffries" target="_blank">Ron Jeffries</a> uno de los fundadores de XP "Siempre implementa las cosas que necesitas actualmente, nunca las que preveas que vas a necesitar".</div>
<div>
<br /></div>
<div>
Incluso si estamos totalmente seguros de que hay algo que necesitaremos en el futuro es mejor no implementarlo ahora por varios motivos. Primero puede que después de todo acabemos por no necesitarlo, y segundo porque puede que lo que necesitemos en el futuro sea bastante diferente de lo que creemos que vamos a necesitar.</div>
<div>
<br /></div>
<div>
El sentido de este principio no es que no hagamos diseños flexibles aplicando los principios que llevamos visto hasta ahora. Sino que no se sobredimensione algo basándose en las cosas que creemos que vamos a necesitar.</div>
<div>
<br /></div>
<div>
Hay dos razones fundamentales para aplicar el principio YAGNI en nuestros diseños:</div>
<div>
<ul>
<li>Ahorramos tiempo, porque no escribimos código que al final resulta que no necesitábamos.</li>
<li>Mejoramos la calidad del código, porque evitamos "contaminar" nuestro código con conjeturas más o menos erróneas que no se usan, pero que acaban apareciendo por todas partes. </li>
</ul>
<div>
<b>Desmontando la falacia del ahorro de tiempo.</b></div>
</div>
<div>
Esta es una excusa muy habitual a la que nos acogemos cuando estamos creando un diseño e imaginamos, soñamos o suponemos lo que necesitaremos en el futuro, "si hacemos tal o cual cosa ahora ahorraremos tiempo". Veamos las opciones posibles:</div>
<div>
<ul>
<li><u>En el mejor de los casos acertamos de pleno</u> y usamos lo que habíamos previsto en el futuro. En este caso (el mejor y el menos probable) sólo hemos empleado en el presente el mismo tiempo que hubiéramos empleado en el futuro para implementar la característica que habíamos previsto. No hay ahorro de tiempo. Alguien podría decir, "si no lo hago ahora en el futuro tendré que rehacer el diseño y refactorizar el código y será más complicado que ahora". Bien, si es lo suficientemente complejo para que a ti mismo te cueste mantenerlo en el futuro es que algo estás haciendo mal, hazlo más simple (mira el siguiente principio más abajo, principio KISS).</li>
<li><u>En un caso intermedio "casi" nos vale en el futuro lo que hemos previsto</u>. En este caso sólo necesitamos hacer unos retoques aquí y allá, unas cuantas pruebas de regresión... etc. Al tiempo de hacer esto hay que añadir el que ya invertimos en hacer la implementación original. En fin, no hay evidentemente ahorro de tiempo sino todo lo contrario.</li>
<li><u>En el peor de los casos al final resultó que no lo necesitamos.</u> El diseño acaba en la basura en el mejor de los casos o peor aún, eternamente "ensuciando" nuestro código y dificultando el mantenimiento. Con respecto al ahorro de tiempo en este caso no voy a comentar nada.</li>
</ul>
<div>
Con lo visto anteriormente creo que queda claro que YAGNI es un principio que deberíamos tener muy en cuenta a la hora de crear nuestros diseños y sobre todo a la hora de "parar de crear".</div>
</div>
<div>
<b><br /></b></div>
<h2>
Principio KISS (Keep It Simple Stupid)</h2>
<div>
Este principio establece que "la mayoría de los sistemas funcionan mejor si se mantienen simples en lugar de hacerlos complejos". La frase se le atribuye al ingeniero aeronáutico<span style="background-color: white; font-family: sans-serif; font-size: 13px; line-height: 19.1875px;"> </span><a href="http://en.wikipedia.org/wiki/Kelly_Johnson_(engineer)" style="background-color: white; background-image: none; color: #0b0080; font-family: sans-serif; font-size: 13px; line-height: 19.1875px;" title="Kelly Johnson (engineer)">Kelly Johnson</a>. No es un principio específico del diseño de software sino más bien de la ingeniería en general, pero su aplicación es muy recomendable también en nuestros diseños. </div>
<div>
<br /></div>
<blockquote class="tr_bq">
No son los lenguajes los que hacen parecer a los programas simples, son los desarrolladores los que hacen parecer simples a los lenguajes. - Robert C. Martin -</blockquote>
<div>
<br /></div>
<div>
Un objetivo a la hora de crear nuestros diseños debe ser la simplicidad y evitar complejidades innecesarias. </div>
<div>
En XP se aplican dos reglas para conseguir mantener simples los diseños:</div>
<div>
<ul>
<li>Diseña las nuevas características de la forma más simple que "crees que podría funcionar". Evita súper diseños fantásticos. Solamente pon ahí la nueva característica y haz que funcionen los test unitarios (tendremos que hablar de tests también en algún momento).</li>
<li>Refactoriza el diseño para conseguir el código más simple y que sigan funcionando todas las características. Sigue todos los principios de diseño que hemos visto para mantener el diseño tan claro como sea posible.</li>
</ul>
<div>
En cada iteración del desarrollo debemos crear el diseño más simple que creemos que podría funcionar. No me refiero al diseño más simple que funcionará, sino al más simple que podría funcionar. Hay que estar seguro de que funcionará pero esto no hay que probarlo hasta que hacemos la implementación del diseño.</div>
</div>
<div>
<br /></div>
<div>
Existen muchos otros principios y citas más o menos célebres que van en la línea del principio KISS. </div>
<div>
<ul>
<li><a href="http://es.wikipedia.org/wiki/Navaja_de_Ockham" target="_blank">Navaja de Ockham</a>: "En igualdad de condiciones, la explicación más sencilla suele ser la correcta". </li>
<li><a href="http://es.wikipedia.org/wiki/Antoine_de_Saint-Exup%C3%A9ry" target="_blank">Antoine de Saint-Exupéry</a> (Autor de El Principito): "Parece que la perfección no se alcanza cuando no queda nada más que añadir, sino cuando no queda nada por quitar".</li>
<li><a href="http://en.wikipedia.org/wiki/Leonardo_da_Vinci">Leonardo da Vinci</a>'s: "La simplicidad es la máxima sofisticación"</li>
</ul>
</div>
<div>
Es importante que entendamos que no estamos buscando la forma más rápida de hacer el diseño, sino obtener como resultado el diseño más simple.</div>
<div>
Simple quiere decir que no hagamos un objeto basado en tablas hash muy eficiente, ordenado, etc. si un array puede hacer esa función. </div>
<div>
<br /></div>
<div>
Con este post acabamos con los principios de diseño más genéricos. Vendrán más un poco más adelante, pero lo siguiente será una serie donde habrá que "remangarse" y entrar en faena. Patrones de diseño. </div>
Unknownnoreply@blogger.com6tag:blogger.com,1999:blog-2728255754328606012.post-88200211874216231712013-11-11T15:34:00.002+01:002013-12-18T10:40:34.478+01:00Inversion of Control<h2>
Inversión de control (IoC)</h2>
<div>
La inversión de control es una técnica de programación mediante la cual se invierte el flujo de ejecución de un programan con respecto a una ejecución tradicional.<br />
<br />
En la programación tradicional nuestro código (cliente) llama a métodos de otras clases de las que dependemos, recibe el resultado y vuelve a llamar a otros métodos etc. controlando así el flujo de ejecución del programa. Mediante la Inversión de control, en nuestro código implementamos métodos que son llamados por esas otras clases de las que dependemos en el momento adecuado.<br />
<br />
<a name='more'></a><br />
Sabemos cuándo se aplica o no la inversión de control identificando a quien inicia las llamadas entre objetos relacionados. Si la llamada la inicia nuestro código no hay inversión de control. Si son las clases de las que depende nuestro código quienes inician las llamadas a métodos de nuestras clases entonces se produce inversión de control.<br />
<br />
<blockquote class="tr_bq">
La diferencia fundamental entre un Framework y una librería de clases es que el Framework realiza IoC</blockquote>
<br />
Es una situación típica cuando extendemos un framework heredando de sus clases abstractas, implementando sus interfaces etc. De hecho, la IoC es probablemente la principal característica de un framework y que lo diferencia de una librería de clases. Una librería de clases es un conjunto de funciones a las que llamamos y recibimos un resultado. Un framework además tiene la capacidad de capturar el flujo de ejecución del programa y a partir de ahí son las clases del framework las que llaman a nuestro código.<br />
<br />
Veamos un ejemplo muy simple. En este primer código vamos a ver cómo usamos la <b>librería de clases Calculator:</b><br />
<pre class="brush:csharp;">namespace CalculatorLibrary
{
public class CalculatorLibrary
{
public int Add(int lhs, int rhs)
{
return lhs + rhs;
}
public int Substract(int lhs, int rhs)
{
return lhs - rhs;
}
}
}
namespace LibraryClient
{
public class CalculatorLibClient
{
public void Main()
{
var calcLib = new CalculatorLibrary();
var value1 = calcLib.Add(30, 5);
Console.WriteLine(value1);
var value2 = calcLib.Substract(value1, 5);
Console.WriteLine(value2);
}
}
}
</pre>
<br />
En este código vemos el uso de una librería de clases por un cliente.<br />
<br />
El cliente instancia una clase de la librería (CalculatorLibrary), llama al método Add, obtiene el resultado y lo escribe en la consola. Después llama al método Substract, obtiene el resultado y lo escribe en la consola. Suponemos que nosotros hemos escrito el cliente y como podemos ver en todo momento dirigimos el flujo de ejecución del programa. En cada momento decidimos la siguiente instrucción a ejecutar.<br />
<br />
Ahora vamos a ver cómo sería este ejemplo mediante un framework que aplica inversión de control.<br />
<pre class="brush:csharp;">namespace CalculatorWithFramework
{
public interface IResultWriter
{
void WriteResult(int result);
}
public class CalculatorFramework
{
private IResultWriter _writer;
public CalculatorFramework(IResultWriter writer)
{
if (writer == null)
throw new ArgumentNullException("writer");
_writer = writer;
}
public void Add(int lhs, int rhs)
{
var val = lhs + rhs;
_writer.WriteResult(val);
}
public void Substract(int lhs, int rhs)
{
var val = lhs - rhs;
_writer.WriteResult(val);
}
}
}
namespace FrameworkClient
{
public class CalculatorFrameworkClient
{
public void Main()
{
// Normalmente la instanciación y la inyección de dependencias
// de ConsoleResultWriter y CalculatorFramework se haría con
// un IoC Container. Por simplicidad se omite y se instancia
// directamente en la clase.
IResultWriter consoleWriter = new ConsoleResultWriter();
var calcFrw = new CalculatorFramework(consoleWriter);
calcFrw.Add(30, 5);
calcFrw.Substract(35, 5);
}
}
public class ConsoleResultWriter : IResultWriter
{
public void WriteResult(int result)
{
Console.WriteLine(result);
}
}
}
</pre>
<br />
Por simplicidad, para poder centrarnos en el concepto de IoC, el código anterior no cumple con <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">principios de diseño SOLID</a>.<br />
<br />
Vamos a analizar este nuevo código.<br />
Lo primero que vemos es que en el framework hemos definido la interfaz <b>IResultWriter</b>. Esta interfaz la deben implementar todas las clases que definan objetos responsables de escribir el resultado de las operaciones de la calculadora. <b>CalculatorFramework </b>recibe por el constructor un objeto que implementa esta interfaz.<br />
<br />
En la parte de cliente creamos una la clase <b>ConsoleResultWriter </b>que implementa la interfaz <b>IResultWriter </b>del framework. En este caso escribe el resultado en la consola, pero podríamos implementar por ejemplo <b>EmailResultWriter </b>que enviaría el resultado por email, <b>PrintResulWriter </b>que envía el resultado a la impresora, etc.<br />
La clase <b>CalculatorFrameworkClient </b>crea una instancia de <b>ConsoleResultWriter</b>, crea una instancia de <b>CalculatorFramework </b>y pasa por el constructor la instancia de <b>ConsoleResultWriter</b>. Después llama a los métodos <b>Add </b>y <b>Substract </b>de <b>CalculatorFramework</b>.<br />
<br />
<b>¿Dónde se produce aquí la inversión de control?</b><br />
Vamos a analizar el flujo de ejecución del programa y veremos donde se produce la inversión de control.<br />
<br />
<ol>
<li><b>CalculatorFrameworkClient </b>crea una instancia de <b>ConsoleResultWriter</b>. <b>CalculatorFrameworkClient </b>inicia la llamada.</li>
<li><b>CalculatorFrameworkClient </b>crea una instancia de <b>CalculatorFramework</b>. <b>CalculatorFrameworkClient </b>inicia la llamada.</li>
<li><b>CalculatorFrameworkClient </b>llama al método <b>Add</b>. <b>CalculatorFrameworkClient </b>inicia la llamada.</li>
<li><b>CalculatorFramework </b>realiza la operación.</li>
<li><b>CalculatorFramework </b>llama a <b>WriteResult</b>. ¡Aquí <b>CalculatorFramework </b>captura el flujo de ejecución del programa y es ahora el framework quien llama a nuestro código! En este caso a <b>ConsoleResultWriter.WriteResult</b>. <b>CalculatorFramework <span style="font-weight: normal;">inicia la llamada.</span><span style="color: red;"> ¡Aquí se produce la inversión de control!</span></b></li>
<li><b>ConsoleResultWriter <span style="font-weight: normal;">escribe en la consola.</span></b></li>
<li><b><span style="font-weight: normal;">El flujo de ejecución vuelve a </span>CalculatorFrameworkClient </b>que llama a <b>Substract</b>.</li>
<li><b>CalculatorFramework </b>realiza la operación.</li>
<li><b>CalculatorFramework </b>llama a <b>WriteResult</b>. <b><span style="color: red;">¡Nuevamente inversión de control!</span></b></li>
<li>El flujo de ejecución vuelve a <b>CalculatorFrameworkClient </b>y acaba.</li>
</ol>
Al igual que la primera implementación el resultado se ha escrito en la consola, pero a diferencia con la primera implementación no es nuestra clase CalculatorFrameworkClient la responsable de decidir cuándo se imprime, sino que esto lo decide el framework. Es decir, cuando hemos llamado a los métodos Add o Substract hemos perdido el control del flujo del programa y ha pasado a ser controlado por las clases de las que depende nuestro código.<br />
Tampoco el framework es responsable de dónde se va escribir el resultado, de eso es responsable el objeto que implementa <b>IResultWriter.</b><br />
<b><br /></b>
IoC es una forma muy potente para eliminar dependencias y dar flexibilidad a nuestros diseños. De ahí que sea una técnica tan usada.<br />
<br />
<blockquote class="tr_bq">
IoC es una forma muy potente para eliminar dependencias y dar flexibilidad a nuestros diseños</blockquote>
<br />
Existen infinidad de ejemplos de IoC. El modelo de eventos de WinForm es un claro ejemplo de IoC. WinForm gestiona el flujo de ejecución del programa, cuando nos suscribimos a un evento lo que pasa realmente es que WinForm (el framewok) llama al manejador de evento que hemos escrito nosotros. Nuestro código no inicia esa llamada, por lo tanto... inversión de control.<br />
<br />
Otro ejemplo típico de IoC es el patrón <a href="http://es.wikipedia.org/wiki/Template_Method_(patr%C3%B3n_de_dise%C3%B1o)" target="_blank">"Template Method"</a>. Este patrón lo vamos a ver en profundidad en una serie dedicada a los patrones de diseño, pero os adelanto que básicamente se trata de definir en una clase base los pasos a realizar para completar una acción. Al heredar de esta clase se sobrescriben cada uno de los pasos pero es la clase base la que gestiona el flujo de ejecución de los pasos en el sentido que dirige el orden en el que los pasos se ejecutan.<br />
<br />
Con lo visto hasta ahora creo que también se puede intuir cómo los IoC Containers realizan inversión de control. Cuando pedimos al container que resuelva una interfaz - Container.Resolve<IResultWriter>() - el container acaba llamando a el constructor de nuestra clase e inyectando sus dependencias si existen.<br />
<br />
<br />
<br /></div>Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-2728255754328606012.post-38888778146812556652013-11-08T10:00:00.000+01:002013-12-18T10:41:50.150+01:00Dependency Injection e IoC Container<h2>
<b>Dependency Injection (DI)</b></h2>
<i>"Suministrar a una clase los objetos de los que depende en lugar de que sea la propia clase quien lo cree".</i><br />
<br />
En el anterior post <a href="http://designcodetips.blogspot.com.es/2013/10/dependency-inversion-principle.html" target="_blank">Dependency Inversion Principle</a> hablamos de cómo siempre crear dependencias a clases abstractas e interfaces de un nivel de abstracción igual o superior al de nuestra clase.<br />
<br />
<a name='more'></a><br />
Esto no llevó a refactorizar un pequeño diseño de esto:<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRx-01CfsowAy1jN7POGSBuvKxDMDYEZtSZKMxKJCrWh-iE249C4qksuR-SbKJHoKbicQBL6toUbUbUglNV7YiGw771ZTeg-YOrw_2I9sugIUACqSXrBKBd87z6_0AN9J9TnueJEpKKGgE/s1600/DIP1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRx-01CfsowAy1jN7POGSBuvKxDMDYEZtSZKMxKJCrWh-iE249C4qksuR-SbKJHoKbicQBL6toUbUbUglNV7YiGw771ZTeg-YOrw_2I9sugIUACqSXrBKBd87z6_0AN9J9TnueJEpKKGgE/s1600/DIP1.png" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
a esto: </div>
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlNWHh8YpEkUbhEf1P9TSysNI7qlUZqMuffFH-qGd29vX-D1yM56ifcbLdeckDCAL6-0a10s-Mgnm7wH8cMhqrr-Qq7E6E6BsCwthWVxRBFQwM7EOILKNRBiRJDJUNzOkTSIUJuvMPWZ_R/s1600/DIP2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlNWHh8YpEkUbhEf1P9TSysNI7qlUZqMuffFH-qGd29vX-D1yM56ifcbLdeckDCAL6-0a10s-Mgnm7wH8cMhqrr-Qq7E6E6BsCwthWVxRBFQwM7EOILKNRBiRJDJUNzOkTSIUJuvMPWZ_R/s1600/DIP2.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Pero intencionadamente dejé pendiente cómo nuestra clase ClientsBL iba a obtener una referencia a ClientsDAL ya que no podemos crear una instancia de una interfaz (IClientsDAL). </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
En el anterior post <a href="http://designcodetips.blogspot.com.es/2013/10/dependency-inversion-principle.html" target="_blank">Dependency Inversion Principle</a> ya adelanté que la solución a ese problema es la DI, pero he preferido explicar esta solución en un post aparte porque creo que es importante aclarar conceptos antes de seguir.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Dependency Inversion Principle, Dependency Injection e Inversion of Control</b></div>
<div class="separator" style="clear: both; text-align: left;">
A menudo estos tres conceptos se confunden o incluso se piensan que son el mismo. Bien, realmente son tres conceptos distintos aunque es cierto que suelen aparecer muy relacionados, quizá ese sea el motivo de la confusión.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Voy a intentar explicar cada uno de ellos:</div>
<div class="separator" style="clear: both; text-align: left;">
<b>Dependency Inversión Principle: </b>Como hemos visto en el post <a href="http://designcodetips.blogspot.com.es/2013/10/dependency-inversion-principle.html" target="_blank">Dependency Inversion Principle</a>, la inversión de dependencias hace referencia a la forma de los objetos de los que depende nuestra clase. Dependemos de interfaces y clases abstractas en lugar de depender de clases concretas.</div>
<div class="separator" style="clear: both; text-align: left;">
<b><br /></b></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Dependecy Injection: </b>Como veremos en este post, hace referencia a cómo una clase obtiene sus dependencias, es decir, los objetos de los que depende.</div>
<div class="separator" style="clear: both; text-align: left;">
<b><br /></b></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Inversion of Control: </b>En el próximo post veremos este concepto en detalle. Este es un concepto muy amplio y aplicable a diferentes contextos. Hace referencia a sobre quién inicia las llamadas.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Sé que las definiciones anteriores no aclaran mucho, pero por el momento lo importante es tener claro que son tres conceptos diferentes aunque trabajan bien juntos.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Bueno vamos a lo que toca ahora, explicar el segundo de estos conceptos. </div>
<div class="separator" style="clear: both; text-align: left;">
<b><br /></b></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Dependency Injection (DI) e Ioc Container.</b></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Antes de empezar y por si alguien trae ideas preconcebidas, lo diré de forma que todos nos entendamos:</div>
<div class="separator" style="clear: both; text-align: center;">
<span style="font-size: large;"><b style="background-color: #fff2cc;">Dependency Injection != Usar un IoC Container</b></span></div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>Se puede realizar DI sin usar un IoC Container ni ninguna otra herramienta externa.</li>
<li>Se puede usar un IoC Container sin realizar DI, de hecho esta es una mala práctica muy extendida.</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: left;">
DI es un patrón usado para crear instancias de objetos con funcionalidad de la que otros objetos dependen sin conocer en tiempo de compilación que clases concretas se usará para proporcionar la funcionalidad.</div>
<div class="separator" style="clear: both; text-align: left;">
DI hace referencia a la forma en que una clase obtiene sus dependencias. Si aplicando el <a href="http://designcodetips.blogspot.com.es/2013/10/dependency-inversion-principle.html" target="_blank">principio de inversión de dependencias</a> nuestras clases dependen de clases abstractas e interfaces que no podemos instanciar, las instancias concretas de esas interfaces deben ser proporcionadas a nuestra clase "desde fuera". Las formas más habituales de "inyectar" esas dependencias a nuestras clases son:</div>
<div class="separator" style="clear: both; text-align: left;">
<u><br /></u></div>
<div class="separator" style="clear: both; text-align: left;">
<u>Inyección por el constructor.</u> En nuestra aplicación para ACME Suministros S.A. la clase ClientsBL quedaría así si inyectamos la dependencia de IClientsDAL por el constructor:</div>
<br />
<pre class="brush:csharp;"> public class ClientsBL
{
private IClientsDAL dalClient;
public ClientsBL(IClientsDAL cliDal)
{
dalClient=cliDal;
}
public IEnumerable<ClientData> GetClientsData()
{
return dalClient.GetClientData();
}
}
class Program
{
static void Main(string[] args)
{
// Uso de ClientsBL inyectando las dependencias
// mediante el constructor
IClientDAL cliDal = new ClientDAL();
ClientsBL cliBL = new ClientsBL(cliDal);
var cliData = cliBL.GetClientsData();
}
}
</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<u>Inyección mediante una propiedad.</u> Veamos cómo sería la inyección mediante una propiedad:<br />
<pre class="brush:csharp;"> public class ClientsBL
{
private IClientsDAL dalClient;
public IClientsDAL CliDal
{
get{return dalClient;}
set{dalClient=value;}
}
public IEnumerable<ClientData> GetClientsData()
{
return dalClient.GetClientData();
}
}
class Program
{
static void Main(string[] args)
{
// Uso de ClientsBL inyectando las dependencias
// mediante una propiedad
IClientDAL cliDal = new ClientsDAL();
ClientsBL cliBL = new ClientsBL();
cliBL.CliDal=cliDal;
var cliData = cliBL.GetClientsData();
}
}
</pre>
<br />
<u>Inyección mediante un método.</u> Así sería mediante un método.<br />
<pre class="brush:csharp;"> public class ClientsBL
{
private IClientsDAL dalClient;
public void SetCliDal(IClientsDAL cliDal)
{
dalClient=cliDal;
}
public IEnumerable<ClientData> GetClientsData()
{
return dalClient.GetClientData();
}
}
class Program
{
static void Main(string[] args)
{
// Uso de ClientsBL inyectando las dependencias
// mediante un método
IClientsDAL cliDal = new ClientsDAL();
ClientsBL cliBL = new ClientsBL();
cliBL.SetCliDal(cliDal);
var cliData = cliBL.GetClientsData();
}
}
</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Implementar DI con Factorías</b></div>
<div class="separator" style="clear: both; text-align: left;">
Una forma de cambiar el código que crea los objetos ClientsBL y ClientsDAL en el método Main sería el uso de una factoría. Por ejemplo:
</div>
<pre class="brush:csharp;">public class Factory
{
public IClientsDAL CreateClientsDAL()
{
return new ClientsDAL();
}
public ClientsBL CreateClientsBL()
{
return new ClientsBL(CreateClientsDAL());
}
}
class Program
{
static void Main(string[] args)
{
// Uso de ClientsBL inyectando las dependencias
// mediante una factoría
ClientsBL cliBL = new Factory().CreateClientsBL();
var cliData = cliBL.GetClientsData();
}
}
</pre>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Usar factorías para implementar DI tiene una serie de problemas que hay que tener en cuenta:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>Todo el código es "hardcode". Todas las opciones están implementadas en la propia factoría, lo que hace que la factoría esté muy relacionada con una implementación en particular.</li>
<li>Las factorías son personalizadas. Cada factoría debe implementarse de forma muy personalizada para que se adapte al contexto en el que se usan.</li>
<li>Tiempo de compilación. Todos los objetos de los que depende el objeto que queremos crear deben conocerse en tiempo de compilación.</li>
<li>Tiempo de vida de los objetos. Es difícil manejar el tiempo de vida de los objetos.</li>
<li>Grafos de dependencias muy pequeños. El mantenimiento de una factoría de este tipo es factible si el grafo de dependencias de los objetos es muy pequeño. Cuando el grafo empieza a crecer el coste de mantenimiento de la factoría se dispara.</li>
</ul>
<div>
Teniendo todo esto en cuenta veamos otra alternativa.</div>
<br />
<div class="separator" style="clear: both; text-align: left;">
<b>Implementar DI con IoC Container</b></div>
<div class="separator" style="clear: both; text-align: left;">
Muchas de las deficiencias y problemas que hemos visto hasta ahora al inyectar dependencias se puede resolver con un contenedor de inversión de control.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Un contenedor de inversión de control (IoC Container) también llamado contenedor de inyección de dependencias (DI Container) es un componente responsable de la gestión de los objetos de la aplicación. </div>
<div class="separator" style="clear: both; text-align: left;">
Hay innumerables IoC Containers entre los que elegir (<a href="https://unity.codeplex.com/" target="_blank">Unity</a>, <a href="http://autofac.org/" target="_blank">Autofac</a>, <a href="http://www.springframework.net/" target="_blank">Spring.NET</a>, <a href="http://www.ninject.org/" target="_blank">Ninject</a>, etc.). </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
La idea básica en todos ellos es la siguiente:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ol>
<li>Registramos todos los componentes en el contenedor.</li>
<li>Resolvemos el componente raíz que necesitamos. Las dependencias son inyectadas automáticamente por el contenedor.</li>
</ol>
<div>
Las ventajas de usar el container son muchas. </div>
<div>
<ul>
<li>Registramos todos los tipos al arrancar la aplicación en un sólo sitio.</li>
<li>Se encarga de crear automáticamente el grafo de dependencias.</li>
<li>La mayoría de IoC Container permite la configuración por código o de forma declarativa (archivo XML).</li>
<li>Gestiona el ciclo de vida de los objetos.</li>
<li>etc.</li>
</ul>
<div>
Veamos cómo sería el ejemplo anterior usando UnityContainer:</div>
<pre class="brush:csharp;">using Microsoft.Practices.Unity;
namespace Acme.BL
{
public class ClientsBL : IClientsBL
{
private IClientsDAL dalClient;
public ClientsBL(IClientsDAL dalCli)
{
dalClient = dalCli;
}
public IEnumerable<ClientData> GetClientsData()
{
return dalClient.GetClientData();
}
}
public interface IClientsBL
{
IEnumerable<ClientData> GetClientsData();
}
public interface IClientsDAL
{
IEnumerable<ClientData> GetClientData();
}
}
namespace Acme.DAL
{
public class ClientsDAL : IClientsDAL
{
public IEnumerable<ClientData> GetClientData()
{
var dataResult = Enumerable.Empty<ClientData>();
// Get data from Database.
// and fill dataResult
return dataResult;
}
}
}
namespace DIMain
{
public static class Program
{
static void Main(string[] args)
{
var container = BuildAndConfigureContainer();
// Resolvemos la interfaz IClientsBL.
// Por cómo hemos configurado el container esto
// devolverá un objeto de tipo ClientsBL al que el
// container le ha inyectado la dependencia CliensDAL.
//
// Es como haber hecho lo siguiente:
//
// var cliDal = new ClientsDAL();
// var clientBL = new ClientsBL(cliDAL);
// var clientData = clientBL.GetClientData();
//
var clientBL = container.Resolve<IClientsBL>();
var clientData = clientBL.GetClientsData();
}
/// <summary>
/// Crea un configura un container.
/// </summary>
/// <returns>
/// Un UnityContainer configurado.
/// </returns>
private static IUnityContainer BuildAndConfigureContainer()
{
// Creamos el container
var container = new UnityContainer();
// Registramos el tipo IClientDAL
// Esto es, "Cuando necesite un objeto que cumpla
// la interfaz IClientsDAL,
// devuelveme una instancia de un objeto ClientsDAL"
container.RegisterType<IClientsDAL, ClientsDAL>();
// Registro el tipo IClientsBL.
container.RegisterType<IClientsBL, ClientsBL>();
return container;
}
}
}
</pre>
</div>
<div>
<br />
<b>Mala práctica común con IoC Container</b></div>
<div class="separator" style="clear: both; text-align: left;">
Antes he mencionado que es posible usar IoC Container sin que ello implique DI. Antes de acabar este post me gustaría que viéramos una mala práctica muy común sobre esto. Supongamos esta implementación de la clase ClientsBL:</div>
<br />
<pre class="brush:csharp;"> public class ClientsBL : IClientsBL
{
private IClientsDAL dalClient;
public ClientsBL()
{
// Por simplicidad no explico cómo crear el Singleton del container
dalClient = Container.Instance.Resolve<IClientsDAL>();
}
public IEnumerable<ClientData> GetClientsData()
{
return dalClient.GetClientData();
}
}
</pre>
<br />
En el constructor de ClientsBL obtenemos una instancia de un objeto que cumple la interfaz IClientsDAL a través del container.<br />
<span style="font-size: large;"><br /></span>
<br />
<div style="text-align: center;">
<span style="font-size: large;">¡Esto no es inyección de dependencias!</span></div>
<br />
Estamos usando el IoC Container sí, pero no estamos realizando inyección de dependencias. La inyección de dependencias implica que no creamos los objetos de los que nuestra clase depende en nuestra propia clase, sino que son inyectados. En este caso lo único que hemos hecho es cambiar la dependencia que teníamos originalmente con ClientesDAL por una dependencia con el container.<br />
Aunque vais a ver que esto en muchos sitios se "vende" como inyección de dependencias porque se usa un IoC Container, nosotros ya sabemos que son cosas distintas y que para que haya inyección de dependencias las dependencias tienen que inyectarse bien por el constructor, mediante una propiedad o mediante un método.<br />
<br />
En el próximo post explicaré en detalle el concepto "Inversión de Control".<br />
<br />
<br />Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-2728255754328606012.post-61219009750619523992013-10-31T17:36:00.000+01:002013-12-18T10:42:44.697+01:00Dependency Inversion Principle<b>Dependency Inversion Principle (DIP)</b><br />
<i> "Depender de abstracciones. No depender de concreciones."</i><br />
<div>
<i><br /></i>
Este post pertenece a la serie <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">Principios SOLID.</a><br />
<i><br /></i></div>
<div>
Hay diferentes formas de definir el DIP:<br />
<br />
<ul>
<li>Las abstracciones no deben depender de los detalles.</li>
<li>El código debe depender de cosas que estén en su mismo nivel de abstracción o superior</li>
<li>Las reglas de alto nivel no deben depender de los detalles de bajo nivel</li>
<li>etc.</li>
</ul>
<div>
Todas ellas se basan en la idea de crear dependencias hacia niveles de abstracción superior.</div>
<div>
<br />
<a name='more'></a>Una regla a seguir para invertir las dependencias es: Si tenemos una clase A que depende de otra clase B de un nivel de abstracción inferior, invertimos esa dependencia haciendo que ambas clases dependan de una tercera clase abstracta o interfaz C con un nivel de abstracción igual a A. Luego vamos a ver esto en detalle.<br />
<b><br /></b></div>
<div>
<b>¿Por qué preocuparse por las dependencias?</b></div>
<div>
Una dependencia es un riesgo. Si mi software necesita un determinado framework instalado y no lo está, mi software no funcionará. Probablemente también necesite un determinado sistema operativo, o un browser si es una aplicación web. </div>
<div>
<br /></div>
<div>
Algunas de esas dependencias las podemos controlar y otras simplemente las ignoramos. Por ejemplo, si desarrollamos una aplicación web normalmente no sabemos cuál será el browser que se utilizará para acceder a ella. Por lo que no desarrollaremos una aplicación web dependiente de un browser concreto.</div>
<div>
<br /></div>
<div>
Manejar el riesgo que implican esas dependencias tiene un coste. A través de la experiencia, del siempre socorrido método de prueba y error, o de tus conocimientos debes decidir si quieres mitigar ese riesgo o no.</div>
<div>
<br /></div>
<div>
<b>Significado de "Inversión" en DIP</b></div>
<div>
Cuando leí por primera vez sobre este principio esta fue mi primera duda y tuve que leer muchas explicaciones más o menos vagas y abstractas hasta que lo entendí. Veamos, según el diccionario:</div>
<div>
<u>Inversión:</u> Acción y efecto de invertir.</div>
<div>
<u>Invertir:</u> Cambiar, sustituyéndolos por sus contrarios, la posición, el orden o el sentido de las cosas.</div>
<div>
<br /></div>
<div>
Así que la cosa "queda más clara". El DIP trata de cambiar el orden de las dependencias. Pero, ¿el orden con respecto a qué? El orden con respecto al diseño top-down.</div>
<div>
<br /></div>
<div>
En el diseño top-down empezamos con problemas de alto nivel y lo descomponemos en partes más pequeñas. Esas partes pequeñas a su vez las descomponemos en otras más pequeñas aún, etc. Es decir, los requerimientos de alto nivel los dividimos cada vez en partes más pequeñas y por consiguiente esos requerimientos dependen de las partes más pequeñas y más detalladas del sistema.</div>
<div>
<br /></div>
<div>
Un ejemplo:</div>
<div>
Supongamos que la empresa ACME Suministros S.A. nos encarga un sistema para hacer la facturación y el envío de la factura electrónica mensual a sus clientes de los suministros que han consumido. Aquí de forma muy simplista pongo la descomposición top-down del problema:</div>
<div>
<br /></div>
<div>
1. Facturar a los clientes</div>
<div>
1.1 Obtener datos de los clientes</div>
<div>
1.1.1 Abrir la conexión</div>
<div>
1.1.2 Ejecutar consulta SQL</div>
<div>
1.1.3 Traducir registros en entidades de negocio</div>
<div>
1.2 Calcular los importes de las facturas</div>
<div>
1.2.1 Calcular Subtotales de los consumos</div>
<div>
1.2.2 Calcular Impuestos aplicables</div>
<div>
1.2.3 Sumar Subtotal e impuestos</div>
<div>
1.3 Generar factura electrónica</div>
<div>
1.3.1 Obtener la plantilla de la factura</div>
<div>
1.3.2 Rellenar la plantilla con los datos</div>
<div>
1.3.3 Generar un archivo pdf.</div>
<div>
1.4 Enviar factura por email</div>
<div>
1.4.1 Crear un email</div>
<div>
1.4.2 Adjuntar el pdf al email</div>
<div>
1.4.3 Conectar con el servidor smtp</div>
<div>
1.4.4 Enviar el email</div>
<div>
<br /></div>
<div>
Bueno, no era tan difícil ¿no? Estos serían los pasos a seguir para realizar el requerimiento.</div>
<div>
<br /></div>
<div>
Vamos a detenernos un poco a analizar esto. Nuestro requerimiento de alto nivel "Facturar a los clientes" depende de "Obtener datos de clientes" que depende de "Ejecutar consulta SQL" (las dependencias siguen a la forma de descomponer el requerimiento). Normalmente las cosas de más bajo nivel son las que suelen ser más propensas al cambio. Tenemos requerimientos de alto nivel ("Facturar a los clientes") que dependen de partes pequeñas que son propensas al cambio. Además los pasos a seguir son muy sensibles a los cambios en los niveles altos y eso es un problema por que los requerimientos cambian. </div>
<div>
<br /></div>
<blockquote class="tr_bq">
Queremos invertir la dependencia con respecto a este tipo de descomposición top-down</blockquote>
<div>
Pues bien, si queremos invertir esa dependencia, manos a la obra. Supongamos que como parte de nuestro sistema para ACME Suministros S.A. escribimos el siguiente código:</div>
<br />
<pre class="brush:csharp;">
namespace Acme.BL
{
public class ClientsBL
{
public IEnumerable<ClientData> GetClientsData()
{
var dalClient = new ClientsDAL();
return dalClient.GetClientData();
}
}
}
namespace Acme.DAL
{
public class ClientsDAL
{
public IEnumerable<ClientData> GetClientData()
{
var dataResult = Enumerable.Empty<clientdata>();
// Get data from Database.
// and fill dataResult
return dataResult;
}
}
}
</pre>
</div>
<div>
El problema de este código es que un módulo de abstracción superior (ClientsBL) depende de otro inferior (ClientsDAL) y ese tipo de dependencia es justo la que queremos invertir. Veamos como:<br />
<br />
Siguiendo la regla para invertir las dependencias crearemos una interfaz IClientesDAL en el nivel de abstracción de ClientsBL de la que dependerán tanto ClientsBL como ClientsDAL<br />
<br />
<pre class="brush:csharp;">
namespace Acme.BL
{
public class ClientsBL
{
private IClientDAL dalClient;
public IEnumerable<ClientData> GetClientsData()
{
return dalClient.GetClientData();
}
}
public interface IClientsDAL
{
IEnumerable<ClientData> GetClientData();
}
}
namespace Acme.DAL
{
public class ClientsDAL : IClientsDAL
{
public IEnumerable<ClientData> GetClientData()
{
var dataResult = Enumerable.Empty<clientdata>();
// Get data from Database.
// and fill dataResult
return dataResult;
}
}
}
</pre>
<br />
Ahora el DIP se cumple porque tanto ClientsBL como ClientsDAL dependen de IClientsDAL que está en un nivel de abstracción igual o superior a ambas (la capa de negocio).<br />
Hemos pasado de esto:<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3vj19UaynkZ74zcA6IpjjqGSz-QTSCbaEKQZYIeyt7K-c_MCDdds6dCY0kPZ4uLo_FsETMRKeaDUgC6VRi-PDQmw0lC62E0voONJYyeYFUnbGJvX-WSrQG8WqZ-jIs_vtCvtAUseeRHoj/s1600/DIP1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3vj19UaynkZ74zcA6IpjjqGSz-QTSCbaEKQZYIeyt7K-c_MCDdds6dCY0kPZ4uLo_FsETMRKeaDUgC6VRi-PDQmw0lC62E0voONJYyeYFUnbGJvX-WSrQG8WqZ-jIs_vtCvtAUseeRHoj/s1600/DIP1.png" /></a></div>
<br />
a esto:<br />
<div class="separator" style="clear: both; text-align: left;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjinMM32I1FZSnYfSuPUYoSkvF7D19S8srkiLTMmfxu-3XdnqAzOwbqtRVrzhQjGCD9Xx2qrvfYo_YbG8lZAVneO1bzcL1AnzDyS2Dm5a-qV07NziJz7xuW8nursIrJ2Tcqr3zUavIiz6mT/s1600/DIP2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjinMM32I1FZSnYfSuPUYoSkvF7D19S8srkiLTMmfxu-3XdnqAzOwbqtRVrzhQjGCD9Xx2qrvfYo_YbG8lZAVneO1bzcL1AnzDyS2Dm5a-qV07NziJz7xuW8nursIrJ2Tcqr3zUavIiz6mT/s1600/DIP2.png" /></a></div>
<br />
Ya no hay dependencias de implementaciones concretas ni de nivel de abstracción inferior.<br />
Según el DIP nunca debemos crear dependencias hacia clases concretas, donde haya una dependencia siempre debe ser hacia una clase abstracta o una interfaz. Seguir este principio hasta las últimas consecuencias puede ser difícil y hay circunstancias en las que no seguirlo es razonable. Sin embargo, debemos tratar de aplicarlo tanto como sea posible.<br />
<br />
Hay que tener en cuenta que este principio se basa en la asunción de que las cosas concretas cambian mucho más que las abstractas. Además las interfaces y clases abstractas son puntos de extensión del diseño. Representan puntos donde el diseño puede ser extendido sin tener que ser modificado. Se cumple de esta forma con el <a href="http://designcodetips.blogspot.com.es/2013/10/the-openclose-principle-principio.html" target="_blank">OCP</a>.<br />
<br />
Soy consciente de que aún queda algo por resolver en el diseño anterior. ClientBL ahora depende de IClientDal, pero no podemos crear una instancia de una interfaz. Entonces, ¿Cómo nos las vamos a arreglar para que ClientBL tenga acceso a un objeto de tipo ClientDAL?<br />
<br />
Esto lo haremos aplicando Inyección de dependencias, pero dejaré la explicación de este concepto y la resolución del diseño para el próximo post.<br />
<br />
Aquí acaba la serie sobre <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">principios SOLID</a>. A partir de ahora nos esperan algunos post explicando otros principios de diseño (DRY, YAGNI, KISS) y algunos conceptos importantes relacionados con lo que hemos visto hasta ahora (Dependency Injection, Inversion of Control).</div>Unknownnoreply@blogger.com5tag:blogger.com,1999:blog-2728255754328606012.post-36035909471590918542013-10-29T13:04:00.002+01:002013-11-13T09:55:14.363+01:00The Interface Segregation Principle<b>The Interface Segregation Principle (ISP)</b><br />
<i>"Un cliente no debe estar obligado a depender de interfaces que no usa"</i><br />
<br />
Este post pertenece a la serie <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">Principios SOLID.</a><br />
<br />
Primero me gustaría aclarar que aunque el nombre de este principio usa la palabra "Interface" se refiere a una interfaz en un sentido amplio de la palabra como cualquier estructura de código que define una parte pública de la clase que la implementa. <br />
<a name='more'></a>Es decir, debemos entender una interfaz tanto en el sentido estricto (IDisposable, IEnumerable, etc...) como en el sentido de una clase abstracta que al heredar de ella obliga a implementar ciertos métodos.<br />
<br />
<blockquote class="tr_bq">
Es preferible varias interfaces pequeñas, cohesionadas y especializadas que una interfaz grande de propósito general </blockquote>
<br />
En este post hablaremos sobre los inconvenientes de interfaces grandes. Las clases con interfaces grandes están normalmente poco cohesionadas, es decir, los métodos y propiedades de la interfaz no suelen tener una relación fuerte con la misma responsabilidad "superior". Y es muy probable que estén violando el <a href="http://designcodetips.blogspot.com.es/2013/10/the-single-responsibility-principle.html">principio de responsabilidad única (SRP)</a> del que ya hablamos en un post anterior.<br />
Hay otra razón más sutil pero muy importante para evitar interfaces grandes. Supongamos la siguiente situación:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpGrddnWzhh_0VJHeO4KWpc64E30oClrBowQgG7LNQUleBhUahjP1QSc78jEZ48iOHS6Ze90qA6qcnMFCrSvbW8UP_YDOAkuE7Ll1GO2DBE-FmHKl1H9-CdznW5PjnNc2wcByREuMnE3JN/s1600/ISP1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhpGrddnWzhh_0VJHeO4KWpc64E30oClrBowQgG7LNQUleBhUahjP1QSc78jEZ48iOHS6Ze90qA6qcnMFCrSvbW8UP_YDOAkuE7Ll1GO2DBE-FmHKl1H9-CdznW5PjnNc2wcByREuMnE3JN/s1600/ISP1.png" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
La interfaz IMethodForAll la implementan tanto ClientA como ClientB. IMethodsForAll tiene métodos que realmente sólo usa ClientA y otros sólo ClientB pero ambos están obligados a implementar todos los métodos. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Supongamos ahora que nuestro jefe nos dice que después de un profundo estudio de las necesidades de los futuros clientes ha llegado a la conclusión de que se necesita agregar un nuevo método a ClientB y que en un alarde de originalidad llamaremos Method3ForClientB.</div>
<div class="separator" style="clear: both; text-align: left;">
Agregamos Method3ForClientB la clase ClientB y a la interfaz. Pero ahora nos encontramos con que debemos modificar también ClientA para que implemente la interfaz correctamente.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
Este ejemplo aunque de una forma simplista nos revela como al tener una interfaz grande cuando modificamos un cliente de la interfaz (ClientB) tenemos que modificar otro cliente (ClientA) que en principio no tiene porque estar relacionados. En otras palabras, produce un "acoplamiento indirecto" entre clientes de la interfaz.<br />
<br />
Para solventar este problema debemos segregar la interfaz en varias interfaces más pequeñas y cohesionadas para cada tipo de cliente. Si un método es común a varias de estas nuevas interfaces se pone el mismo método en todas las interfaces que lo necesiten (esto facilita mucho al cliente la comprensión de la interfaz que necesita implementar).<br />
<br />
Bueno, la idea está clara: <i>"dividir las interfaces grandes en varias interfaces pequeñas más cohesionadas".</i> Pero no nos volvamos locos. No podemos crear una interfaz para cada cliente, ni cosas como versiones de la misma interfaz al estilo IMethodsForClientA_v2, IMethodsForClientA_v3, etc. Esto asusta sólo con pensarlo.<br />
En lugar de eso, tendremos que tratar de agrupar los clientes por categorías y crear una interfaz para cada categoría de clientes. Si un cliente tiene que implementar métodos de varias categorías entonces implementará varias interfaces y solucionado.<br />
<br />
Un ejemplo de todo esto que hemos visto es la clase abstracta MembershipProvider del framework .NET.<br />
Esta es la clase abstracta de la que se debe heredar para implementar tu propio sistema de credenciales en una aplicación ASP.NET. Esta clase abstracta tiene<b> ¡27 métodos abstractos!</b> que te obliga a sobrescribir cuando heredas de ella y<b> ¡puedes escribir tu propio "provider" sobrescribiendo sólo el método ValidateUser!</b>. Una interfaz enorme y puedes crear clientes que sólo necesitan un método.<br />
<br />
Un problema que nos surge al usar clases abstractas o interfaces grande de terceros (como el caso de MembershipProvider) es que no podemos cambiarlas. Para separar estar interfaces grandes en otras mas pequeñas y cohesionadas podemos usar el <b>Patrón Adapter</b> que nos permite ocultar la parte de esa interfaz grande que no nos interesa.<br />
El<b> patrón adapter</b> lo veremos en profundidad junto con muchos otros en dentro de la próxima serie de post sobre patrones de diseño.<br />
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-2728255754328606012.post-60584048697665683752013-10-28T12:55:00.000+01:002013-12-18T10:45:30.480+01:00The Liskov's Substitution Principle (LSP)<h2>
The Liskov's Substitution Principle (LSP)</h2>
<div>
<i>"Las subclases deben poder sustituirse por sus clases base"</i></div>
<div>
<br />
Este post pertenece a la serie <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">Principios SOLID.</a><br />
<br /></div>
<div>
En el post <a href="http://designcodetips.blogspot.com.es/2013/10/the-openclose-principle-principio.html" target="_blank">"The Open/Close Principle (OCP)"</a> hablamos de que el OCP es la base para crear código mantenible y reutilizable. Además vimos que el concepto clave del OCP era al abstracción. Uno de los principales mecanismos para soportar abstracción en los lenguajes de tipos estáticos como C# es la herencia.<br />
<a name='more'></a></div>
<div>
Mediante herencia podemos crear clases derivadas que se ajustan a las interfaces definidas por las clases base.</div>
<div>
En este post vamos a ver la forma de crear buenas jerarquías de herencias. Y como evitar que nuestras jerarquía de clases violen el principio Open/Close.</div>
<div>
<br /></div>
<div>
El LSP establece que si un método recibe como parámetro una clase base, esta clase base debe poder sustituirse por una clase derivada sin afectar a la funcionalidad del método.<br />
<br />
<br /></div>
<div>
<br /></div>
<div>
O lo que es lo mismo, desde un punto de vista más práctico: debemos asegurarnos que las clases derivadas sólo extienden a la clase base pero en ningún caso modifican o reemplazan su comportamiento. De lo contrario las clases derivadas pueden producir efectos no deseados al se usadas en los métodos.</div>
<blockquote class="tr_bq">
Una clase derivada extiende a la clase base pero no reemplaza su comportamiento</blockquote>
La importancia de este principio se hace más evidente cuando se consideran las consecuencias de su violación. Si una función no se ajusta al LSP, entonces esa función aunque reciba como parámetro una clase base debe conocer todas las clases derivadas de la clase base para realizar su funcionalidad. Además este método violará también <a href="http://designcodetips.blogspot.com.es/2013/10/the-openclose-principle-principio.html" target="_blank">el principio Open/Close</a> por lo que cuando cambie la jerarquía de la clase base deberá cambiar también el método.<br />
<br />
Una de las formas más evidentes de violación de este principio es la siguiente. Supongamos el siguiente método para dibujar una figura:<br />
<pre class="brush:csharp;">void DrawShape(Shape shape)
{
if (shape.GetType() == typeof(Square))
DrawSquare((Square)shape);
else if (shape.GetType() == typeof(Circle))
DrawCircle((Circle)shape);
}
</pre>
<br />
Aunque se que en algún momento todos hemos visto algo como el método anterior. Claramente DrawShape supone un problema. El método debe cambiar cada vez que agregamos una clase derivada de Shape. De hecho DrawShape está bastante próximo a lo podríamos considerar "un atentado" contra el diseño orientado a objetos.<br />
<br />
<b>Cuadrado y rectángulo</b><br />
Hay otras formas más sutiles de violación del LSP. Consideremos el ejemplo típico del dilema del rectángulo y el cuadrado.<br />
Supongamos una clase Rectangle y una clase Square como las siguientes:<br />
<pre class="brush:csharp;">public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }
}
public class Square : Rectangle
{
public override int Height
{
get { return base.Height; }
set { SetWidthAndHeight(value); }
}
public override int Width
{
get { return base.Width; }
set { SetWidthAndHeight(value); }
}
// Both sides of a square are equal.
private void SetWidthAndHeight(int value)
{
base.Height = value;
base.Width = value;
}
}
</pre>
<br />
Bien, tenemos una clase Square que hereda de Rectangle, al fin y al cabo un cuadrado es un rectángulo. Todo parece correcto, sin embargo, la clase Square viola el LSP por que cambia el comportamiento de las propiedades Width y Height de su clase base.<br />
<br />
<b>El problema real</b><br />
Las clases Rectangle y Square parecen ser correctas. La clase Square mantiene una consistencia matemática (sus lados siempre son iguales). Podríamos pasar un objeto Square a un método que acepte un tipo Rectangle y el objeto Square seguiría comportándose como un cuadrado y manteniendo su consistencia.<br />
<br />
Por lo tanto, podemos llegar a la conclusión que el modelo anterior es correcto y consistente. Sin embargo, <b>un modelo que sea por si mismo consistente no tiene por que serlo para todos los usuarios. </b>Veamos el siguiente código para entender esto:<br />
<pre class="brush:csharp;">[TestMethod]
public void CheckRectangleArea(Rectangle rect)
{
rect.Width = 4.0;
rect.Height = 5.0;
Assert.AreEquals(rect.Width * rect.Height, 20);
}
</pre>
<br />
Este método de prueba establece el alto y el ancho de lo que cree que es un rectángulo. El método funciona bien si pasamos como parámetro un objeto de tipo Rectangle, pero lanza una excepción si pasamos un parámetro de tipo Square.<br />
Al desarrollar el método de prueba asumimos correctamente que al cambiar la altura de un rectángulo su ancho permanece inalterado. Este es un ejemplo de un método que recibe una clase base como parámetro pero que no funciona correctamente si pasamos una clase derivada. El método de prueba no hace sino poner de manifiesto la violación del LSP cometida al implementar la clase Square. Evidentemente además del LSP la implementación de Square viola el principio Open/Close.<br />
<br />
<b>La validez de un modelo no es intrínseca.</b><br />
Lo visto anteriormente nos lleva a la conclusión de que un modelo visto de forma aislada no se puede validar correctamente. El anterior modelo parecía correcto, pero el uso con una asunción correcta sobre mismo (al cambiar el alto de un rectángulo su ancho permanece inalterable) hizo que el modelo fallara.<br />
<br />
La validación de un modelo no podemos hacerla de forma aislada. Hay que hacerla desde el punto de vista de las asunciones razonables que quien lo use hará del modelo.<br />
<br />
<b>Entonces, ¿Que hemos hecho mal?</b><br />
¿Qué ha pasado?¿Por qué un modelo de un rectángulo y un cuadrado aparentemente correcto está mal?<br />
Después de todo, ¿un cuadrado no es un rectángulo?¿No se da entre ellos la relación "es un"?<br />
No! Quizá un cuadrado sea un rectángulo. Pero un objeto de tipo Square no es un objeto de tipo Rectangle.<br />
¿Por qué?. Pues porque el comportamiento de un objeto Square no es consistente con el comportamiento de un objeto Rectangle. Es decir, desde el punto de vista del comportamiento un cuadrado no es un rectángulo. Y no debemos olvidar que cuando desarrollamos software realmente estamos desarrollando el comportamiento de un sistema.<br />
<br />
Así, el LSP deja claro que la relación "es un" en la jerarquía de herencia de clases debe verse desde un punto de vista del comportamiento público de las clases. Es decir, un cuadrado es un rectángulo (hereda de rectángulo) si el comportamiento público de un cuadrado es consecuente con el comportamiento público de un rectángulo. En este caso hemos visto que no es así por que el comportamiento público de un rectángulo establece que el ancho y el alto cambian de forma independiente, mientras que en un cuadrado cambian a la vez.<br />
<br />
<blockquote class="tr_bq">
El LSP deja claro que la relación "es un" en la jerarquía de herencia de clases debe verse desde un punto de vista del comportamiento público de las clases</blockquote>
<b>Diseño por contrato.</b><br />
Para establecer el contrato de un método entre otras cosas establecemos las condiciones que se deben cumplir antes de llamar a un método (pre-condiciones) y las que se deben cumplir después de llamar al método. Si hubiéramos hecho explícitamente el contrato de Rectangle veríamos por ejemplo una pos-condición en el método set de la propiedad Width que establece que el valor pasado como parámetro (value) debe ser asignado a la propiedad Width y la propiedad Height debe permanecer inalterada.<br />
<br />
Claramente la clase Square viola la pos-condición del contrato de Rectangle por que altera el valor de la propiedad Height, por lo que Rectangle no es sustituible por Square y viola el LSP.<br />
<br />
En términos de contrato una clase derivada es sustituible por su clase base si:<br />
<br />
<ol>
<li>Sus pre-condiciones son menos restrictivas que las de la clase base.</li>
<li>Sus pos-condiciones son más restrictivas que las de la clase base.</li>
</ol>
<div>
En el caso de Square las pos-condición del método set de la propiedad Width es menos restrictiva que la de Rectangle. La de Rectangle no permite que varíe el valor de Height mientras que la de Square si. Por este motivo Square viola el LSP.</div>
<div>
<br /></div>
<div>
<b>¿Como implementar los contratos en nuestro código?</b></div>
<div>
<span style="background-color: white;"><span style="font-family: sans-serif; font-size: x-small;"><span style="line-height: 19.1875px;">El término "Design by Contract" fue acuñado por</span></span><a href="http://en.wikipedia.org/wiki/Bertrand_Meyer" style="font-family: sans-serif; font-size: 13px; line-height: 19.1875px;" target="_blank"> </a></span><a href="http://en.wikipedia.org/wiki/Bertrand_Meyer" target="_blank">Bertrand Meyer</a><span style="background-color: white; font-family: sans-serif; font-size: 13px; line-height: 19.1875px;"> en relación con el diseño de su lenguaje de programación "Eiffel" y es una marca comercial registrada por Eiffel Software por lo que no hay que confundirla con el concepto genérico de diseño por contrato. Microsoft llama a su implementación del diseño por contrato <a href="http://research.microsoft.com/en-us/projects/contracts/" target="_blank">"Code Contracts"</a></span></div>
<div>
<br /></div>
<br />
Bien, visto todo lo anterior sólo nos queda resolver como implementar correctamente este modelo para que se ajuste al LSP. Este sería un modelo más correcto para Rectangle y Square:<br />
<pre class="brush:csharp;"> public abstract class Shape
{
public virtual double Width { get; set; }
public virtual double Height { get; set; }
}
public class Rectangle : Shape
{
}
public class Square : Shape
{
public override double Height
{
get { return base.Height; }
set { SetWidthAndHeight(value); }
}
public override double Width
{
get { return base.Width; }
set { SetWidthAndHeight(value); }
}
// Both sides of a square are equal.
private void SetWidthAndHeight(double value)
{
base.Height = value;
base.Width = value;
}
}
</pre>
<br />
El LSP es una característica importante de los diseños que se ajustan al principio Open/Close. Solamente cuando las clases derivadas son sustituibles por sus clases bases, los métodos que usan las clases bases son completamente reutilizables e independientes de la evolución de la jerarquía de herencia de la clase base. Por lo tanto no se necesitan cambiar cuando cambia esta jerarquía y se ajustan al principio Open/Close.<br />
<br />Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-2728255754328606012.post-60914658420240436662013-10-24T12:05:00.001+02:002013-12-18T10:47:43.605+01:00The Single Responsibility Principle (Principio de responsabilidad única)<h2>
Single Responsibility Principle (SRP)</h2>
<div>
<i>"Una clase sólo debe tener una responsabilidad. O lo que es lo mismo, una clase debe tener una y sólo una razón para cambiar". </i>(Robert C. Martin)<br />
<i><br /></i>
Este post pertenece a la serie <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">Principios SOLID.</a><br />
<i><br /></i>
Realmente este parece un principio simple, sin embargo no hay que dejarse engañar por las apariencias. A lo largo de este post veremos es necesario conocer en profundidad todas las repercusiones de este principio para poder aplicarlo correctamente.<br />
<a name='more'></a><br />
<i><br /></i>
Debemos considerar una responsabilidad como una razón para cambiar. En esencia este principio establece que si una clase tiene dos responsabilidades deberemos dividir la clase en dos y cada una de ellas gestionará una sola responsabilidad. De esta manera si en el futuro cambia un requerimiento y esto se traduce en un cambio en una responsabilidad deberemos hacer el cambio solamente en la clase que maneja esa responsabilidad.<br />
<br />
<b>¿Pero, por que es importante separar responsabilidades en clases separadas?</b><br />
Si tenemos una clase con dos responsabilidades que no cumple el SRP y debemos hacer cambios en una de sus responsabilidades corremos el riesgo de que el cambio afecte también al funcionamiento de la otra responsabilidad.<br />
<br />
Veamos un ejemplo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF1y44XMyQo8SmSrZolNb7ySiwoSx11Rsdx9BzNGMR5Gi85tLBAcK9fHfG-qodi8kUHJ-7XJH-t4XrkJg8exokN_sAN9d0qIyATzTgDptZvHDTUzaAJfFj87syKe-yWzxlve2lN8TITbg1/s1600/SRP1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiF1y44XMyQo8SmSrZolNb7ySiwoSx11Rsdx9BzNGMR5Gi85tLBAcK9fHfG-qodi8kUHJ-7XJH-t4XrkJg8exokN_sAN9d0qIyATzTgDptZvHDTUzaAJfFj87syKe-yWzxlve2lN8TITbg1/s1600/SRP1.png" height="158" width="320" /></a></div>
<br />
En el diagrama la clase Rectangle tiene dos métodos. Uno muestra un rectángulo en la pantalla y otro calcula el área del rectángulo. Hay un módulo de cálculo de geometrías que usa la clase Rectangle para realizar el cálculo de áreas y nunca dibuja la figura en pantalla. Por otro lado una aplicación de diseño gráfico que usa la clase Rectangle fundamentalmente para dibujar la figura en la pantalla.<br />
<br />
Este diseño rompe el SRP. La clase Rectangle tiene dos responsabilidades. Una realizar el cálculo del área del rectángulo y otra dibujar la figura en pantalla.<br />
<br />
La violación del SRP presenta en este caso diversos problemas. Primero necesitamos incluir el GUI en el módulo de cálculo de geometrías por la dependencia que Rectangle tiene sobre GUI. Segundo si un cambio en los requisitos de la aplicación de diseño gráfico provoca un cambio en la clase Rectangle, nos obligaría a recompilar y distribuir de nuevo el módulo de cálculo de geometrías.<br />
<br />
Un mejor diseño sería separar las dos responsabilidades de la clase Rectangle en dos clases distintas, como se muestra a continuación:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPCQwriQAF434teMH1wDkB5q1EsJ7rtdTIOr2lnUVM579X0W17jth2soscJkkTwW1ZE0F_9vNqnTIRfs9b-Rmz-svQZXvgf1qFKn7paLCvCwr2PrMIPG4kQFP2eM98KN6dnU6SQulRtr2e/s1600/SRP2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPCQwriQAF434teMH1wDkB5q1EsJ7rtdTIOr2lnUVM579X0W17jth2soscJkkTwW1ZE0F_9vNqnTIRfs9b-Rmz-svQZXvgf1qFKn7paLCvCwr2PrMIPG4kQFP2eM98KN6dnU6SQulRtr2e/s1600/SRP2.png" height="144" width="320" /></a></div>
<br />
La responsabilidad de cálculo del área ahora se ha traspasado a la clase GeometricRectangle. Ahora los cambios que se realicen en la responsabilidad de dibujar el rectángulo no afectará en modo alguno al cálculo del área.<br />
<br />
<h2>
Una interpretación más amplia del SRP</h2>
En su articulo <a href="http://www.developerfusion.com/article/137636/taking-the-single-responsibility-principle-seriously/" target="_blank">"Taking the Single Responsibility Principle Seriously"</a> <b>Ralf Westphal</b> redefine y amplia el alcance el SRP. Para <b>Ralf Westphal</b> la definición de <b>Robert C. Martin</b> se circunscribe a las clases, pero el SRP es un principio de deben aplicarse a otro tipo de estructuras de código como métodos o estructuras superiores como módulos o incluso aplicaciones.<br />
<br />
<b>Ralf Westphal</b> sugiere esta definición para el SRP:<br />
<i>"Una unidad funcional en un determinado nivel de abstracción sólo debe ser responsable de un solo aspecto de los requisitos de un sistema. Un aspecto de los requisitos es un rasgo o característica de requisitos, que puede cambiar de forma independiente de otros aspectos".</i></div>
<div>
<i><br /></i></div>
<div>
Esto necesita ser revisado con un poco más de detenimiento. Aunque la definición va en línea con la de <b>Robert C. Martin, </b>reconoce responsabilidades a varios niveles por lo que<b> </b>no se centra en la estructura de la clase sino que habla de unidad funcional. Una aplicación tiene una responsabilidad (de grano grueso, claro) y un evento también tiene una responsabilidad (de grano muy fino).</div>
<div>
<br />
Al no estar esta definición centrada en el código, se pueden relacionar el SRP con los requisitos de una unidad funcional incluso aunque aún no exista código. Por lo tanto podemos analizar los diferentes aspectos que se derivan de los requisitos funcionales y extraer cada uno de esos aspectos en unidades funcionales distintas. Este proceso se irá repitiendo con una granularidad cada vez más fina guiando así el diseño de software.<br />
<br /></div>
<div>
Las responsabilidades no deben solaparse al mismo nivel de abstracción. Dos métodos de la misma clase deben ser responsables de diferentes aspectos, pero ambos métodos deben estar preocupados de la misma responsabilidad de nivel superior, por ejemplo la persistencia en un repositorio.<br />
<br />
Aplicar esta perspectiva del SRP requiere de una sólida comprensión de que aspectos encontramos en los requisitos. Esto es fácil para los aspectos más típicos como los relacionados con la tecnología (acceso a una base de datos, leer/escribir en el sistema de archivos, conectar a servicios web, etc.). Pero hay más aspectos de los que parece a primera vista. Hay aspectos sutiles que se suelen pasar por alto y que nos llevan a escribir un código que no cumple completamente con el SRP y hace que mantenerlo y evolucionarlo sea más difícil de lo que nos gustaría.<br />
<br />
<b>Aspectos típicos.</b><br />
Como ya he mencionado, los aspectos típicos son fáciles de detectar y suelen estar relacionados con la tecnología. En general, cualquier interacción de nuestro software con el entorno es un aspecto y puede cambiar de forma independiente. Pueden ser interacciones del entorno con nuestro software (un usuario interactua con nuestro software y puede hacerlo a través de una consola, una página web, un cliente de escritorio etc.); como de nuestro software con el entorno (al arrancar el software lee un archivo xml para establecer la configuración, accede a una base de datos, interactua con un componente de GIS para posicionar objetos en un mapa, etc.). Todos los anteriores son aspectos y debemos crear para cada uno de ellos una unidad funcional que es responsable de gestionar ese aspecto.<br />
<br />
<b>Aspectos sutiles.</b><br />
Es relativamente fácil detectar aspectos funcionales y no funcionales. Aislarlos en unidades funcionales con una única responsabilidad es cuestión de disciplina. Esta es la base para tener aplicaciones, módulos y clases limpias.<br />
<i>Pero, ¿y los métodos? ¿cómo aplicamos el SRP en los métodos?</i> Al fin y al cabo es en los métodos donde se escribe el "payload" del código.<br />
Vamos a ver una serie de aspectos que a menudo encontramos mezclados en los métodos. Estos son aspectos sutiles. No son fáciles de ver para todo el mundo porque en la mayoría de los casos esas mezclas de aspectos están arraigadas tanto en el pensamiento común como en las herramientas de desarrollo.<br />
<br />
Es muy fácil caer en la mezcla de un aspecto funcional con otros no funcionales. Este es un ejemplo de Robert C. Martin<br />
<br />
<pre class="brush:csharp;">public interface IModem
{
void Dial(String number);
void Hangup();
void Send(char c);
char Recv();
}
</pre>
Esto es muy común. A un alto nivel de abstracción esta interfaz describe un aspecto de comunicación. ¿Por qué no reunir todo lo necesario para la comunicación en una misma interfaz?.<br />
Tras una inspección más detallada debemos entender que en una comunicación por modem existen dos aspectos distintos y que pueden cambiar de forma independiente; por un lado establecer la comunicación y por otro enviar datos. La forma en que se termina una comunicación puede cambiar de forma independiente de como se envían y reciben datos.<br />
<br />
Efectuaremos un pequeño cambio a ver que pasa. Consideraremos a un modem como un recurso y haremos que implemente la interfaz IDisposable para facilitar su liberación. Así podríamos pasar de este código para usar el modem<br />
<pre class="brush:csharp;">IModem m = new IsdnModem();
m.Dial(…);
m.Send(…);
m.Hangup();
</pre>
<br />
A este otro
<br />
<pre class="brush:csharp;">using(IModem m = new IsdnModem())
{
m.Dial(…);
m.Send(…);
}
</pre>
<br />
La interfaz ahora quedaría de esta forma:<br />
<pre class="brush:csharp;">public interface IModem : IDisposable
{
void Dial(String number);
void Send(char c);
char Recv();
}
</pre>
Este es un cambio simple que se centra en la forma de liberar la conexión, sin embargo afecta a código que nada tiene que ver con el manejo de la conexión del modem.<br />
<br />
Ahora vamos a refactorizar este código para alinearlo con el SRP. Como ya hemos visto el manejo de la conexión del modem es un aspecto relacionado con la infraestructura, así que vamos a extraerlo a su propia interfaz. Ahora tendremos una interfaz para cada uno de los aspectos que hemos detectado, el manejo del la conexión y el envio/recepción de datos.<br />
<pre class="brush:csharp;">public interface IModemConnection : IDisposable
{
void Dial(String number);
}
public interface IModemDataExchange
{
void Send(char c);
char Recv();
}
</pre>
<br />
Bien, ya tenemos nuestros dos aspectos separados en diferentes unidades funcionales. Para la comunicación por modem el manejo de la conexión es un aspecto no funcional y el envío y recepción de datos es el aspecto funcional, ya que el propósito principal de la comunicación por modem es enviar y recibir datos. Tener que establecer una comunicación primero es un "mal necesario".<br />
Ahora podríamos implementar estas dos interfaces en una misma clase. Esto probablemente sea lo más fácil en la practica. Sin embargo, si queremos hacer una aplicación más estricta del SRP creando implementaciones independientes para cada aspecto debemos hacer aún un pequeño cambio.<br />
<pre class="brush:csharp;">public interface IModemConnection : IDisposable
{
IModemDataExchange Dial(String number);
}
public interface IModemDataExchange
{
void Send(char c);
char Recv();
}
</pre>
<br />
La implementación de IModemConnection se convierte en una factoria para la implementación de IModemDataExchange. Esto parece un buen equilibrio. Consigue la separación de los dos aspectos de la comunicación por modem mientras que queda claro que ambos aspectos forman parte del mismo aspecto superior (de grano más grueso), la comunición por modem. Por otro lado tambien asegura que no se pueda usar la implemetación de IModemDataExchange antes de establecer la conexión (método Dial).<br />
<br />
Otro ejemplo típico de mezcla de aspectos funcionales y no funcionales:<br />
<pre class="brush:csharp;">void StoreCustomer(Customer c)
{
trace.Write("Storing customer…");
using(db.Connect(…))
{
var tx = db.OpenTransaction();
try
{
db.ExecuteSql(…); // Store name and address
db.ExecuteSql(…); // Store contact data
tx.Commit();
}
catch(Exception ex)
{
tx.Rollback();
trace.Write("Failed to store customer");
log.Log("Storing customer failed; exception: {0}", ex);
throw new ApplicationException(…);
}
}
}
</pre>
<br />
Este es un código bastante típico. Pero, ¿cuántos aspectos distintos mezcla este método?.<br />
La responsabilidad obvia de este método es guardar los datos de un cliente, sin embargo las líneas que realmente hacen este trabajo están enterradas entre muchas líneas que manejan aspectos no funcionales. Además de guardar los datos de un cliente este método es también responsable de escribir trazas, abrir la conexión a la base de datos, manejar una transacción, manejar excepciones y escribir en el log.<br />
<br />
Esta mezcla de aspectos funcionales y no funcionales hace que el mantenimiento de este método sea más difícil, pero además de "meter ruido" hace que intuir el propósito del método sea complicado.<br />
<br />
La programación orientada a aspectos (AOP) trata de resolver este tipo de problema. Existen innumerable frameworks de AOP como PostSharp o AspectJ que ofrecen diferentes maneras de separar los aspectos no funcionales de los funcionales. Me dejo apuntado para el futuro escribir una serie sobre AOP.<br />
<br />
<blockquote class="tr_bq">
Encontrar y separar las responsabilidades unas de otras es gran parte de lo que el diseño de software es.</blockquote>
<br />
Como hemos visto el SRP es uno de los principios de diseño más simples en cuanto a concepto y a la vez dificil de aplicar correctamente. Encontrar y separar las responsabilidades unas de otras es gran parte del trabajo que debemos realizar en el diseño de software. De hecho en el resto de principios de diseño que iremos viendo volveremos a este concepto de una forma o de otra.<br />
<br />
<i><br /></i></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-2728255754328606012.post-1296780220611951162013-10-21T09:32:00.000+02:002014-01-31T08:56:58.554+01:00The Open/Close Principle (Principio Abierto/Cerrado)<h2>
The Open/Close Principle (OCP)</h2>
<i>"Un módulo debe estar abierto para la extensión pero cerrado para la modificación."</i><br />
<i><br /></i>
Este post pertenece a la serie <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">Principios SOLID.</a><br />
<i><br /></i>
De todos los principios de diseño este es quizá el más importante. Quiere decir que debemos diseñar nuestros módulos de forma que puedan extenderse sin tener que modificarlos. En otras palabras, tenemos que poder cambiar lo que el módulo hace sin cambiar el código fuente del módulo.<br />
<a name='more'></a><br />
Quizá esto suene contradictorio, pero hay diferentes técnicas para conseguir aplicar este principio. Todas estas técnicas se basan en la abstracción, de hecho la abstracción es el concepto clave del OCP.<br />
<blockquote class="tr_bq">
La abstracción es el concepto clave del OCP</blockquote>
Supongamos que nuestro cliente "Cálculos de Áreas ACME" nos solicita una aplicación para calcular la suma de las áreas de una colección de rectángulos. Esto no supone ningún problema de entrada para nosotros.<br />
Primero escribimos nuestra clase para el rectángulo.<br />
<pre class="brush:csharp;">public class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
}
</pre>
<br />
Después escribimos una clase para calcular el área de la colección de rectángulos.<br />
<pre class="brush:csharp;">public class AreaCalculator
{
public double Area(Rectangle[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.Width * shape.Height;
}
return area;
}
}
</pre>
<br />
Todo perfecto, nuestra aplicación funciona. Pero antes de entregarla, el responsable de marketing de "Cálculos de Áreas ACME" nos llama para decirnos que van a hacer una gran campaña de publicidad ofreciendo el servicio de "Cálculo de áreas de círculos" y necesita que nuestra aplicación soporte también el calculo del área de círculos.<br />
<br />
Bien. Llegamos a la conclusión que no nos costaría mucho satisfacer al cliente si hacemos algunas modificaciones al código del método Area. Escribimos primero una clase para representar al área de tipo círculo.<br />
<pre class="brush:csharp;">public class Circle
{
public double Radius { get; set; }
}
</pre>
Y hacemos unos cambios en nuestro método Area()
<br />
<pre class="brush:csharp;">public double Area(object[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
if (shape is Rectangle)
{
Rectangle rectangle = (Rectangle)shape;
area += rectangle.Width * rectangle.Height;
}
else
{
Circle circle = (Circle)shape;
area += circle.Radius * circle.Radius * Math.PI;
}
}
return area;
}
</pre>
<br />
¿Cual es el problema con este código?<br />
Evidentemente la estructura de if/else en el método Area. Desde la perspectiva de OCP esta estructura nos obligará a modificar esta clase cada vez que agregamos un tipo nuevo de área a calcular.<br />
Aunque en un ejemplo sencillo como este esto no parezca un problema muy grave. Lo habitual es que el software que se diseña de esta forma está plagado de este tipo de estructuras if/else o switch por todo el código.<br />
<br />
<b>Una solución que aplique OCP a nuestro problema.</b><br />
Una forma de solucionar este problema es hacer que todas las áreas implementen una interfaz común de la que dependa nuestro método Area()<br />
Escribimos la interfaz y reescribimos nuestras clases Rectangle y Circle para que implementen la interfaz.<br />
<br />
<pre class="brush:csharp;">public interface IShape
{
double GetArea();
}
public class Rectangle : IShape
{
public double Width { get; set; }
public double Height { get; set; }
public double GetArea()
{
return Width * Height;
}
}
public class Circle : IShape
{
public double Radius { get; set; }
public double GetArea()
{
return Radius * Radius * Math.PI;
}
}
</pre>
<br />
Ahora reescribimos nuestra clase AreaCalculator
<br />
<pre class="brush:csharp;">public class AreaCalculator
{
public double Area(IShape[] shapes)
{
double area = 0;
foreach (var shape in shapes)
{
area += shape.GetArea();
}
return area;
}
}
</pre>
<br />
Hemos movido la responsabilidad de calcular el área de nuestra clase AreaCalculator a cada uno de los tipos de áreas (circulo y rectángulo) consiguiendo un código más robusto y haciendo que no sea necesario modificar el código de AreaCalculator si aparecen nuevos tipos de áreas a calcular.<br />
<br />
<blockquote class="tr_bq">
Aplicar el OCP es la base para crear código mantenible y reutilizable</blockquote>
<br />
Aplicando OCP a nuestro código podemos conseguir módulos que podemos extender sin tener que cambiarlos. Es decir, podemos extender su funcionalidad sin tocar el código que ya está escrito. De esta manera es menos probable cometer errores.<br />
Incluso si no es posible aplicar el OCP completamente, una aplicación parcial ya será un gran avance en cuanto a calidad de nuestro código.<br />
<br />
Unknownnoreply@blogger.com6tag:blogger.com,1999:blog-2728255754328606012.post-36662240214368524502013-10-21T09:31:00.000+02:002014-01-31T08:56:38.097+01:00Introducción a los principios de diseño<h2>
¿Que son los principios de diseño de software?</h2>
<div>
Este post pertenece a la serie <a href="http://designcodetips.blogspot.com.es/search/label/SOLID" target="_blank">Principios SOLID.</a><br />
<br />
Los principios de diseño de software son una serie de directrices que nos ayudan a crear buenos diseños de software.<br />
<a name='more'></a></div>
<div>
Según Robert Martin hay cuatro síntomas que determinan un mal diseño y que debemos evitar:</div>
<div>
<ol>
<li><b>Rigidez.</b> Rigidez es la tendencia del software a ser cambiado incluso de forma trivial. Cada cambio provoca a su vez una cadena de cambios en módulos relacionados, y lo que en principio es un pequeño cambio se convierte en una maratón de cambios interminable. Cuando el software se comporta de esta manera los gestores del proyecto suelen ser cada vez más reticentes a realizar cambios que no sean críticos, ya que no se tiene certeza de cuando van a terminar. Lo que empieza como un error de diseño, acaba como una política de empresa de "rigidez oficial".</li>
<li><b>Fragilidad. </b>La fragilidad está relacionada con la rigidez y es la tendencia del software a fallar cuando se realizan cambios. A veces los fallos se producen en módulos que no están relacionados conceptualmente con el módulo donde se han realizado los cambios. Según aumenta la fragilidad aumenta también la probabilidad de rotura al realizar cambios. En un plano más subjetivo tanto en los responsables del proyecto como en los clientes empieza a aparecer la desconfianza y se pierde la credibilidad de los desarrolladores.</li>
<li><b>Inmovilidad. </b>La inmovilidad es la imposibilidad de reutilizar piezas de software de un proyecto en otro o entre áreas del mismo proyecto. Es frecuente que un desarrollador descubra que necesita un módulo muy parecido a otro que ya ha escrito otro desarrollador. Sin embargo, también es frecuente que descubra que dicho módulo tiene muchas relaciones de las que depende. Después de mucho trabajo el desarrollador descubre que el esfuerzo y el riesgo que conlleva separar ese módulo de sus dependencias es mayor del que está dispuesto a asumir, y entonces simplemente reescribe el módulo en lugar de reutilizarlo.</li>
<li><b>Viscosidad.</b> Cuando un desarrollador se enfrenta a un cambio, normalmente encontramos varios métodos para afrontarlo. Algunas de esos métodos respetan el diseño y otros no. Cuando los métodos que respetan el diseño son más difíciles de emplear que los que no lo respetan, se dice que la viscosidad del diseño es alta.</li>
</ol>
<div>
Estos cuatro síntomas son signos que revelan un diseño pobre, y cualquier aplicación donde aparecen irá sufriendo una degradación cada vez mayor.</div>
</div>
<h2>
</h2>
<h2>
Principios de diseño SOLID </h2>
<div>
Los principios SOLID para diseños orientados a objetos es una guía de cinco principios que fueron enunciados por Robert Martin en el año 2000. Estos principios se aplican todos juntos con la finalidad de conseguir que el código de software sea más legible, más facilmente mantenible y extensible en el tiempo.
</div>
<div>
<br /></div>
<div>
El acrónimo SOLID responde a las iniciales del nombre en inglés de estos cinco principios.</div>
<h3>
<ul>
<li>S. Principio de responsabilidad única <span style="font-size: small;"><span style="font-weight: normal;">(Single Responsibility Principle).<i> "Un componente de software sólo debe tener una responsabilidad o lo que es lo mismo un componente de software solo debería tener un motivo para cambiar."</i></span></span></li>
<li>O. Principio de Abierto/Cerrado<span style="font-size: small; font-weight: normal;"> (Open/Close Principle).<i> "Los componentes de software deben estar abiertos a la extensión pero cerrados a las modificaciones."</i></span></li>
<li>L. Principio de sustitución de Liskov <span style="font-size: small; font-weight: normal;">(Liskov's Substitution Principle). <i>"Las subclases deben ser sustituibles por su clase base."</i></span></li>
<li>I. Principio de segregación de interfaces <span style="font-size: small; font-weight: normal;">(Interface Segregation Principle).<i> "Muchas interfaces específicas del cliente son mejores que una interfaz de uso general."</i></span></li>
<li>D. Principio de inversión de dependencias <span style="font-size: small;"><span style="font-weight: normal;">(Dependency Inversion Principle). </span><i><span style="font-weight: normal;">"Depender de abstracciones. No depender de concreciones."</span></i></span></li>
</ul>
</h3>
<div>
En próximos post veremos en profundidad cada uno de estos principios con ejemplos.<br />
<br />
Además también echaremos un vistazo a otros principios de diseño que por algún motivo suelen asociarse a metodologías de eXtreme Programming aunque para mí son principios de diseño de lo más básico que deberíamos tener en consideración en cualquier desarrollo. Estos principios son:<br />
<br />
<ul>
<li><b>DRY.</b> Don`t Repeat Yourself (No te repitas).<i> "Cada pieza de conocimiento debe tener una representación única, inequívoca y autorizada dentro de un sistema."</i></li>
<li><b>YAGNI. </b>You Aren’t Gonna Need It (No lo vas a necesitar).<i> "Siempre implementa las cosas cuando las necesites, no cuando preveas que las vas a necesitar."</i></li>
<li><b>KISS. </b>Keep It Simple, Stupid (Mantenlo simple, estúpido). <i>"Los sistemas más eficaces son los que mantienen la simplicidad, evitando la complejidad innecesaria."</i></li>
</ul>
<br /></div>
Unknownnoreply@blogger.com5