Skip to content

Patrones de Diseño y Arquitecturas

Módulo 6: Patrones de Diseño y Arquitecturas

Section titled “Módulo 6: Patrones de Diseño y Arquitecturas”

Los patrones de diseño y arquitecturas bien definidas son fundamentales para crear aplicaciones mantenibles, escalables y robustas. En este módulo aprenderemos los patrones más importantes para aplicaciones Java Swing.

Arquitectura MVC (Modelo - Vista - Controlador)

Section titled “Arquitectura MVC (Modelo - Vista - Controlador)”

MVC es un patrón arquitectónico que separa la aplicación en tres componentes principales, facilitando el mantenimiento y la escalabilidad.

  1. Modelo: Representa los datos y la lógica de negocio
  2. Vista: Maneja la presentación y la interfaz de usuario
  3. Controlador: Gestiona la interacción entre Modelo y Vista

Implementación práctica con formulario CRUD

Section titled “Implementación práctica con formulario CRUD”
public class UsuarioModel {
private int id;
private String nombre;
private String email;
private String telefono;
private boolean activo;
// Constructores
public UsuarioModel() {}
public UsuarioModel(String nombre, String email, String telefono, boolean activo) {
this.nombre = nombre;
this.email = email;
this.telefono = telefono;
this.activo = activo;
}
// Getters y Setters
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getNombre() { return nombre; }
public void setNombre(String nombre) { this.nombre = nombre; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getTelefono() { return telefono; }
public void setTelefono(String telefono) { this.telefono = telefono; }
public boolean isActivo() { return activo; }
public void setActivo(boolean activo) { this.activo = activo; }
// Validaciones del modelo
public boolean esValido() {
return nombre != null && !nombre.trim().isEmpty() &&
email != null && !email.trim().isEmpty() &&
email.contains("@");
}
@Override
public String toString() {
return "UsuarioModel{" +
"id=" + id +
", nombre='" + nombre + ''' +
", email='" + email + ''' +
", activo=" + activo +
'}';
}
}

La arquitectura por capas organiza la aplicación en niveles con responsabilidades específicas.

PresentacionLayer.java
// Capa de Presentación - Solo maneja la interfaz
public class PresentacionLayer {
private UsuarioView vista;
private UsuarioController controlador;
public PresentacionLayer() {
// Inicializar solo componentes de UI
vista = new UsuarioView();
// El controlador conecta con la capa de negocio
UsuarioService service = new UsuarioService();
controlador = new UsuarioController(vista, service);
vista.setVisible(true);
}
}
UsuarioService.java
// Capa de Negocio - Lógica de validación y reglas de negocio
public class UsuarioService {
private UsuarioDAO dao;
public UsuarioService() {
this.dao = new UsuarioDAO();
}
public boolean crearUsuario(UsuarioModel usuario) {
// Validaciones de negocio
if (!validarReglas(usuario)) {
return false;
}
// Verificar duplicados
if (existeEmail(usuario.getEmail())) {
throw new RuntimeException("Ya existe un usuario con ese email");
}
// Aplicar reglas de negocio
usuario.setEmail(usuario.getEmail().toLowerCase());
return dao.insertar(usuario);
}
public boolean actualizarUsuario(UsuarioModel usuario) {
if (!validarReglas(usuario)) {
return false;
}
// Verificar que el usuario existe
UsuarioModel existente = dao.obtenerPorId(usuario.getId());
if (existente == null) {
throw new RuntimeException("Usuario no encontrado");
}
return dao.actualizar(usuario);
}
public boolean eliminarUsuario(int id) {
// Verificar reglas de negocio antes de eliminar
if (tieneRegistrosRelacionados(id)) {
throw new RuntimeException("No se puede eliminar: tiene registros relacionados");
}
return dao.eliminar(id);
}
public List<UsuarioModel> obtenerTodosLosUsuarios() {
return dao.obtenerTodos();
}
private boolean validarReglas(UsuarioModel usuario) {
// Reglas de negocio específicas
if (usuario.getNombre().length() < 2) {
throw new RuntimeException("El nombre debe tener al menos 2 caracteres");
}
if (!usuario.getEmail().matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$")) {
throw new RuntimeException("Formato de email inválido");
}
return true;
}
private boolean existeEmail(String email) {
return dao.buscarPorEmail(email) != null;
}
private boolean tieneRegistrosRelacionados(int id) {
// Verificar en otras tablas si tiene registros relacionados
return false; // Implementar según necesidades
}
}
UsuarioDAO.java
// Capa de Datos - Solo acceso a datos, sin lógica de negocio
public class UsuarioDAO {
private Connection conexion;
public UsuarioDAO() {
this.conexion = ConexionDB.getInstance().getConexion();
}
public boolean insertar(UsuarioModel usuario) {
String sql = "INSERT INTO usuarios (nombre, email, telefono, activo) VALUES (?, ?, ?, ?)";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, usuario.getNombre());
stmt.setString(2, usuario.getEmail());
stmt.setString(3, usuario.getTelefono());
stmt.setBoolean(4, usuario.isActivo());
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
System.err.println("Error insertando usuario: " + e.getMessage());
return false;
}
}
public List<UsuarioModel> obtenerTodos() {
List<UsuarioModel> usuarios = new ArrayList<>();
String sql = "SELECT * FROM usuarios ORDER BY nombre";
try (Statement stmt = conexion.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
UsuarioModel usuario = mapearResultSet(rs);
usuarios.add(usuario);
}
} catch (SQLException e) {
System.err.println("Error obteniendo usuarios: " + e.getMessage());
}
return usuarios;
}
public UsuarioModel obtenerPorId(int id) {
String sql = "SELECT * FROM usuarios WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return mapearResultSet(rs);
}
}
} catch (SQLException e) {
System.err.println("Error obteniendo usuario por ID: " + e.getMessage());
}
return null;
}
private UsuarioModel mapearResultSet(ResultSet rs) throws SQLException {
UsuarioModel usuario = new UsuarioModel();
usuario.setId(rs.getInt("id"));
usuario.setNombre(rs.getString("nombre"));
usuario.setEmail(rs.getString("email"));
usuario.setTelefono(rs.getString("telefono"));
usuario.setActivo(rs.getBoolean("activo"));
return usuario;
}
}

El patrón Singleton asegura que una clase tenga una sola instancia y proporciona acceso global a ella.

ConexionSingleton.java
public class ConexionSingleton {
private static ConexionSingleton instancia;
private Connection conexion;
private static final String URL = "jdbc:mysql://localhost:3306/swing_app";
private static final String USUARIO = "root";
private static final String PASSWORD = "password";
// Constructor privado para evitar instanciación externa
private ConexionSingleton() {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
this.conexion = DriverManager.getConnection(URL, USUARIO, PASSWORD);
System.out.println("Conexión establecida exitosamente");
} catch (ClassNotFoundException | SQLException e) {
System.err.println("Error estableciendo conexión: " + e.getMessage());
}
}
// Método sincronizado para obtener la instancia (thread-safe)
public static synchronized ConexionSingleton getInstance() {
if (instancia == null) {
instancia = new ConexionSingleton();
}
return instancia;
}
public Connection getConexion() {
try {
// Verificar si la conexión sigue activa
if (conexion == null || conexion.isClosed()) {
instancia = new ConexionSingleton();
}
} catch (SQLException e) {
System.err.println("Error verificando conexión: " + e.getMessage());
}
return conexion;
}
public void cerrarConexion() {
try {
if (conexion != null && !conexion.isClosed()) {
conexion.close();
System.out.println("Conexión cerrada");
}
} catch (SQLException e) {
System.err.println("Error cerrando conexión: " + e.getMessage());
}
}
}

El patrón DAO encapsula el acceso a datos y proporciona una interfaz uniforme.

UsuarioDAOImpl.java
// Interfaz genérica para operaciones CRUD
public interface GenericDAO<T, ID> {
boolean insertar(T entidad);
T obtenerPorId(ID id);
List<T> obtenerTodos();
boolean actualizar(T entidad);
boolean eliminar(ID id);
}
// Implementación específica para Usuario
public class UsuarioDAOImpl implements GenericDAO<UsuarioModel, Integer> {
private Connection conexion;
public UsuarioDAOImpl() {
this.conexion = ConexionSingleton.getInstance().getConexion();
}
@Override
public boolean insertar(UsuarioModel usuario) {
String sql = "INSERT INTO usuarios (nombre, email, telefono, activo) VALUES (?, ?, ?, ?)";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, usuario.getNombre());
stmt.setString(2, usuario.getEmail());
stmt.setString(3, usuario.getTelefono());
stmt.setBoolean(4, usuario.isActivo());
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
throw new RuntimeException("Error insertando usuario", e);
}
}
@Override
public UsuarioModel obtenerPorId(Integer id) {
String sql = "SELECT * FROM usuarios WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setInt(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return mapearUsuario(rs);
}
}
} catch (SQLException e) {
throw new RuntimeException("Error obteniendo usuario por ID", e);
}
return null;
}
@Override
public List<UsuarioModel> obtenerTodos() {
List<UsuarioModel> usuarios = new ArrayList<>();
String sql = "SELECT * FROM usuarios ORDER BY nombre";
try (Statement stmt = conexion.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
usuarios.add(mapearUsuario(rs));
}
} catch (SQLException e) {
throw new RuntimeException("Error obteniendo todos los usuarios", e);
}
return usuarios;
}
@Override
public boolean actualizar(UsuarioModel usuario) {
String sql = "UPDATE usuarios SET nombre = ?, email = ?, telefono = ?, activo = ? WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setString(1, usuario.getNombre());
stmt.setString(2, usuario.getEmail());
stmt.setString(3, usuario.getTelefono());
stmt.setBoolean(4, usuario.isActivo());
stmt.setInt(5, usuario.getId());
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
throw new RuntimeException("Error actualizando usuario", e);
}
}
@Override
public boolean eliminar(Integer id) {
String sql = "DELETE FROM usuarios WHERE id = ?";
try (PreparedStatement stmt = conexion.prepareStatement(sql)) {
stmt.setInt(1, id);
return stmt.executeUpdate() > 0;
} catch (SQLException e) {
throw new RuntimeException("Error eliminando usuario", e);
}
}
private UsuarioModel mapearUsuario(ResultSet rs) throws SQLException {
UsuarioModel usuario = new UsuarioModel();
usuario.setId(rs.getInt("id"));
usuario.setNombre(rs.getString("nombre"));
usuario.setEmail(rs.getString("email"));
usuario.setTelefono(rs.getString("telefono"));
usuario.setActivo(rs.getBoolean("activo"));
return usuario;
}
}

Aplicación principal integrando todos los patrones

Section titled “Aplicación principal integrando todos los patrones”
AplicacionPrincipal.java
public class AplicacionPrincipal {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
// Configurar Look and Feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());
// Inicializar la aplicación con arquitectura por capas
new PresentacionLayer();
} catch (Exception e) {
System.err.println("Error iniciando aplicación: " + e.getMessage());
e.printStackTrace();
}
});
// Agregar shutdown hook para cerrar conexiones
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
ConexionSingleton.getInstance().cerrarConexion();
System.out.println("Aplicación cerrada correctamente");
}));
}
}

En este módulo hemos aprendido:

Arquitectura MVC
  • Separación clara de responsabilidades
  • Modelo para datos y lógica de negocio
  • Vista para interfaz de usuario
  • Controlador para coordinar interacciones
Arquitectura por Capas
  • Capa de Presentación (UI)
  • Capa de Negocio (lógica y validaciones)
  • Capa de Datos (acceso a base de datos)
Patrones de Diseño
  • Singleton: Una sola instancia para recursos compartidos
  • DAO: Encapsulación del acceso a datos
  • Separación de responsabilidades: Código más mantenible

Estos patrones y arquitecturas proporcionan una base sólida para desarrollar aplicaciones Java Swing robustas, escalables y fáciles de mantener.

🐝