En esta sección, vamos a llevar a cabo la implementación del proyecto final utilizando los conocimientos adquiridos a lo largo del curso. Este proyecto integrará varios aspectos de Groovy, desde la sintaxis básica hasta características avanzadas y prácticas recomendadas.

Objetivos del Proyecto

  1. Desarrollar una aplicación completa que utilice Groovy para resolver un problema específico.
  2. Aplicar conceptos de programación orientada a objetos, incluyendo clases, herencia, y polimorfismo.
  3. Utilizar colecciones y closures para manejar datos de manera eficiente.
  4. Implementar características avanzadas como metaprogramación y transformaciones AST.
  5. Integrar Groovy con Java y otras tecnologías como XML, JSON, y bases de datos.
  6. Desarrollar pruebas unitarias y realizar depuración del código.
  7. Utilizar herramientas del ecosistema Groovy como Gradle y Spock.

Descripción del Proyecto

Vamos a desarrollar una aplicación de gestión de tareas (To-Do List) que permita a los usuarios:

  • Crear, leer, actualizar y eliminar tareas.
  • Categorizar tareas.
  • Marcar tareas como completadas.
  • Guardar y cargar tareas desde un archivo JSON.
  • Interactuar con una base de datos para persistencia de datos.

Estructura del Proyecto

El proyecto se dividirá en varios módulos:

  1. Modelo de Datos: Definición de clases y estructuras de datos.
  2. Persistencia: Manejo de almacenamiento y recuperación de datos.
  3. Lógica de Negocio: Implementación de la lógica principal de la aplicación.
  4. Interfaz de Usuario: Interacción con el usuario a través de la consola.
  5. Pruebas: Desarrollo de pruebas unitarias y de integración.

Paso 1: Modelo de Datos

Definición de Clases

Vamos a definir las clases principales que representarán las tareas y las categorías.

class Task {
    String id
    String title
    String description
    boolean completed
    Category category

    String toString() {
        return "${completed ? '[x]' : '[ ]'} ${title} - ${description} (Category: ${category?.name})"
    }
}

class Category {
    String id
    String name

    String toString() {
        return name
    }
}

Explicación del Código

  • Task: Representa una tarea con un id, title, description, completed (booleano) y una category.
  • Category: Representa una categoría con un id y name.
  • toString(): Método sobrescrito para proporcionar una representación legible de las tareas y categorías.

Paso 2: Persistencia

Guardar y Cargar Tareas desde JSON

Vamos a utilizar la biblioteca groovy.json.JsonSlurper y groovy.json.JsonOutput para manejar la persistencia de datos.

import groovy.json.JsonSlurper
import groovy.json.JsonOutput

class TaskRepository {
    List<Task> tasks = []
    List<Category> categories = []

    void saveToFile(String filePath) {
        def data = [tasks: tasks, categories: categories]
        new File(filePath).text = JsonOutput.prettyPrint(JsonOutput.toJson(data))
    }

    void loadFromFile(String filePath) {
        def file = new File(filePath)
        if (file.exists()) {
            def data = new JsonSlurper().parseText(file.text)
            tasks = data.tasks.collect { new Task(it) }
            categories = data.categories.collect { new Category(it) }
        }
    }
}

Explicación del Código

  • TaskRepository: Clase que maneja la lista de tareas y categorías.
  • saveToFile(): Método para guardar las tareas y categorías en un archivo JSON.
  • loadFromFile(): Método para cargar las tareas y categorías desde un archivo JSON.

Paso 3: Lógica de Negocio

Operaciones CRUD

Implementaremos métodos para crear, leer, actualizar y eliminar tareas.

class TaskService {
    TaskRepository repository

    TaskService(TaskRepository repository) {
        this.repository = repository
    }

    void addTask(String title, String description, String categoryId) {
        def category = repository.categories.find { it.id == categoryId }
        def task = new Task(id: UUID.randomUUID().toString(), title: title, description: description, completed: false, category: category)
        repository.tasks << task
    }

    List<Task> getAllTasks() {
        return repository.tasks
    }

    void updateTask(String taskId, String newTitle, String newDescription, boolean completed) {
        def task = repository.tasks.find { it.id == taskId }
        if (task) {
            task.title = newTitle
            task.description = newDescription
            task.completed = completed
        }
    }

    void deleteTask(String taskId) {
        repository.tasks.removeIf { it.id == taskId }
    }
}

Explicación del Código

  • TaskService: Clase que maneja la lógica de negocio de las tareas.
  • addTask(): Método para agregar una nueva tarea.
  • getAllTasks(): Método para obtener todas las tareas.
  • updateTask(): Método para actualizar una tarea existente.
  • deleteTask(): Método para eliminar una tarea.

Paso 4: Interfaz de Usuario

Interacción con el Usuario

Implementaremos una interfaz de usuario simple basada en la consola.

class TaskApp {
    TaskService taskService

    TaskApp(TaskService taskService) {
        this.taskService = taskService
    }

    void run() {
        println "Welcome to the To-Do List App"
        while (true) {
            println "\n1. Add Task\n2. View Tasks\n3. Update Task\n4. Delete Task\n5. Save and Exit"
            def choice = System.console().readLine("Choose an option: ")
            switch (choice) {
                case '1':
                    addTask()
                    break
                case '2':
                    viewTasks()
                    break
                case '3':
                    updateTask()
                    break
                case '4':
                    deleteTask()
                    break
                case '5':
                    saveAndExit()
                    return
                default:
                    println "Invalid option. Please try again."
            }
        }
    }

    void addTask() {
        def title = System.console().readLine("Enter task title: ")
        def description = System.console().readLine("Enter task description: ")
        def categoryId = System.console().readLine("Enter category ID: ")
        taskService.addTask(title, description, categoryId)
        println "Task added successfully."
    }

    void viewTasks() {
        def tasks = taskService.getAllTasks()
        tasks.each { println it }
    }

    void updateTask() {
        def taskId = System.console().readLine("Enter task ID to update: ")
        def newTitle = System.console().readLine("Enter new task title: ")
        def newDescription = System.console().readLine("Enter new task description: ")
        def completed = System.console().readLine("Is the task completed? (yes/no): ") == 'yes'
        taskService.updateTask(taskId, newTitle, newDescription, completed)
        println "Task updated successfully."
    }

    void deleteTask() {
        def taskId = System.console().readLine("Enter task ID to delete: ")
        taskService.deleteTask(taskId)
        println "Task deleted successfully."
    }

    void saveAndExit() {
        taskService.repository.saveToFile("tasks.json")
        println "Tasks saved. Exiting..."
    }
}

Explicación del Código

  • TaskApp: Clase que maneja la interacción con el usuario.
  • run(): Método principal que muestra el menú y maneja las opciones del usuario.
  • addTask(): Método para agregar una nueva tarea.
  • viewTasks(): Método para ver todas las tareas.
  • updateTask(): Método para actualizar una tarea existente.
  • deleteTask(): Método para eliminar una tarea.
  • saveAndExit(): Método para guardar las tareas y salir de la aplicación.

Paso 5: Pruebas

Pruebas Unitarias

Vamos a utilizar Spock para escribir pruebas unitarias.

import spock.lang.Specification

class TaskServiceSpec extends Specification {
    def "should add a new task"() {
        given:
        def repository = new TaskRepository()
        def service = new TaskService(repository)

        when:
        service.addTask("Test Task", "This is a test task", null)

        then:
        repository.tasks.size() == 1
        repository.tasks[0].title == "Test Task"
    }

    def "should update an existing task"() {
        given:
        def repository = new TaskRepository()
        def service = new TaskService(repository)
        def task = new Task(id: "1", title: "Old Task", description: "Old description", completed: false)
        repository.tasks << task

        when:
        service.updateTask("1", "New Task", "New description", true)

        then:
        task.title == "New Task"
        task.description == "New description"
        task.completed == true
    }
}

Explicación del Código

  • TaskServiceSpec: Clase de especificación de Spock para probar TaskService.
  • should add a new task: Prueba que verifica que una nueva tarea se agrega correctamente.
  • should update an existing task: Prueba que verifica que una tarea existente se actualiza correctamente.

Conclusión

En esta sección, hemos implementado una aplicación de gestión de tareas utilizando Groovy. Hemos cubierto la definición del modelo de datos, la persistencia, la lógica de negocio, la interfaz de usuario y las pruebas unitarias. Este proyecto final integra varios conceptos y características de Groovy, proporcionando una experiencia práctica y completa.

Próximos Pasos

  • Refactorización y Mejora: Revisa el código y busca oportunidades para mejorar la estructura y eficiencia.
  • Añadir Funcionalidades: Considera agregar nuevas características como notificaciones, integración con servicios externos, etc.
  • Despliegue: Implementa el despliegue de la aplicación en un entorno de producción.

¡Felicidades por completar el proyecto final y el curso de Groovy!

© Copyright 2024. Todos los derechos reservados