java

Java: aplicación de gestión de gastos personales

Hoy vamos a resolver un ejercicio de programación en Java usando programación orientada a objetos y lo que conlleva.

Veremos unas clases y clases abstractas que van a servir para llevar el control de gastos personales.

Te aviso desde ahora que solo te enseñaré las clases, pero no su implementación, ya que solo eso es lo que falta: un menú para invocar a los métodos de las clases e imprimir los resultados.

Obviamente tú puedes implementar este menú en consola, en una GUI, en un servidor web, etcétera.

Descripción del ejercicio

La práctica consiste en crear una aplicación de gestión de gastos personales. A través de un menú interactivo por la consola, introducir ingresos y gastos para así poder llevar un pequeño control de nuestra economía.

Clase Usuario

Esta clase será la encargada de gestionar un único usuario. Éste se creará al inicio del programa leyendo datos por el teclado. Ejemplo de datos correctos:

  • Nombre: Alberto
  • Edad: 23

El DNI deberá tener un formato concreto, está comprobación la realizará en la función setter, la cual devolverá un booleano conforme es correcto o no. Si el DNI es correcto quedará asignado.

Formato correcto:

  • Los primeros 8 caracteres solo podrán ser numéricos.
  • El ultimo caracteres deberá ser una letra entre la A y la Z.
  • El guion entre los números y la letra es opcional admitiendo ambas
    posibilidades.
  • DNI: 78844112L
  • DNI: 78844112-L

Tendrá una función toString con la que devolver su contenido.

Clases Gasto e Ingreso

Las clases Gasto e Ingreso heredarán de Dinero (clase abstracta) y tendrán un único constructor en el que se inicializarán los valores recibidos por parámetros.

Además, tendrán una función toString con la que devolver su contenido.

Clase Cuenta

Clase donde se gestionarán todos los movimientos de dinero tanto ingresos como gastos.
Inicialmente (en el constructor) se recibirá el usuario que es dueño de la cuenta y el saldo inicial será de 0.

Al añadir un nuevo ingreso se sumará al saldo de la cuenta teniendo en esta variable nuestro dinero real, la función devolverá el saldo de la cuenta.

Al añadir un nuevo gasto se debe comprobar si se dispone de saldo suficiente, en caso contrario se deberá lanzar una nueva excepción del tipo GastoException(), pero el programa no debe finalizar.

Si se dispone de saldo suficiente se restará el importe del gasto y se devolverá el saldo de la cuenta.

Las funciones getGastos y getIngresos nos devolverán todos los movimientos de un tipo u otro. Tendrán una función toString con la que devolverá el usuario y su saldo.

Código de Java

Una vez que hemos visto los requisitos de esta aplicación, veamos el código. Comencemos viendo la clase abstracta Dinero:

abstract class Dinero {
    private double dinero;
    private String description;

    public double getDinero() {
        return dinero;
    }

    public void setDinero(double dinero) {
        this.dinero = dinero;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Recuerda que una clase abstracta no se puede instanciar, solo se puede extender desde otras clases. Y hablando de las clases que la extienden tenemos al gasto y al ingreso:

class Gasto extends Dinero {
    public Gasto(double gasto, String description) {
        this.setDinero(gasto);
        this.setDescription(description);
    }

    @Override
    public String toString() {
        return this.getDescription() + " " + this.getDinero();
    }
}

class Ingreso extends Dinero {
    public Ingreso(double ingreso, String description) {
        this.setDinero(ingreso);
        this.setDescription(description);
    }

    @Override
    public String toString() {
        return this.getDescription() + " " + this.getDinero();
    }
}

Por otro lado vamos a tener al usuario. Recuerda que se debe validar el DNI así que presta atención al método setDNI:

class Usuario {
    private String nombre;
    private int edad;
    private String DNI;

    public Usuario() {

    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }

    public String getDNI() {
        return DNI;
    }

    public boolean setDNI(String DNI) {
        // Solo puede medir 9 (sin guión) o 10 (con guión)
        if (DNI.length() != 9 && DNI.length() != 10) {
            return false;
        }
        // Si mide 10, debe tener un guión en la posición 8
        if (DNI.length() == 10) {
            // Extraerlo y regresar false si no es un guión
            String posibleGuion = String.valueOf(DNI.charAt(8));
            if (!posibleGuion.equals("-")) {
                return false;
            }
        }
        // Hasta ahora sabemos que mide 9 o 10 y que el guión es válido (esté presente o no)
        String primerosOcho = DNI.substring(0, 8);
        String ultimo = String.valueOf(DNI.charAt(DNI.length() - 1));
        // Comprobar que los primeros 8 sean numéricos
        if (!primerosOcho.matches("[0-9]+")) {
            return false;
        }
        // Comprobar que el último sea una letra
        if (!ultimo.matches("[A-Z]+")) {
            return false;
        }
        // Si llegamos hasta aquí y no regresamos arriba, entonces el DNI es válido
        this.DNI = DNI;
        return true;
    }

    @Override
    public String toString() {
        return "Usuario{" +
                "nombre='" + nombre + '\'' +
                ", edad=" + edad +
                ", DNI='" + DNI + '\'' +
                '}';
    }
}

Después de eso tenemos la clase Cuenta que como lo solicita el ejercicio va a recibir un Usuario:

class Cuenta {
    private double saldo;
    private Usuario usuario;
    private List<Gasto> gastos;
    private List<Ingreso> ingresos;

    public Cuenta(Usuario usuario) {
        this.usuario = usuario;
        this.saldo = 0;
        this.gastos = new ArrayList<>();
        this.ingresos = new ArrayList<>();
    }

    public double getSaldo() {
        return saldo;
    }

    public void setSaldo(double saldo) {
        this.saldo = saldo;
    }

    public Usuario getUsuario() {
        return usuario;
    }

    public void setUsuario(Usuario usuario) {
        this.usuario = usuario;
    }

    public double addIngresos(String description, double cantidad) {
        this.setSaldo(this.getSaldo() + cantidad);
        this.ingresos.add(new Ingreso(cantidad, description));
        return this.getSaldo();
    }

    public double addGastos(String description, double cantidad) throws GastoException {
        if (this.getSaldo() < cantidad) {
            throw new GastoException();
        }
        this.setSaldo(this.getSaldo() - cantidad);
        this.gastos.add(new Gasto(cantidad, description));
        return this.getSaldo();
    }

    public List<Gasto> getGastos() {
        return gastos;
    }


    public List<Ingreso> getIngresos() {
        return ingresos;
    }

    @Override
    public String toString() {
        return "Cuenta{" +
                "saldo=" + saldo +
                ", usuario=" + usuario +
                ", gastos=" + gastos +
                ", ingresos=" + ingresos +
                '}';
    }
}

Fíjate en la línea 37. Dentro de ella estamos lanzando la excepción GastoException como lo solicita el ejercicio, misma que es una excepción personalizada de Java y se ve así:

class GastoException extends Exception {
    public GastoException() {
    }
}

Poniendo todo junto

El código completo de todas las clases junto con el método Main que usa un poco de ellas (no todo, pues como te lo dije, no he implementado el menú) queda así:


import java.util.ArrayList;
import java.util.List;

abstract class Dinero {
    private double dinero;
    private String description;

    public double getDinero() {
        return dinero;
    }

    public void setDinero(double dinero) {
        this.dinero = dinero;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}


class Gasto extends Dinero {
    public Gasto(double gasto, String description) {
        this.setDinero(gasto);
        this.setDescription(description);
    }

    @Override
    public String toString() {
        return this.getDescription() + " " + this.getDinero();
    }
}

class Ingreso extends Dinero {
    public Ingreso(double ingreso, String description) {
        this.setDinero(ingreso);
        this.setDescription(description);
    }

    @Override
    public String toString() {
        return this.getDescription() + " " + this.getDinero();
    }
}

class Usuario {
    private String nombre;
    private int edad;
    private String DNI;

    public Usuario() {

    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public int getEdad() {
        return edad;
    }

    public void setEdad(int edad) {
        this.edad = edad;
    }

    public String getDNI() {
        return DNI;
    }

    public boolean setDNI(String DNI) {
        // Solo puede medir 9 (sin guión) o 10 (con guión)
        if (DNI.length() != 9 && DNI.length() != 10) {
            return false;
        }
        // Si mide 10, debe tener un guión en la posición 8
        if (DNI.length() == 10) {
            // Extraerlo y regresar false si no es un guión
            String posibleGuion = String.valueOf(DNI.charAt(8));
            if (!posibleGuion.equals("-")) {
                return false;
            }
        }
        // Hasta ahora sabemos que mide 9 o 10 y que el guión es válido (esté presente o no)
        String primerosOcho = DNI.substring(0, 8);
        String ultimo = String.valueOf(DNI.charAt(DNI.length() - 1));
        // Comprobar que los primeros 8 sean numéricos
        if (!primerosOcho.matches("[0-9]+")) {
            return false;
        }
        // Comprobar que el último sea una letra
        if (!ultimo.matches("[A-Z]+")) {
            return false;
        }
        // Si llegamos hasta aquí y no regresamos arriba, entonces el DNI es válido
        this.DNI = DNI;
        return true;
    }

    @Override
    public String toString() {
        return "Usuario{" +
                "nombre='" + nombre + '\'' +
                ", edad=" + edad +
                ", DNI='" + DNI + '\'' +
                '}';
    }
}

class Cuenta {
    private double saldo;
    private Usuario usuario;
    private List<Gasto> gastos;
    private List<Ingreso> ingresos;

    public Cuenta(Usuario usuario) {
        this.usuario = usuario;
        this.saldo = 0;
        this.gastos = new ArrayList<>();
        this.ingresos = new ArrayList<>();
    }

    public double getSaldo() {
        return saldo;
    }

    public void setSaldo(double saldo) {
        this.saldo = saldo;
    }

    public Usuario getUsuario() {
        return usuario;
    }

    public void setUsuario(Usuario usuario) {
        this.usuario = usuario;
    }

    public double addIngresos(String description, double cantidad) {
        this.setSaldo(this.getSaldo() + cantidad);
        this.ingresos.add(new Ingreso(cantidad, description));
        return this.getSaldo();
    }

    public double addGastos(String description, double cantidad) throws GastoException {
        if (this.getSaldo() < cantidad) {
            throw new GastoException();
        }
        this.setSaldo(this.getSaldo() - cantidad);
        this.gastos.add(new Gasto(cantidad, description));
        return this.getSaldo();
    }

    public List<Gasto> getGastos() {
        return gastos;
    }


    public List<Ingreso> getIngresos() {
        return ingresos;
    }

    @Override
    public String toString() {
        return "Cuenta{" +
                "saldo=" + saldo +
                ", usuario=" + usuario +
                ", gastos=" + gastos +
                ", ingresos=" + ingresos +
                '}';
    }
}

class GastoException extends Exception {
    public GastoException() {
    }
}

public class Main {

    public static void main(String[] args) {
        Usuario usuario = new Usuario();
        usuario.setNombre("Luis");
        usuario.setEdad(24);
        boolean ok = usuario.setDNI("12345678A");
        Cuenta cuenta = new Cuenta(usuario);
        System.out.println(cuenta);
    }
}

Como siempre te digo: eres libre de descargar, mejorar y modificar el código. También te dejo con más artículos de programación en Java para que aprendas más sobre este lenguaje.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

17 horas hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

1 semana hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

1 semana hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

1 semana hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

1 semana hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

1 semana hace

Esta web usa cookies.