Programación 3

Universidad de Alicante, 2023–2024

Práctica 5

Peso relativo de está práctica en la nota de prácticas: 15%

Herencia de interfaz

La herencia de interfaz nos permite modelar los sistemas pensando en la responsabilidad que deben cumplir los objetos en lugar de en cómo están implementados. Este hecho nos permite construir modelos complejos escalables y poco acoplados de manera natural.

En esta práctica trabajaremos sobre un sistema para el procesamiento de textos que hemos diseñado utilizando de manera exhaustiva la herencia de interfaz, empleando sólo la herencia de implementación en las situaciones en que realmente es conveniente.

La librería que hemos creado permite representar documentos que luego podrán ser exportados a distintos formatos. En la implementación actual se permite exportar a HTML y a markdown. Hay cientos de fuentes con información de estos formatos. Esta página y esta otra (en inglés) contienen información sencilla y completa sobre ambos formatos. Como nota al margen, la mayoría de las páginas del sitio web de Programación 3 están escritas en markdown y exportadas a HTML.

Proporcionamos el código de esta librería en el proyecto base adjunto.

NOTA: En el diseño sólo se han considerado asociaciones en lugar de composiciones para simplificar el código de la práctica y evitar tener que realizar copias defensivas.

Estructura de un documento

Los documentos HTML y markdown se estructuran en elementos tipo bloque (block) o tipo “en línea” (inline). Los elementos tipo bloque ocupan todo el ancho de la página aunque el contenido ocupe menos. Cuando ponemos dos contenidos tipo bloque, éstos se apilan verticalmente unos encima de otros. Los elementos tipo “en línea” se ajustan al contenido, y suelen aparecer unos al lado de los otros en la misma línea. Típicamente estos elementos se incrustan en un párrafo. En esta página web hemos rodeado los cuadros de los elementos tipo bloque con líneas discontinuas verdes, y los elementos tipo “en línea” con líneas de puntos de color rojo.

¿Qué tengo que hacer?

Tu trabajo en esta práctica consiste en añadir nuevas funcionalidades a esta librería. Leer detenidamente la sección siguiente (Descripción del diseño) y entender cómo funciona esta librería es clave para poder implementar con éxito lo que te pedimos en la sección Nuevas funcionalidades.

Descripción del diseño

Las distintos interfaces y clases de la librería están repartidos en diferentes paquetes. En los diagramas UML, un paquete se representa mediante una caja con una pestaña superior que contiene el nombre del paquete. Fíjate en cada diagrama para saber donde encontrar o colocar cada interfaz/clase.

Bloques y líneas

Durante la práctica utilizaremos diversas estructuras basadas en herencia de implementación y de interfaz que nos ayudarán a ahorrar código a la vez que nos permitirán ampliar de manera cómoda la aplicación.

En función de lo que hemos descrito anteriormente, podemos decir que un documento está compuesto por elementos de tipo bloque y elementos de tipo inline como mostramos en el siguiente diagrama. Como al final un documento será una agregación de muchos elementos de ambos tipos, inline (IParagraphContent) y bloque (IBlock), hacemos que ambos hereden de la interfaz IDocumentElement.

Figura 1. Elementos de un documento.
Figura 1. Elementos de un documento.

En los siguientes apartados describimos poco a poco el modelo. Es interesante ver cómo podemos aislar partes de éste sin mostrar el modelo completo porque con los interfaces abstraemos responsabilidades y comportamientos. Ésta es una de las ventajas de la programación orientada a objetos.

Estructuras compuestas anidadas

En el sistema que hemos diseñado nos encontramos en varias ocasiones con elementos de un tipo dado que contienen otros elementos que a su vez pueden ser de ese mismo tipo. Esto nos ocurre con los bloques. Un bloque puede contener elementos de tipo inline pero también contener otros elementos también de tipo bloque, de manera que se pueden crear estructuras anidadas. En el ejemplo de bloques y líneas puedes ver cómo hay elementos bloque (los rodeados en verde), dentro de otros bloques.

Más adelante veremos cómo tenemos otros elementos compuestos similares como listas con viñetas. El propio documento es una agregación de bloques y de elementos inline.

Para no tener que programar varias veces esta estructura, hemos creado una clase abstracta AbstractComposite cuya responsabilidad es contener listas de elementos IDocumentElement. Para poder crear estructuras anidadas hacemos que AbstractComposite implemente la interfaz IDocumentElement.

Así, podremos meter una AbstractComposite dentro de otra, y hacer estructuras como ésta:

  • AbstractComposite
    • Bloque
      • Inline
      • Inline
    • Bloque
    • AbstractComposite
      • Bloque
        • Inline
      • Bloque
        • Inline
      • AbstractComposite
        • Bloque
        • Inline
Figura 2. Estructuras compuestas anidadas
Figura 2. Estructuras compuestas anidadas

Vemos que el método addItem es protegido porque será usado sólo por las clases que lo hereden, de forma que podremos restringir el tipo de elementos que añadamos.

(Para más información sobre métodos que no se comentan en este enunciado, consulta el código fuente de la librería que proporcionamos en el proyecto base. Los métodos export…() los entenderás cuando leas la sección Exportación).

Documento

Un documento (Document), al contener una estructura compuesta, posiblemente anidada, de bloques y líneas, hemos hecho que herede de AbstractComposite. En un documento, todos los elementos deben estar dentro de al menos un bloque, por ello sólo tenemos un método add(IBlock). Este método, en su implementación, invocará al addItem de la clase base AbstractComposite (esto lo podemos hacer porque IBlock hereda de IDocumentElement).

Figura 3. Documento como elemento compuesto.
Figura 3. Documento como elemento compuesto.

Véase cómo con este diseño podríamos en el futuro crear documentos embebidos dentro de otros documentos pues Document implementa indirectamente la interfaz IDocumentElement.

Párrafos

Los párrafos Paragraph son bloques que contienen otros bloques o elementos inline. Esta funcionalidad la conseguimos simplemente haciendo que Paragraph implemente IBlock y herede de AbstractComposite.

Figura 4. Párrafos.
Figura 4. Párrafos.

No hemos puesto ningún método para añadir contenido en Paragraph. El contenido del párrafo se inicializará con los parámetros que recibe en el constructor. Hemos hecho dos constructores sobrecargados, uno con una lista de IParagraphContent, y otro poliádico por facilitar luego la instanciación de párrafos simples. Ambos constructores añaden estos elementos al párrafo usando el método addItem heredado de AbstractComposite.

Véase cómo se puede añadir un párrafo a un documento simplemente contando con que un párrafo se comporta como un bloque, o lo que es lo mismo, que Paragraph implementa IBlock.

Listas con viñetas

Las listas con viñetas son estructuras como ésta:

  • Esto es un elemento de la lista con viñetas no ordenada.
  • Éste es otro elemento.

o como ésta:

  1. Primer elemento de una lista numerada u ordenada.
  2. Segundo elemento.

Las listas con viñetas son elementos tipo bloque que contienen a su vez más elementos tipo bloque. Por ello hemos usado el mismo principio que para diseñar los párrafos, hacemos que una lista con viñetas se comporte como un bloque y use herencia de implementación para aprovechar el código de AbstractComposite.

Figura 5. Listas.
Figura 5. Listas.

Podemos ver los dos tipos de listas con viñetas disponibles, las ordenadas y las no ordenadas.

Finalmente, de forma similar a Paragraph, sólo dejamos añadir elementos en el constructor.

Encabezamientos

Los encabezamientos, o títulos de sección, son textos que se comportan como bloques. Según la jerarquía del encabezamiento tendrán un nivel (1, 2, etc.) para disponer de “Encabezamiento 1”, “Encabezamiento 2”, etc.

Como es de suponer, en el documento nos encontraremos varios tipos distintos de objetos textuales. Por ello hemos creado una clase AbstractTextContent cuya responsabilidad es la de encapsular un texto, y en el futuro, añadir alguna funcionalidad más (como veremos en las tareas que tienes que realizar después).

Como los encabezamientos deben comportarse como bloques y aprovechar la implementación de AbstractTextContent utilizamos el diseño que mostramos en el siguiente diagrama.

Figura 6. Encabezamientos.
Figura 6. Encabezamientos. (nota: AbstractTextContent no tiene método export(), es una errata del diagrama)

Podemos ver cómo podríamos crear un documento sólo con encabezamientos añadiendo al documento bloques de tipo Heading.

Se ha considerado controlar la excepción EditorException de que una cabecera Heading no se pueda crear con un nivel menor de uno.

Bloques de código

Otro tipo de bloque de texto similar al de los encabezamientos es el que nos permite mostrar código fuente incrustado en un documento. Un bloque de código es un texto (AbstractTextContent) que se comporta como un IBlock y que opcionalmente tiene información sobre el lenguaje de programación en que está escrito el código que contiene.

Figura 7. Bloques de código.
Figura 7. Bloques de código.

Líneas de separación

El último tipo de elemento tipo bloque que incluimos en la librería es el de los separadores horizontales en forma de línea como ésta:


Hemos creado una interfaz IMark para representar todos los bloques que no tienen contenido adicional (como texto o imágenes). La clase que representa estos separadores horizontales es HorizontalRule.

Figura 8. Líneas de separación.
Figura 8. Líneas de separación.

Imágenes

El primero y más sencillo de los elementos tipo inline es la imagen, como se puede ver en el diagrama de clases UML a continuación.

Figura 9. Imágenes.
Figura 9. Imágenes.

Hemos dejado en el diagrama los párrafos y el documento para que se vea cómo se podría crear un documento sólo con imágenes. Para ello crearíamos una o varias imágenes (que implementan IParagraphContent), y construiríamos uno o varios párrafos pasándoles como parámetro esas imágenes. Finalmente, esos párrafos los añadiríamos a un Document.

Textos

Nos vamos a encontrar dos tipos de textos. Aquéllos que se encuentran dentro de un bloque párrafo y que por tanto se deben comportar como texto inline(IParagraphContent) y otros huérfanos de párrafo que actúan por sí mismos como bloques (IBlock), Estos últimos los usaremos en casos como los items de las listas con viñetas (AbstractListBlock heredado por OrderedListBlock y UnorderedListBlock).

Usamos la capacidad de aplicar herencia múltiple de interfaz para modelar este requisito en cualquier elemento de texto a través de la interfaz IText.

Figura 10. Textos.
Figura 10. Textos.

Mostramos en el UML los párrafos y las listas con viñetas para que se vea cómo podríamos insertar texto en ambos. Si necesitamos insertar un texto dentro de un párrafo, crearemos un objeto Text y lo pasaremos al constructor de Paragraph, que al recibir IParagraphContent admite un Text que implementa esa interfaz.

Si queremos crear una lista con viñetas a la que añadir un texto, crearemos un Text que proporcionaremos en la lista de IBlock que enviamos al constructor de AbstractListBlock, pues Text también implementa IBlock.

Código en línea

Un texto especial es el código en línea InlineCode. Su comportamiento e implementación son prácticamente iguales a Text. No hemos hecho que herede de éste porque en el futuro podríamos añadir funcionalidades a Text que no queremos que tenga InlineCode.

Figura 11. Código 'en línea'
Figura 11. Código ‘en línea’.

Mostramos el diagrama completo a continuación de las clases e interfaces que hemos introducido anteriormente.

Figura 12. Modelo.
Figura 12. Elementos de un documento.

Haz una pausa en tu lectura de este enunciado. Antes de continuar, asegúrate de que has entendido cuáles son los diferentes tipos de elementos de que se compone un documento y como se relacionan entre sí.


Exportación

Para exportar el documento a diversos formatos podríamos añadir un método para exportar cada uno de los formatos en cada clase (exportHTML(), exportMarkdown(),…). Sin embargo este enfoque tiene, entre otros, un problema importante: nos obliga a modificar las clases para cada formato nuevo.

Para evitar este problema, y permitir ampliar el número de formatos de exportación sin necesidad de modificar las clases del modelo, creamos dos interfaces: IExportable e IExporter.

Cada clase que queramos que se pueda exportar deberá implementar la interfaz IExportable. El código del método export sólo delega la responsabilidad de exportar al IExporter que recibe como parámetro con un código similar a éste:

class X implements IExportable {
    ····
    public void export(IExporter e) {
        e.export(this);
    }
    ····
}

Para que se sepa exportar la clase X, en el interfaz IExporter se deberá añadir un método String export(X exportable). Este procedimiento lo repetiremos para cada clase que se quiera exportar.

Para exportar a diferentes formatos sólo tendremos que crear clases concretas que implementen IExporter.

Figura 13. IExporter e IExportable
Figura 13. IExporter e IExportable

En el diagrama de clases que mostramos arriba, vemos cómo hemos hecho que Document implemente la interfaz IExportable para que el documento completo se pueda exportar. Para forzar que todos los elementos de la jerarquía implementen el comportamiento de ser exportados, hacemos que IDocumentElement herede la interfaz IExportable. De esta forma, todas las clases del modelo tendrán al final un método export como el que hemos descrito arriba. En el diagrama anterior hemos añadido Image y Paragraph como ejemplo, pero como puedes comprobar en todos los diagramas ya mostrados, todas las clases sobrescriben el método export porque de manera indirecta les llega la obligación de implementarlo desde IExportable.

Para cada formato que queramos exportar creamos una clase que implementa IExporter. Así, para esta práctica hemos creado dos clases, HTMLExporter y MarkdownExporter. Puedes ver el código fuente en el proyecto Eclipse adjunto para ver cómo se exporta cada tipo de elemento de un documento.

Comportamientos adicionales

Es habitual tener que añadir más capacidades a las clases que tenemos en la jerarquía de clases actual sin tener que cambiarlas. Esto es especialmente necesario cuando las clases a las que queremos añadir comportamientos están en una librería que no es nuestra y no podemos modificar.

Negritas y cursivas

Para poder añadir nuevas características a las clases, como la adición de hipervínculos, negritas y cursivas, para cada nuevo comportamiento añadimos una nueva clase. Para añadir cursiva y negrita hemos creado las clases ItalicsTextDecorator y BoldTextDecorator respectivamente.

Figura 14. Decoradores de texto.
Figura 14. Decoradores de texto.

Un AbstractDecorator es una clase que contiene un elemento (decoratedElement) al que añadirá una característica adicional a través de una subclase. Un AbstractTextDecorator es simplemente un AbstractDecorator que se comportará como un IText. Así, en todos los sitios donde podamos usar un IText podremos sustituirlo por cualquier clase que herede de AbstractTextDecorator. De esta forma, cuando queramos poner una negrita, usaremos en lugar de Text un BoldTextDecorator que habremos construido con un código como éste:

IText element = new BoldTextDecorator(new Text("Texto"));

Si quisiéramos crear un texto en negrita y en cursiva sólo rodeamos la negrita con una cursiva:

IText element = new ItalicsTextDecorator(new BoldTextDecorator(new Text("Texto")));

Véase cómo estos nuevos elementos de la jerarquía, al implementar indirectamente IExportable obligan a que las nuevas clases sobrecarguen el método export y que IExporter disponga de métodos para tratarlas (en el diagrama hemos omitido el resto de métodos de IExporter para facilitar la lectura).

Hipervínculos

Algo similar haremos para añadir enlaces tanto a textos como a imágenes.

Figura 15. Hipervínculos como decoradores.
Figura 15. Hipervínculos como decoradores.

Un LinkParagraphContentDecorator, a través de AbstractParagraphContentDecorator, es un IParagraphContent.
Si quisiéramos sustituir una image por otra que tenga un hipervínculo haremos:

IParagraphContent imageWithLink = new LinkParagraphContentDecorator(new Image("logo_ua.png", "UA Logo"), "https://www.ua.es/");

Mostramos las clases e interfaces introducidos en los dos diagramas de clases mostrados en las Figuras 14 y 15 en éste:

Figura 16. Decoradores.
Figura 16. Decoradores.

Tareas a realizar

Hasta aquí todo el código del modelo y de los exportadores que hemos descrito y que se encuentran implementados en el proyecto base. Describimos a partir de aquí el trabajo que debes realizar.

Nuevas funcionalidades

Usando como ejemplo funcionalidades similares que ya están diseñadas debes extender la jerarquía de clases con tres nuevas clases que deberán heredar y/o implementar lo que sea necesario para que pasen los tests unitarios que se incluyen en el proyecto de partida. Ten en cuenta que cada vez que añadas una nueva clase al modelo deberás añadir ese componente a la interfaz IExporter con los cambios que eso generará en las clases descendientes.

  • Actualmente tenemos una clase HorizontalRule que baja de línea y dibuja una línea horizontal. Se debe crear una clase BreakLine que sólo provoca un salto de línea. En markdown ese salto se codifica simplemente con un doble \n, es decir \n\n. En HTML se utiliza el elemento <br>. Puedes consultar más información en esta página. Como esta clase se podrá incluir como bloque aislado pero también dentro de un párrafo, deberá implementar adicionalmente la interfaz IParagraphContent.
  • El sistema incluye formas de especificar negritas y cursivas con las clases BoldTextDecorator e ItalicsTextDecorator. Debes añadir la clase StrikeThroughDecorator para texto tachado. En markdown se debe rodear el texto a tachar entre “~~” (por ejemplo “~~esto está tachado~~” aparecería como esto está tachado). En HTML se debe incrustar el texto en un elemento “<del>” (por ejemplo “<del>esto está tachado</del>”). Puedes consultar más información en esta página.
  • Finalmente debes añadir la clase Quote para escribir un párrafo de cita como puedes ver en esta página. En markdown sólo debes prefijar el contenido con un carácter ‘>’. En HTML se incluye el contenido en un elemento “<blockquote>” (véase esta página). Puedes considerar que un párrafo de cita es un párrafo con esta funcionalidad enriquecida usando herencia para ello. Debes sobrecargar los dos constructores heredados de Paragraph. Ten en cuenta que hacer que Quote herede de Paragraph no es que lo decore sino que se exporta de forma distinta. Para ello deberás sobrescribir el método export y añadir el método export(Quote) en IExporter. En la exportación de HTML deberás cambiar la etiqueta <p> que se usa en párrafo por <blockquote>.

Implementación de una clase anónima

En esta parte debes hacer que las clases Image y AbstractTextContent implementen la interfaz ITextCaseModifiable. Es conveniente que implementes el método changeCase en AbstractTextContent y no en sus subclases para evitar repetir el mismo código varias veces.

El resultado de la operación changeCase modificará el atributo text en el caso de AbstractTextContent con el código this.text = modifier.changeCase(this.text);. En el caso de Image, modificará el atributo alt con el código this.alt = modifier.changeCase(this.alt);.

Figura 17. Modificadores de texto.
Figura 17. Modificadores de texto.

Para ponerlo en práctica deberás completar la clase TextsToUpperCase de forma que el método createTextModifier() cree y devuelva un objeto de clase anónima que implemente la interfaz ITextCaseModifier y pase un texto a mayúsculas (se comprobará que efectivamente sea anómima y no se cree una clase con nombre). Puedes usar el método toUpperCase de la clase String para realizar la transformación.

Uso de la librería

Haciendo uso de la jerarquía de clases ya completada debes rellenar el código de la clase es.ua.dlsi.prog3.p5.client.ExampleDocumentCreator para que retorne un documento que genere los ficheros example_document.md y example_document.html que se incluyen en los tests unitarios de forma que la prueba unitaria ExampleDocumentCreatorTest no falle.

Para poder realizar esta parte de la práctica deberás entender bien el significado de los interfaces y las clases abstractas, y por qué se ha usado herencia de implementación o herencia de interfaz.

Puedes consultar un ejemplo de creación de otra estructura de objetos similar en el en el método setUp() del test unitario es.ua.dlsi.prog3.p5.model.DocumentTest que proporcionamos en el proyecto base.

Cuando uses el constructor de Heading deberás capturar la excepción EditorException y relanzarla como excepción no verificada, pues estamos nosotros creando las clases y no debería lanzar excepción nunca porque estamos creando encabezamientos correctos.

Pruebas unitarias

Proporcionamos pruebas unitarias en la carpeta test/ del proyecto base que comprueban el adecuado comportamiento de las clases. Es importante que entiendas qué se prueba y cómo se hace.

Documentación

No es necesario documentar esta práctica

Puedes documentar el código que escribas, de hecho te lo recomendamos, pero no se evaluará.

Requisitos mínimos para evaluar la práctica

  • La práctica debe poder ejecutarse sin errores de compilación.
  • Ninguna operación debe emitir ningún tipo de comentario o mensaje por salida estándar, a menos que se indique lo contrario. Evita también los mensajes por la salida de error.
  • Se debe respetar de manera estricta el formato del nombre de todas las propiedades de las clases, tanto en cuanto a ámbito de visibilidad como en cuanto a tipo y forma de escritura. En particular se debe respetar escrupulosamente la distinción entre atributos de clase y de instancia, así como las mayúsculas y minúsculas en los identificadores.

Entrega de la práctica

La práctica se entrega durante el control en el servidor de prácticas del DLSI.

Debes subir allí un archivo comprimido con tu código fuente (sólo archivos .java). En un terminal, sitúate en el directorio ‘src’ de tu proyecto Eclipse e introduce la orden

tar czvf prog3-p5.tgz *

Esto comprimirá todo el código que hay en src/, incluyendo el de aquellas clases que ya se daban implementadas. Esto es correcto y debes entregarlo así.

Sube este fichero prog3-p5.tgz al servidor de prácticas. Sigue las instrucciones de la página para entrar como usuario y subir tu trabajo.

Evaluación 

La corrección de la práctica es automática. Esto significa que se deben respetar estrictamente los formatos de entrada y salida especificados en los enunciados, así como la interfaz pública de las clases, tanto en la signatura de los métodos (nombre del método, número, tipo y orden de los argumentos de entrada y el tipo devuelto) como en el funcionamiento de éstos. Así, por ejemplo, el método Clase(int,int) debe tener exactamente dos argumentos de tipo int.

Tienes más información sobre el sistema de evaluación de prácticas en la ficha de la asignatura.

Además de la corrección automática, se va a utilizar una aplicación detectora de plagios.

Se indica a continuación la normativa aplicable de la Escuela Politécnica Superior de la Universidad de Alicante en caso de plagio:


“Los trabajos teórico/prácticos realizados han de ser originales. La detección de copia o plagio supondrá la calificación de”0" en la prueba correspondiente. Se informará la dirección de Departamento y de la EPS sobre esta incidencia. La reiteración en la conducta en esta u otra asignatura conllevará la notificación al vicerrectorado correspondiente de las faltas cometidas para que estudien el caso y sancionen según la legislación vigente".


Aclaraciones

  • Aunque no se recomienda, se pueden añadir los atributos y métodos privados que se considere oportuno a las clases. No obstante, eso no exime de implementar TODOS los métodos presentes en el enunciado, ni de asegurarse de que funcionan tal y como se espera, incluso si no se utilizan nunca en la implementación de la práctica. 
  • Cualquier aclaración adicional aparecerá en este enunciado.
  • Si quieres saber más, la práctica se ha diseñado empleando patrones de diseño, en concreto los patrones Composite, Visitor y Decorator. El diseño con patrones no se estudia en esta asignatura sino en cursos posteriores.