Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Resumen
La reutilizacin no supone un nuevo concepto en el desarrollo del software. Se ha estado reutilizando bibliotecas de software durante mucho tiempo, pero viendo al cdigo fuente como el nico objeto de la reutilizacin y generalmente empleando tcnicas intuitivas. Sin embargo, actualmente, el concepto de reutilizacin ha evolucionado hacia la idea de que todo el conocimiento y los productos derivados de la produccin de software son susceptibles de ser reutilizados en la construccin de nuevos sistemas, surgiendo de esta forma el concepto de asset o de componente software reutilizable. Cuando en una organizacin se decide introducir la reutilizacin en sus desarrollos, aparecen dos actividades perfectamente diferenciadas: el desarrollo de componentes software reutilizables (desarrollo para la reutilizacin) y el desarrollo con componentes reutilizables existentes (desarrollo con reutilizacin). El objetivo de este documento es presentar los principios fundamentales para el diseo de componentes reutilizables, pero desde el prisma de la orientacin al objeto. Para documentar los diseos orientados al objeto que se recogen en este documento se hace uso de UML 1.0.
Palabras Clave
Reutilizacin de software, componentes software, anlisis orientado al objeto, diseo orientado al objeto, principios de diseo, desarrollo para reutilizacin, UML.
Agradecimientos
Este documento ha sido generado en el seno del grupo GIRO (Grupo de Investigacin en Reutilizacin y Orientacin al Objeto) compuesto por miembros del Departamento de Informtica de la Universidad de Valladolid y por miembros del rea de Lenguajes y Sistemas Informticos de la Universidad de Burgos. Desde aqu queremos agradecer la inestimable colaboracin y las correcciones sugeridas por el resto de los miembros de este grupo.
Tabla de contenidos
1. Concepto de reutilizacin______________________________________________ 1 2. Actividades de la reutilizacin __________________________________________ 1
2.1 Desarrollo para la reutilizacin ___________________________________________ 1 2.2 Desarrollo con reutilizacin ______________________________________________ 1
3. Orientacin al objeto y reutilizacin _____________________________________ 2 4. El proceso de desarrollo para la reutilizacin______________________________ 3 5. Anlisis del dominio en el desarrollo para la reutilizacin ___________________ 4
5.1 Definicin del dominio___________________________________________________ 4 5.2 Recoleccin de aplicaciones del dominio ____________________________________ 4 5.3 Anlisis de las aplicaciones del dominio y desarrollo de modelos y de componentes 4 5.4 Preparacin del soporte de la reutilizacin __________________________________ 5
11. El principio de equivalencia reutilizacin/revisin________________________ 37 12. El principio de cierre comn _________________________________________ 38 13. El principio de reutilizacin comn ___________________________________ 38 14. El principio de dependencia acclica ___________________________________ 38 15. El principio de las dependencias estables _______________________________ 41
15.1 Mtricas de estabilidad ________________________________________________ 41
17. Bibliografa_______________________________________________________ 45
ii
Tabla de Figuras
Figura 1. Ciclo de vida general del desarrollo para reutilizacin. ______________________ 3 Figura 2. Identificacin y emplazamiento__________________________________________ 6 Figura 3. Identificacin de clases de objetos _______________________________________ 9 Figura 5. Diseo que no se ajusta al principio abierto/cerrado. _______________________ 17 Figura 4. Ambigedad en la herencia mltiple ____________________________________ 14 Figura 6. Diseo que cumple el principio abierto/cerrado. ___________________________ 18 Figura 7. El cuadrado es_un rectngulo. _________________________________________ 22 Figura 8. Diagrama de estructura del programa Copiar. ____________________________ 27 Figura 9. El programa Copiar con un diseo orientado al objeto. _____________________ 29 Figura 10. Estructura de niveles incorrecta. ______________________________________ 31 Figura 11. Solucin con niveles abstractos. _______________________________________ 31 Figura 12. Ejemplo de la bombilla. _____________________________________________ 32 Figura 13. Diseo incorrecto para el problema del botn y la bombilla. ________________ 32 Figura 14. Diseo correcto para el problema del botn y la bombilla. _________________ 34 Figura 15. Diseo de la puerta con alarma._______________________________________ 36 Figura 16. Solucin con herencia mltiple. _______________________________________ 37 Figura 17. Dependencia entre paquetes. _________________________________________ 39 Figura 18. Diagrama de paquetes sin ciclos. ______________________________________ 39 Figura 19. Diagrama de paquetes con ciclos. _____________________________________ 40 Figura 20. Eliminacin del ciclo con el principio de inversin de la dependencia._________ 40 Figura 21. Ejemplo para el clculo de la estabilidad. _______________________________ 42 Figura 22. Secuencia principal.________________________________________________ 43 Figura 23. Zonas de exclusin. _________________________________________________ 44
Tabla de Listados
Listado 1. No cumple el principio abierto/cerrado (figuras.c). ________________________ Listado 2. Cdigo que si cumple el principio abierto/cerrado (figura.cpp). ______________ Listado 3. Clase Rectangulo.___________________________________________________ Listado 4. Clase Cuadrado.____________________________________________________ Listado 5. Utilizacin de la clase Cuadrado sin problemas. __________________________ Listado 6. Funcin CambiaAspecto. _____________________________________________ Listado 7. Prdida de las invariantes del Cuadrado con la funcin CambiaAspecto. _______ Listado 8. Nueva definicin de las clases Rectangulo y Cuadrado. _____________________ Listado 9. Las invariantes de la clase Cuadrado se mantienen. ________________________ Listado 10. Solucin que viola el principio abierto/cerrado en la funcin CambiaAspecto. __ Listado 11. Programa Copiar. _________________________________________________ Listado 12. Mdulo Copiar modificado y ms dependiente de los mdulos de ms bajo nivel. Listado 13. Solucin al problema del programa de copia. ____________________________ Listado 14. Primera implementacin del problema botn/bombilla. ____________________ Listado 15. Implementacin para el problema del botn y la bombilla. _________________ Listado 16. Clase Puerta. _____________________________________________________ Listado 17. Clase Temporizador. _______________________________________________ 19 20 22 22 23 23 23 24 24 25 28 29 30 33 34 35 35
iii
1. Concepto de reutilizacin
El trmino reutilizacin fue originalmente postulado por M.D. McIloroy en la conferencia de la NATO de 1968 sobre Ingeniera del Software [McIlroy76], y desde entonces la reutilizacin del software ha sido, y sigue siendo, uno de los principales temas de investigacin en el campo de la Ingeniera del Software, citndose a menudo como una de las principales tcnicas para incrementar la productividad de los desarrolladores de software. As Mili et al. [Mili95] llegan a afirmar que La investigacin en estas dcadas en los campos de la Ingeniera del Software y de la Inteligencia Artificial ha dejado algunas alternativas, pero la reutilizacin es la nica aproximacin realista para llegar a los ndices de productividad y calidad que la industria del software necesita. Aunque por otra parte, este mismo autor reconoce que no se han conseguido grandes avances en la adopcin sistemtica de la reutilizacin en el proceso de construccin del software. El propsito de la reutilizacin es mejorar la eficiencia, la productividad y la calidad del desarrollo software. As la reutilizacin puede definirse como cualquier procedimiento que produce o ayuda a producir un sistema mediante el nuevo uso de algn elemento procedente de un esfuerzo de desarrollo anterior [Freeman87b] o como la utilizacin de elementos software existentes durante la construccin de un nuevo sistema software [Krueger92]. Se va a denominar componente reutilizable o asset a cualquier componente que es especficamente desarrollado para ser utilizado, y es actualmente utilizado, en ms de un contexto [Karlsson95]. Un componente reutilizable puede pertenecer a cualquier nivel de abstraccin de un desarrollo, es decir, puede ser desde una especificacin de requisitos a una biblioteca de funciones.
2. Actividades de la reutilizacin
Cuando la reutilizacin est presente en un proceso de desarrollo software, este debe integrar todas las actividades necesarias para producir y reutilizar componentes software. As se distinguen dos actividades principales dentro de la reutilizacin: el desarrollo para la reutilizacin y el desarrollo con reutilizacin.
2.1 Desarrollo para la reutilizacin El desarrollo para la reutilizacin consiste en la realizacin que cumplen un conjunto de restricciones sobre su reutilizacin y su calidad. A la hora de desarrollar componentes reutilizables es fundamental centrarse en criterios de calidad, en detrimento de los costes de produccin de los componentes.
Cuando se va a crear un componente reutilizable debe analizarse la variabilidad de los requisitos que satisface dicho componente, de forma que se construya como un componente genrico que pueda ser especializado en el momento de su reutilizacin para ajustarse a unos requisitos especficos.
2.2 Desarrollo con reutilizacin Las tcnicas utilizadas en el proceso de desarrollo con reutilizacin dependen en gran medida de los componentes que se hayan preparado en el proceso de desarrollo para la reutilizacin.
El desarrollo con reutilizacin consiste en la generacin de nuevos productos software integrando elementos existentes, de forma directa o pasando por un proceso de adaptacin. Aparecen as cuatro problemas fundamentales [Business92a], [Krueger92]: La seleccin y recuperacin de los componentes La comprensin y evaluacin de los componentes La adaptacin de los componentes La integracin de los componentes
clases describen los objetos en el dominio y la forma en que interaccionan. Una instanciacin de un framework es un ensamblado de objetos que trabajan juntos y solucionan un problema particular. Un framework debe ocultar las partes de diseo que son comunes a todas las instancias, y dejar accesibles las partes que deben ser especializadas. La reutilizacin de un framework conlleva dos actividades: definir las nuevas clases que se necesiten y configurar un conjunto de objetos.
Anlisis Anlisis
Componentes reutilizables
Implementacin Implementacin
Pruebas Pruebas
El desarrollo para la reutilizacin no implica que se tengan que llevar a cabo una serie de actividades de forma secuencial, sino ms una interconexin de actividades que son iteradas. Esta caracterstica se cumple especialmente en el desarrollo de componentes especficos de un dominio.
3
La vista externa, especificacin de requisitos, y la vista interna, reingeniera, de diferentes aplicaciones se analiza en diferentes momentos del proceso de desarrollo. Las actividades de desarrollo iteran entre los modelos de anlisis, los diseos y las implementaciones. Las guas de reutilizacin fuerzan modificaciones en las versiones actuales de los componentes que repercutirn posteriormente en el anlisis de otras aplicaciones. El desarrollo para la reutilizacin no es una actividad que forme parte del ciclo de vida de desarrollo clsico. El desarrollo para la reutilizacin debe ir en paralelo con los ciclos de vida de los productos de la organizacin.
5.1 Definicin del dominio El objetivo es definir el dominio lo ms estrechamente posible, pero manteniendo visibles las relaciones con otros dominios adyacentes.
Los componentes especficos del dominio deben permanecer separados de los componentes especficos de un producto y de los componentes de propsito general. Deben realizarse modelos de dominio que expliquen y describan el dominio y las relaciones entre los diferentes componentes.
5.2 Recoleccin de aplicaciones del dominio Se puede encontrar informacin sobre el dominio en antiguos productos. Las especificaciones, diseos, cdigo fuente, casos de prueba... de antiguas aplicaciones, que posiblemente no sern orientadas al objeto, pueden convertirse en potenciales fuentes de componentes reutilizables mediante un proceso de reingeniera. Esta reingeniera se orienta a separar los componentes propios del dominio de los componentes generales y del producto. 5.3 Anlisis de las aplicaciones del dominio y desarrollo de modelos y de componentes Durante el estudio de la documentacin de los productos antiguos, se pueden desarrollar diferentes modelos que pueden servir tambin como componentes reutilizables. Los modelos de anlisis pueden servir como modelos de dominio a la vez que como componentes reutilizables.
5.4 Preparacin del soporte de la reutilizacin Los ingenieros del software que van a desarrollar con reutilizacin deben contar con alguna gua que les permita decidir cuando reutilizar y cuando no reutilizar un determinado componente.
Los componentes especialmente complejos, como los frameworks orientados al objeto, deben acompaarse de un manual de reutilizacin, que describa como adaptarlo y configurarlo para diferentes requisitos.
6.1 Actividades generales en el AOO para la reutilizacin El AOO constituye el acto por el que se determinan las abstracciones que subyacen en los requisitos. Es el proceso de razonamiento que comienza con los requisitos y finaliza
con un conjunto de objetos que expresan dichas abstracciones, as como con los mensajes que soportan los comportamientos requeridos. Para Monarchi et al [Monarchi92] el proceso del anlisis del dominio del problema conlleva tres actividades: La identificacin de las clases semnticas, los atributos, el comportamiento y las relaciones (generalizaciones, agregaciones y asociaciones). El emplazamiento de las clases, atributos y comportamiento. La especificacin del comportamiento dinmico mediante paso de mensajes.
IDENTIFICACIN
Jerarqua AKO Clases Jerarqua APO Estructura Emplazado en Emplazado en Asociacin
Atrib.
Mtod.
EMPLAZAMIENTO
Figura 2. Identificacin y emplazamiento
Las recomendaciones que se citan a continuacin pueden aplicarse anlisis orientado al objeto para la reutilizacin, viniendo derivadas algunas de ellas del anlisis tradicional. Algunas de las consideraciones que se van a realizar no son especficas de un proceso de reutilizacin, pero influyen en su mejora. 1: Recoger informacin de la mayor cantidad de fuentes posible. Un sistema basado en una visin nica puede no cumplir los requisitos actuales o futuros. Para conseguir un componente estable, se debe conseguir informacin de varias fuentes diferentes. 2: Captura de todos los requisitos como modelos de anlisis. La completa comprensin de los requisitos es esencial en un desarrollo software. A la hora de desarrollar para la reutilizacin toma una especial importancia la captura de los requisitos no funcionales, debido a que con frecuencia contribuyen a aumentar la reutilizacin de un componente. 3: Utilizar una notacin que sea fcil de comprender. Para facilitar la reutilizacin de un componente en futuros desarrollos es esencial que los modelos sean fciles de comprender por el que los va a reutilizar. La notacin utilizada para realizar los modelos se convierte as en un factor clave, especialmente si se tiene en cuenta el amplio nmero de metodologas de ADOO existentes, cada una con sus propias aportaciones en lo tocante a la notacin. La comunidad de orientacin al objeto est haciendo esfuerzos por convergir las diferentes tendencias en ADOO hacia un punto comn. En este sentido cabe
destacar el Lenguaje de Modelado Unificado (UML Unified Modeling Language) de Rational Software Corporation [Rational97a]. UML se basa en los trabajos de tres de los ms conocidos expertos en metodologas orientadas al objeto (Graddy Booch, Jim Rumbaugh y Ivar Jacobson), y es una combinacin de notacin [Rational97d], una semntica [Rational97b], [Rational97c] y un metamodelo para un lenguaje de modelado. Aunque su total aceptacin como estndar por parte de OMG (Object Management Group) no se ha producido (y no est claro que se produzca tal cual), si se puede asegurar que se ha convertido en el estndar de facto de las notaciones en la orientacin al objeto [Ohnjec97]. 4: Refinar y formalizar los modelos de anlisis. Se debe refinar el modelo conceptual para eliminar clases redundantes y para buscar niveles de mayor abstraccin. 5: Uso de la herencia como mecanismo de especializacin de tipos. La estructura de herencia1 constituye un importante mecanismo para el soporte de la reutilizacin al permitir la extensin y el refinamiento. Las estructuras de herencia que primero se localizan son propias del dominio del problema. El concepto de herencia, cuando se corresponde con la relacin de especializacin/generalizacin de clases, permite organizar la informacin en una estructura jerrquica de componentes, en la que los elementos de alto nivel capturan el comportamiento comn de todos sus elementos derivados. De esta forma se establece un mecanismo, mediante el cual se permite compartir (especializacin) y/o factorizar (generalizacin) la informacin, posibilitando no slo la reutilizacin, sino tambin la extensibilidad del software, al permitir definir nuevos componentes que pueden heredar el comportamiento y la representacin de los componentes existentes [Marqus95]. El concepto de herencia puede corresponderse con otros tipos de relaciones diferentes a la especializacin/generalizacin, destacando los procesos de agregacin y composicin. Sin embargo, una de las caractersticas diferenciales del desarrollo de aplicaciones utilizando la orientacin al objeto es su naturaleza incremental. Este aspecto de desarrollo incremental en el que se van incorporando clases a medida que el diseo orientado al objeto progresa, puede dar lugar a jerarquas de clases sin sentido, siendo fundamental la obtencin de una jerarqua de clases correcta para la comprensin y el seguimiento de los modelos y su reutilizacin [Marqus96]. 6: Analizar aplicaciones existentes dentro del dominio para identificar caractersticas comunes. Si se han desarrollado varias aplicaciones pueden existir varias soluciones al problema. Se debe tratar de obtener las abstracciones adecuadas y colocarlas en un modelo general.
1
Rumbaugh et al [Rumbaugh96] sostienen que los trminos herencia, generalizacin y especializacin se refieren a aspectos de la misma idea y suele ser posible utilizarlos de forma intercambiable. Herencia hace alusin al mecanismo empleado para compartir atributos y operaciones empleando la relacin de herencia. La generalizacin y la especializacin son dos puntos de vista distintos de la misma relacin vista desde la superclase o desde las subclases. La palabra generalizacin proviene del hecho consistente en que la superclase generaliza a las subclases. La especializacin hace alusin al hecho consistente en que las subclases refinan o especializan a la superclase.
El mayor problema para llevar a cabo esta actividad puede ser la accesibilidad a la informacin necesaria para analizar las aplicaciones. 7: Generalizar de forma que se soporten cambios futuros en los componentes. La introduccin de abstracciones de alto nivel facilitan el cambio de los componentes sin tener que reestructurar la arquitectura, sino simplemente extendiendo la jerarqua mediante herencia. 8: Indicar las variantes de los sistemas existentes y de los sistemas futuros dentro del dominio. Mediante el uso de la herencia para sealar las diferencias entre los sistemas, obliga a los analistas a establecer abstracciones estables que permanezcan invariantes durante un largo perodo de tiempo. 9: Realizacin de una revisin formal de los documentos generados en la fase de anlisis. El examen de los documentos generados durante la fase de anlisis puede dar como resultado la deteccin de errores y el descubrimiento de nuevos requisitos que pueden ser tiles para obtener un componentes completamente reutilizable. Las nueve recomendaciones anteriores son lo suficientemente genricas como para que se tengan en cuenta en cualquier caso, pero no constituyen una gua o metodologa para la realizacin de un anlisis orientado al objeto. Por este motivo se van a citar y comentar brevemente los ocho pasos que habra dar para construir un modelo de objetos segn una de las mejores metodologas orientadas al objeto, OMT [Rumbaugh96]. a. Identificacin de los objetos y de las clases El primer paso es la identificacin de las clases de objetos relevantes en el dominio del problema2, evitando estructuras de implementacin. Debe comenzarse por la enumeracin de los candidatos a clases de objetos que se encuentran en la descripcin escrita del problema. En esta primera fase no hay que ser especialmente selectivo. Con frecuencia, las clases se corresponden con los sustantivos de la especificacin escrita del problema. El siguiente paso es descartar las clases innecesarias e incorrectas segn los siguientes criterios: Clases redundantes: Clases que expresan la misma informacin. Se debe retener la que tenga el nombre ms descriptivo. Clases irrelevantes: Clases que tienen poco o nada que ver con el problema. Deben ser eliminadas. Clases vagas: Clases con unos lmites mal definidos, o con un mbito excesivo. Una clase debe representar un concepto especfico. Atributos: Los nombres que describen objetos individuales deben recalificarse como atributos. Por ejemplo, nombre, sexo... Sin embargo, si la existencia independiente de una propiedad es importante, se debe crear una clase con ella.
Operaciones: Si un nombre describe una operacin que se aplica a objetos y que no es propiamente manipulada en s, entonces no es una clase. Sin embargo, si una operacin posee caractersticas propias debe modelarse como una clase. Roles: El nombre de una clase debera reflejar su naturaleza intrnseca, y no el rol que desempea en una asociacin. Estructuras de implementacin: Las estructuras ajenas al mundo real deben ser eliminadas del anlisis. Las estructuras de datos tales como listas enlazadas, rboles, matrices y tablas, son casi siempre de implementacin.
Definicin de requisitos
Extraer nombres
Clases de objetos
b. Preparacin de un diccionario de datos Debe prepararse un diccionario de datos para todas las entidades del modelo, escribiendo un prrafo que describa con precisin y absoluta claridad cada clase de objetos. El diccionario de datos tambin describe las asociaciones, atributos y operaciones. c. Identificacin de asociaciones y agregaciones Se identifican las asociaciones entre clases. Toda dependencia entre dos o ms clases o una referencia de una clase a otra es una asociacin. Las asociaciones suelen corresponderse con verbos de estado o con locuciones verbales3. Como en el caso de las clases, deben extraerse todos los candidatos. En este momento no debe perderse mucho tiempo intentando distinguir una asociacin de una agregacin, se debe utilizar lo que parezca ms natural en este momento. El siguiente paso es descartar las asociaciones innecesarias e incorrectas, siguiendo los siguientes criterios: Asociaciones entre clases eliminadas: Si se ha eliminado alguna de las clases que interviene en la asociacin debe eliminarse sta o redefinirla en trminos de otras clases. Asociaciones irrelevantes o de implementacin: Hay que eliminar cualquier asociacin que est fuera del dominio del problema, o que afecte a estructuras de implementacin. Acciones: Las asociaciones deben describir propiedades estructurales del dominio del problema, y no sucesos transitorios. Asociaciones ternarias: La mayora de las asociaciones entre tres o ms clases se pueden descomponer en asociaciones binarias.
Asociaciones derivadas: Deben omitirse las asociaciones que puedan ser definidas en trminos de otras porque son redundantes. Por ejemplo Abuelo puede definirse en trminos de una parejas de asociaciones Padre de. Asociaciones de nombre incorrecto: No debe indicarse cmo o por qu se produce una situacin; hay que decir lo que es. Nombres de rol: Tienen que aadirse donde convenga. Los nombres de rol describen el papel desempeado por la asociacin desde el punto de vista de la otra clase. Por ejemplo, en la asociacin Trabaja para, la Compaa tiene el rol de empresario y la Persona tiene el rol de empleado. Si slo existe una asociacin entre una pareja de clases, y el nombre de la clase describe correctamente su papel, se pueden omitir los nombres del rol. Asociaciones cualificadas: Normalmente, el nombre identifica al objeto en un cierto contexto; la mayora de los nombres son nicos desde el punto de vista global. El contexto se combina con el nombre para identificar de forma nica al objeto. El cualificador distingue a los objetos del lado muchos de la asociacin. Multiplicidad: Hay que especificarla, aunque se debe tener presente que la multiplicidad suele variar a lo largo del anlisis. Se debe desconfiar de los valores de multiplicidad iguales a uno. Asociaciones inexistentes: Las asociaciones inexistentes que se vayan descubriendo deben ir aadindose al modelo.
d. Identificacin de los atributos de los objetos y de los enlaces El siguiente paso es la identificacin de los atributos de los objetos. Los atributos son propiedades de los objetos individuales. Los atributos no deben ser objetos, debe utilizarse una asociacin para mostrar la relacin entre dos objetos. Los atributos suelen corresponderse con nombres seguidos por frases posesivas, por ejemplo el color de la silla. Los adjetivos suelen representar valores de los atributos especficos. A diferencia de las clases y las relaciones, es menos probable que los atributos se describan por completo en la definicin del problema. Durante el anlisis deben evitarse los atributos que sean slo de implementacin y asegurarse que cada atributo recibe un nombre significativo. Los atributos derivados debe omitirse o marcarse de forma clara. Por ejemplo, edad puede derivarse de fecha de nacimiento y de fecha actual. Los atributos de enlace tambin deben ser identificados. Un atributo de enlace es una propiedad del enlace de dos objetos, en lugar de ser una propiedad de un objeto individual. Por ejemplo, la asociacin muchos a muchos entre Accionista y Compaa tiene como atributo de enlace el nmero de acciones. A continuacin deben filtrarse los atributos con el fin de eliminar aquellos que sean innecesarios o incorrectos siguiendo los siguientes criterios: Objetos: Si es importante la existencia independiente de una entidad, y no slo su valor, se trata de un objeto. La distincin depende con frecuencia de la aplicacin. Por ejemplo, en una lista de correos Ciudad sera un atributo, mientras que en un censo Ciudad sera un objeto. Una entidad que tiene caractersticas propias dentro del problema es un objeto.
10
Cualificadores: Si el valor de un atributo depende de un contexto particular, hay que pensar recalificar el atributo como cualificador. Por ejemplo, nmero de empleado no es una propiedad nica para una persona que tenga dos trabajos, lo que hace es cualificar la asociacin Compaa que emplea a persona. Nombres: Un nombre es un atributo de un objeto cuando no depende del contexto y sobre todo cuando no necesita ser nico. Si un nombre responde afirmativamente a las preguntas Selecciona el nombre uno de los objetos de un conjunto? Puede un objeto del conjunto tener ms de un nombre?, se trata de un cualificador de una asociacin. Identificadores: Los lenguajes orientados al objeto tienen la nocin de identificador de objetos para hacer alusin no ambigua a un objeto. Estos identificadores no deben enumerarse en los modelos de objetos al ser una propiedad intrnseca del objeto. Slo deben enumerarse aquellos atributos que existan en el dominio del problema. Atributos de enlace: Si una propiedad depende de la existencia de un enlace, es un atributo del enlace y no de un objeto relacionado. Los atributos de enlace son evidentes en las asociaciones muchos a muchos, ya que no pueden asociarse a ninguna de las clases como consecuencia de su multiplicidad. Los atributos de enlace son ms sutiles en asociaciones uno a muchos, porque se pueden asociar al objeto de la parte muchos sin prdida de informacin. Valores internos: Si un atributo describe el estado interno de un objeto que es invisible fuera del objeto, hay que eliminarlo del anlisis. Detalles finos: Los atributos menores, que no afecten a la mayora de las operaciones, deben omitirse. Atributos discordantes: Son aquellos que parecen ser completamente distintos e independientes de todos los dems; pueden indicar una clase que debiera fragmentarse en dos distintas.
e. Refinamiento mediante herencia La siguiente actividad es organizar las clases empleando la herencia para compartir una estructura comn. La herencia se puede aadir en dos direcciones: generalizando aspectos comunes de las clases existentes en una superclase4 o bien refinando las clases existentes para obtener subclases especializadas5. Cuando se lleva a cabo una factorizacin o refinamiento ascendente se buscan clases con atributos, asociaciones u operaciones similares, de forma que en cada generalizacin se define una superclase para compartir caractersticas comunes. Las especializaciones por refinamiento progresivo suelen verse directamente a partir del dominio del problema, La herencia mltiple se puede utilizar para compartir caractersticas de varios objetos, pero teniendo en cuenta que se est incrementando la complejidad conceptual y de implementacin.
4 5
11
f. Verificacin de la existencia de las vas de acceso adecuadas para las probables consultas Se deben seguir las vas de acceso por el diagrama de modelo de objetos para comprobar si se obtienen resultados sensatos. Cuando se espera un valor nico, hay una va que lo proporcione? Para la multiplicidad muchos, existe una forma de seleccionar valores nicos cuando sea necesario? g. Iteracin y refinamiento del modelo Un modelo de objetos rara vez es correcto en la primera pasada. Todo el proceso de desarrollo de software es una continua iteracin; las distintas partes del modelo se encuentran en diferentes fases de acabado. Si se encuentra un error hay que volver a la etapa anterior, si es necesario, para corregirlo. h. Agrupamiento de las clases en mdulos El ltimo paso del modelado es agrupar las clases en folios y mdulos. Las clases que estn fuertemente acopladas deben ir agrupadas, pero teniendo en cuenta que un folio tiene una cantidad prefijada de informacin. Un mdulo es un conjunto de clases (uno o ms folios) que captura algn subconjunto lgico del mdulo completo.
6.2 Actividades generales en el DOO para la reutilizacin Aunque los lenguajes OO, los mtodos y las herramientas continan su proceso de maduracin, el diseo de las aplicaciones orientadas al objeto retiene algo de ese oscurantismo propio de los desarrollos software [Lea96]. En este trabajo se pretende exponer una serie de recomendaciones generales y bsicas del DOO, teniendo como objetivo de fondo la reutilizacin del software que se genere y de los diseo en s.
El propsito del diseo es crear una arquitectura para el sistema que va a desarrollarse, y establecer las tcticas comunes que deben utilizarse por parte de elementos dispares del sistema [Booch96]. Durante el diseo orientado al objeto, se ejecuta la estrategia seleccionada durante el anlisis y se rellenan los detalles. Se produce un desplazamiento del nfasis, pasando a los conceptos del dominio de la solucin. Los objetos identificados durante el anlisis sirven como esqueleto del diseo. Las operaciones identificadas durante el anlisis deben expresarse en forma de algoritmos, descomponiendo las operaciones complejas en operaciones internas ms sencillas. Las clases, atributos y asociaciones del anlisis deben implementarse en forma de estructuras de datos especficas. Ser preciso introducir nuevas clases de objetos6 [Rumbaugh96]. En el nivel arquitectnico es importante mostrar el agrupamiento de clases en categoras de clases, arquitectura lgica, y el agrupamiento de mdulos en subsistemas, arquitectura fsica. Un problema en la fase de diseo es el rpido incremento de la complejidad, debido al incremento del nmero de clases, mtodos y atributos. Los subsistemas se introducen para manejar el sistema de forma ms abstracta.
12
La fuerte cohesin funcional es una propiedad importante que deben cumplir los buenos subsistemas. Un subsistema con una fuerte cohesin funcional es un subsistema que realiza una funcin u ofrece un comportamiento perfectamente delimitado. Ivar Jacobson [Jacobson92] establece una serie de criterios para seleccionar los subsistemas: Cuando en un subsistema debe hacerse algunos cambios, estos cambios no deben afectar a ms de un subsistema de bajo nivel. Cada subsistema debe tener una alta cohesin. Los subsistemas deben tener un bajo acoplamiento, limitndose este a comunicaciones entre los subsistemas. Debe tratarse de ocular el comportamiento o funcionalidad dentro de los subsistemas.
De cara a la reutilizacin, los diseos de los subsistemas deben cumplir una alta cohesin, de forma que las clases que forman el subsistema se vean afectadas al mismo tiempo por un cambio en los requisitos, y un bajo acoplamiento, minimizando las colaboraciones entre subsistemas de forma que los cambios en un subsistema afecten lo menos posible a otros. Otra recomendacin de cara a la reutilizacin viene de la mano de crear el mayor nmero de clases abstractas posible, de esta forma las clases abstractas encapsulan el comportamiento que puede ser reutilizado mediante la herencia. Esta actividad ayuda a encontrar la abstraccin ms adecuada, ya que al definir tantas clases abstractas como haya sido posible significa que se ha factorizado tanto como haya sido posible el comportamiento comn. La ltima fase del diseo recibe el nombre de diseo detallado, y es la fase en que las clases y sus mtodos son definidos, siendo muy importante cuidar los aspectos de reutilizacin debido a que las clases y los mtodos constituyen la interfaz de los componentes que se utilizarn en los desarrollos con reutilizacin. En general se pueden mencionar los siguientes puntos comunes en la fase de diseo detallado: La utilizacin de la herencia conlleva multitud de ventajas: los componentes sern menores, ms sencillos de entender y de modificar, y por consiguiente de reutilizar. El protocolo lo constituyen las interfaces de las clases, los mtodos pblicos de las clases. Utilizando un protocolo estndar los objetos mantienen una interfaz similar y una forma comn de nombrar los mtodos. El polimorfismo reduce el nmero de interfaces diferentes que se deben tener en cuenta. Deben evitarse las clases demasiado grandes, transformndose en nuevas abstracciones o en pequeas jerarquas de herencia.
En forma de recetario se pueden dar las siguientes recomendaciones para aplicarlas en el diseo detallado:
13
1: Si se identifican propiedades generales comunes a algunas clases se puede crear una superclase que las rena. Esta es una gua de diseo ascendente. El establecimiento de la jerarqua de clases es preferible hacerla en fases anteriores. Desde el punto de vista de la reutilizacin las superclases debe capturar las caractersticas comunes en un grado de abstraccin mayor, mientras que las subclases deben recoger las diferencias. Los desarrolladores que reutilicen estos diseos podrn encontrar la abstraccin ms adecuada para extender el software mediante herencia. Si las caractersticas comunes no se capturan de forma adecuada en las superclases, el desarrollador con reutilizacin se ver obligado a realizar grandes modificaciones y redefiniciones. 2: Minimizar el uso de la herencia mltiple La herencia mltiple puede ser causa de problemas en el momento de reutilizar un componente. Las estructuras de herencia son mucho ms sencillas si no es utiliza la herencia mltiple.
A B C
Adems muchos lenguajes de programacin orientados al objeto tienen problemas con la D herencia mltiple (o simplemente no la soportan) especialmente cuando la estructura de herencia Figura 4. Ambigedad en la implica ambigedad, (ver Figura 4). Para herencia mltiple profundizar ms en temas de herencia mltiple, en sus problemas y en los algoritmos para solventarlos se recomienda consultar la tesis doctoral del Dr. Jos Manuel Marqus [Marqus95]. 3: Si una operacin X de una clase se implementa para realizar una operacin similar en otra clase, entonces esta operacin debe denominarse tambin X (Introduccin de la recursin). La introduccin de la recursin es importante para conseguir unos protocolos estandarizados que permitan utilizar el polimorfismo de forma ms extensa. Adems de esta forma, cuando se vaya a desarrollar con reutilizacin slo se tendrn que manejar un conjunto limitado de nombres de mtodos. 4: Cada mtodo debe realizar slo una tarea De esta forma el protocolo de la clase ser ms fcil de entender y por lo tanto ms fcil de reutilizar. 5: Las subclases deben ser especializaciones La herencia que no comparte caractersticas comunes debe evitarse. Las subclases deben ser autenticas especializaciones de las superclases. 6: La cima de una jerarqua de herencia debe ser una clase abstracta Es mejor heredar de una clase abstracta que de una clase concreta. Una clase concreta debe ofrecer una definicin para la representacin de sus datos, y algunas subclases necesitan diferentes representaciones. Dado que una clase abstracta no tiene que ofrecer una representacin de sus datos, las futuras subclases podrn
14
utilizar cualquier representacin sin entrar en conflicto con lo que hayan heredado. 7: Cuidar que el protocolo total de la clase sea pequeo El protocolo total de una clase consiste en todos los mtodos definidos en la clase y en todos los mtodos heredados de sus superclases que no han sido redefinidos o anulados. Si se tienen muchos mtodos en las clases de los ltimos niveles de la jerarqua se tiene una abstraccin muy compleja, y esta jerarqua puede ser dividida en varias jerarquas ms pequeas y menos complejas. Desde el punto de vista de la reutilizacin es ms difcil reutilizar clases grandes, siendo ms fciles de entender y modificar clases ms pequeas. 8: Usar clases abstractas en tantos niveles como sea posible en la jerarqua de herencia Una clase abstracta es una clase que no representa completamente un objeto. En su lugar representa un amplio rango de diferentes clases de objetos. Sin embargo, esta representacin comprende slo las caractersticas de aquellas clases que tienen objetos en comn. Por tanto, una clase abstracta slo ofrece una descripcin parcial de sus objetos [Martin92a]. Una clase abstracta est diseada para ser reutilizada y acta como una plantilla para clases concretas. Debe utilizar protocolos estndares y reunir generalidades de una jerarqua de herencia. Una forma de incrementar la reutilizacin es utilizar clases abstractas en varios niveles de la jerarqua de herencia. 9: Las jerarquas de herencia deben ser prudentemente profundas y estrechas Si una superclase tiene ms de diez subclases directamente dependiendo de ella, es recomendable buscar una nueva abstraccin. La desventaja de tener jerarquas demasiados profundas es la dificultad de comprender su comportamiento. 10: Bsqueda de la uniformidad en las reglas para los nombres Es importante para las personas que vayan a reutilizar los componentes manejar un limitado conjunto de nombres de clases y mtodos. Un buen ejemplo de esta caracterstica se tiene en las bibliotecas de Eiffel, donde todos los nombres de operaciones en la biblioteca de estructuras de datos han sido estandarizados. En caso de no existir una notacin estndar en la organizacin se puede adoptar algn convenio entre los desarrolladores, o emplear alguna notacin ampliamente aceptada como puede ser la notacin hngara. A continuacin, y como referencia, se citan las reglas de Ottinger para nombres de variables y clases: Utilizar nombres pronunciables. Evitar nombres codificados. No ser demasiado ingenioso pensando nombres, o slo sern entendidos por las personas que coincidan en el sentido del humor o que recuerden la broma. Cuando un concepto pueda representarse por varias palabras elegir una. Evitar nombres con doble sentido o ambiguos.
15
Utilizar nombres o frases con algn verbo. Utilizar nombres del dominio de la solucin. Utilizar nombres del dominio del problema. Nada es intuitivo. Evitar nombres o acrnimos que tengan otros significados. Cuidar que los nombres mantengan su significado dentro cualquier contexto. No crear contextos artificiales. Cuidar la forma en que se deshacen las ambigedades. 11: Mantener las signaturas de los mtodos consistentes Los mtodos que realizan funciones similares deben tener los mismos nombres, y devolver el mismo tipo de dato. Todas las nociones que se han comentado referentes al diseo orientado al objeto se reafirman en lo que Robert C. Martin [Martin97a] denomina los principios del DOO. Estos principios son: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. El principio de abierto/cerrado. cip ab rra El principio de sustitucin de Liskov. cip ci de El principio de inversin de dependencia. cip de cia El principio de separacin de la interfaz. cip ci ter El principio de equiivalencia reutilizacin/revisin. cip equ cia iliz ci El principio de cierre comn. cip cierre El principio de reutilizacin comn. cip iliz ci El principio de dependencia acclica. cip nde cia acclica El principio de las dependencias estables. cip nde cia tab El principio de las abstracciones estables. cip ccio tab
7. El principio de abierto/cerrado
D_T_c\_ccYcdU]QcSQ]RYQ^TebQ^dUcecSYS\_cTUfYTQ5cd_TURU c\ cc cdU cS ^T Uc cS cTUf 5cd_T dU^UbcU U^ ]U^dU SeQ^T_ cU `bUdU^TU aeU \_c cYcdU]Qc U^ Se cYcdU TUcQbb_\\QT_c`UbTebU^]vcTU\Q`bY]UbQfUbcY^ \\Q c` ^] cTU\Q` Qf
,YDU-DFREVRQ>-DFREVRQ@ Existen muchas heursticas asociadas con el DOO. Por ejemplo: todas las variables miembro deben ser privadas, o las variables globales deben evitarse, o la utilizacin de identificacin de tipos en tiempo de ejecucin es peligroso. El principio de diseo que subyace en muchas de estas heursticas es el principio abierto/cerrado, enunciado por Bertrand Meyer [Meyer88]. Este principio dice lo siguiente: ;Pb T]cXSPSTb b^UcfPaT R[PbTb \Sd[^b Ud]RX^]Tb STQT] TbcPa ]cX b^ R Sd PQXTacPb_PaPbdTgcT]bX]_Ta^RTaaPSPb_PaPbd\^SXUXRPRX] b_ PbdT T]b _ ^R b_ Pbd\^SX %HUWUDQG0H\HU>0H\HU@
16
Este es el principio constituye la gua ms importante para un diseador de software orientado al objeto. El diseo de mdulos cuyo comportamiento pueda ser modificado sin realizar modificaciones en el cdigo fuente de dichos mdulos, es la caracterstica en la que se basan los beneficios del diseo orientado al objeto. La naturaleza, simultneamente abierta y cerrada de los sistemas orientados al objeto da soporte a la facilidad de mantenimiento, de reutilizacin y de verificacin. As, un sistema reutilizable debe estar abierto, en el sentido de que tienen que ser fciles de extender, y cerrados en el sentido de que deben estar listos para ser utilizados [Graham96]. Cuando un nico cambio en un programa produce una cascada de cambios en los mdulos dependientes, el programa exhibe unos atributos no deseables debidos a un mal diseo. As, el programa se convierte en un programa frgil, rgido, impredecible y en consecuencia NO REUTILIZABLE. Este principio ataca este vicio de forma directa, expresando la idea de que nunca se debe cambiar el diseo de los mdulos. Cuando cambien los requisitos, se extiende el comportamiento de los mdulos aadiendo nuevo cdigo, pero nunca cambiando el cdigo que ya funciona [Martin96a]. En resumen, los mdulos que cumplen el principio abierto/cerrado tienen dos atributos primarios: 1. Estn abiertos para su extensin Eso implica que el comportamiento de los mdulos puede ser extendido. Se puede hacer que un mdulo se comporte de formas nuevas cuando los requisitos de la aplicacin cambien. 2. Estn cerrados para su modificacin El cdigo fuente del mdulo es inalterable. No se permite realizar cambios al cdigo fuente. La clave de este principio est en la abstraccin. Se pueden crear abstracciones que sean fijas y que representen un grupo ilimitado de posibles comportamientos. Las abstracciones son un conjunto de clases base, y el grupo ilimitado de los posibles comportamientos viene representado por todas las posibles clases derivadas. De esta forma un mdulo esta cerrado para su modificacin al depender de una abstraccin que es fija. Pero el comportamiento del mdulo puede ser extendido mediante la creacin de clases derivadas. La Figura 5 muestra un diseo simple que no cumple el principio abierto/cerrado. Tanto la clase cliente como la clase servidor son clases concretas. La clase cliente usa a la clase servidor. Si se desea que un objeto cliente use un objeto servidor diferente, se debe cambiar la clase cliente para nombrar la nueva clase servidor.
Cliente
Servidor
17
La Figura 6 muestra el diseo que cumple el principio abierto/cerrado. En este caso, la clase ServidorAbstracto es una clase abstracta con mtodos virtuales puros. La clase Cliente usa esta abstraccin, y por tanto los objetos de la clase Cliente utilizarn los objetos de las clases derivadas de la clase ServidorAbstracto. Si se desea que los objetos de la clase Cliente utilicen diferentes clases Servidor, se deriva una nueva clase de la clase ServidorAbstracto, y la clase Cliente permanece inalterada.
Cliente
ServidorAbstracto Abstracta
Servidor
7.1 Ejemplo del principio abierto/cerrado: La abstraccin de la figura Se tiene una aplicacin que es capaz de dibujar crculos y cuadrados. Los crculos y los cuadrados deben ser dibujados en un determinado orden, por lo tanto se crear una lista en el orden apropiado de crculos y de cuadrados para que la aplicacin recorra la lista en ese orden y dibuje cada crculo o cada cuadrado.
Utilizando una aproximacin estructurada que no se ajusta al principio abierto/cerrado, utilizando lenguaje C por ejemplo, se puede resolver este problema como se muestra en el Listado 1. Se puede observar que tanto la estructura Cuadrado como la estructura Circulo tienen el primer elemento en comn, que no es ms que un identificador del tipo de figura. La funcin DibujaFiguras recorre la matriz de punteros a elementos de tipo Figura, examinando el tipo de figura y llamando a la funcin apropiada. Sin embargo, la funcin DibujaFiguras no cumple el principio abierto/cerrado porque no est cerrada a nuevos tipos de figuras. Si se quiere extender esta funcin para dibujar rectngulos, se tiene que modificar la funcin DibujaFiguras. En el Listado 2 se presenta la solucin al problema planteado, pero de forma que cumpla el principio abierto/cerrado. Como lenguaje de programacin se ha elegido C++. En este caso, se ha definido la clase Figura como clase abstracta. Esta clase cuenta con un mtodo virtual puro denominado Dibuja. Las clases Circulo y Cuadrado son clases derivadas de la clase Figura. Por su parte, la funcin DibujaFiguras no necesita modificarse en el caso de que se aadan nuevas figuras a la jerarqua. De acuerdo con esto se puede extender el comportamiento de la funcin DibujaFiguras sin modificar nada en absoluto de su cdigo, cumpliendo as el principio abierto/cerrado. Como comentario a este principio se puede decir que nunca se tiene un programa completamente cerrado. En general, no importa lo cerrado que est un mdulo, siempre existe algn tipo de cambio que demuestra que no estaba cerrado. Entonces, dado que un mdulo nunca estar cerrado al 100%, el cierre debe ser estratgico. El diseador debe seleccionar los cambios para los cuales estar cerrado su diseo.
18
#include <stdio.h> #include <stdlib.h> #define MAXIMO 10 #define ELEMENTOS 5 enum TipoFigura {circulo, cuadrado}; struct Figura { TipoFigura Tipo; }; struct Punto { float x, y; }; struct Circulo { TipoFigura Tipo; float Radio; struct Punto Centro; }; struct Cuadrado { TipoFigura Tipo; float Lado; struct Punto SuperiorIzquierda; }; typedef struct Figura *PunteroFigura; void DibujaCuadrado(struct Cuadrado *); void DibujaCirculo(struct Circulo*); void DibujaFiguras(PunteroFigura lista[], int); void DibujaCuadrado(struct Cuadrado *c) { printf("\nCuadrado"); printf("\nPunto superior izquierda: %f %f", c->SuperiorIzquierda.x, c->SuperiorIzquierda.y); printf("\nLongitud del lado: %f", c->Lado); } void DibujaCirculo(struct Circulo *c) { printf("\nCrculo"); printf("\nCentro: %f %f", c->Centro.x, c->Centro.y); printf("\nLongitud del radio: %f", c->Radio); } void DibujaFiguras(PunteroFigura lista[], int n) { int i; struct Figura *f; for (i=0; i<n; i++) { f = lista[i]; switch (f->Tipo) { case cuadrado: DibujaCuadrado((struct Cuadrado*)f); break; case circulo: DibujaCirculo((struct Circulo*)f); break; } } } void main(void) { struct Cuadrado *c1; struct Circulo *c2; int i; PunteroFigura lista[MAXIMO]; for (i=0; i<ELEMENTOS; i++) { if (i%2==0) { c1 = (struct Cuadrado *) malloc (sizeof(struct Cuadrado)); c1->Tipo = cuadrado; c1->SuperiorIzquierda.x = c1->SuperiorIzquierda.y = i*2; c1->Lado = i*2.5; lista[i]= (struct Figura *)c1; } else { c2 = (struct Circulo *) malloc (sizeof(struct Circulo)); c2->Tipo = circulo; c2->Centro.x = c2->Centro.y = 5.0+i; c2->Radio = 10.0/i; lista[i]= (struct Figura *)c2; } } DibujaFiguras(lista, ELEMENTOS); }
19
#include <iostream.h> #define MAXIMO 10 #define ELEMENTOS 5 class Figura { public: virtual void Dibuja() = 0; }; class Punto { float x, y; public: Punto(float x1, float y1) {x=x1; y=y1;} float LeerX(void) { return x;} float LeerY(void) { return y;} }; class Circulo: public Figura { float Radio; Punto Centro; public: Circulo(float x1, float y1, float r1):Centro(x1,y1) { Radio = r1;} virtual void Dibuja() { cout << "\nCrculo"; cout << "\nCentro: " << Centro.LeerX() << " " << Centro.LeerY(); cout << "\nLongitud del radio:" << Radio; } }; class Cuadrado:public Figura{ float Lado; Punto SuperiorIzquierda; public: Cuadrado(float x1,float y1,float l1):SuperiorIzquierda(x1, y1) { Lado = l1;} virtual void Dibuja() { cout << "\nCuadrado"; cout << "\nPunto superior izquierda: " <<SuperiorIzquierda.LeerX() << " " << SuperiorIzquierda.LeerY(); cout << "\nLongitud del lado: " << Lado; } }; void DibujaFiguras(Figura *lista[], int); void DibujaFiguras(Figura *lista[], int n) {for (int i=0; i<n; lista[i++]->Dibuja());} void main(void) { Figura *lista[MAXIMO]; for (char i=0; i<ELEMENTOS; i++) { if (i%2==0) lista[i] = new Cuadrado (i*2.0, i*2.0, i+5.0); else lista[i] = new Circulo (i*3.0, i*2.5, i + 10.0); } DibujaFiguras(lista, ELEMENTOS); }
7.2 El principio abierto/cerrado y las heursticas del DOO. El famoso principio de Meyer est detrs de las principales heursticas y convenciones del diseo orientado al objeto. Se pueden destacar:
Todas las variables miembro de una clase deben ser privadas. Esta es una de las convenciones del DOO ms difundidas. Las variables miembro de las clases deben ser conocidas exclusivamente por los mtodos de las clases que las definen.
20
Esta afirmacin en relacin con el principio abierto/cerrado se puede razonar desde el punto de vista que las variables de la clase pueden cambiar, y cada funcin que dependa de esas variables debe ser cambiada. Por tanto, ninguna funcin que dependa de una variable puede estar cerrada con respecto a esa variable. No se deben utilizar variables globales. El argumento de esta aseveracin es similar al de tener variables miembro pblicas. Ningn mdulo que dependa de una variable global puede estar cerrado con respecto a otro mdulo que modifique el valor de dicha variable.
8.1 Ejemplo de violacin del principio de Liskov Supngase que una aplicacin que funciona correctamente y que est instalada en diferentes lugares utiliza la clase Rectangulo que se muestra a continuacin en el Listado 3:
21
class Rectangulo { double alto, ancho; public: Rectangulo(double _alto, double _ancho) {alto=_alto; ancho=_ancho;} void EstableceAlto(double tmp) {alto=tmp;} void EstableceAncho(double tmp) {ancho=tmp;} double Alto() const {return alto;} double Ancho() const {return ancho;} }; Listado 3. Clase Rectangulo.
En un momento dado, los requisitos de los usuarios cambian y exigen que se pueda trabajar tambin con cuadrados. Si se ve la herencia como la relacin semntica es_un (ISA), cualquier tipo de objeto nuevo que cumpla la relacin es_un con algn tipo de objeto ya creado, puede derivarse de la clase del objeto ya existente. Claramente, un cuadrado es un rectngulo para todos los propsitos. Dado que se cumple la relacin es_un, se puede derivar la clase Cuadrado de la clase Rectangulo, como se muestra en la Figura 7.
Rectangulo
Cuadrado
La utilizacin de la herencia es considerada por muchos como una de las tcnicas fundamentales de la orientacin al objeto, as en el ejemplo un cuadrado es un rectngulo, y por lo tanto la clase Cuadrado debe derivarse de la clase Rectangulo, como puede verse en el Listado 4.
class Cuadrado: public Rectangulo { public: Cuadrado(double lado):Rectangulo(lado, lado){} void EstableceAlto(double lado) { Rectangulo::EstableceAlto(lado); Rectangulo::EstableceAncho(lado); } void EstableceAncho(double lado) { Rectangulo::EstableceAlto(lado); Rectangulo::EstableceAncho(lado); } }; Listado 4. Clase Cuadrado.
Sin embargo, esto puede conducir a unos problemas bastante sutiles e importantes, que no se suelen prever hasta el momento de la codificacin. En primer lugar se puede apreciar que aunque Cuadrado no necesita las dos variables miembro (alto, ancho), las hereda y obliga a que las funciones EstableceAlto y EstableceAncho deban ser redefinidas en la clase Cuadrado debido a que en un cuadrado el alto y el ancho tienen el mismo valor. Teniendo en cuenta esta redefinicin (ver Listado 4) el programa principal que aparece en el Listado 5 funcionar sin problemas, debido a que al cambiar su altura cambia automticamente su anchura y viceversa, permaneciendo las invariantes de la clase Cuadrado intactas.
22
void main (void) { Cuadrado c1(2); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida: 2 2 c1.EstableceAlto(5); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida 5 5 c1.EstableceAncho(10); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida 10 10 } Listado 5. Utilizacin de la clase Cuadrado sin problemas.
Pero, qu sucedera con alguna funcin que recibiese una referencia o un puntero a un objeto Rectangulo y modificase su altura o su anchura? Pues simplemente que la funcin fallara si a dicha funcin se le pasa un objeto Cuadrado. Por ejemplo, considrese una funcin que modifica la relacin de aspecto entre la anchura y la altura, de forma que ajusta la anchura a la mitad de su altura (ver Listado 6).
void CambiaAspecto(Rectangulo &r) { r.EstableceAncho(r.Alto()*.5); } Listado 6. Funcin CambiaAspecto.
Si se tiene el programa principal del, al llamar a la funcin CambiaAspecto con un objeto Cuadrado sta no mantendr las invariantes del cuadrado porque llama al mtodo EstableceAncho de la clase Rectangulo y no cambiar su altura. Esta es una clara violacin del principio de Liskov. El motivo del fallo es que los mtodos EstableceAncho y EstableceAlto no han sido declarados como virtuales en la clase Rectangulo.
void main (void) { Rectangulo r1(8, 7); cout << r1.Alto() << " " << r1.Ancho() << endl; //Salida: 8 7 CambiaAspecto(r1); cout << r1.Alto() << " " << r1.Ancho() << endl; //Salida: 8 4 Cuadrado c1(2); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida: 2 2 CambiaAspecto(c1); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida: 2 1 } Listado 7. Prdida de las invariantes del Cuadrado con la funcin CambiaAspecto.
Esto es muy fcil de solucionar, pero el problema de fondo es que, cuando la creacin de una clase derivada provoca cambios en la clase base, siempre implica un fallo de diseo. En el Listado 8 aparecen las definiciones de la clase Rectangulo y de la clase Cuadrado. Si con la definicin de las clases Rectangulo y Cuadrado del Listado 8 se probase la funcin CambiaAspecto del Listado 6, con el programa principal del Listado 9, la funcin CambiaAspecto no cumplira su cometido al recibir un objeto de la clase Cuadrado, ya que llamara al mtodo EstableceAncho redefinido en la clase
23
Cuadrado, cambiando as su altura y su anchura, pero por el contra se habr conseguido mantener las invariantes del cuadrado inalteradas.
class Rectangulo { double alto, ancho; public: Rectangulo(double _alto, double _ancho) {alto=_alto; ancho=_ancho;} virtual void EstableceAlto (double tmp) {alto=tmp;} virtual void EstableceAncho(double tmp) {ancho=tmp;} double Alto() const {return alto;} double Ancho() const {return ancho;} }; class Cuadrado: public Rectangulo { public: Cuadrado(double lado): Rectangulo(lado, lado){} virtual void EstableceAlto(double lado) { Rectangulo::EstableceAlto(lado); Rectangulo::EstableceAncho(lado); } virtual void EstableceAncho(double lado) { Rectangulo::EstableceAlto(lado); Rectangulo::EstableceAncho(lado); } }; Listado 8. Nueva definicin de las clases Rectangulo y Cuadrado.
void main (void) { Cuadrado c1(2); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida: 2 2 CambiaAspecto(c1); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida: 1 1 } Listado 9. Las invariantes de la clase Cuadrado se mantienen.
Dado que un cliente de la clase Rectangulo puede funcionar de forma inadecuada cuando se le pasa un objeto de la clase Cuadrado, los objetos de la clase Cuadrado no pueden ser sustituidos por los objetos de la clase Rectangulo. Se puede realizar codificar la funcin CambiaAspecto de forma que si detecta que recibe un objeto de clase Cuadrado levante una excepcin, como se puede apreciar en el Listado 10. Pero esta solucin crea una dependencia de la funcin CambiaAspecto con la clase Cuadrado, adems cada vez que se vaya a crear una nueva clase derivada de la clase Rectangulo, debe aadirse una comprobacin a todas las funciones que estn en conflicto, con lo que se estar violando el principio abierto/cerrado.
24
#include <iostream.h> #include <typeinfo.h> class Rectangulo { double alto, ancho; public: Rectangulo(double _alto, double _ancho) {alto=_alto; ancho=_ancho;} virtual void EstableceAlto(double tmp) {alto=tmp;} virtual void EstableceAncho(double tmp) {ancho=tmp;} double Alto() const {return alto;} double Ancho() const {return ancho;} }; class Cuadrado: public Rectangulo { public: Cuadrado(double lado):Rectangulo(lado, lado){} virtual void EstableceAlto(double lado) { Rectangulo::EstableceAlto(lado); Rectangulo::EstableceAncho(lado); } virtual void EstableceAncho(double lado) { Rectangulo::EstableceAlto(lado); Rectangulo::EstableceAncho(lado); } }; void CambiaAspecto(Rectangulo &); void CambiaAspecto(Rectangulo &r) { if (typeid(r) == typeid(Cuadrado)) throw "\nNo se puede ajustar el aspecto de un cuadrado."; r.EstableceAncho(r.Alto()*.5); } void main (void) { try { Rectangulo r1(8, 7); cout << r1.Alto() << " " << r1.Ancho() << endl; //Salida: 8 7 CambiaAspecto(r1); cout << r1.Alto() << " " << r1.Ancho() << endl; //Salida: 8 4 Cuadrado c1(2); cout << c1.Alto() << " " << c1.Ancho() << endl; //Salida: 2 2 CambiaAspecto(c1); cout << c1.Alto() << " " << c1.Ancho() << endl; //Levanta una excepcin } catch(char *msg) { cout << msg << endl; } }
De lo anterior se puede obtenerse una importante conclusin. Un modelo, visto por separado, no puede ser validado. La validez de un modelo slo puede expresarse en trminos de sus clientes. Por ejemplo, si se examina el modelo de las clases Rectangulo y Cuadrado por separado (Listado 8) se llega a la conclusin de que es consistente y vlido, aunque como se ha demostrado que cuando se han tenido en cuenta una serie de circunstancias el diseo se ha roto. Por tanto, para considerar si un diseo es adecuado, no basta con estudiar el diseo aislado, sino que se deben considerar las circunstancias que hagan los usuarios del diseo. Volviendo al ejemplo del rectngulo y el cuadrado, se concluye que un cuadrado puede ser un rectngulo, pero un objeto Cuadrado no es un objeto Rectangulo, porque en trminos de comportamiento, el comportamiento de un objeto Cuadrado no es consistente con el comportamiento de un objeto Rectangulo.
25
El principio de Liskov deja claro que en DOO la relacin ISA se refiere al comportamiento. No al comportamiento privado intrnseco, sino al comportamiento externo pblico, que es el comportamiento del que dependen los clientes. As, para cumplir el principio de Liskov, y por tanto el principio abierto/cerrado, todas las clases derivadas deben cumplir el comportamiento que esperan los clientes de sus clases bases. Existe una fuerte relacin entre el principio de Liskov y el concepto de diseo por contrato expuesto por Bertrand Meyer [Meyer88]. Segn esta forma de disear, los mtodos de las clases tienen precondiciones y postcondiciones. Las precondiciones deben ser ciertas para que se ejecute el mtodo, y despus de su ejecucin, el mtodo de garantizar que se cumple las postcondiciones. Meyer enuncia la siguiente regla para las precondiciones y postcondiciones para las clases derivadas:
SeQ^T_cUbUTUVY^Ue^]}d_T_U^e^QS\QcUTUbYfQTQc\_cU S _cUb Ue^] _ ^e^QS UT c _c `eUTU bUU]`\QjQb ce `bUS_^TYSY^ `_b e^Q ]vc ceQfU i ce ce `_cdS_^TYSY^`_be^Q]vcVeUbdU cdS ^` be^Q] cV
%HUWUDQG0H\HU>0H\HU@ Lo que quiere decir esta famosa regla es que cuando se usa un objeto a travs de la interfaz de su clase base, los usuarios conocen slo las precondiciones y las postcondiciones de la clase base. Por tanto, los objetos derivados no pueden ser tales que sus precondiciones sean ms estrictas que las de su clase base, esto es, deben aceptar cualquier cosa que la clase base pueda aceptar. Adems, las clases derivadas deben cumplir, al menos, todas las postcondiciones que cumpla la clase base. Ciertos lenguajes, como Eiffel, soportan directamente las precondiciones y las postcondiciones, es decir, se declaran y mediante sistemas en tiempo de ejecucin se verifican. C++ no dispone de esta caracterstica, por lo que el soporte del diseo por contrato debe hacerse manualmente considerando las precondiciones y postcondiciones de cada mtodo, y asegurar que no se viola la regla de Meyer.
26
las abstracciones, pero las abstracciones no deben depender de los detalles. Esto es, todas las funciones y las estructuras de datos de alto nivel deben ser completamente independientes de las funciones y estructuras de datos de bajo nivel. El principio de inversin de dependencia puede enunciarse como sigue: 0 ;^b \Sd[^b ST P[c^ ]XeT[ ]^ STQT] ST_T]STa ST [^b \Sd[^b ST ST Sd QPY^]XeT[0\Q^bSTQT]ST_T]STaST[PbPQbcaPRRX^]Tb ^] 0\ bS ]S aST[ bP RRX ]Tb 1 ;Pb PQbcaPRRX^]Tb ]^ STQT] ST_T]STa ST [^b STcP[[Tb ;^b STcP[[Tb RRX ]Tb [[T TcP[[T STQT]ST_T]STaST[PbPQbcaPRRX^]Tb ]S aST[ bP 5REHUW&0DUWLQ>0DUWLQF@ La palabra inversin se debe a que en los mtodos ms tradicionales de desarrollo de software, como pueden ser el Anlisis y el Diseo Estructurado, se tiende a crear estructuras software en las que los mdulos de alto nivel dependen de los mdulos de bajo nivel, y en los que las abstracciones dependen de los detalles. De hecho, en los mtodos estructurados uno de los objetivos es definir una jerarqua de subprogramas que describen como los mdulos de alto nivel realizan llamadas a los mdulos de bajo nivel. Sin embargo, la estructura de dependencia de un programa orientado al objeto bien diseado est invertida con respecto a la estructura de dependencia de los mtodos estructurales tradicionales. Los mdulos de alto nivel contienen las polticas de decisiones y los modelos de negocio de las aplicaciones. Estos modelos contienen la identidad de la aplicacin. Cuando estos mdulos dependen de los mdulos de bajo nivel, los cambios de los mdulos de bajo nivel tendran efectos directos en los mdulos de alto nivel, y podran forzar cambios en ellos. Este planteamiento no tiene sentido. Los mdulos de alto nivel son los que deben forzar los cambios en los mdulos de bajo nivel, pero nunca al revs. Por tanto, los mdulos de alto nivel nunca deben depender de los mdulos de bajo nivel de ninguna forma. Cuando los mdulos de alto nivel dependen de los mdulos de bajo nivel, es muy difcil reutilizarlos en contextos diferentes, convirtindose en un software inmvil. Sin embargo, cuando los mdulos de alto nivel son independientes de los mdulos de bajo nivel, los mdulos de alto nivel pueden ser reutilizados de forma bastante simple.
9.1 Ejemplo del principio de inversin de dependencias Como ejemplo se va a tomar un programa sumamente simple, el cual va a tener la misin de mandar los caracteres que se introduzcan por el teclado a un fichero en disco. El diagrama de estructura que se correspondera con dicho programa se muestra en la Figura 8.
Copiar
tecla tecla fichero
LeerTeclado
EscribeFichero
27
En el diagrama de estructura de la Figura 8 se puede apreciar que se tienen tres mdulos, de forma que el mdulo Copiar llama a los otros dos, como se puede corroborar en el Listado 11.
#include <stdio.h> #include <conio.h> #include <stdlib.h> void Copiar(char *); char LeerTeclado(void); void EscribeFichero(char *, char);
void Copiar(char *cad) { char tecla; while ((tecla = LeerTeclado()) != EOF) EscribeFichero(cad,tecla); } char LeerTeclado(void) { char tecla; tecla=getch(); tecla != 13 ? printf("%c", tecla) : printf("\n"); return tecla; } void EscribeFichero(char *fichero, char tecla) { FILE *out; if ((out = fopen(fichero, "a+")) == NULL) { fprintf(stderr, "Error al crear el fichero.\n"); exit(-1); } tecla != 13 ? fprintf(out, "%c", tecla) : fprintf(out, "\n"); fclose(out); } void main(void) { Copiar("pp.txt"); }
Los dos mdulos de ms bajo nivel son reutilizables, se pueden utilizar en otros programas para acceder al teclado y para guardar caracteres en un fichero de texto. Esta es la misma reutilizacin que se obtiene de las rutinas de una biblioteca. Sin embargo, el mdulo Copiar, que es el que encierra la poltica del proceso y sera deseable reutilizar, no se puede reutilizar en ningn otro proceso que no haga referencia al teclado y a un fichero. Por lo tanto, se tiene que el mdulo Copiar es dependiente del disco y no puede reutilizarse en otro contexto diferente. As, si se quisiese aadir una nueva funcionalidad al programa Copiar, por ejemplo que pudiese mandar los datos a un fichero de texto o a la impresora, habra que modificar el mdulo Copiar aadiendo una condicin que seleccione el dispositivo en funcin de una bandera, ver Listado 12. Esto aade cada vez ms interdependencias en el sistema, de forma que cuantos ms dispositivos se introduzcan mayor ser el grado de dependencia del mdulo Copiar con varios mdulos de bajo nivel. Como consecuencia se habr obtenido un cdigo rgido y frgil.
28
void Copiar(enum Dispositivo dev, char *cad) { char tecla; while ((tecla = LeerTeclado()) != EOF) if (dev==disco) EscribeFichero(cad,tecla); else EscribeImpresora(tecla); } Listado 12. Mdulo Copiar modificado y ms dependiente de los mdulos de ms bajo nivel.
Para solventar estas dependencias del mdulo de alto nivel (Copiar) de los mdulos de bajo nivel (EscribeFichero, EscribeImpresora) se debe buscar la forma de independizar el mdulo Copiar de los detalles que l controla, para que de esta forma pueda ser reutilizado sin problemas, esto es, se debe buscar un mdulo que copie caracteres de cualquier dispositivo de entrada a cualquier dispositivo de salida, para lo cual se debe tener presente el principio de inversin de dependencias. El diagrama de clases de la Figura 9 muestra una clase Copiar que contiene dos clases abstractas, una clase abstracta Lector y otra clase abstracta Escritor. De esta forma se tiene un ciclo trivial en el que la clase Copiar obtiene un carcter del Lector y se la manda al Escritor, pero de forma totalmente independiente de los mdulos de bajo nivel. De esta forma se han invertido las dependencias, la clase Copiar depende de abstracciones, y los lectores y escritores especializados dependen de las mismas abstracciones.
Copiar
Lector Abstractallllllllll
Escritor Abstractallllllllll
Lector de Teclado
Escritor en Disco
De esta forma ahora se puede reutilizar la clase Copiar de forma independiente de los dispositivos fsicos. Se pueden aadir nuevos tipos de lectores y de escritores sin que la clase Copiar dependa en absoluto de ellos.
29
#include <iostream.h> #include <fstream.h> #include <string.h> class Lector { public: virtual char leer() = 0; }; class Escritor { public: virtual void escribir(char) = 0; }; class LectorTeclado : public Lector { public: virtual char leer() { char tecla; cin.get(tecla); return tecla; } }; class EscritorFichero :public Escritor { char nombre[25]; public: EscritorFichero(char *cad) {strcpy(nombre,cad);} virtual void escribir(char tecla) { fstream fichero; fichero.open(nombre, ios::app); fichero << tecla; fichero.close(); } }; void Copiar(Lector& l, Escritor& e) { char tecla; while ((tecla=l.leer()) != EOF) e.escribir(tecla); };
void main (void) { EscritorFichero E1("pp.txt"); LectorTeclado L1; Copiar (L1, E1); }
9.2 Niveles Segn expresa Grady Booch [Booch96b] ... todas las arquitecturas orientadas al objeto bien estructuradas tienen niveles claramente definidos, donde cada nivel ofrece algn conjunto de servicios coherentes a travs de una interfaz bien definida y controlada.
30
Sin embargo, una interpretacin errnea de esta afirmacin puede llevar al diseador a crear una estructura similar a la que se presenta en la Figura 10. En este diagrama, la clase que contiene la poltica de alto nivel utiliza un mecanismo de bajo nivel, el cual a su vea utiliza una clase de utilidad con un alto nivel de detalle. Aunque esto pueda parecer apropiado, tiene una desventaja intrnseca, la capa de poltica es sensible a los cambios que se produzcan en la capa de utilidad. La dependencia es transitiva: la capa que contiene la poltica depende de algo que depende de una capa de utilidad, por lo tanto la capa de ms alto nivel depende de la capa de ms bajo nivel.
Nivel de mayor abstraccin Nivel de mecanismo Nivel de utilidad Figura 10. Estructura de niveles incorrecta.
En la Figura 11 se presenta un modelo ms apropiado. Cada uno de los niveles de ms bajo nivel se representa por una clase abstracta, de forma que los propios niveles se representan por clases derivadas de estas clases abstractas. Las clases de alto nivel utilizan el nivel siguiente a travs de la interfaz abstracta. De esta manera, ninguno de los niveles depende del resto de niveles. En su lugar, los niveles dependen de sus clases abstractas. No slo se consigue eliminar la dependencia transitiva entre el nivel de mayor abstraccin y el nivel de utilidad, sino que adems desaparece la dependencia directa entre la capa de mayor nivel de abstraccin y la capa de mecanismo.
Nivel de mayor abstraccin Interfaz del mecanismo Abstracta
Nivel de mecanismo
Utilizando este modelo, el nivel que retiene la poltica del mismo no se ve afectado por los cambios en el nivel de mecanismo o en el nivel de utilidad, de modo que el nivel de mayor abstraccin puede ser reutilizado en cualquier contexto en el que se definan los
31
mdulos de bajo nivel que cumplan la interfaz de la capa de mecanismo. As, mediante la inversin de las dependencias, se ha creado una estructura que es ms flexible, resistente y mvil.
9.3 Ejemplo de aplicacin del principio de inversin de dependencias y de nivelado La inversin de dependencia puede aplicarse cada vez que una clase manda mensajes a otra. Como ejemplo, se va a considerar el caso de un botn que controla una bombilla.
Se tiene un objeto botn que controla un evento externo, el que un usuario lo haya pulsado. La bombilla se ve afectada por el comportamiento externo, si recibe el mensaje de encendido, ilumina algo, si recibe el mensaje de apagado, deja de emitir luz.
Encendido
Botn Botn
Apagado
Un primer diseo, no muy acertado, puede ser el que se muestra en la Figura 13, donde la clase botn depende directamente de la clase bombilla.
Botn
Bombilla
Una implementacin para el diseo de la Figura 13 se muestra en el Listado 14. Claramente se viola el principio de inversin de dependencias, debido a que la poltica de alto nivel de la aplicacin no se encuentra separada de los mdulos de bajo nivel. De esta forma la abstraccin de alto nivel depende de forma automtica de los mdulos de bajo nivel. En el cdigo se puede apreciar que el mdulo boton.cpp incluye el fichero bombilla.h, lo cual implica que la clase Boton debe modificarse, o al menos recompilarse, cada vez que se modifica la clase Bombilla, impidindose que se reutilice la clase Boton para controlar cualquier otro objeto. Para encontrar la poltica de alto nivel de la aplicacin deben fijarse las abstracciones que subyacen en la aplicacin, los elementos que permanecen invariantes cuando los detalles cambian. En el ejemplo que se est desarrollando, el botn y la bombilla, la abstraccin subyacente es la deteccin de una accin sobre el botn, y la transmisin de dicha accin al elemento destino. Sin embargo, los mecanismos utilizados para detectar la accin del usuario, o cul es el objeto destino son detalles irrelevantes. Para cumplir el principio de inversin de dependencias, se debe aislar la abstraccin de los detalles del problema, y hacer que los detalles dependan de las abstracciones.
32
----------------------------- bombilla.h --------------------------------class Bombilla { public: void Encendido(); void Apagado(); }; ------------------------------ boton.h ---------------------------------enum Estado {OFF, ON}; class Bombilla; class Boton { Bombilla *b1; Estado estado; public: Boton(Bombilla& b, Estado e):b1(&b){estado = e;} void Deteccion(); void CambiaEstado(); }; ----------------------------- bombilla.cpp -----------------------------#include <iostream.h> #include "bombilla.h" void Bombilla::Encendido() { cout << "Toy encendida" << endl; } void Bombilla::Apagado() { cout << "Toy apagada" << endl; } ------------------------------ boton.cpp -------------------------------#include "boton.h" #include "bombilla.h" #include <iostream.h> void Boton::Deteccion() { (estado==ON) ? b1->Encendido() : b1->Apagado(); } void Boton::CambiaEstado() { estado = (estado==ON) ? OFF : ON; } ----------------------------- prueba.cpp -------------------------------#include "boton.h" #include "bombilla.h"
void pulsar(Boton& b) { b.CambiaEstado(); } void main(void) { Bombilla bombi; Boton boton(bombi, OFF); boton.Deteccion(); pulsar(boton); boton.Deteccion(); }
En la Figura 14 se muestra un diseo ms correcto para el problema del botn y la bombilla, habindose separado la abstraccin de la clase Boton de sus detalles de implementacin. Una implementacin en C++ que se corresponde con este diseo se tiene en el Listado 15.
33
Botn Abstracta
ClienteBotn Abstracta
ImplementaBotn
Bombilla
---- ClienteBoton.h ---class ClienteBoton { public: virtual void Encendido() = 0; virtual void Apagado() = 0; }; ---- boton.h ---class ClienteBoton; class Boton { protected: ClienteBoton* cliente; public: Boton(ClienteBoton& b1):cliente(&b1){}; virtual void Deteccion() = 0; virtual void CambiaEstado() = 0; }; ----- bombilla.h ---#include "ClienteBoton.h" class Bombilla : public ClienteBoton { public: virtual void Encendido(); virtual void Apagado(); }; ---- ImplementaBoton.h ---#include "boton.h" enum Estado {OFF, ON}; class ImplementaBoton : public Boton { Estado estado; public: ImplementaBoton(ClienteBoton&, Estado); virtual Estado DevuelveEstado(); virtual void CambiaEstado(); virtual void Deteccion(); }; ---- bombilla.cpp ----#include <iostream.h> #include "bombilla.h"
void Bombilla::Encendido() { cout << "Toy encendida" << endl; } void Bombilla::Apagado() { cout << "Toy apagada" << endl; } ---- ImplementaBoton.cpp ----#include "ImplementaBoton.h" #include "ClienteBoton.h" ImplementaBoton::ImplementaBoton(ClienteBoton& b, Estado e): Boton(b) {estado = e;} void ImplementaBoton::CambiaEstado() { estado = (estado==ON) ? OFF : ON; } Estado ImplementaBoton::DevuelveEstado() { return estado; } void ImplementaBoton::Deteccion() { (estado == ON) ? cliente->Encendido() : cliente->Apagado(); } ---- prueba.cpp ---#include "ImplementaBoton.h" #include "bombilla.h"
void pulsar(ImplementaBoton& b) { b.CambiaEstado(); } void main(void) { Bombilla bombi; ImplementaBoton boton(bombi, OFF); boton.Deteccion(); pulsar(boton); boton.Deteccion(); }
34
10.1 Ejemplo del principio de separacin de la interfaz Supngase que se est desarrollando un sistema de seguridad, en el que existe una clase que es Puerta, que puede estar candada o descandada, y que es capaz de saber si est abierta o cerrada (Ver Listado 1).
class Puerta { public: virtual void Candada() = 0; virtual void Descandada() = 0; virtual int EstaAbierta() = 0; }; Listado 16. Clase Puerta.
La clase Puerta es una clase abstracta, por lo tanto los clientes de la misma podrn utilizar objetos que cumplan la interfaz de la clase Puerta, con total independencia de las implementaciones de las puertas especficas. Considrese ahora una puerta que cuando lleve abierta un determinado tiempo haga sonar una alarma, y cuya clase va a denominarse PuertaConAlarma. Para conseguir este objetivo, los objetos de la clase PuertaConAlarma deben comunicarse con los objetos de la clase Temporizador, la cual se muestra en el Listado 17.
class Temporizador { public: void Registro(int tiempo, ClienteTemporizador* cl); }; class ClienteTemporizador { public: virtual void TimeOut() = 0; };
35
Cuando un objeto Puerta quiera ser informado de que ha excedido el tiempo que le est permitido estar abierto, llama a la funcin Registro del Temporizador. Los argumentos de esta funcin son el lmite de tiempo y un puntero a un objeto ClienteTemporizador, cuya funcin TimeOut ser llamada cuando el tiempo expire. En la Figura 15 se muestra una posible solucin al problema que se est tratando.
Temporizador
ClienteTemporizador
Puerta
Se ha forzado a la clase Puerta, y por tanto a la clase PuertaConAlarma, a heredar de ClienteTemporizador. Esta solucin es problemtica, debido a que ahora Puerta depende de ClienteTemporizador, y no todas las puertas necesitan de este control de tiempo, de hecho la abstraccin original de Puerta no contemplaba en absoluto el tiempo. Segn este diseo, todas las puertas que se deriven de Puerta y que no tengan que contemplar el temporizador, debern implementar una funcin nula de TimeOut, adems las aplicaciones que usen las especializaciones de Puerta tendrn que incluir la definicin de la clase ClienteTemporizador, aunque no la use. Esto es lo que se conoce como contaminacin de la interfaz. La interfaz de la clase Puerta ha sido contaminada con una interfaz que no requiere, slo por el beneficio de una de sus subclases. La solucin a este problema viene por aplicar el principio de separacin de la interfaz en el diseo, como se puede apreciar en la. Aqu se ha hecho uso de la herencia mltiple, de forma que la clase PuertaConAlarma herede de la clase Puerta y de la clase ClienteTemporizador, de esta forma los clientes de las dos clases bases podrn recibir objetos de PuertaConAlarma, utilizando el mismo objeto a travs de interfaces separadas. Adems, las clases que se deriven de la clase Puerta, y que no necesiten el temporizador, no tendrn necesidad de implementar funciones nulas de TimeOut, ni las aplicaciones que las usen tendrn que incluir la especificacin de la clase ClienteTemporizador. En este caso el uso de la herencia mltiple se adapta perfectamente a la solucin del problema, aunque muchos autores recomiendan que no se haga uso de ella.
36
La separacin de las interfaces poda haberse llevado de otras formas, por ejemplo mediante el uso de delegacin, siguiendo patrn de adaptacin7 descrito en el GOF [Gamma95].
Puerta
ClienteTemporizador
El patrn de adaptacin (adapter pattern o wrapper pattern) convierte la interfaz de una clase en otra interfaz que el cliente espera. Permite que clases, que de otro modo no podran debido a interfaces incompatibles, trabajar juntas. 8 Un paquete es un mecanismo de propsito general para organizar elementos en grupos. Es posible incluir paquetes dentro de paquetes. Un sistema puede pensarse como un paquete de nivel superior, con todo el resto del sistema contenido en l [Rational97c].
37
38
Cuando se tiene que afrontar un proyecto de grandes dimensiones se buscan soluciones del tipo de dividir el entorno de desarrollo en paquetes. Los paquetes se convierten as en unidades de trabajo. Cuando un equipo encargado de un paquete lo tiene funcional, le asigna un nmero de versin y lo pone a disposicin del resto de equipos, a la vez que este equipo puede seguir modificndolo en su rea privada de trabajo. Cuando se realizan nuevas revisiones de un paquete, el resto de equipos deciden si adoptan la nueva revisin inmediatamente, o si por el contrario continan utilizando la revisin antigua. Este es un proceso de trabajo lgico y ampliamente difundido. Sin embargo, para que funcione se debe gestionar una estructura de dependencia de paquetes, en la cual no puede haber ciclos. Si hay ciclos en esta estructura de dependencia no se podr evitar lo que se conoce como sndrome de la maana siguiente. Los paquetes dependen unos de los otros. De forma especfica, una clase de un paquete puede incluir un fichero cabecera de una clase de otro paquete diferente. Esto se muestra en un diagrama de clases como una relacin de dependencia entre paquetes, Figura 17.
Paquete dependiente Paquete
En la Figura 18, se muestra un diagrama de paquetes. Se puede apreciar una estructura tpica de paquetes ensamblados en una aplicacin, esta estructura es un grafo. Los paquetes son los nodos, y las relaciones de dependencia los arcos. Como las relaciones de dependencia tienen direccin, se tiene una estructura de grado dirigido. Con independencia del paquete que se elija para iniciar el recorrido de las relaciones dependencias es imposible volver al mismo paquete. Esta estructura no tiene ciclos, por tanto es un grafo dirigido acclico.
Mi Aplicacin
Mis Tareas
39
Supngase que un cambio de requisitos fuerza a cambiar una clase en el paquete MisDialogos de forma que incluya una cabecera de clase de MiAplicacin, entonces se creara un ciclo como se muestra en la Figura 19.
Mi Aplicacin
Mis Tareas
Este ciclo crea algunos problemas inmediatos. El paquete MisTareas depende ahora de todos los paquetes del sistema, con lo que se hace muy difcil de desarrollar. El paquete MisDilogos sufre del mismo mal, as MiAplicacin, MisTareas y MisDilogos deben desarrollarse al mismo tiempo, volvindose a producir las interferencias. Es posible romper el ciclo, y volver a tener el grafo de dependencia como un DAG. La forma ms adecuada de hacerlo es aplicar el principio de inversin de dependencia. En el caso de la Figura 19 se puede crear una clase base que tenga la interfaz que el paquete MisDilogos necesita. Se coloca la clase base en el paquete MisDilogos y se hereda dentro del paquete MiAplicacin. Esto invierte la dependencia entre MisDilogos y MiAplicacin, rompiendo el ciclo, Figura 20.
MisDilogos
MiAplicacin
X
Antes Despus MisDilogos
MiAplicacin
Servidor X
40
15.1 Mtricas de estabilidad Robert C. Martin [Martin95] propone una serie de mtricas que permiten calcular la estabilidad posicional de un paquete. Estas mtricas son:
Acoplamientos Aferentes (Ca): Se define como el nmero de clases fuera del paquete que dependen de las clases contenidas en el paquete. Acoplamientos Eferentes (Ce): Se define como el nmero de clases dentro del paquete que dependen de clases externas al paquete. Inestabilidad (I): Esta mtrica est comprendida en el intervalo [0, 1], de forma que I=0 indica un paquete completamente estable, y por el contrario I=1 indica un paquete completamente inestable. La inestabilidad se define mediante la siguiente relacin: Ce I= Ca + Ce
41
Los trminos Ca y Ce pueden calcularse contando el nmero de clases exteriores al paquete que tienen dependencias con las clases internas al mismo. En el ejemplo que se presenta en la Figura 21, se va a proceder a calcular la estabilidad del paquete que se encuentra en el centro de la figura. Se tienen 4 clases externas al paquete que tienen dependencia con las clases internas (la clase A se deriva de la clase 1, la clase B contiene objetos de la clase 2, la clase C contiene objetos de la clase 2, y la clase E se deriva de la clase 1), por lo tanto se tiene que Ca=4. Por otra parte se tiene que existen 3 relaciones de las clases interiores del paquete que tienen como destino clases exteriores al paquete, es decir existen 3 dependencias de las clases interiores del paquete con clases exteriores (la clase 1 contiene objetos de la clase G, la clase 1 se deriva de la clase H, y la clase 2 tiene una relacin de asociacin con la clase I), por lo tanto Ce=3. As, I =3/7.
C A B D E
G H
Ca = 4 Ce = 3 I = 3/7
Cuando la inestabilidad, I, es 1 significa que el paquete ningn paquete depende del paquete que se est midiendo, y por el contrario este paquete si depende de otros paquetes, siendo un paquete inestable: es no responsable y dependiente. La falta de paquetes que dependan de l no le ofrece razones para no cambiar, y los paquetes de los que depende pueden darle amplias razones para cambiar. Por el contrario, cuando la inestabilidad es nula, I=0, significa que uno o varios paquetes dependen de l, pero l no depende de nadie. Es responsable e independiente, convirtindose en un paquete completamente estable. Los mdulos que dependen de l hacen fuerza para que sea difcil de cambiar, y como l no depende de otros paquetes no se ve forzado a cambiar.
42
El principio de las dependencias estables dice que la mtrica I de un paquete debe ser mayor que las mtricas I de los paquetes de los que l depende, esto es la mtrica I debe decrecer en la direccin de la dependencia. Se debe tener presente que no es deseable que todos los paquetes sean completamente estables, porque el sistema no podra modificarse.
16.1 Mtricas de abstraccin Se puede establecer una mtrica de la abstraccin de un paquete, que se va a representar por A. Este valor mide la proporcin de clases abstractas dentro de un paquete con relacin al nmero total de clases dentro del paquete.
A= Clases Abstractas Clases Totales
La mtrica A est definida en el intervalo [0, 1]. El valor A=0 implica que el paquete no tiene clases abstractas. El valor A=1 indica que el paquete slo contiene clases abstractas. Una vez definidas las mtricas I y A, se puede establecer una relacin entre la estabilidad y la abstraccin. Se puede representar en unos ejes coordenados esta relacin, colocando en el eje de ordenadas la medida de la abstraccin y en el eje de abscisas la medida de la estabilidad. Si se unen los dos puntos que representan los paquetes ms interesantes se obtendr que los paquetes que son completamente estables y abstractos se concentrarn en el punto (0, 1), mientras que los paquetes que son completamente inestables y concretos se concentrarn en el punto (1, 0), como se refleja en la Figura 22.
(0, 1)
Abstraccin
Se pr cue in nc ci ia pa l
(1, 0)
Inestabilidad 1
Figura 22. Secuencia principal.
43
Se debe tener presente que todos los paquetes no se encuentran localizados en una de estas dos posiciones. Los paquetes pueden tener diferentes grados de abstraccin y estabilidad. Por ejemplo, es muy frecuente que una clase abstracta se derive de otra clase abstracta, siendo la derivacin una abstraccin que conlleva una dependencia, decreciendo as su estabilidad. Dado que no se puede forzar a que todos los paquetes se encuentren localizados en los puntos (0, 1) o (1, 0), se debe admitir la existencia de puntos dentro del grfico Abstraccin/Estabilidad que definen posiciones razonables para los paquetes. Se puede establecer donde se encuentran localizados estos puntos definiendo las reas donde los paquetes no pueden encontrarse, es decir, encontrando las reas de exclusin. As, un paquete que se encuentre en el rea cercana al punto A=0 y I=0 ser un paquete altamente estable y concreto. Esto no es deseable porque es un paquete rgido. No puede ser extendido porque no es abstracto, y adems ser difcil de cambiar debido a su estabilidad. De esto se deduce que un paquete bien diseado no debe encontrarse en el rea cercana al punto (0, 0), as el rea cercana a este punto es una zona de exclusin. Un paquete con A=1 y I=1 no es deseable, quizs imposible, porque es completamente abstracto y nadie depende de l. Es rgido porque no es extensible. La zona (1, 1) constituye otra zona de exclusin. La lnea que une los puntos (0, 1) y (1, 0) representa los paquetes cuya abstraccin se encuentra equilibrada con su estabilidad, denominndose a esta lnea secuencia principal.
(0, 1) 1
Abstraccin
Se p cu ( 0 r in e n c de , 0) c ex Z o ip i a cl n a al u
s i n
(1, 0)
Inestabilidad 1
Figura 23. Zonas de exclusin.
Un paquete que se encuentre en la secuencia principal no es demasiado abstracto para su estabilidad, ni demasiado inestable para su abstraccin. Tiene un nmero adecuado de clases concretas y abstractas en proporcin a su acoplamiento aferente y a su acoplamiento eferente. Aunque claramente lo ms deseable es que un paquete se encuentre en uno de los puntos extremos de la secuencia principal. De acuerdo con lo expresado hasta ahora se puede enunciar una nueva mtrica que mida la distancia de un paquete a la secuencia principal. Esta mtrica se representar por D y ser la distancia perpendicular de un paquete a la secuencia principal, y viene definida por la siguiente relacin:
D=
A+I -1 2
Esta mtrica se encuentra definida en el intervalo [0, ~0,707]. Se puede normalizar esta mtrica al intervalo [0, 1] utilizando una forma simplificada que se denominar D y que vendr representada por la relacin siguiente:
D = A + I - 1
44
17. Bibliografa
[Booch96] Booch, Grady. Anlisis y Diseo Orientado a Objetos con Aplicaciones. 2 Ed. Addison-Wesley/Daz de Santos. 1996. [Booch96b] Booch, Grady. Object Solutions. Addison-Wesley. 1996. [Business92a] Definition of the Development Process. Esprit project 5311 (Business Class), Report BC.R.TS.W3.T31. Release 2. 1992. [Devis97] Devis Botella, Ricardo. C++. STL Plantillas Excepciones Roles y Objetos. Paraninfo. 1997. [Freeman87b] Freeman, P. A Perspective on Reusability. IEEE Tutorial: Software Reusability (ed. P. Freeman), IEEE Computer Society Press, pp. 2-8. 1987. [Gamma95] Gamma, Erich, Helm, Richard, Johnson, Ralph, Vlissides, John. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley. 1995. [Garca95] Garca Pealvo, Francisco Jos. Curso de C++. Revisin 2. ALI CyL, Valladolid. 1995. [Graham96] Graham, Ian. Mtodos Orientados a Objetos. 2 Edicin. AddisonWesley/Daz de Santos. 1996. [Jacobson92] Jacobson, Ivar, Christerson, M., Jonsson, P., Overgaard, G. ObjectOriented Software Engineering: A Use Case Driven Approach. Addison-Wesley. 1992. [Joyanes94] Joyanes Aguilar, Luis. C++ a su Alcance: Un Enfoque Orientado a Objetos. McGraw Hill. 1994. [Joyanes96] Joyanes Aguilar, Luis. Programacin Orientada a Objetos. Conceptos, Modelado, Diseo y Codificacin en C++. McGraw-Hill. 1996. [Karlsson95] Karlsson, Even-Andr. Software Reuse. A Holistic Approach. John Wiley & Sons Ltd. 1995. [Kernighan91] Kernighan, Brian W., Ritchie, Dennis M. El Lenguaje de Programacin C. 2 Edicin. Prentice Hall. 1991. [Krueger92] Krueger, Charles W. Software Reuse. ACM Computing Surveys. Vol. 24, N 2, pp. 131-183. June 1992. [Lea96] Lea, Doug. A Position Statement on Object Oriented Design. ACM Computing Surveys, 28A(4), December 96. http://g.oswego.edu/dl/html/acmPos.html. 1996. [Liskov88] Liskov, Barbara. Data Abstraction and Hierarchy. SIGPLAN Notices, Vol. 23(5), May 1988. [Marqus95] Marqus Corral, Jos Manuel. Jerarquas de Herencia en el Diseo de Software Orientado al Objeto. Tesis Doctoral, Universidad de Valladolid, 1995. [Marqus96] Marqus Corral, Jos Manuel. Reutilizacin y Diseo Orientado al Objeto: Informe de Resultados y Propuestas de Investigacin. En las actas de las I Jornadas de Trabajo en Ingeniera del Software, Sevilla, 14-15 de Noviembre de 1996. [Martin92a] Martin, Robert C. Abstract Classes and Pure Virtual Functions. C++ Report. June/July 1992. [Martin93] Martin, Robert C. OO(A, D, P (C++)). C++ Report. March 1993. [Martin95] Martin, Robert C.OO Design Quality Metrics. An Analysis of Dependencies. ROAD. September - October, 1995. [Martin96a] Martin, Robert C. The Open Closed Principle. C++ Report, January 1996. [Martin96b] Martin, Robert C. The Liskov Substitution Principle. C++ Report, March 1996.
45
[Martin96c] Martin, Robert C. The Dependency Inversion Principle. C++ Report. May 1996. [Martin96d] Martin, Robert C. The Interface Segregation Principle. C++ Report. August 1996. [Martin96e] Martin, Robert C. Granularity. C++ Report. November-December 1996. [Martin97a] Martin, Robert C. Principles of OOD. OMA. 1997. [Martin97b] Martin, Robert C. Stability. C++ Report. February 1997. [McIlroy76] McIlroy, M. D. Mass-produced Software Components. In J.M. Buxton, P. Naur, and B. Randell, editors, Software Engineering Concepts and Techniques; 1968 NATO Conference on Software Engineering, pp. 88-98. Van Nostrand Reinhold, 1976. [Meyer88] Meyer, Bertrand. Object Oriented Software Construction. Prentice Hall, 1988. [Mili95] Mili, Hafedh, Mili, Fatma, Mili, Ali. Reusing Software: Issues and Research Directions. IEEE Transactions on Software Engineering. Vol. 21. N 6, pp. 528-562. June 1995. [Monarchi92] Monarchi, David E., Puhr, Gretchen I. A Research Typology for Object-Oriented Analysis and Design. Communicatios of the ACM. Vol. 35, N 9, pp. 35-47. September 1992. [Ohnjec97] Ohnjec, Viktor. Converging on OOAD Agreement. Applications Development Trends, Vol. 4, N2, Feb. 1997, http://www.admag.com/feb97/fe203.htm. 1997. [Piattini96] Piattini Velthuis, Mario Gerardo. Tecnologa Orientada al Objeto. En las notas del curso Tecnologa Orientada al Objeto. ALI-CyL, Valladolid, Noviembre 1996. [Prieto-Daz91] Prieto-Daz, Rubn, Arango, Guillermo. Domain Analysis and Software Systems Modeling. IEEE Computer Society Press. 1991. [Rational97a] Rational Software Corporation. Unified Modeling Language. UML Summary. Rational Software Corporation. Version 1.0. 13 January 1997. [Rational97b] Rational Software Corporation. Unified Modeling Language. UML Semantics. Rational Software Corporation. Version 1.0. 13 January 1997. [Rational97c] Rational Software Corporation. Unified Modeling Language. UML Semantics Appendix M1 UML Glossary. Rational Software Corporation. Version 1.0. 13 January 1997. [Rational97d] Rational Software Corporation. Unified Modeling Language. Notation Guide. Rational Software Corporation. Version 1.0. 13 January 1997. [Rumbaugh96] Rumbaugh, James, Blaha, Michael, Premerlani, William, Eddy, Frederick, Lorensen, William. Modelado y Diseo Orientados a Objetos. Metodologa OMT. Prentice Hall. 1996. [Sparks96] Sparks, Steve, Benner, Kevin, Faris, Chris. Managing Object Oriented Framework Reuse . IEEE Computer. September 1996, pp. 52-61. 1996
46