domingo, 17 de abril de 2011

Banda toca en vivo con iPhones y iPads como instrumentos

Una banda de rock adolescente de Kazan, Rusia, es el primer grupo del mundo que tocan una serie de conciertos en vivo utilizando sólo iPads de Apple y el iPhone como instrumentos musicales.

En lugar de utilizar los instrumentos tradicionales, los adolescentes descargan las aplicaciones para la bateria, teclados y guitarras y el juego de sus gadgets de Apple en el escenario, a través de una mesa de mezcla descargado. Juegan las cubiertas y éxitos de las leyendas de rock como Nirvana, y decir a sus audiencias les gusta mucho sus interpretaciones originales. Ruslan Halikov, el guitarrista de la Cooperativa de estilo utiliza el iPhone dice que es mucho más difícil de tocar una guitarra virtual que es real, por no mencionar el rock and roll plantea no verse bien en todo. Sin embargo, el público parece que le gusta lo que hacen.

Si bien estos tipos puede ser el primero en jugar a las vacas de Apple en efectivo en actuaciones en directo, el iPhone y el IPAD han sido utilizado de largo para hacer música de grupos de aficionados, y algunas de sus actuaciones son realmente muy buenos. Usted puede encontrar un montón de vídeos en YouTube.





lunes, 11 de abril de 2011

¿IPAD 3 llegará este año?

A principios de este año, los rumores han sugerido que podría estar recibiendo dos revisiones el IPAD este año. El IPAD 2 fue lanzado en marzo, pero se había sugerido que el IPAD 3 no puede ser que muy a la zaga, posiblemente en septiembre de 2011.

Sin embargo, es ahora citando fuentes componente socio que Apple todavía no ha proporcionado ninguna información sobre la fabricación del IPAD 3. Esto les lleva a concluir que el IPAD 3 no será enviado hasta el próximo año:

Las fuentes señalaron que aún no han recibido ninguna notificación de la próxima generación de productos IPAD y no creen IPAD 2 es un producto de transición. Con IPAD dos de pronóstico fuerte para el envío de Apple, las fuentes creen IPAD 3 no aparecerá en el corto plazo.

Conoce mas sobre el rumor del Ipad 3 aqui.

miércoles, 6 de abril de 2011

Por qué tu blog necesita una página LinkedIn

En mi experiencia, muchos bloggers no creen que necesitan utilizar LinkedIn. Ellos ven LinkedIn como algo que es más para las personas que trabajan en el mundo empresarial, sólo para aquellos que se puso un traje y corbata e ir a trabajar en una oficina todos los días. ¡Qué triste! Ellos están realmente desaparecidas porque simplemente no entienden cómo LinkedIn pueden beneficiarse.

He aquí siete ejemplos de cómo LinkedIn puede ayudar a impulsar los esfuerzos de todos los blogs para:

1. Mejorar el tráfico

Después de haber configurado su perfil de LinkedIn, pasa algún tiempo en los grupos de LinkedIn. Hay una pestaña de 'grupos' que le gusten. LinkedIn utiliza la información publicada en su perfil para que coincida con los grupos con intereses similares. Desplácese a través de estos grupos sugiere y lee sus descripciones. Seleccione dos o tres grupos a unirse. Lee sus mensajes y ver cómo funcionan las cosas. Usted puede obtener información valiosa que puede ayudarle en su nicho. Una vez que usted se sienta cómodo en su grupo, hacer comentarios en una discusión.

Esta es su introducción en el grupo así que asegúrese de que tiene bien pensado de como dejar un comentario que añade información sobre lo que se está discutiendo. Si alguien hace una pregunta y usted tiene información que vale la pena, asegúrese de responder con su respuesta. comentarios del anuncio y responder a las preguntas que señalar a la atención de los miembros de su grupo. Como usted se convierte en un miembro activo de su grupo, la gente estará interesada en ti. Que van a leer su perfil y ver el enlace a su blog. Ellos, naturalmente, hacen clic en el enlace de su blog, y lo visita! Usted tiene más tráfico a tu blog.

2. Conviértete en un gurú

Cuando usted participa en el grupo de LinkedIn, con el tiempo usted ganará la reputación de ser un gurú de la información y de ideas grandes. Los lectores que ven como un líder en su área de contenido. (Usted también querrá asegurarse de que incluyen un enlace a su perfil de LinkedIn en su 'sobre' la página en su blog y en su información en el cuadro de autor que tiene al final de cada post. Esto también ayudará a establecer su identidad como un gurú.)

3. Aumentar el comercio

¿Es el propósito de su blog para promover su negocio? Si es así, cada persona que llega a su sitio por medio de su cuenta de LinkedIn es un cliente potencial. Asegúrese de que todos los grupos que se unan está conectado de alguna manera con el producto que usted ofrece. De esta forma, los visitantes de LinkedIn tendrá un interés natural en lo que tiene que ofrecer. Si hay cerca no es un partido, los visitantes serán infelices. Puede ser que incluso se quejan de que en el foro de discusión de su grupo. Definitivamente no queremos que eso ocurra. Razón de más para unirse a grupos que se adapten a su nicho.

4. Expanda su red

A medida que interactúan con personas de su grupo de LinkedIn, esto le proporciona la oportunidad perfecta para preguntar si se puede vincular a ellos. Debido a que usted está en su grupo y usted se ha establecido como un miembro respetado de la comunidad, más probable es que estén dispuestos. Entonces, cuando usted tiene una necesidad o si desea promocionar su blog, usted tendrá más personas en su red de recabar la ayuda.

5. Obtener los mensajes de evaluación

La gente en la red de LinkedIn y las personas de su grupo es una gran fuente para las personas que podrían estar dispuestos a escribir un post como invitado para tu blog. Seleccione a alguien que admiras y que podía escribir con autoridad en su área de contenido. Seleccione dos o tres temas posibles. Pregúntales si estarían interesados ​​en escribir sobre uno de los temas. Probablemente será honrado y estara dispuesto a escribir un post en el blog para ti.

6. Aumentar el ranking de Google

A medida que desarrolla su perfil de LinkedIn público, cuidadosamente incorpora una o dos palabras clave que coinciden con el contenido de su blog. Esas palabras clave, junto con el nombre de su blog y su URL, le ayudará a su ranking de Google. No agregue palabras clave al azar y no haga las cosas con palabras clave de su perfil. Asegúrese de que lo que escribes tiene sentido para el lector y se profesional.

7. Aspecto profesional

Tener su dirección LinkedIn como perfil público en tu blog te hace ver profesional. Esto es fundamental si tu promueves un negocio. Los visitantes a su sitio te verán como una persona de negocios. Ellos tienen confianza en su producto, su servicio, su fiabilidad, e incluso en la seguridad de hacer negocios con usted.

Sé que mi blog se ha beneficiado de mi cuenta de LinkedIn. La tuya también lo hará. Se tarda sólo unos minutos para inscribirse. A continuación, sentarse y disfrutar de estos beneficios de dicha asociación.

Enlaces:
www.creatublog.co

lunes, 4 de abril de 2011

El contenido NO es el Rey; Las relaciones son el rey

¿Qué fue primero, el huevo o la gallina? Si había un par de horas que en realidad podría debatir la respuesta a esta pregunta con usted, pero, por ahora, vamos a usar esto como una analogía para hacer dinero en línea.

¿Qué fue primero, el contenido o la relación? Todo el mundo me dice cómo hacer dinero en línea implica que el contenido debe ser impresionante, de hecho, muchos recomiendan tener el mejor contenido. Sin embargo, si se piensa en esta simple pregunta de qué fue primero, el contenido o la relación, usted comenzará a darse cuenta de que el dinero no está en el contenido, sino más bien, en la relación.

En crea tu blog, que una vez habló de cómo el contenido era el rey y un canal de la exposición era su reina. Sin embargo, con el tiempo me he dado cuenta de que su contenido no tiene sentido sin el canal, la exposición, que no es más que una serie de relaciones que le permite hacer publicidad de su contenido. Algo me quedó claro:

El contenido no era el rey, sino más bien las propias relaciones.
¿Qué fue primero, el contenido o la relación? La relación se hizo, y es por esta razón que las relaciones deben ser su objetivo principal, si tiene la intención de hacer dinero en línea. Su contenido no se consigue leer por cualquier persona si no se lo muestra en primer lugar. La gente es que se quiere conectar con, y en el Curso de por qué la gente, que hacen todo lo que explica por qué sus interacciones con la gente son sus tácticas más importante como un vendedor del Internet. Ahora, estoy diciendo que su contenido puede ser una serie de mensajes gramaticalmente incorrecta con imágenes borrosas y una pancarta como título creado por su hijo de tres años en Paint. Tal vez si usted está vendiendo lápices de colores a los niños, pero por lo demás, usted va a desear para crear el contenido real, independientemente de si ve las relaciones como más importante que el contenido en sí.

Entonces, ¿cómo se puede aplicar este modo de pensar y cambiar la forma en que se comportan en línea? Para empezar, usted debe ir al encuentro de su público, no esperar a que vengan a ti. Si todo lo que hacen es centrarse en el contenido a continuación, sus relaciones nunca existen o van a morir si usted tenía alguna para empezar. Trabaja duro para aparecer en todas partes a su audiencia potencial podría ser, ya sea una serie de foros, canales de YouTube, podcasts, blogs de otros, etc, todo en un esfuerzo para obtener su información frente a sus caras.

Las relaciones son el nuevo "Contenido"
que usted debe considerar "el rey".
¿Alguna vez ir a una fiesta y estar conectado todo en persona de la sala que es el más social? ¿Adivina qué?, Internet no es diferente. Si usted es esa persona fría en la habitación con las respuestas y que es más importante, que está dispuesto a escuchar a las preguntas, en primer lugar, entonces usted tendrá una multitud de seguidores en muy poco tiempo. ¿Esa persona social puede sentarse en un coche en medio del partido sin hacer nada, pero los mensajes de texto en su teléfono? No, estaba arriba y sobre en la línea de buffet y en las esquinas donde los huéspedes se reunían.

Escribiendo hasta el contenido en tu blog para que nadie lee, pero su madre
es el equivalente de centrarse únicamente en el contenido como su "rey".
Hacer las relaciones y el dinero llegará.

Este cambio de mentalidad que tendrá un gran impacto en tu blog, te lo prometo. Es más, el público literalmente le ruego a vender sus opiniones, ideas y productos.

La gente ama a un líder, y si las relaciones son su "rey", entonces el líder será algo que se hace de forma natural. Hablando de forma natural, los seres humanos, naturalmente, quiere ser dirigido por alguien que ofrece algo diferente que el resto. Esto no necesariamente tiene que ser la mejor opción para ellos, pero siempre es diferente de lo que se utilizan para entonces seguirá siguiente.

Al ser usted mismo, sí será diferente a cualquier otra persona.
Escuche a su audiencia a convertirse en el líder en su nicho.
Ser diferente por ser uno mismo, la construcción de relaciones y escuchar, entonces el dinero va a seguir llegando.

http://www.creatublog.co

jueves, 31 de marzo de 2011

Inyección de Dependencias

Como colofón de la serie de cinco artículos dedicados a los principios SOLID, en esta ocasión toca hablar del Principio de Inyección de Dependencias (Dependency Inyection, DI).

Introducción

Si nos remontamos a los primeros años de la programación, nos encontraremos con programas rígidos repletos de código monolítico y lineal. La propia evolución hizo aparecer conceptos hoy por hoy imprescindibles como la modularidad y la reutilización de componentes, conceptos fundamentales en el paradigma de la Programación Orientada a Objetos.

La modularidad y reutilización de clases conlleva un flujo de comunicación entre instancias cuyo mal uso deriva en un hándicap que limita la flexibilidad, robustez y reusabilidad del código debido a la dependencia o alto acoplamiento entre las clases.

En la figura 1 podemos ver un sencillo diagrama de clases de un sistema de adquisición y control de datos meteorológicos. Existen dos clases participantes: una para la captura de la temperatura, y otra que representa a la estación meteorológica. Ambas tienen una responsabilidad a la hora de mostrar los datos, como puede apreciarse en el listado 1.

Listado 1
public class EstacioMeteorologica
{
public void MostrarDatos()
{
Console.WriteLine(
string.Format("Datos a {0} n", DateTime.Now));
Termometro termometro = new Termometro();
termometro.MostrarTemperaturaActual();
}
}
public class Termometro
{
public int Valor { get; set; }
public void MostrarTemperaturaActual ()
{
Console.WriteLine(
string.Format("Temperatura: {0} º", Valor));
}
}

Identificando el problema

Cuando hablamos en términos de calidad, solemos utilizar los adjetivos "bueno" o "malo" para definir la calidad de un diseño. Sin embargo, no siempre utilizamos los argumentos o criterios que sustentan la afirmación "éste es un mal diseño". Existe un conjunto de criterios más allá del siempre subjetivo TNTWIWHDI (That’s Not The Way I Would Have Done It, "Yo no lo habría hecho así") acuñado por Robert C. Martin, y son los que miden el nivel de rigidez, la fragilidad y la inmovilidad del sistema.

En nuestro ejemplo de la estación meteorológica, podemos afirmar que el diseño es rígido, porque cualquier cambio será difícil de llevar a cabo, ya que no conocemos el impacto que la modificación de una clase de bajo nivel (clase Termometro) tendrá sobre la clase de alto nivel (clase EstacioMeteorologica).

Cuando los cambios tienen una repercusión en otras entidades, no necesariamente dependientes, se dice que un sistema o aplicación es frágil. Si nos fijamos en el listado 1, la clase EstacioMeteorologica depende tanto de Termometro como de System.Console. Un cambio del flujo de salida de datos del programa (por ejemplo, a una impresora en lugar de System.Console) repercutiría en las clases de bajo nivel.

El termino inmovil lo utilizamos para medir el nivel de dependencia entre una parte del diseno y otros datos no directos. El ejemplo es inmovil porque la clase Estacio] Meteorologica depende de las clases Termometro y System. Console para mostrar los datos. Dicho en otras palabras, no podriamos extraer la clase de mayor nivel y utilizarla con otras entidades. Lo mismo pasaria con la clase de bajo nivel por su dependencia de System.Console.

Nota: Entre los criterios que permiten determinar si un diseño es bueno o malo están los que miden su nivel de rigidez, fragilidad e inmovilidad.

Planteemos un nuevo diseño a nuestro sistema. En primer lugar, eliminemos la dependencia que la clase Termometro tiene de System.Console, ya le que estamos otorgando la responsabilidad de salida por pantalla cuando realmente no le corresponde. El resultado sería el que se muestra en el listado 2.

Listado 2
public class EstacioMeteorologica
{
public void MostrarDatos()
{
Termometro termometro = new Termometro();
string temperatura =
termometro.MostrarTemperaturaActual();
Console.WriteLine(
string.Format("Datos a {0} n{1}",
DateTime.Now, temperatura));
}
}
public class Termometro
{
public int Valor { get; set; }
public string MostrarTemperaturaActual ()
{
return string.Format("Temperatura:{0} º", Valor);
}
}

Ahora la clase Termometro ha quedado libre de dependencias, y por tanto es reutilizable. Sin embargo, aún EstacioMeteorologica depende tanto de System.Console como de Termometro. Por otro lado, la clase Termometro no es más que una representación de un valor referencial meteorológico cualquiera; por tanto, podríamos abstraer la interfaz IMeteoReferencia, tal y como se muestra en el listado 3, y hacer que la clase Termometro la implemente. Esto es un ejemplo de aplicación del patrón Fachada (Façade), mediante el cual simplificamos la firma de varias clases a través de una única interfaz.

Listado 3
public interface IMeteoReferencia
{
int Valor { get; set; }
string Mostrar();
}
public class Termometro : IMeteoReferencia
{
public int Valor { get; set; }
public string Mostrar()
{
return string.Format("Temperatura:{0} º", Valor);
}
}

Ahora que hemos abstraído la interfaz, ésta nos servirá como contrato para las clases que quieran utilizarla. Esto nos permitirá desacoplar la clase EstacioMeteorologica de Termometro, tal y como muestra el listado 4.

Listado 4
public class EstacioMeteorologica
{
private IMeteoReferencia termometro;
public EstacioMeteorologica()
{
termometro = new Termometro();
}
public void MostrarDatos()
{
Console.WriteLine(
string.Format("Datos a {0}", DateTime.Now));
Console.WriteLine(termometro.Mostrar());
}
}

Sin embargo, aún no hemos solucionado el problema, pese a que estamos más cerca. Lo que pretendemos es eliminar completamente la instanciación de la clase Termometro, y la solución pasa por inyectar la dependencia directamente a través del constructor, como se muestra en el listado 5.

Listado 5
public class EstacioMeteorologica
{
private IMeteoReferencia termometro;
public EstacioMeteorologica(
IMeteoReferencia termometro)
{
this.termometro = termometro;
}
public void MostrarDatos()
{
Console.WriteLine(
string.Format("Datos a {0}", DateTime.Now));
Console.WriteLine(termometro.Mostrar());
}
}

El Principio de Inyección de Dependencias

Robert C. Martin afirma en el Principio de Inyección de Dependencias:

A. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones.
B. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.

Imaginemos por un momento la solución inicial de la estación meteorológica (listado 1). La clase de alto nivel EstacioMeteorologica depende de la clase de bajo nivel Termometro (o Barometro, Anemometro, etc.). Toda la lógica de la solución se implementaría en la clase de alto nivel, y cualquier modificación en las clases de bajo nivel tendría repercusión no únicamente sobre la definición de la clase de alto nivel, sino sobre la propia lógica de la aplicación, llegando incluso a forzar cambios en la misma, cuando debería ser la clase de alto nivel la que debería forzar el cambio a las clases de bajo nivel sin comprometer la lógica de la aplicación; es decir, justamente lo contrario. Además, la clase de alto nivel sería difícilmente reusable debido a este acoplamiento. Sencillamente, y resumiendo, la clase EstacioMeteorologica no debe depender de la clase Termometro; en todo caso, al contrario.

Existen tres formas de implementación de la Inyección de Dependencias:

  • por constructor
  • por setter
  • por interfaz.
El primer caso lo hemos visto en la sección anterior, donde hemos inyectado la dependencia a través del constructor de la clase; el listado 6 muestra una generalización. La inyección por setter se realiza a través de una propiedad de la clase (listado 7); y por último, la inyección por interfaz se realiza a través de un método, recibiendo como parámetro el objeto a inyectar (listado 8).

Listado 6
IMeteoReferencia referencia = ObtenerReferencia();
EstacioMeteorologica estacion =
new EstacioMeteorologica(referencia);

Listado 7
EstacioMeteorologica estacion = new EstacioMeteorologica();
estacioMeteorologica.Referencia = ObtenerReferencia();

Listado 8
EstacioMeteorologica estacion = new EstacioMeteorologica();
estacioMeteorologica.LecturaContador(ObtenerReferencia());

Inversión de control y contenedores

No podemos hablar de DI sin dejar de hablar de la Inversión de control (Inversion of Control, IoC). IoC también es conocido como Principio de Hollywood, nombre derivado de las típicas respuestas de los productores de cine a los actores noveles: "no nos llames; nosotros lo haremos".

IoC invierte el flujo de control de un sistema en comparación con la programación estructurada y modular. En el fondo, DI es una implementación de IoC. Aún hoy existe la discusión acerca de si IoC es un principio, un patrón o ambas cosas a la vez. IoC, en definitiva, es una característica fundamental de un framework, y de hecho lo que lo hace realmente diferente a una librería de funciones.

En escenarios de producción, las clases no son tan triviales como la que hemos presentado en este artículo. Imagine por un momento que la interfaz IMeteoReferencia tiene una implementación de IEntradaDatos e IVerificador, y éstas a su vez implementan otras interfaces. En realidad, obtendremos una jerarquía de dependencias (figura 3), cuyo manejo en tiempo de diseño es imposible de gestionar "manualmente"; es aquí donde entra a jugar el término contenedor IoC (IoC Container).

El principal cometido de un contenedor IoC, a diferencia de una factoría, es el de gestionar el ciclo de vida de los objetos. El contenedor IoC registra una implementación específica para cada tipo de interfaz y retorna una instancia de objeto. Esta resolución de objetos tiene lugar en un único punto de las aplicaciones; normalmente, a nivel de infraestructura.

Conclusión

Con este artículo, hemos tratado de mostrar de una forma práctica la relación existente entre dependencias, detalles y abstracciones. Con el Principio de Inyección de Dependencias, ponemos fin a la última de las siglas que componen SOLID. Existen libros íntegros que hablan de este principio, y podrá encontrar en Internet una gran cantidad de recursos relacionados.

A lo largo de esta serie sobre los principios SOLID, hemos presentado aspectos muy importantes que debemos tener en cuenta ante cualquier nuevo desarrollo, y hemos visto cómo muchas de las problemáticas lógicas del diseño pueden ser reducidas mediante la aplicación de estos principios. Trate de entender cada uno de los principios desde un punto de vista práctico. Algunos de ellos (y lo digo por experiencia) son realmente complejos de llevar a la práctica; recuerde además que son principios, no reglas.

Para finalizar, agradecer a Hadi Hariri, quien me ha servido de "enciclopedia de consulta" para esta serie, por su apoyo y ayuda en todo momento.

Por José Miguel Torres

Principio de Segregación de Interfaces

Principio de Segregación de Interfaces (Interface Segregation Principle, ISP), que trata sobre las desventajas de las interfaces "pesadas" y guarda una estrecha relación con el nivel de cohesión de las aplicaciones.

Como parte de la serie dedicada a presentar los cinco principios SOLID de programación, este mes nos centramos en el Principio de Segregación de Interfaces (Interface Segregation Principle, ISP). Este principio trata sobre las desventajas de las interfaces "pesadas", y guarda una estrecha relación con el nivel de cohesión de las aplicaciones. En este artículo veremos qué perjuicios ocasionan las interfaces "pesadas", qué impacto tienen en la cohesión del sistema y cómo en muchas ocasiones vulneran el Principio de Responsabilidad Única, y cómo ISP ofrece una solución a estos problemas.

El Principio de Segregación de Interfaces

El Principio de Segregación de Interfaces fue utilizado por primera vez por Robert C. Martin durante unas sesiones de consultoría en Xerox. Por aquella época, Xerox estaba diseñando una impresora multifuncional. El software diseñado para la impresora funcionaba y se adaptaba perfectamente a las necesidades iniciales de la impresora; sin embargo, conforme fue evolucionando, y por lo tanto cambiando, se hizo cada vez más difícil de mantener. Cualquier modificación tenía un gran impacto global sobre el sistema. La utilización de ISP permitió reducir los riesgos de las modificaciones y otorgó una mayor facilidad al mantenimiento. El ISP declara que:

Los clientes no deben ser forzosamente dependientes de las interfaces que no utilizan.

A continuación veremos la utilidad de ISP y su significado, una vez que identifiquemos qué es una interfaz "pesada" y qué problemas lleva asociados.

Interfaces "pesadas"

Observemos el diagrama de clases de la figura 1. Básicamente, consta de dos modelos de impresoras representadas por las clases Modelo1998 y Modelo2000, ambas herederas de la clase abstracta ImpresoraMultifuncional.

Inicialmente, la clase abstracta ImpresoraMultifuncional declaraba los métodos correspondientes a las funciones típicas que realiza una impresora multifuncional, como son la propia impresión, el escaneado, el envío de fax y la cancelación de cualquier operación. La impresora Modelo1998 fue el primer modelo en basarse en esta interfaz; poco después se añadió un nuevo modelo, Modelo2000, que además de las funciones anteriores añadía la posibilidad de hacer fotocopias.

Posteriormente, surgió un nuevo modelo (Modelo2002) que se basaba en la misma clase abstracta ImpresoraMultifuncional e incorporaba el soporte para comunicaciones TCP/IP en lugar del servicio de fax; este modelo permitía enviar un documento directamente por correo electrónico, evitando así los altos costes de telefonía. El problema se presenta al implementar en Modelo2002 el método heredado EnviarFax, ya que dicho modelo prescinde de dicha funcionalidad. Una posible implementación sería la que se presenta en el listado 1.

Listado 1:
class Modelo2002 : ImpresoraMultifuncional
{
public override void Imprimir()
{
Impresion.EnviarImpresion();
}
public override void Escanear()
{
Escaner.DigitalizarAFormatoPng();
}
public override void Cancelar()
{
Impresion.CancelarImpresion();
}
public override void EnviarFax()
{
throw new System.NotImplementedException();
}
public void EnviarEMail()
{
// Enviamos por correo electrónico
}
}

El método EnviarFax no se implementa, y por consiguiente una llamada al método generaría una excepción del tipo NotImplementedException. Sí, es cierto que podríamos quitar dicha excepción y podríamos dejar el método vacío; pero entonces el programador que utilice la clase se encontrará con un método que sencillamente no hace nada. Esto podríamos intentar solucionarlo de varias formas: mediante documentación, indicando que el método no es funcional; mediante comentarios en el código (pero es posible que un programador que utilice la clase no tenga acceso al código), etc. En cualquier caso, el problema seguiría existiendo, y solo estaríamos ocultándolo.

De "pesada" a confusa

Es importante que nos concienciemos de este problema. En nuestro ejemplo, se trata de un único método, y eludir el problema puede ser bastante obvio; pero si la clase abstracta implementara una docena de métodos y únicamente utilizáramos tres o cuatro de ellos en un contexto no tan claro como el de las impresoras multifuncionales, el problema se haría más complejo.

Un ejemplo de esto lo tenemos en el propio .NET Framework. La clase abstracta System.Web.Security.Member - shipProvider contiene todos los métodos necesarios para la autenticación ASP.NET: valida credenciales accediendo a algún mecanismo de almacenamiento (SQL Server, sistema de archivos, etc.), bloquea usuarios, gestiona contraseñas, etc. Si implementamos nuestro propio proveedor de autenticación e implementamos la clase MembershipProvider, seguramente solo utilizaremos algunos de los métodos heredados (es decir, implementaremos únicamente ciertas funcionalidades disponibles en la clase base), y en este caso no es tan evidente cuáles de esos métodos deberemos redefinir. ¿Cómo sabría el programador que utilice nuestra clase qué métodos ésta implementa? ¿Qué comportamiento debería tener nuestra clase ante la llamada a un método no implementado? En definitiva, ¿qué valor real tienen los métodos que están disponibles pero no implementados? La respuesta es: ninguno, aparte de crear confusión.

En nuestro ejemplo, la clase Modelo2000 implementa, al contrario que Modelo1998, la característica de Fotocopiar. Dicho método se implementa en la propia clase Modelo2000, y quizás nos hayamos preguntado por qué no hemos añadido el método a la clase abstracta ImpresoraMultifuncional. El motivo puede ser bien dispar. Quizás quién diseñó el sistema decidió no tocar la clase abstracta y extender la clase Modelo2000; sin embargo, resulta que a partir de Modelo2000 todas las impresoras tienen soporte de fotocopia y por lo tanto todos los modelos deberán implementar el método Fotocopiar. Utilizando esta estrategia, tenemos que vulnerar el principio DRY (Don't Repeat Yourself); y lo que es más preocupante, otro programador puede tener la ocurrencia o la necesidad de modificar el contrato o el nombre del método. En definitiva, ello complica el mantenimiento del sistema; cualquier modificación sobre del método Fotocopiar implicará buscarlo por todo el código, aumentando por tanto el riesgo de error. Es contradictorio que tengamos encapsulados en una clase abstracta miembros que no usamos, por ejemplo, en Modelo2002, mientras que otros que sí serían firmes candidatos a serlos, como el caso del método Fotocopiar, no lo son.

Segregación de interfaces

Para solucionar el problema, debemos segregar las operaciones en pequeñas interfaces. Una interfaz es un contrato que debe cumplir una clase, y tales contratos deben ser específicos, no genéricos; esto nos proporcionará una forma más ágil de definir una única responsabilidad por interfaz - de otra forma, violaríamos además el Principio de Responsabilidad Única (Single Responsibility Principle, SRP).

Retomando de nuevo el ejemplo práctico, volvamos a replantear el sistema y separemos las responsabilidades por interfaces. En el listado 2 podemos ver el resultado de dicha segregación.

Listado 2:
public interface IImprimible
{
void Imprimir();
}
public interface IFotocopiable
{
void Fotocopiar();
}
public interface IEscaneable
{
void Escanear();
}
public interface IFaxCompatible
{
void EnviarFax();
void RecibirFax();
}
public interface ITcpIpCompatible
{
void EnviarEMail();
}
class Modelo1998 : IImprimible, IEscaneable, IFaxCompatible
{
// ...
}
class Modelo2000 : IImprimible, IEscaneable, IFaxCompatible,
IFotocopiable
{
// ...
}
class Modelo2002 : IImprimible, IEscaneable, IFotocopiable,
ITcpIpCompatible
{
// ...
}

Conclusión

Mediante la segregación de interfaces, el planteamiento del diseño otorga una mayor cohesión al sistema, lo que se traduce, por una parte, en un menor coste de mantenimiento, y por otra, en un menor riesgo de errores y una mejor localización de los mismos.

martes, 29 de marzo de 2011

Principio de Sustitución de Liskov

Tercer principio de programación SOLID. En esta ocasión presentamos los fundamentos del Principio de Sustitución de Liskov y cómo la aplicación de este principio tiene una repercusión directa sobre las jerarquías de herencia entre clases.

En nuestra entrega anterior, hablábamos de lo útil que puede ser el Principio Open/Closed para desarrollar código fácil de mantener y reusar mediante el diseño de clases abiertas a la extensión y cerradas a la modificación, y para conseguirlo nos basamos en dos características clave de la Programación Orientada a Objetos (POO): la abstracción y el polimorfismo.

En lenguajes OO como C# o VB.NET, la clave para conseguir la abstracción y polimorfismo de entidades es mediante la herencia, y es precisamente en esta característica en la que se basa el Principio de Sustitución de Liskov (Liskov Substitution Principle, LSP). Cuáles son los fundamentos básicos de diseño que debe seguir la herencia en un caso particular, o cuál es la mejor forma de crear jerarquías de herencia entre clases, son algunas de las preguntas a las que responde dicho principio.

El Principio de Sustitución de Liskov

Echemos un vistazo al código del listado 1. Este código trata de calcular los impuestos de un vehículo en base a la matricula (antigüedad) y la cilindrada del mismo.

Listado 1
class Vehiculo
{
public string Marca { get; set; }
public string Modelo { get; set; }
public int Cilindrada { get; set; }
}
class Ciclomotor: Vehiculo
{
public string ObtenerNumLicencia()
{
// Devuelve número de licencia
}
}
class Coche: Vehiculo
{
public string ObtenerMatricula()
{
// Devuelve matrícula
}
}
class Impuestos
{
public void CalcularImpuesto(Vehiculo vehiculo)
{
string matricula = ((Coche) vehiculo).ObtenerMatricula();
ServicioCalculoImpuestos(matricula, vehiculo.Cilindrada);
}
}

En 1987, Barbara Liskov presentó en una conferencia sobre jerarquía y abstracción de datos la siguiente definición:
Si por cada objeto o1 del tipo S existe un objeto o2 del tipo T tal que para todos los programas P definidos en términos de T, el comportamiento de P permanece invariable cuando o1 es sustituido por o2, entonces S es un subtipo de T.

Básicamente, LSP afirma que si tenemos dos objetos de tipos diferentes –Coche y Ciclomotor– que derivan de una misma clase base –Vehiculo–, deberíamos poder reemplazar cada uno de los tipos –Coche/Ciclomotor y viceversa– allí dónde el tipo base –Vehiculo– esté implementado. En el ejemplo anterior tenemos un claro caso de violación del LSP, ya que la ejecución del método CalcularImpuesto generará una excepción de conversión de tipo si el objeto pasado por parámetro es de tipo Ciclomotor en lugar de Coche, pese a que ambas clases derivan de la misma clase base Vehiculo.

Podríamos pensar en solucionar el problema de la forma que se expone en el listado 2. Pese a que el compilador no genere ninguna excepción de conversión de tipo, esta clase aún viola el LSP. Esto es debido a que estamos forzando a un objeto Vehiculo pasado como parámetro a comportarse como Ciclomotor o Coche. Además, esta aproximación vulnera el Principio Open/Closed que vimos en la entrega anterior, ya que ante cualquier nueva entidad que derive de Vehiculo deberemos modificar el método CalcularImpuesto.

Listado 2
public void CalcularImpuesto(Vehiculo vehiculo)
{
string matricula = string.Empty;
if (vehiculo.GetType().Name == "Coche")
matricula = ((Coche) vehiculo).ObtenerMatricula();
else if (vehiculo.GetType().Name == "Ciclomotor")
matricula = ((Ciclomotor)vehiculo).ObtenerNumLicencia();
ServicioCalculoImpuestos(matricula, vehiculo.Cilindrada);
}

Un objeto también es comportamiento

Equivocadamente, tendemos a pensar que una clase únicamente representa datos; eso es cierto solamente en el caso en los objetos de transferencia de datos (DTO) y poco más. En el caso general, las clases incorporan métodos, que son los que aportan la clave diferencial en la POO: el comportamiento.

En el caso anterior, probablemente no tendríamos problemas de herencia si obviáramos los métodos de las clases Coche y Ciclomotor, pero no es el caso. Los métodos ObtenerMatricula y ObtenerNumLicencia probablemente se conecten a un servicio o repositorio de datos externo, o calculen sus resultados según la fecha de matriculación y por tanto ese dato no se almacena en la clase. Dichos métodos otorgan un comportamiento a la clase, y es en la implementación de la herencia dónde puede verse modificado el comportamiento de una clase.

Un ejemplo clásico que encontraremos si buscamos referencias acerca del LSP en Internet o en nuestra biblioteca es el de herencia entre dos clases Rectangulo y Cuadrado; este ejemplo refleja lo que denominamos una incorrecta implementación de la herencia. Fijémonos en el código del listado 3 y el diagrama de clases de la figura 1.

Listado 3
public class Rectangulo
{
public virtual int Ancho { get; set; }
public virtual int Alto { get; set; }
}
public class Cuadrado : Rectangulo
{
public override int Ancho
{
get
{
return base.Ancho;
}
set
{
base.Ancho = value;
base.Alto = value;
}
}
public override int Alto
{
get
{
return base.Alto;
}
set
{
base.Ancho = value;
base.Alto = value;
}
}
}
Nota: En el caso general, las clases incorporan métodos, que son los que aportan la clave diferencial en la POO: el comportamiento

Si ejecutamos el código del listado 4, podremos apreciar cómo se vulnera el LSP. La idea subyacente es que en realidad la clase derivada Cuadrado no solo debe “ser un” sino que también debe “comportarse como un” Rectangulo, y efectivamente no lo hace, ya que la propiedad Cuadrado.Alto modifica tanto la altura como el ancho.

Listado 4
[Test]
public void AreaRectangulo()
{
Rectangulo r = new Cuadrado { Ancho = 5, Alto = 2 };
// Fallará, pues Cuadrado establece
// a 2 el ancho y el alto
Assert.IsEqual(r.Ancho * r.Alto, 10); // false
}

Nota: Al sobrescribir métodos en las clases heredadas, debemos especificar una precondición menos restrictiva y una poscondición más restrictiva que las especificadas en la clase base

Este ejemplo demuestra además la estrecha relación que existe entre LSP y el Diseño por Contratos (Design by Contract – DbC) expuesto por Bertrand Meyer. Utilizando DbC, declaramos en los métodos unas precondiciones y poscondiciones; la precondición debe ser cierta antes de ejecutar el método y tras la ejecución, mientras que el propio método debe garantizar que la poscondición se cumpla.

Pues bien, existe una pequeña variación de DbC cuando se aplica en la herencia. Los métodos de la clase base implementan sus propias precondiciones y poscondiciones; sin embargo, cuando sobrescribimos dichos métodos en las clases heredadas debemos especificar una precondición menos restrictiva y una poscondición más restrictiva que las especificadas respectivamente en la clase base - de otra forma, se violaría el LSP. La razón es que el cliente de la llamada conoce la precondición y poscondición de la clase base, pero no la de la clase heredada, y por lo tanto no podemos suponer que el cliente conozca la precondición del método de la clase heredada, y por tanto debemos ser menos restrictivos en la precondición. Debido a la baja restricción de la precondición y para asegurar el comportamiento correcto tras la ejecución del método, la poscondición debe ser más restrictiva.

El ejemplo del Rectangulo y el Cuadrado, en la propiedad Rectangulo.Alto podríamos establecer la poscondición como:
POSTCONDICIÓN —> (Alto == value && Ancho == Ancho)

Es decir, el valor de Alto debe contener el nuevo valor y el valor de Ancho debe permanecer inalterado. Por lo tanto, la poscondición de Cuadrado.Alto será menos restrictiva al no cumplir el segundo predicado Ancho==Ancho, y en consecuencia Cuadrado.Alto viola el contrato de la clase base y por consiguiente el LSP.

Conclusión

No olvidemos que una clase son datos más comportamiento, y que dicho comportamiento no debe ser sacrificado entre herencias. Por tanto, minimizaremos el impacto de una incorrecta implementación y por tanto de la modificación del comportamiento aplicando el Principio de Sustitución de Liskov.

Por José Miguel Torres

viernes, 25 de marzo de 2011

Principio Open/Closed (II)

Continuamos con el segundo principio SOLID sobre la Programación Orientada a Objetos.

Ya hemos visto la definición del principio Open/Closed en el artículo anterior, y ahora continuamos con nuestra explicación y ponemos ejemplos de como implementar dicho principio.

Fundamentos de la orientación a objetos

La cuestión se centra en cómo minimizar el impacto de una modificación en nuestro sistema, sin comprometer OCP; esto es, manteniendo la "simbiosis" entre las dos características del principio: abierto en extensión y cerrado en modificación.

Volvamos a la entidad Tarea del ejemplo anterior. Por lo que hemos podido ver, los métodos dependen en gran medida del estado de la tarea. Así, una tarea podrá finalizarse o cancelarse dependiendo de su estado previo, pues no podremos cancelar una tarea que haya sido finalizada. De la misma forma, introduciendo el nuevo estado EstadosTarea. Pospuesta implementaríamos un nuevo método llamado Posponer, cuya lógica sería obvia: únicamente podría posponerse una tarea que estuviera en estado pendiente. En definitiva, todo gira alrededor del estado de la tarea, y por tanto el comportamiento de la misma dependerá del estado en que se encuentre. Una opción sería encapsular dicho estado en una clase auxiliar e implementar en ella los métodos Finalizar, Cancelar y Posponer, mediante los cuales definimos el comportamiento, tal y como se muestra en el listado 4, para luego delegar los métodos del objeto Tarea hacia dicha clase.

Listado 4
class EstadosTareaHelper
{
public virtual void Finalizar(EstadosTarea estado)
{
switch ( estado) {
case EstadosTarea.Pendiente:
// finalizamos
case EstadosTarea.Pospuesta:
throw new ApplicationException("Imposible finalizar. Tarea no completada");
default:
throw new ArgumentOutOfRangeException();
}
}
public virtual void Cancelar(EstadosTarea estado)
{
switch (estado) {
// ...
// cancelamos
}
}
public virtual void Posponer(EstadosTarea estado)
{
switch (estado) {
// ...
// posponemos
}
}
}

Nota: Cuando un requisito cambie, lo que debemos hacer es extender el comportamiento añadiendo código, y no modificando el existente.

Pese a que hayamos extraido y aislado el estado de la entidad Tarea, aun no hemos resuelto el problema. De hecho, ahora hemos aislado la responsabilidad en la clase EstadosTareaHelper; sin embargo, estamos algo mas cerca de la solucion. Estudiemos de nuevo los estados -metodos- de la clase Estados- TareaHelper. La logica de cada accion esta escrita en todos los metodos y por tanto se repite; es decir, todos los metodos contemplan la opcion de Finalizar una tarea, y en base a ello actuan de una forma u otra. La operacion Posponer no podra ejecutarse si el estado de la tarea es Cancelada, y la operacion Cancelar unicamente podra ejecutarse si el estado es Pendiente. A traves de este razonamiento, podemos detectar un patron: un mismo contrato .los metodos. y diferentes comportamientos en base a un estado. Esto en OO puede ser solucionado mediante polimorfismo, como se muestra en el listado 5.

Listado 5
abstract class EstadoTareaBase
{
protected Tarea _tarea;
public abstract void Finalizar();
public abstract void Cancelar();
public abstract void Posponer();
}
class EstadoTareaPendiente : EstadoTareaBase
{
public override void Finalizar()
{
// finalizamos
}
public override void Cancelar()
{
// cancelamos
}
public override void Posponer()
{
// posponemos
}
}
class EstadoTareaFinalizada : EstadoTareaBase
{
public override void Finalizar()
{
throw new ApplicationException("Tarea ya finalizada");
}
public override void Cancelar()
{
throw new ApplicationException("Imposible cancelar. Tarea finalizada");
}
public override void Posponer()
{
throw new ApplicationException("Imposible posponer. Tarea finalizada");
}
}
class EstadoTareaCancelada : EstadoTareaBase
{
public override void Finalizar()
{
throw new ApplicationException("Imposible finalizar. Tarea cancelada");
}
public override void Cancelar()
{
throw new ApplicationException("Tarea ya cancelada");
}
public override void Posponer()
{
throw new ApplicationException("Imposible posponer. Tarea cancelada");
}
}
class EstadoTareaPospuesta : EstadoTareaBase
{
public override void Finalizar()
{
throw new ApplicationException("Imposible posponer. Tarea finalizada");
}
public override void Cancelar()
{
// cancelamos
}
public override void Posponer()
{
throw new ApplicationException("Tarea ya pospuesta");
}
}
class Tarea
{
private EstadoTareaBase _estadoTarea;
public Tarea()
{
_estadoTarea = new EstadoTareaPendiente();
}
public void Finalizar()
{
_estadoTarea.Finalizar();
}
public void Cancelar()
{
_estadoTarea.Cancelar();
}
public void Posponer()
{
_estadoTarea.Posponer();
}
}

Básicamente, lo que hemos hecho es crear una clase por cada estado en lugar de tener una única clase cuyos métodos están basados en sentencias condicionadas por el estado de la tarea (switch o if). Además, con esta nueva implementación hemos delegado la responsabilidad de finalizar, cancelar o posponer a una nueva clase EstadoTareaBase que hemos marcado como abstracta. La clase Tarea implementará sus propios métodos y delegará la responsabilidad a través de las clasesestados que heredan de EstadoTareaBase. Debido a que la clase Tarea gira en torno a un estado, asumimos que el estado inicial por defecto es Pendiente, y así lo especificamos en el constructor, instanciando EstadoTareaPendiente.

En realidad, hemos aplicado un patrón ya conocido, el patrón de diseño State, ya que el comportamiento de la clase cambia dependiendo del estado, en este caso, de la tarea, y por lo tanto hemos abstraído cada uno de los estados como entidades independientes. Ante un nuevo requisito en el que intervenga un nuevo estado, lo único que deberemos hacer es crear una nueva clase que herede de EstadoTareaBase e implementar los métodos virtuales, extendiendo así el comportamiento de la aplicación sin comprometer el código existente.

Conclusión

Cuando hablábamos el mes pasado del Principio de Responsabilidad Única, argumentamos la importancia de que cada clase tuviera una y solo una responsabilidad dentro del sistema, de forma que cuanto menos impacto tenga una clase en el conjunto global del sistema, menos repercusión global tendrá una modificación de la clase en dicho sistema. Este mismo argumento es la línea que pretende seguir el Principio Open/Closed, que pese a ser relativamente sencillo de comprender conceptualmente, no sucede lo mismo cuando se aplica. Las claves para la correcta aplicación de este principio son la abstracción y el polimorfismo, como hemos podido ver en el ejemplo.

Por José Miguel Torres

miércoles, 23 de marzo de 2011

Principio Open/Closed (I)

Este es el segundo de una serie de cinco principios SOLID y su aplicación en la Programación Orientada a Objetos.

Después de examinar en nuestra entrega anterior el Principio de Responsabilidad Única, este mes nos adentramos en otro de los principios, que además guarda una estrecha relación con el primero: el Principio Open/Closed.

odas las aplicaciones cambian durante su ciclo de vida, y siempre vendrán nuevas versiones tras la primera release. No por ello debemos adelantarnos a desarrollar características que el cliente podría necesitar en el futuro; si nos pusiéramos en el papel de adivinos, seguramente fallaríamos y probablemente desarrollaríamos características que el cliente nunca necesitará. El principio YAGNI ("You Ain’t Gonna Need It" o "No vas a necesitarlo"), utilizado en la Programación Extrema, previene de implementar nada más que lo que realmente se requiera. La idea es desarrollar ahora sobre los requisitos funcionales actuales, no sobre los que supongamos que aparecerán dentro de un mes.

La actitud de adelantarnos a los acontecimientos es un mecanismo de defensa que en ocasiones acusamos los desarrolladores para prevenir lo que tarde o temprano será inevitable: la modificación. Lo único que podemos hacer es minimizar el impacto de una futura modificación en nuestro sistema, y para ello es imprescindible empezar con un buen diseño, ya que la modificación de una clase o módulo de una aplicación mal diseñada generará cambios en cascada sobre las clases dependientes que derivarán en unos efectos indeseables. La aplicación se convierte, así, en rígida, impredecible y no reutilizable.

Ahora bien, ¿cómo debemos plantear nuestras aplicaciones para que se mantengan estables ante cualquier modificación?

El Principio Open/Closed

El Principio Open/Closed (Open/Closed Principle, OCP) fue acuñado por el Dr. Bertrand Meyer en su libro "Object Oriented Software Construction" y afirma que:
Una clase debe estar abierta a extensiones, pero cerrada a las modificaciones.

OCP es la respuesta a la pregunta que hacíamos anteriormente, ya que argumenta que deberíamos diseñar clases que nunca cambien, y que cuando un requisito cambie, lo que debemos hacer es extender el comportamiento de dichas clases añadiendo código, no modificando el existente.

Las clases que cumplen con OCP tienen dos características:

  • Son abiertas para la extensión; es decir, que la lógica o el comportamiento de esas clases puede ser extendida en nuevas clases.
  • Son cerradas para la modificación, y por tanto el código fuente de dichas clases debería permanecer inalterado.
Podría parecer que ambas características son incompatibles, pero eso no es así. Veamos un ejemplo de una clase que rompe con OCP. Supongamos un sistema de gestión de proyectos al estilo de Microsoft Project. Obviemos de momento la complejidad real que existe en dicho sistema, y centrémonos únicamente en la entidad Tarea, tal y como muestra la figura 1. Dicha clase viene determinada por uno de los estados Pendiente, Finalizada o Cancelada, representados mediante la enumeración EstadosTarea. Además, la clase implementa dos métodos, Cancelar y Finalizar que cambian, si es posible, el estado de la tarea. En el listado 1 podemos ver la implementación inicial del método Finalizar.

Listado 1
public void Finalizar()
{
switch (_estadoTarea)
{
case EstadosTarea.Pendiente:
// finalizamos
break;
case EstadosTarea.Finalizada:
throw new ApplicationException("Tarea ya finalizada");
case EstadosTarea.Cancelada:
throw new ApplicationException("Imposible finalizar. Tarea cancelada");
default:
throw new ArgumentOutOfRangeException();
}
}

Un cambio típico solicitado por el cliente de la aplicación sería la adición de un nuevo estado para controlar las tareas que se han pospuesto, con lo que la adaptación a esta modificación podría ser la expuesta en el listado 2. Aparentemente, parece una modificación trivial; sin embargo, este cambio puede replicarse en otros métodos o clases que utilicen la enumeración EstadosTarea, de forma que en nuestro caso también deberíamos modificar el método Cancelar (listado 3).

Listado 2
public void Finalizar()
{
switch (_estadoTarea)
{
case EstadosTarea.Pendiente:
// finalizamos
break;
case EstadosTarea.Finalizada:
throw new ApplicationException("Tarea ya finalizada");
case EstadosTarea.Cancelada:
throw new ApplicationException("Imposible finalizar. Tarea cancelada");
case EstadosTarea.Pospuesta:
throw new ApplicationException("Imposible finalizar. Tarea no completada");
default:
throw new ArgumentOutOfRangeException();
}
}

Listado 3
public void Cancelar()
{
switch (_estadoTarea)
{
case EstadosTarea.Pendiente:
// cancelamos
_estadoTarea = EstadosTarea.Cancelada;
break;
case EstadosTarea.Finalizada:
throw new ApplicationException("Imposible cancelar. Tarea finalizada");
case EstadosTarea.Cancelada:
throw new ApplicationException("Tarea ya cancelada");
case EstadosTarea.Pospuesta:
// cancelamos
_estadoTarea = EstadosTarea.Cancelada;
break;
default:
throw new ArgumentOutOfRangeException();
}
}

En definitiva, por cada nuevo estado que implementemos tendremos que identificar todas las clases que lo utilizan (tanto la clase Tarea como las clases lógicamente involucradas) y modificarlas, violando no únicamente OCP sino también el Principio DRY ("Don’t Repeat Yourself", "No te repitas"), otro principio que pretende reducir al máximo cualquier tipo de duplicación. En este tipo de modificaciones existe una alta probabilidad de olvidar modificar algún método relacionado con el nuevo estado implementado en el enumerador EstadosTarea, lo que elevaría la probabilidad de aparición de un nuevo bug.

En el siguiente artículo sobre el principio Open/Closed continuamos con la explicación y finalizamos con un ejemplo.

Por José Miguel Torres

jueves, 17 de marzo de 2011

Principio de responsabilidad única (II)

Continuamos con el Principio de Responsabilidad Única, una de las bases de la programación oriendada a objetos.

Continuando con el artículos anterior sobre el principio de resposabilidad única dentro del manual sobre los principios fundamentales de la Programación Orientada a Objetos, pasamos a ver:

Detectando responsabilidades

La piedra angular de este principio es la identificación de la responsabilidad real de la clase. Según SRP, una responsabilidad es "un motivo de cambio"; algo que en ocasiones es difícil de ver, ya que estamos acostumbrados a pensar un conjunto de operaciones como una sola responsabilidad.

Si implementamos la clase Factura tal y como se muestra en el listado 1, podríamos decir que la responsabilidad de esta clase es la de calcular el total de la factura y que, efectivamente, la clase cumple con su cometido. Sin embargo, no es cierto que la clase contenga una única responsabilidad. Si nos fijamos detenidamente en la implementación del método CalcularTotal, podremos ver que, además de calcular el importe base de la factura, se está aplicando sobre el importe a facturar un descuento o deducción y un 16% de IVA. El problema está en que si en el futuro tuviéramos que modificar la tasa de IVA, o bien tuviéramos que aplicar una deducción en base a una tarifa por cliente, tendríamos que modificar la clase Factura por cada una de dichas razones; por lo tanto, con el diseño actual las responsabilidades quedan acopladas entre sí, y la clase violaría el principio SRP.

Listado 1
public class Factura
{
public string _codigo;
public DateTime _fechaEmision;
public decimal _importeFactura;
public decimal _importeIVA;
public decimal _importeDeduccion;
public decimal _importeTotal;
public ushort _porcentajeDeduccion;
// Método que calcula el total de la factura
public void CalcularTotal()
{
// Calculamos la deducción
_importeDeduccion = (_importeFactura * _porcentajeDeduccion) / 100;
// Calculamos el IVA
_importeIVA = _importeFactura * 0.16m;
// Calculamos el total
_importeTotal = (_importeFactura - _importeDeduccion) + _importeIVA;
}
}

Separando responsabilidades

El primer paso para solucionar este problema es separar las responsabilidades; para separarlas, primero hay que identificarlas. Enumeremos de nuevo los pasos que realiza el método CalcularTotal1:
  • Aplica una deducción. En base a la base imponible se calcula un descuento porcentual.
  • Aplica la tasa de IVA del 16% en base a la base imponible.
  • Calcula el total de la factura, teniendo en cuenta el descuento y el impuesto.
En este método se identifican tres responsabilidades. Recuerde que una responsabilidad no es una acción, sino un motivo de cambio, y por lo tanto se deberían extraer las responsabilidades de deducción e impuestos en dos clases específicas para ambas operaciones; estableciendo por un lado la clase IVA y por otro la clase Deduccion, tal y como se presenta en el listado 2.

Listado 2
public class IVA
{
public readonly decimal _iva = 0.16m;
public decimal CalcularIVA(decimal importe)
{
return importe * _iva;
}
}
public class Deduccion
{
private decimal _deduccion;
public Deduccion(ushort porcentaje)
{
_deduccion = porcentaje;
}
public decimal CalcularDeduccion(decimal importe)
{
return (importe * _deduccion) / 100;
}
}

Ambas clases contienen datos y un método y se responsabilizan únicamente en calcular el IVA y la deducción, respectivamente, de un importe. Además, con esta separación logramos una mayor cohesión y un menor acoplamiento, al aumentar la granularidad de la solución. La correcta aplicación del SRP simplifica el código y se traduce en facilidad de mantenimiento, mayores posibilidades de reutilización de código y de crear unidades de testeo específicas orientadas a cada clase/responsabilidad. El listado 3 muestra la nueva versión de la clase Factura, que hace uso de las dos nuevas clases IVA y Deduccion.

Listado 3
public class Factura
{
public string _codigo;
public DateTime _fechaEmision;
public decimal _importeFactura;
public decimal _importeIVA;
public decimal _importeDeduccion;
public decimal _importeTotal;
public ushort _porcentajeDeduccion;
// Método que calcula el total de la factura
public void CalcularTotal()
{
// Calculamos la deducción
Deduccion deduccion = new Deduccion(_porcentajeDeduccion);
_importeDeduccion = deduccion.CalcularDeduccion(_importeFactura);
// Calculamos el IVA
IVA iva = new IVA();
_importeIVA = iva.CalcularIVA(_importeFactura);
// Calculamos el total
_importeTotal = (_importeFactura - _importeDeduccion) + _importeIVA;
}
}

Nota: La correcta aplicación del SRP simplifica el código y se traduce en facilidad de mantenimiento, mayores posibilidades de reutilización de código y de crear unidades de testeo específicas para cada responsabilidad

Ampliando el abanico de "responsabilidades"

Comentábamos anteriormente que no es fácil detectar las responsabilidades, ya que generalmente tendemos a agruparlas. No obstante, existen escenarios o casuísticas en los que "se permite" una cierta flexibilidad. Robert C. Martin expone un ejemplo utilizando la interfaz Modem:

interface Modem
{
void dial(int pNumber);
void hangup();
void send(char[] data);
char[] receive();
}

En este ejemplo se detectan dos responsabilidades, relacionadas con la gestión de la comunicación (dial y hangup) y la comunicación de datos (send y receive). Efectivamente, cada una de las funciones puede cambiar por diferentes motivos; sin embargo, ambas funciones se llamarán desde distintos puntos de la aplicación y no existe una dependencia entre ellas, con lo que no perderíamos la cohesión del sistema.

Conclusión

Pensemos siempre en el ciclo de vida de una aplicación, y no únicamente en su diseño y desarrollo. Toda aplicación sufre modificaciones a causa de cambios en los requisitos o arreglo de fallos existentes, y el equipo de desarrollo puede variar; si a ello le sumamos que el código es poco mantenible, los costes de mantenimiento se dispararán, y cualquier modificación se presentará como una causa potencial de errores en entidades relacionadas dentro del sistema.
 

©2009 - 2013 Pichujitos | Theme diseñado por chicoloco123 para Fuutec.com | Ir arriba ↑