Mostrando entradas con la etiqueta Diseño Software. Mostrar todas las entradas
Mostrando entradas con la etiqueta Diseño Software. Mostrar todas las entradas

miércoles, 9 de febrero de 2011

Versionado de ensamblados de .NET

Uno de los temas que menos cuidado se le suele prestar en el desarrollo de software es el del versionado de componentes. Y es que gracias a las versiones, podemos saber exactamente cuando se compiló un ensamblado (e incluso saber la etiqueta de la versión de código fuente que la generó) y si hay otros ensamblados que son compatibles.

En .NET, las versiones se establecen en el fichero AssemblyInfo.cs que se puede encontrar dentro de la carpeta de “Properties” del proyecto:

    1 using System.Reflection;

    2 using System.Runtime.InteropServices;

    3 

    4 // General Information about an assembly is controlled through the following

    5 // set of attributes. Change these attribute values to modify the information

    6 // associated with an assembly.

    7 [assembly: AssemblyTitle("Project Title")]

    8 [assembly: AssemblyDescription("Some description")]

    9 [assembly: AssemblyConfiguration("Debug")]

   10 [assembly: AssemblyCompany("My Company of Software")]

   11 [assembly: AssemblyProduct("Product Name")]

   12 [assembly: AssemblyCopyright("Copyright © My Company, 2011")]

   13 [assembly: AssemblyTrademark("")]

   14 [assembly: AssemblyCulture("")]

   15 

   16 // Setting ComVisible to false makes the types in this assembly not visible

   17 // to COM components.  If you need to access a type in this assembly from

   18 // COM, set the ComVisible attribute to true on that type.

   19 [assembly: ComVisible(false)]

   20 

   21 // The following GUID is for the ID of the typelib if this project is exposed to COM

   22 [assembly: Guid("f446dcb3-68fb-49d9-a057-fe50383ce4c3")]

   23 

   24 // Version information for an assembly consists of the following four values:

   25 //

   26 //      Major Version

   27 //      Minor Version

   28 //      Build Number

   29 //      Revision

   30 //

   31 // You can specify all the values or you can default the Build and Revision Numbers

   32 // by using the '*' as shown below:

   33 // [assembly: AssemblyVersion("1.0.*")]

   34 [assembly: AssemblyVersion("1.0.0.0")]

   35 [assembly: AssemblyFileVersion("1.0.0.0")]

Las últimas líneas del fichero están relacionadas con la versión del ensamblado y con la versión del fichero.

AssemblyFileVersion

Su principal objetivo es el de identificar de manera uniequivoca a un ensamblado. A partir de los valores de la versión podemos saber la versión de código fuente que compiló el ensamblado. Tambien nos permite reconocer si se trata de una versión de “Service Pack” o de “Hotfix”.

Las versiones de un fichero están basadas en un código de 4 números. El uso de cada uno de los valores depende de cada equipo de desarrollo. Personalmente, les doy el siguiente significado a los valores de versión:

  • Major Version: Versión principal de producto. No suele cambiar a lo largo de todo el ciclo de desarrollo salvo que el desarrollo requiera rehacer todo el código desde cero. Evidentemente no se mantiene ninguna compatibilidad con versiones anteriores.
  • Minor Version: Cambio importante en las interfaces del ensamblado que hace que se rompa la compatibilidad con versiones anteriores. Al igual que la versión principal, este valor se cambia de manera manual.
  • Build Number: Número calculado automáticamente por el proceso de compilación en orden creciente de manera que dos compilaciones seguidas no generen el mismo valor. Existen muchos esquemas de generación de este valor. Quizá el más conocido es el de basado en el número de días transcurridos desde el 1 de Enero de 2000.
  • Revision: Número tambien generado automáticamente por el sistema de compilación. Por ejemplo, si se generan varias versiones el mismo día, la versión de Build no cambia, por lo que está va incrementandose de uno en uno por cada compilación.

AssemblyVersion

Los ensamblados de .NET usan esta versión para enlazarse con otros ensamblados. Esto es, cuando por ejemplo el ensamblado A.dll referencia al ensablado B.dll con version “1.0.0.0”, si sustituimos este último ensamblado por la versión “1.0.0.1”, A.dll no será capaz de encontrarlo pues espera encontrar la versión con la que fue compilado. Por este motivo no se debe dejar a la ligera la opción de Visual Studio de autoincrementar la versión del ensamblado en cada compilación.

El significado de los valores de la versión puede ser:

  • Major Version: Se actualiza de manera manual. Normalmente significa un cambio radical en la estructura del ensamblado. Por ejemplo porque se ha vuelto a reescribir gran parte del código. Normalmente implica rompe la compatibilidad con versiones anteriores.
  • Minor Version: Se han añadido nuevas funcionalidades al ensamblado. Y por tanto, puede suponer cambios en la interfaz pero tratando de mantener la compatibilidad con versiones anteriores. Su modificación es manual.
  • Build Number: Valor calculado automatiamente y que identifica el número de compilación realizado sobre el código fuente.
  • Revision: Normalmente identifican ligeros cambios sobre el código. Por ejemplo solución de hotfixes. Aquellos ensamblados donde únicamente varíe el número de revisión son totalmente intercambiables entre sí.

AssemblyInformationalVersion

Este es otro atributo que puede ser usado dentro de AssemblyInfo. Su objetivo es el de exponer una versión del ensamblado a nivel de producto, documentación, instalador, etc. En ningún momento esta información es usada por Windows o por otros ensamblados de .NET.

Esta versión se identifica con tres valores:

  • Major Release: Normalmente asociada a la versión principal de producto y no suele variar en todo el ciclo de desarrollo del proyecto salvo que se tenga que rehacer todo el contenido del ensamblado.
  • Minor Release: Este número se asocia a incrementos en la funcionalidad del ensamblado.
  • Maintenance Release: Los incrementos en este valor suelen reflejar pequeños cambios normalmente asociados a correción de errores, rendimiento o estabilidad. Los clientes del ensamblado no aprecian un cambio en la funcionalidad importada.

domingo, 3 de octubre de 2010

El patrón Modelo-Vista-Presentador (MVP) a examen

Desde hace tiempo llevo enfrentándome con diversos patrones de diseño cuyo objetivo principal es el de separar la interfaz de usuario de la lógica de las aplicaciones. Desde mis años de estudiante universitario con Smalltalk y el patrón Modelo-Vista-Controlador (MVC), hasta MVVM con WPF, pasando por MVP, MP y sus variaciones.

Cada vez que he tenido que me embarcaba a usar uno de estos patrones de diseño me encontraba con la misma curva de aprendizaje. Un concepto bastante sencillo pero que ante ciertos escenarios que encontraba en proyectos reales uno no sabe exactamente qué solución tomar.

En esta entrada quiero centrarme en el patrón de diseño Modelo-Vista-Presentador (MVP), pero yendo más allá de la descripción académica de este patrón y enfocándome en cómo puede ser aplicado en aplicaciones reales.

Como he comentado antes, MVP es otro patrón de diseño que tiene como objetivo separar la interfaz de usuario de la lógica de las aplicaciones.

Básicamente este patrón consiste en 3 componentes:

  • La vista. Compuesta de las ventanas y controles que forman la interfaz de usuario de la aplicación.
  • El modelo. Que es donde se lleva a cabo toda la lógica de negocio.
  • El presentador. Escucha los eventos que se producen en la vista y ejecuta las acciones necesarias a través del modelo. Además puede tener acceso a las vistas a través de las interfaces que la vista debe implementar.

image

El concepto de este patrón es bastante sencillo. Por un lado tengo la vista, que se encarga de mostrar la información al usuario y de interactuar con él para hacer ciertas operaciones. Por otro lado, tenemos el modelo que, ignorante de cómo la información es mostrada al usuario, realiza toda la lógica de las aplicaciones usando las entidades del dominio. Y por último tenemos al presentador que es el que “presenta” a ambos actores sin que haya ningún tipo de dependencia entre ellos.

El propósito del presentador tal y como se ha definido en este patrón no establece de manera clara el grado de control que este puede hacer de la vista.

Dependiendo de este grado de control podemos encontrarnos variaciones en la manera de ver MVP. De hecho, algunas de ellas, dejan de ser MVP para convertirse en MVC, por lo cual debemos tener cuidado.

MVP como Controlador Supervisado

Por un lado, podemos tener un presentador que no gestione la forma en que la información es mostrada en la vista. Es la vista quien define la lógica de cómo la información es formateada y mostrada en la pantalla a partir de los controles que contiene. En este caso, el presentador únicamente los casos más complejos para facilitar el trabajo de la vista. Martin Fowler llama a esta variación Controlador Supervisado.

image

Cuando la vista recibe algún evento de ratón o teclado por parte del usuario, delega el control del evento en el presentador. Este puede realizar ciertas operaciones relacionadas con la vista como el control del estado de los controles y después realizar la llamada a algún comando en el modelo que realice la operación requerida por el usuario. El modelo realiza las operaciones pudiendo realizar cambios en su estado generando el evento correspondiente, el cual es manejado por la vista para actualizar los controles de la pantalla.

Hay que tener en cuenta de que el hecho de que la vista pueda hacer referencia al modelo nos da como resultado un diagrama muy parecido al de MVC. Aunque este enfoque nos permita el uso de técnicas de Data Binding. Con lo cual la cantidad de código que la vista y presentador necesitan, se disminuye.

Este enfoque nos va a permitir el uso de técnicas de Data Binding sobre el modelo. Por tanto, la cantidad de líneas de código fuente en la vista y el presentador disminuyen. Si nos fijamos en el diagrama, este es similar al de MVC por lo que hay que tener mucho cuidado en no caer en un cambio de patrón.

MVP como Vista Pasiva

Por otro lado, podemos hacer que el presentador gestione totalmente cómo la información se muestra en la vista. Es decir, tenemos una vista "tonta", sin ningún tipo de lógica, cuya única función es la de mostrar la información que se le pasa a través de la interfaz de la vista. Martin Fowler llama a esta variación Vista Pasiva.

image

En este caso, cuando un usuario realiza alguna operación sobre la interfaz de usuario, la vista delega los eventos sobre el presentador. Este realizará algún cambio sobre la vista para indicar el cambio de estado y hará las llamadas a los comandos sobre el modelo para llevar a cabo la operación requerida por el usuario. Cuando el modelo provoque cambios en su estado, estos serán recogidos por el presentador (al contrario que en el controlador supervisado, que era la vista quien atendía a estos cambios de estado en el modelo), el cual pedirá al modelo los cambios realizados para luego actualizar la vista acorde a los cambios recibidos.

Ejemplo

Supongamos el siguiente ejemplo: Un programa que para una fecha que le introduzcamos, te diga si es pasado, futuro o la fecha del día actual.

image

Cuando el usuario introduce la fecha y pulsa el botón OK, la vista delega el evento de pulsación de botón sobre el presentador. El presentador toma la fecha introducida del cuadro de texto y valida que el contenido tiene el formato de fecha correcto.

Nota: He obviado la posibilidad de usar un control específico para introducir fechas y así ver en qué lugar se pude hacer la validación de datos.

Si la fecha introducida no fuera correcta, el presentador comunicaría al usuario este hecho a través de la vista.

En caso de que la fecha fuese correcta, el presentador realizaría la operación de comprobar la fecha usando el modelo. Al recibir el resultado de la operación, el presentador mostraría el resultado a través de la vista. Por ejemplo, modificando el texto de la etiqueta con el resultado.

En el siguiente diagrama vemos la secuencia de llamadas realizada para el caso de Vista Pasiva:

image

En caso de usar la variación de Controlador Supervisado, supondría que la vista toma el resultado directamente del modelo y que además, la vista define la lógica de cómo el resultado se muestra en la pantalla. En el caso de que la validación de la fecha fallara, el presentador podría, por ejemplo, enviar una excepción hacia la vista que al capturarla decidiría como se mostraría al usuario.

Actualización: 
He creado en Codeplex un proyecto que hace uso de este patrón. Se trata de un sencillo ejemplo de máquina tragaperras sobre Windows Forms. El enlace al proyecto es este.

viernes, 4 de junio de 2010

S.O.L.I.D. – El principio de la Responsabilidad Única (SRP) (parte 2)

El Principio de Responsabilidad Única (o en inglés Single Responsibility Principle (SRP)) fue descrito por Tom DeMarco y Meilir Page-Jones en un trabajo que llamado “Cohesión” en el que se definen las relaciones de los elementos de un módulo. Este principio nos viene a decir que una clase sólo debería tener una única razón para cambiar.

“Una clase debe tener una única razón para cambiar.”

Lo que trata de decirnos este principio es que debemos huir de aquellas clases monolíticas que aglutinen varias responsabilidades. Pero, ¿qué es una responsabilidad? Desde el punto de vista de SRP se podría decir que una responsabilidad en una clase es una razón para cambiar esa clase. Es decir, si encontramos que hay más de una razón por la que una clase pueda cambiar entonces es que esa clase tiene más de una responsabilidad.

¿Y no sería más sencillo decir que una clase debería tener una sola razón para existir en lugar de para cambiar? Cuidado, porque esto nos podría llevar a hacer muy malos diseños de sistemas. Llevado al pie de la letra podría encontrarme con cientos de clases en mi sistema, cada una con una única función. Lo que haría al sistema nada mantenible.

El punto clave que nos dice las razones por la que una clase puede cambiar va a depender del contexto en el que se va a dar uso a esa clase. Pongamos por ejemplo una clase que represente al motor de un coche. ¿Necesitamos conocer el régimen de revoluciones del motor?, ¿el peso?, ¿número de cilindros?, ¿presión del inyector de gasolina?, ¿o lo que nos interesa es simplemente poder arrancarlo y esperar que haga andar a un coche para llevaros de un sitio a otro? La respuesta a estas preguntas va a depender del contexto en el cual usemos la clase motor. No va a tener las mismas necesidades sobre esta clase un fabricante de coches que un usuario que usa el coche para ir de un sitio a otro. El fabricante de coches va a notar un número mayor de responsabilidades en el motor que el usuario del coche. Por tanto, para el fabricante, este principio recomendaría dividir la clase motor en otras más pequeñas que cumplan con las especificaciones de manera individual.

Veamos otro ejemplo típico de violación del SRP: Supongamos que tenemos la clase Employee (Empleado) en un sistema de gestión de una empresa cualquiera. Esta clase nos permite realizar las tareas esperadas sobre un empleado: Cargor y almacenarlo en una base de datos, generar la nómina, información básica del empleado, etc.

image

Ahora supongamos dos aplicaciones que hacen uso de la clase Employee. Una para ser usada por el departamento de recursos humanos para la gestión de las nominas del personal y otra para la gestión de los proyectos que lleva la empresa.

¿Podemos pensar que no se está siguiendo el SRP? Lo que sería lo mismo, ¿creemos que la clase Employee tiene más de una razón por la que pueda cambiar? A mi se me ocurren unas cuantas: Cambiar el formato de almacenamiento de base de datos, modificar los campos que definen a un empleado, cambiar la lógica de generación de nóminas, etc. Es decir, esta clase tiene varias responsabilidades: Es responsable de la persistencia de los clientes, responsable de caracterizar a un empleado, responsable de generar las nóminas, etc.

Las consecuencias de violar el SRP en este caso son dos:

  • La clase Employee tiene una dependencia con la clase AccountService para poder realizar el cálculo de las nóminas. Por tanto la aplicación de gestión de proyectos, a la hora de hacer el despliegue de esa aplicación, tambien debe incluir la librería que contiene esa clase aunque no la necesite.
  • Si alguna de las aplicaciones necesita implementar nueva funcionalidad y requiere cambiar la definición de la clase Employee, este cambio arrastraría cambios en el resto de aplicaciones que requerirían adaptarse a los nuevos cambios de la clase. En caso de olvidarnos, las consecuencias serían impredecibles.

Una mejora en el diseño sería separar las responsabilidades en clases distintas

image

Hemos creado dos nuevas clases: una para la gestión de nóminas y otra para el almacenamiento en la base de datos. Hay que fijarse en el detalle de que la clase Employee no depende de las nuevas clases EmployeeAccount y de EmployeeStorage, sino que la dependencia la tienen las aplicaciones.

En definitiva. Este principio es uno de los más simples de SOLID, y sin embargo de los más difíciles de implementar correctamente. Aplicando SRP,podemos alcanzar niveles más bajos de acoplamiento y una cohesión más alta del sistema.

sábado, 29 de mayo de 2010

S.O.L.I.D. Principios de Diseños Orientados a Objeto (parte 1)

En la entrada anterior hablaba de los beneficios de hacer desarrollo software con un nivel bajo de acoplamiento, alta cohesión y una fuerte encapsulación. Cuando nos encontramos con proyectos de pequeño tamaño, estos conceptos más o menos los podemos ir controlando. Pero cuando trabajamos en equipos donde se abarcan proyectos de gran envergadura, es complicado. Sobre todo si los miembros del grupo no tienen claros estos conceptos.

En los años 90, Robert C. Martin, hace una recopilación de principios a modo de vía para conseguir que estos desarrollos empresariales alcancen un bajo nivel de acoplamiento, alta cohesión y una fuerte encapsulación. Estos principios se englobaron dentro de las siglas S.O.L.I.D.

  • S: Single Responsibility Principle (SRP) - Principio de Responsabilidad Única
  • O: Open-Closed Principle (OCP) – Principio de Abierto-Cerrado
  • L: Liskov Substitution Principle (LSP) – Principio de Substitución de Liskov
  • I: Interface Segregation Principle (ISP) – Principio de Segregación de Interface
  • D: Dependency Inversion Principle (DIP) – Principio de Inversión de Dependencia

image

El Principio de Responsabilidad Única (SRP) nos dice que una clase sólo debe tener una única razón para cambiar. Este principio nos ayuda a dirigir la cohesión y controlar el acoplamiento.

El Principio de Abierto-Cerrado (OCP) nos indica cómo diseñar clases de manera que estas se puedan extender sin tener que modificar su funcionalidad principal. Aplicando este principio conseguimos sistemas bien encapsulados y altamente cohesivos.

El Principio de Substitución de Liskov (LSP) se centra en los conceptos de encapsulación y cohesión. Y dice que una clase que implemente una interfaz o herede de una clase base no debe violar la intención o la semántica de la abstracción heredada.

El Principio de Segregación de la Interfaz (ISP) nos habla de como las clases no deben exponer interfaces que las aplicaciones que las usan no necesitan. Aplicando este principio se obtiene una mejora en la encapsulación y cohesión de sistemas.

El Principio de Inversión de Dependencias (DIP) está enfocado en hacernos entender de como los detalles de implementación debe depender de la funcionalidad de alto nivel que requiere el sistema y no al revés. Y de como conseguir invertir esta relación de manera satisfactoria.

Aunque todos los principios SOLID están relacionados entre sí.Voy a dedicar una entrada a profundizar sobre cada uno de estos dando ejemplos muy básicos y explicativos que ayuden a entender la manera en que pueden ser usados en nuestros desarrollos.