Organización de código
Principios de organización
Un proyecto Java bien organizado es fácil de navegar, mantener y escalar. La organización no es solo estética, afecta directamente la productividad.
Estructura básica de un proyecto
mi-proyecto/├── src/│ ├── main/│ │ ├── java/│ │ │ └── com/empresa/proyecto/│ │ │ ├── Main.java│ │ │ ├── modelo/│ │ │ ├── servicio/│ │ │ ├── repositorio/│ │ │ └── util/│ │ └── resources/│ │ ├── application.properties│ │ └── log4j2.xml│ └── test/│ ├── java/│ │ └── com/empresa/proyecto/│ └── resources/├── target/ (generado por Maven)├── pom.xml (Maven) o build.gradle (Gradle)└── README.mdConvenciones de nomenclatura
Packages
com.empresa.proyectocom.empresa.proyecto.modelocom.empresa.proyecto.serviciocom.empresa.proyecto.repositorio- Todo en minúsculas
- Sin guiones bajos ni caracteres especiales
- Dominio invertido (com.empresa en lugar de empresa.com)
- Específico y descriptivo
Clases e interfaces
public class Usuario { } // PascalCasepublic interface Repositorio { } // PascalCasepublic enum EstadoPedido { } // PascalCaseMétodos y variables
public void calcularTotal() { } // camelCaseprivate int contadorIntentos; // camelCaseConstantes
public static final int MAX_INTENTOS = 3;public static final String URL_BASE = "https://api.ejemplo.com";Organización por capas
Modelo (Entidades/DTOs)
Representan datos:
package com.empresa.proyecto.modelo;
public class Usuario { private Long id; private String nombre; private String email;
// Constructores, getters, setters}
public record UsuarioDTO(String nombre, String email) { }Repositorio (Acceso a datos)
Interactúa con la base de datos:
package com.empresa.proyecto.repositorio;
public interface UsuarioRepositorio { Usuario buscarPorId(Long id); List<Usuario> buscarTodos(); void guardar(Usuario usuario); void eliminar(Long id);}Servicio (Lógica de negocio)
Contiene la lógica principal:
package com.empresa.proyecto.servicio;
public class UsuarioServicio { private final UsuarioRepositorio repositorio;
public UsuarioServicio(UsuarioRepositorio repositorio) { this.repositorio = repositorio; }
public Usuario crearUsuario(String nombre, String email) { validarEmail(email); Usuario usuario = new Usuario(nombre, email); return repositorio.guardar(usuario); }
private void validarEmail(String email) { if (!email.contains("@")) { throw new IllegalArgumentException("Email inválido"); } }}Controlador (API/UI)
Maneja entradas y salidas:
package com.empresa.proyecto.controlador;
public class UsuarioControlador { private final UsuarioServicio servicio;
public UsuarioControlador(UsuarioServicio servicio) { this.servicio = servicio; }
public void registrarUsuario(String nombre, String email) { try { Usuario usuario = servicio.crearUsuario(nombre, email); System.out.println("Usuario creado: " + usuario.getId()); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); } }}Separación de responsabilidades
Una clase, una responsabilidad
// ❌ Clase que hace demasiadopublic class Usuario { private String nombre; private String email;
public void guardarEnBaseDatos() { } public void enviarEmail() { } public void generarReporte() { }}
// ✅ Responsabilidades separadaspublic class Usuario { private String nombre; private String email;}
public class UsuarioRepositorio { public void guardar(Usuario usuario) { }}
public class EmailServicio { public void enviar(Usuario usuario, String mensaje) { }}
public class ReporteServicio { public void generar(Usuario usuario) { }}Organización de archivos
Un archivo por clase pública
// ❌ Usuario.java contiene múltiples clases públicaspublic class Usuario { }public class Direccion { } // Error de compilación
// ✅ Un archivo por clase pública// Usuario.javapublic class Usuario { }
// Direccion.javapublic class Direccion { }Clases internas están bien:
public class Usuario { private Direccion direccion;
// Clase interna privada private static class Direccion { String calle; String ciudad; }}Packages comunes
Estructura típica
com.empresa.proyecto/├── Main.java // Punto de entrada├── config/ // Configuración│ └── DatabaseConfig.java├── modelo/ // Entidades y DTOs│ ├── Usuario.java│ ├── Pedido.java│ └── dto/│ └── UsuarioDTO.java├── repositorio/ // Acceso a datos│ ├── UsuarioRepositorio.java│ └── PedidoRepositorio.java├── servicio/ // Lógica de negocio│ ├── UsuarioServicio.java│ └── PedidoServicio.java├── controlador/ // API/UI│ └── UsuarioControlador.java├── excepcion/ // Excepciones personalizadas│ └── UsuarioNoEncontradoException.java└── util/ // Utilidades ├── StringUtil.java └── DateUtil.javaVisibilidad y encapsulación
Modificadores de acceso
public class Ejemplo { public int publico; // Accesible desde cualquier lugar protected int protegido; // Mismo package + subclases int defaultAcceso; // Mismo package (sin modificador) private int privado; // Solo esta clase
public void metodoPublico() { } protected void metodoProtegido() { } void metodoDefault() { } private void metodoPrivado() { }}Regla general
// ✅ Por defecto, todo privadopublic class Usuario { private String nombre; private int edad;
// Exponer solo lo necesario public String getNombre() { return nombre; }}Cohesión y acoplamiento
Alta cohesión (bueno)
Los elementos de una clase están relacionados:
// ✅ Alta cohesiónpublic class Pedido { private List<LineaPedido> lineas; private BigDecimal total;
public void agregarLinea(LineaPedido linea) { lineas.add(linea); calcularTotal(); }
private void calcularTotal() { total = lineas.stream() .map(LineaPedido::getSubtotal) .reduce(BigDecimal.ZERO, BigDecimal::add); }}Bajo acoplamiento (bueno)
Las clases dependen poco entre sí:
// ❌ Alto acoplamientopublic class Servicio { private ImplementacionConcreta impl = new ImplementacionConcreta();}
// ✅ Bajo acoplamiento (inyección de dependencias)public class Servicio { private final Interfaz dependencia;
public Servicio(Interfaz dependencia) { this.dependencia = dependencia; }}Constantes y configuración
Clase de constantes
public final class Constantes { private Constantes() { } // Prevenir instanciación
public static final int MAX_REINTENTOS = 3; public static final String URL_API = "https://api.ejemplo.com";}Enums para valores relacionados
public enum EstadoPedido { PENDIENTE, PROCESANDO, ENVIADO, ENTREGADO, CANCELADO}
// Usoif (pedido.getEstado() == EstadoPedido.ENVIADO) { // ...}Utilidades
Clases utility
public final class StringUtils { private StringUtils() { } // No instanciable
public static boolean esVacio(String str) { return str == null || str.trim().isEmpty(); }
public static String capitalizar(String str) { if (esVacio(str)) return str; return str.substring(0, 1).toUpperCase() + str.substring(1); }}
// Usoif (StringUtils.esVacio(nombre)) { throw new IllegalArgumentException("Nombre requerido");}Recursos (resources/)
application.properties
# Base de datosdb.url=jdbc:mysql://localhost:3306/midbdb.usuario=rootdb.password=secret
# Logginglogging.level=INFOArchivos de configuración
resources/├── application.properties // Configuración principal├── log4j2.xml // Logging├── messages.properties // Internacionalización└── static/ // Archivos estáticos (web) ├── css/ ├── js/ └── images/Buenas prácticas
- Organiza por feature, no por tipo (cuando el proyecto crece):
// ❌ Por tipo (difícil de navegar en proyectos grandes)modelo/ Usuario.java Pedido.javaservicio/ UsuarioServicio.java PedidoServicio.java
// ✅ Por feature (más escalable)usuario/ Usuario.java UsuarioServicio.java UsuarioRepositorio.javapedido/ Pedido.java PedidoServicio.java PedidoRepositorio.java-
Evita packages vacíos o con una sola clase.
-
No uses packages "misc", "util", "helper" como cajones de sastre.
-
Mantén packages pequeños (idealmente <10 clases).
-
Package-private por defecto si solo se usa internamente en el package.
Próximo paso
Aprende sobre packages y módulos en Java: Packages y módulos →