Saltar al contenido principal

MVC

El Modelo-Vista-Controlador (MVC) es un patrón de diseño arquitectónico que separa una aplicación en tres componentes principales: modelo (model), vista (view) y controlador (controller). Este patrón facilita la gestión del código y mejora la escalabilidad y mantenimiento de las aplicaciones.

  • Modelo: representa la lógica de negocio o los datos de la aplicación. Su principal responsabilidad es gestionar y manipular los datos. El modelo no debe saber nada sobre la vista o el controlador.
  • Controlador: actúa como intermediario entre el modelo y la vista. Su trabajo es recibir las entradas del usuario (por ejemplo, comandos o selecciones), procesarlas y actualizar el modelo y la vista en consecuencia. El controlador es responsable de tomar decisiones sobre la lógica de la aplicación.
  • Vista: es responsable de mostrar la información al usuario. En el caso de una interfaz de terminal, la vista será responsable de mostrar las salidas en el terminal o consola. La vista solo muestra los datos y captura las interacciones del usuario, no debe realizar ningún tipo de lógica de negocio.

Ejemplo

Veamos un ejemplo de cómo implantar un sistema sencillo de gestión de usuarios donde el controlador recibe comandos de terminal, el modelo gestiona los datos y la vista los presenta.

En este ejemplo hacemos una pequeña adaptación de este patrón, concretamente en el controlador.

Modelo

El modelo gestionará los datos, en este caso, a los usuarios.

Usuario.java
public class Usuario {
private String nome;
private String email;

public Usuario(String nome, String email) {
this.nome = nome;
this.email = email;
}

public String getNome() {
return nome;
}

public String getEmail() {
return email;
}

public String toString() {
return "Nome: " + nome + ", Email: " + email;
}
}

Controlador

Conecta la vista y el modelo y contiene la lógica de negocio. Contiene métodos con acciones que se pueden realizar en la aplicación. Llama a los métodos de las clases del modelo y devuelve las respuestas a la vista. Podemos utilizar el patrón Singleton.

GestionUsuarios.java
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

class GestionUsuarios {
private static GestionUsuarios INSTANCE; // Singleton

private List<Usuario> usuarios;

private GestionUsuarios() {
usuarios = new ArrayList<>();
}

public void addUsuario(Usuario usuario) {
usuarios.add(usuario);
}

public List<Usuario> getUsuarios() {
return usuarios;
}

public Optional<Usuario> getUsuarioByNombre(String nome) {
for (Usuario usuario : usuarios) {
if (usuario.getNome().equalsIgnoreCase(nome)) {
return Optional.of(usuario);
}
}
return Optional.empty();
}

// Patrón Singleton
public static GestionUsuarios getInstance() {
if(INSTANCE == null)
INSTANCE = new GestionUsuarios();
return INSTANCE;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
Adaptación del MVC

Este ejemplo de controlador no sería propiamente un controlador. Esto es debido a que almacena datos (List<Usuario> usuarios), y el controlador no lo debería realizar.

Esto es porque no utilizamos ningún sistema de persistencia. Por ejemplo, si utilizáramos una base de datos, esa lista ya no existiría (los datos se almacenarían en una tabla usuarios de la base de datos). De este modo, ya no almacenaría datos y por lo tanto sí se podría considerar un controlador al uso.

Vista

Gestiona la presentación de los datos y la interacción con el usuario, en este caso en la terminal. En este ejemplo, mostraremos la lista de usuarios o un único usuario, dependiendo de la acción. Incluimos una clase abstracta con métodos muy prácticos a partir de la cual se pueden crear nuevos menús utilizando herencia.

Menu.java
import java.util.List;
import java.util.Scanner;

public abstract class Menu {
private Scanner scanner;

/**
* Constructor de menú
*/
public Menu() {
scanner = new Scanner(System.in);
}

/**
* Arranca el menú
*/
public void run() {
this.mostrar();
}

/**
* Método abstracto, que es necesario implementar para mostrar por terminal
*/
protected abstract void mostrar();

/**
* Pide por teclado una cadena de texto
* @param frase que se muestra al usuario
* @return
*/
protected String getString(String frase) {
System.out.print(frase);
String mensaxe = this.scanner.nextLine();
return mensaxe;
}

/**
* Pide por teclado un double
* @param frase que se muestra al usuario
* @return
*/
protected double getDouble(String frase) {
System.out.print(frase);
double mensaxe = this.scanner.nextDouble();
this.scanner.nextLine(); // Limpiar buffer
return mensaxe;
}

/**
* Pide por teclado un int
* @param frase que se muestra al usuario
* @return
*/
protected int getInt(String frase) {
System.out.print(frase);
int mensaxe = this.scanner.nextInt();
this.scanner.nextLine(); // Limpiar buffer
return mensaxe;
}

/**
* Imprime una frase por pantalla añadiendo una nueva línea
* @param frase
*/
protected void printMessage(String frase) {
System.out.println(frase);
}

/**
* Imprime una lista de elementos
* @param <T> Clase genérica para poder realizarlo con una lista de cualquier tipo
* @param lista
*/
protected <T> void printList(List<T> lista) {
for(T elemento: lista) {
printMessage(elemento.toString());
}
}
}
MenuPrincipal.java
import java.util.Optional;

public class MenuPrincipal extends Menu {

protected void mostrar() {
boolean continuar = true;

while (continuar) {
this.printMessage("\nElige una opción:");
this.printMessage("a. Añadir usuario");
this.printMessage("b. Buscar usuario por nombre");
this.printMessage("s. Salir");

String opcion = this.getString("\n\t> ");

switch (opcion) {
case "a" -> {
String nombre = this.getString("Introduce el nombre del usuario: ");
String email = this.getString("Introduce el correo electrónico: ");
GestionUsuarios.getInstance().addUsuario(new Usuario(nombre, email));
}
case "b" -> {
String nombreBuscar = this.getString("Introduce el nombre del usuario a buscar: ");
Optional<Usuario> usuario = GestionUsuarios.getInstance().getUsuarioByNombre(nombreBuscar);
if(usuario.isPresent())
System.out.println(usuario.get());
else
System.out.println("No hay usuario con ese nombre");

}
case "s" -> {
continuar = false;
}
default -> {
System.out.println("Opción no válida.");
}
}
}
}
}

Programa principal

El programa principal inicia la interfaz de terminal.

App.java
public class App {
public static void main(String[] args) {
new MenuPrincipal().run();
}
}

Organización en paquetes de un programa

Una posible organización en paquetes podría ser la siguiente:

  • controller: aquí tendríamos la clase donde almacenamos todos los datos del programa. Podemos utilizar en esta clase el patrón Singleton.
  • model: aquí tenemos todas las clases que representan el modelo de datos de nuestro programa. Dentro de este paquete podemos crear más paquetes para segmentar más nuestro modelo de datos.
    • exceptions: Aquí almacenamos todas las excepciones que creemos.
  • view: almacenamos todas las clases que tienen que ver con la vista.
  • utils: otras clases útiles para nuestro programa.
  • App.java: fichero que lanza el programa.