El Patrón Proxy en Java

📅 Actualizado en febrero 2026 ✍️ Ángel López ⏱️ 18 min de lectura ✓ Nivel intermedio ★ ★ ★ ★ ★ (5/5)

🏗️ ¿Qué es el patrón Proxy?

El patrón Proxy es un patrón de diseño estructural que proporciona un sustituto o representante de otro objeto para controlar el acceso a él. En términos sencillos, un proxy actúa como un intermediario que se interpone entre el cliente y el objeto real (también llamado sujeto real o servicio), ofreciendo la misma interfaz pero añadiendo una capa de control.

La palabra «proxy» proviene del latín procurator, que significa «el que actúa en nombre de otro». En el mundo del software, esta metáfora es precisa: el objeto proxy recibe las peticiones del cliente, realiza algún procesamiento adicional (verificación de permisos, carga diferida, registro de accesos…) y delega la operación al objeto real cuando es apropiado.

💡 Concepto clave: El proxy implementa la misma interfaz que el objeto real. Esto permite que el cliente lo use sin saber que está interactuando con un intermediario, cumpliendo el principio de sustitución de Liskov.

A diferencia del patrón Bridge, que separa abstracción de implementación, el Proxy mantiene la misma abstracción pero controla cuándo, cómo y si se accede al objeto real. Y a diferencia del patrón Adapter, que adapta una interfaz a otra, el Proxy no cambia la interfaz: la replica exactamente.

📜 Contexto histórico: el Gang of Four

El patrón Proxy fue catalogado formalmente en 1994 por Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides — conocidos colectivamente como el Gang of Four (GoF) — en su influyente obra Design Patterns: Elements of Reusable Object-Oriented Software. Lo clasificaron dentro de los patrones estructurales, junto con Adapter, Bridge, Composite, Decorator, Facade y Flyweight.

Sin embargo, el concepto de intermediario en computación es mucho más antiguo. Los servidores proxy de red, que actúan como intermediarios entre un cliente y un servidor remoto, ya existían en la década de 1980. La idea de interponer un representante que controle el acceso a un recurso es un principio fundamental de la ingeniería de software que trasciende los lenguajes de programación.

✅ Dato histórico: El concepto de «objeto apoderado» aparece en los primeros sistemas de objetos distribuidos como CORBA (Common Object Request Broker Architecture), donde los proxies remotos permitían invocar métodos en objetos alojados en otras máquinas como si fueran locales.

Los GoF identificaron tres variantes principales: proxy remoto, proxy virtual y proxy de protección. Con el tiempo, la comunidad de desarrollo ha añadido variantes adicionales como el proxy de caché, el proxy de registro (logging proxy) y el proxy inteligente (smart proxy).

🎯 Problema que resuelve

Imaginemos que tenemos un objeto cuya creación o acceso es costoso en recursos. Podría tratarse de una imagen de alta resolución que ocupa megabytes en disco, una conexión a una base de datos remota, o un servicio que requiere autenticación. Sin el patrón Proxy, el cliente tendría que asumir directamente el coste de crear y gestionar ese objeto:

Java — Problema: carga inmediata costosa
// Sin proxy: todas las imágenes se cargan al crear el documento
public class Editor {
    private List<ImagenReal> imagenes = new ArrayList<>();

    public void abrirDocumento(String ruta) {
        // ⚠️ Carga TODAS las imágenes de disco inmediatamente
        for (String archivo : obtenerRutasImagenes(ruta)) {
            imagenes.add(new ImagenReal(archivo)); // Lectura costosa
        }
    }
}

🔹 Escenarios típicos donde el Proxy es la solución

EscenarioProblemaTipo de Proxy
Imágenes en un documentoCargar todas al abrir es lentoProxy Virtual
Objetos en servidor remotoEl cliente no accede directamenteProxy Remoto
Recursos sensiblesNo todos los usuarios deben accederProxy de Protección
Consultas repetidas a BDRepetir la misma consulta es ineficienteProxy de Caché
Auditoría de accesosRegistrar quién accede y cuándoProxy de Registro

📐 Diagrama UML del patrón Proxy

La estructura del patrón Proxy se compone de tres participantes fundamentales, conectados mediante una interfaz común:

Diagrama UML — Estructura del patrón Proxy
        ┌─────────────────────┐
        │    <<interface>>     │
        │       Sujeto         │
        ├─────────────────────┤
        │ + operacion(): void  │
        └──────────┬──────────┘
                   │ implements
          ┌────────┴────────┐
          │                 │
┌─────────┴───────┐  ┌─────┴──────────────┐
│   SujetoReal    │  │      Proxy          │
├─────────────────┤  ├────────────────────┤
│ + operacion()   │  │ - sujetoReal: Sujeto│
│   // lógica real│  │ + operacion(): void │
└─────────────────┘  └────────────────────┘
                          │ usa ──▶ SujetoReal

🔹 Participantes del patrón

ParticipanteResponsabilidad
Sujeto (Subject)Interfaz común que declara las operaciones. Permite la sustitución transparente.
SujetoReal (RealSubject)El objeto real con la lógica de negocio. El proxy delega en él cuando es apropiado.
ProxyMantiene referencia al SujetoReal. Controla el acceso: creación bajo demanda, permisos, caché o registro.

🗂️ Tipos de Proxy

El Gang of Four identificó tres variantes principales. Con la evolución del software, la comunidad ha consolidado variantes adicionales:

TipoTambién llamadoPropósito principal
VirtualLazy ProxyRetrasar la creación del objeto costoso hasta que se necesite
De ProtecciónProtection ProxyControlar el acceso según permisos o roles
RemotoRemote Proxy, StubRepresentar localmente un objeto en otro espacio de direcciones
De CachéSmart ProxyAlmacenar resultados para evitar operaciones repetidas
De RegistroLogging ProxyRegistrar operaciones para auditoría

⏳ Proxy Virtual (Lazy Loading)

El Proxy Virtual es la variante más utilizada. Su propósito es retrasar la creación de un objeto costoso hasta el momento en que realmente se necesite (lazy initialization). El ejemplo clásico de los GoF es el editor de documentos con imágenes pesadas.

Java — Interfaz Imagen (Sujeto)
public interface Imagen {
    void mostrar();
    int getAncho();
    int getAlto();
    String getNombre();
}
Java — ImagenReal (SujetoReal): carga costosa
import java.io.File;

public class ImagenReal implements Imagen {
    private String rutaArchivo;
    private byte[] datosPixeles;
    private int ancho, alto;

    public ImagenReal(String rutaArchivo) {
        this.rutaArchivo = rutaArchivo;
        cargarDesdeArchivo();  // ⚠️ Operación costosa
    }

    private void cargarDesdeArchivo() {
        System.out.println("  [ImagenReal] Cargando: " + rutaArchivo);
        try { Thread.sleep(500); } catch (InterruptedException e) { }
        this.datosPixeles = new byte[1024 * 1024]; // 1 MB
        this.ancho = 1920;
        this.alto = 1080;
        System.out.println("  [ImagenReal] Cargada: " + ancho + "x" + alto);
    }

    @Override public void mostrar() {
        System.out.println("  [ImagenReal] Mostrando: " + rutaArchivo);
    }
    @Override public int getAncho()  { return ancho; }
    @Override public int getAlto()   { return alto; }
    @Override public String getNombre() {
        return new File(rutaArchivo).getName();
    }
}
Java — ProxyImagen (Proxy Virtual)
public class ProxyImagen implements Imagen {
    private String rutaArchivo;
    private ImagenReal imagenReal;  // null hasta que se necesite

    public ProxyImagen(String rutaArchivo) {
        this.rutaArchivo = rutaArchivo;
        System.out.println("  [Proxy] Proxy creado para: " + rutaArchivo);
    }

    private void cargarSiNecesario() {
        if (imagenReal == null) {
            System.out.println("  [Proxy] Primera solicitud → cargando...");
            imagenReal = new ImagenReal(rutaArchivo);
        }
    }

    @Override public void mostrar() {
        cargarSiNecesario();
        imagenReal.mostrar();
    }
    @Override public int getAncho() {
        cargarSiNecesario();
        return imagenReal.getAncho();
    }
    @Override public int getAlto() {
        cargarSiNecesario();
        return imagenReal.getAlto();
    }
    @Override public String getNombre() {
        return new java.io.File(rutaArchivo).getName();
    }
}
Java — Demostración del Proxy Virtual
public class DemoProxyVirtual {
    public static void main(String[] args) {
        System.out.println("=== Creando documento con 3 imágenes ===");
        Imagen img1 = new ProxyImagen("/fotos/paisaje_4k.jpg");
        Imagen img2 = new ProxyImagen("/fotos/retrato_hd.png");
        Imagen img3 = new ProxyImagen("/fotos/diagrama_uml.svg");

        System.out.println("\n=== Documento abierto (0 cargadas) ===");
        System.out.println("Nombre img1: " + img1.getNombre());

        System.out.println("\n=== Scroll hasta img2 ===");
        img2.mostrar();   // Carga por primera vez

        System.out.println("\n=== img2 de nuevo ===");
        img2.mostrar();   // Ya cargada — no recarga
    }
}
Salida esperada
=== Creando documento con 3 imágenes ===
  [Proxy] Proxy creado para: /fotos/paisaje_4k.jpg
  [Proxy] Proxy creado para: /fotos/retrato_hd.png
  [Proxy] Proxy creado para: /fotos/diagrama_uml.svg

=== Documento abierto (0 cargadas) ===
Nombre img1: paisaje_4k.jpg

=== Scroll hasta img2 ===
  [Proxy] Primera solicitud → cargando...
  [ImagenReal] Cargando: /fotos/retrato_hd.png
  [ImagenReal] Cargada: 1920x1080
  [ImagenReal] Mostrando: /fotos/retrato_hd.png

=== img2 de nuevo ===
  [ImagenReal] Mostrando: /fotos/retrato_hd.png
✅ Observa: Al crear el documento, solo se instancian ProxyImagen (ligeros). La imagen real se carga en la primera llamada a mostrar(). La segunda llamada reutiliza la instancia ya cargada.

🔒 Proxy de Protección (acceso controlado)

El Proxy de Protección controla el acceso al objeto real en función de los permisos del solicitante. El proxy verifica las credenciales antes de delegar la operación al sujeto real.

Java — Proxy de Protección para documento confidencial
public interface Documento {
    String leer();
    void escribir(String contenido);
    void eliminar();
}

public class DocumentoConfidencial implements Documento {
    private String contenido;
    private final String titulo;

    public DocumentoConfidencial(String titulo, String contenido) {
        this.titulo = titulo;
        this.contenido = contenido;
    }
    @Override public String leer() { return contenido; }
    @Override public void escribir(String nuevoContenido) {
        this.contenido = nuevoContenido;
        System.out.println("[Doc] Actualizado: " + titulo);
    }
    @Override public void eliminar() {
        this.contenido = null;
        System.out.println("[Doc] Eliminado: " + titulo);
    }
}

public class ProxyDocumentoProtegido implements Documento {
    private final DocumentoConfidencial docReal;
    private final String rolUsuario;

    public ProxyDocumentoProtegido(DocumentoConfidencial doc, String rol) {
        this.docReal = doc;
        this.rolUsuario = rol;
    }

    @Override public String leer() {
        if ("INVITADO".equals(rolUsuario)) {
            throw new SecurityException("Acceso denegado: invitados no pueden leer");
        }
        System.out.println("[Proxy] Lectura concedida a: " + rolUsuario);
        return docReal.leer();
    }

    @Override public void escribir(String contenido) {
        if (!"ADMIN".equals(rolUsuario) && !"EDITOR".equals(rolUsuario)) {
            throw new SecurityException("Rol " + rolUsuario + " no puede escribir");
        }
        System.out.println("[Proxy] Escritura concedida a: " + rolUsuario);
        docReal.escribir(contenido);
    }

    @Override public void eliminar() {
        if (!"ADMIN".equals(rolUsuario)) {
            throw new SecurityException("Solo ADMIN puede eliminar");
        }
        System.out.println("[Proxy] Eliminación concedida a: " + rolUsuario);
        docReal.eliminar();
    }
}
⚠️ Importante: El Proxy de Protección no reemplaza un sistema de seguridad completo. En aplicaciones reales, se complementa con frameworks como Spring Security para autenticación, autorización y auditoría integral.

🌐 Proxy Remoto

El Proxy Remoto proporciona una representación local de un objeto que reside en otro espacio de direcciones: otra JVM, otro servidor o incluso otro continente. En Java, este concepto es la base de RMI (Remote Method Invocation) y Enterprise JavaBeans (EJB). El stub generado por RMI es precisamente un proxy remoto.

Java — Ejemplo conceptual de Proxy Remoto
public interface ServicioClima {
    String obtenerTemperatura(String ciudad);
}

public class ServicioClimaReal implements ServicioClima {
    @Override
    public String obtenerTemperatura(String ciudad) {
        return "22°C en " + ciudad;
    }
}

public class ProxyServicioClima implements ServicioClima {
    private final String urlServidor;

    public ProxyServicioClima(String urlServidor) {
        this.urlServidor = urlServidor;
    }

    @Override
    public String obtenerTemperatura(String ciudad) {
        System.out.println("[ProxyRemoto] Petición a: " + urlServidor);
        // Aquí iría HttpClient, JSON, manejo de errores...
        try { Thread.sleep(200); } catch (InterruptedException e) { }
        return "22°C en " + ciudad + " (vía proxy remoto)";
    }
}

💾 Proxy de Caché (Smart Proxy)

El Proxy de Caché almacena resultados de operaciones previas para evitar repetirlas. Es útil cuando el objeto real realiza operaciones costosas cuyos resultados no cambian frecuentemente.

Java — Proxy de Caché para consultas a BD
import java.util.HashMap;
import java.util.Map;

public interface RepositorioProductos {
    String buscarPorId(int id);
}

public class RepositorioProductosBD implements RepositorioProductos {
    @Override
    public String buscarPorId(int id) {
        System.out.println("  [BD] SELECT * FROM productos WHERE id=" + id);
        try { Thread.sleep(300); } catch (InterruptedException e) { }
        return "Producto #" + id + " (desde BD)";
    }
}

public class ProxyCacheProductos implements RepositorioProductos {
    private final RepositorioProductosBD repoReal;
    private final Map<Integer, String> cache = new HashMap<>();

    public ProxyCacheProductos(RepositorioProductosBD repo) {
        this.repoReal = repo;
    }

    @Override
    public String buscarPorId(int id) {
        if (cache.containsKey(id)) {
            System.out.println("  [Cache] HIT para id=" + id);
            return cache.get(id);
        }
        System.out.println("  [Cache] MISS → delegando a BD id=" + id);
        String resultado = repoReal.buscarPorId(id);
        cache.put(id, resultado);
        return resultado;
    }

    public void invalidar(int id) { cache.remove(id); }
    public void invalidarTodo()   { cache.clear(); }
}

🔀 Proxy vs. otros patrones estructurales

El patrón Proxy se confunde con otros patrones que «envuelven» un objeto. La diferencia está en la intención:

PatrónIntención¿Cambia interfaz?¿Controla acceso?
ProxyControlar el acceso❌ No✅ Sí
DecoratorAñadir funcionalidad❌ No❌ No
AdapterConvertir interfaces✅ Sí❌ No
BridgeSeparar abstracción/implementación✅ Sí❌ No
FacadeSimplificar interfaz compleja✅ Sí❌ No
💡 Regla mnemotécnica: El Proxy controla, el Decorator añade, el Adapter traduce, el Bridge desacopla y el Facade simplifica.

🧩 Ejemplo integrador: sistema de documentos

Este ejemplo combina Proxy Virtual con Proxy de Registro en un sistema de gestión documental, demostrando cómo los proxies pueden encadenarse:

Java — Sistema integrador con proxies encadenados
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

// ── Interfaz común ──
public interface Recurso {
    String obtenerContenido();
    String getNombre();
    long getTamano();
}

// ── Sujeto Real ──
public class ArchivoGrande implements Recurso {
    private final String nombre;
    private final byte[] datos;

    public ArchivoGrande(String nombre) {
        this.nombre = nombre;
        System.out.println("    [Archivo] Cargando '" + nombre + "'...");
        try { Thread.sleep(800); } catch (InterruptedException e) { }
        this.datos = new byte[5 * 1024 * 1024]; // 5 MB
        System.out.println("    [Archivo] Cargado (" + getTamano()/1024 + " KB)");
    }
    @Override public String obtenerContenido() {
        return "Contenido de " + nombre + " (" + datos.length + " bytes)";
    }
    @Override public String getNombre() { return nombre; }
    @Override public long getTamano()   { return datos.length; }
}

// ── Proxy Virtual ──
public class ProxyVirtualRecurso implements Recurso {
    private final String nombre;
    private ArchivoGrande archivoReal;

    public ProxyVirtualRecurso(String nombre) { this.nombre = nombre; }

    private void cargarSiNecesario() {
        if (archivoReal == null) archivoReal = new ArchivoGrande(nombre);
    }
    @Override public String obtenerContenido() {
        cargarSiNecesario();
        return archivoReal.obtenerContenido();
    }
    @Override public String getNombre() { return nombre; }
    @Override public long getTamano()   {
        cargarSiNecesario();
        return archivoReal.getTamano();
    }
}

// ── Proxy de Registro ──
public class ProxyRegistroRecurso implements Recurso {
    private final Recurso recursoInterno;
    private final List<String> log = new ArrayList<>();
    private static final DateTimeFormatter FMT =
            DateTimeFormatter.ofPattern("HH:mm:ss.SSS");

    public ProxyRegistroRecurso(Recurso recurso) {
        this.recursoInterno = recurso;
    }
    @Override public String obtenerContenido() {
        String e = FMT.format(LocalDateTime.now()) + " → obtenerContenido()";
        log.add(e);
        System.out.println("    [Log] " + e);
        return recursoInterno.obtenerContenido();
    }
    @Override public String getNombre() { return recursoInterno.getNombre(); }
    @Override public long getTamano()   { return recursoInterno.getTamano(); }
    public List<String> getRegistro() { return List.copyOf(log); }
}

// ── Demo ──
public class DemoSistemaDocumental {
    public static void main(String[] args) {
        // Encadenamiento: Log → Virtual → ArchivoGrande
        var informe = new ProxyRegistroRecurso(
                new ProxyVirtualRecurso("informe_2025.pdf"));
        var presentacion = new ProxyRegistroRecurso(
                new ProxyVirtualRecurso("presentacion_q4.pptx"));

        System.out.println("1. Documentos registrados (no cargados)");
        System.out.println("   - " + informe.getNombre());
        System.out.println("   - " + presentacion.getNombre());

        System.out.println("\n2. Abriendo informe:");
        System.out.println("   " + informe.obtenerContenido());

        System.out.println("\n3. Informe de nuevo (ya cargado):");
        System.out.println("   " + informe.obtenerContenido());

        System.out.println("\n4. Presentación nunca se cargó ✅");

        System.out.println("\n5. Log de accesos:");
        informe.getRegistro().forEach(e -> System.out.println("   • " + e));
    }
}
💡 Encadenamiento: ProxyRegistroRecurso envuelve a ProxyVirtualRecurso, que controla la creación de ArchivoGrande. Cada proxy tiene una responsabilidad clara (SRP).

✅ Buenas prácticas y errores frecuentes

🔹 Buenas prácticas

PrácticaMotivo
Usar interfaces, no clases concretasPermite sustituir proxy ↔ real sin cambiar el cliente
Un proxy = una responsabilidadEvita proxies «omnibus» que cachean, validan y logean a la vez
Considerar java.lang.reflect.ProxyPara proxies dinámicos sin código repetitivo
Documentar que se trabaja con proxyFacilita mantenimiento y depuración
Gestionar concurrencia en cachéUsar ConcurrentHashMap en entornos multihilo

🔹 Errores frecuentes

ErrorConsecuenciaSolución
No implementar toda la interfazViolación del contratoImplementar todos los métodos
Proxy virtual sin null-checkNullPointerExceptionManejar excepciones en cargarSiNecesario()
Caché sin invalidaciónDatos obsoletosDefinir TTL o invalidación por eventos
Confundir Proxy con DecoratorDiseño con intención incorrectaPreguntarse: ¿controlo acceso o añado funcionalidad?

📝 Ejercicios prácticos

Ejercicio 1 — Proxy Virtual para conexión a BD

Crea una interfaz ConexionBD con métodos conectar(), ejecutarConsulta(String sql) y cerrar(). Implementa ConexionBDReal que simule 2 segundos de conexión. Luego implementa ProxyConexionBD que retrase la conexión hasta la primera consulta.

Ver solución
Java — Solución Ejercicio 1
public interface ConexionBD {
    void conectar();
    String ejecutarConsulta(String sql);
    void cerrar();
}

public class ConexionBDReal implements ConexionBD {
    private boolean conectada = false;

    @Override public void conectar() {
        System.out.println("[BD] Conectando...");
        try { Thread.sleep(2000); } catch (InterruptedException e) { }
        conectada = true;
        System.out.println("[BD] Conectada");
    }
    @Override public String ejecutarConsulta(String sql) {
        if (!conectada) throw new IllegalStateException("No conectada");
        return "Resultado de: " + sql;
    }
    @Override public void cerrar() {
        conectada = false;
        System.out.println("[BD] Cerrada");
    }
}

public class ProxyConexionBD implements ConexionBD {
    private ConexionBDReal conexionReal;

    private void asegurarConexion() {
        if (conexionReal == null) {
            conexionReal = new ConexionBDReal();
            conexionReal.conectar();
        }
    }
    @Override public void conectar() {
        System.out.println("[Proxy] Registrada (se conectará al primer uso)");
    }
    @Override public String ejecutarConsulta(String sql) {
        asegurarConexion();
        return conexionReal.ejecutarConsulta(sql);
    }
    @Override public void cerrar() {
        if (conexionReal != null) { conexionReal.cerrar(); conexionReal = null; }
    }
}

Ejercicio 2 — Proxy de Protección con roles

Diseña una interfaz CuentaBancaria con consultarSaldo(), depositar(double) y retirar(double). Implementa un proxy que permita a todos consultar saldo, pero solo al «TITULAR» depositar y retirar, y al «AUTORIZADO» solo depositar.

Ver solución
Java — Solución Ejercicio 2
public interface CuentaBancaria {
    double consultarSaldo();
    void depositar(double cantidad);
    void retirar(double cantidad);
}

public class CuentaReal implements CuentaBancaria {
    private double saldo;
    public CuentaReal(double saldoInicial) { this.saldo = saldoInicial; }
    @Override public double consultarSaldo() { return saldo; }
    @Override public void depositar(double c) {
        saldo += c;
        System.out.println("Depósito: +" + c + " → " + saldo);
    }
    @Override public void retirar(double c) {
        if (c > saldo) throw new IllegalArgumentException("Fondos insuficientes");
        saldo -= c;
        System.out.println("Retiro: -" + c + " → " + saldo);
    }
}

public class ProxyCuentaProtegida implements CuentaBancaria {
    private final CuentaReal cuentaReal;
    private final String rol;

    public ProxyCuentaProtegida(CuentaReal cuenta, String rol) {
        this.cuentaReal = cuenta; this.rol = rol;
    }
    @Override public double consultarSaldo() {
        return cuentaReal.consultarSaldo();
    }
    @Override public void depositar(double c) {
        if (!"TITULAR".equals(rol) && !"AUTORIZADO".equals(rol))
            throw new SecurityException("Rol " + rol + " no puede depositar");
        cuentaReal.depositar(c);
    }
    @Override public void retirar(double c) {
        if (!"TITULAR".equals(rol))
            throw new SecurityException("Solo TITULAR puede retirar");
        cuentaReal.retirar(c);
    }
}

Ejercicio 3 — Proxy de Caché con TTL

Extiende ProxyCacheProductos para que cada entrada tenga un TTL de 5 segundos. Pasado ese tiempo, la siguiente consulta va a BD y actualiza la caché.

Ver solución
Java — Solución Ejercicio 3
import java.util.HashMap;
import java.util.Map;

public class ProxyCacheTTL implements RepositorioProductos {
    private final RepositorioProductosBD repoReal;
    private final Map<Integer, String> cache = new HashMap<>();
    private final Map<Integer, Long> timestamps = new HashMap<>();
    private final long ttlMs;

    public ProxyCacheTTL(RepositorioProductosBD repo, long ttlMs) {
        this.repoReal = repo; this.ttlMs = ttlMs;
    }

    @Override public String buscarPorId(int id) {
        long ahora = System.currentTimeMillis();
        if (cache.containsKey(id)) {
            long edad = ahora - timestamps.get(id);
            if (edad < ttlMs) {
                System.out.println("[Cache] HIT (edad:" + edad + "ms) id=" + id);
                return cache.get(id);
            }
            System.out.println("[Cache] EXPIRADO id=" + id);
            cache.remove(id); timestamps.remove(id);
        }
        System.out.println("[Cache] MISS → BD id=" + id);
        String r = repoReal.buscarPorId(id);
        cache.put(id, r); timestamps.put(id, ahora);
        return r;
    }
}
// Uso: new ProxyCacheTTL(repoBD, 5000) → TTL 5 segundos

❓ Preguntas frecuentes sobre El Patrón Proxy en Java

Las dudas más comunes respondidas de forma clara y directa.

El Proxy controla el acceso al objeto real (puede decidir si se crea, si se permite el acceso o si se cachea), mientras que el Decorator añade funcionalidad adicional sin controlar el acceso. Ambos envuelven un objeto, pero con intenciones distintas.
Debes usar un Proxy Virtual cuando la creación del objeto real es costosa (lectura de disco, conexión de red, cálculo intensivo) y existe la posibilidad de que el cliente nunca lo necesite. El proxy retrasa la creación hasta el primer uso real.
No necesariamente. Cada tipo de proxy tiene una única responsabilidad clara: el virtual gestiona la carga diferida, el de protección gestiona el control de acceso, y el de caché gestiona la reutilización. Si un proxy intenta hacer varias cosas a la vez, sí podría violar el SRP.
Sí, es muy habitual. Un Proxy puede combinarse con Factory Method para decidir qué implementación real instanciar, con Singleton para garantizar una única instancia del objeto real, o con Observer para notificar cambios. La combinación depende de los requisitos del sistema.
Sí. Java ofrece la clase java.lang.reflect.Proxy y la interfaz InvocationHandler para crear proxies dinámicos en tiempo de ejecución. Esto permite interceptar llamadas a métodos sin escribir una clase proxy estática para cada interfaz. Frameworks como Spring lo usan extensivamente para AOP.
Valora este artículo

💬 Foro de discusión

¿Tienes dudas sobre El Patrón Proxy en Java? Comparte tu pregunta con la comunidad.

¿Tienes cuenta? o comenta como invitado ↓

Todavía no hay mensajes. ¡Sé el primero en participar!