Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Patrones de Diseño Estructurales

Proxy

Controlar acceso a otro objeto

Universidad Nacional de Rio Negro - Sede Andina

Definición

El patrón Proxy proporciona un sustituto (proxy) para otro objeto para controlar su acceso, permitiendo operaciones adicionales antes/después de acceder al objeto real.

Origen e Historia

Gang of Four 1994. Surge de sistemas distribuidos: necesidad de interceder antes de acceder a objetos remotos. Popularizado en RPC, ORM (Hibernate), y frameworks web.

Motivación

Necesario cuando:

Contexto

Patrón: Cliente → Proxy → RealSubject

Anatomía:

Tipos:

Cuando aplica

Usá Proxy cuando:

Cuando no aplica

Evitá Proxy cuando:

Consecuencias de su uso

Positivas

Negativas

Alternativas

AspectoProxyDecoratorFacade
IntenciónControlar accesoAgregar responsabilidadesSimplificar
CreaciónProxy crea realCliente inyectaInternos
RelaciónUno-a-unoMúltipleCoordinación

Estructura

Problema

// ❌ Sin Proxy: cliente directo a recurso costoso
class BaseDatos {
    public String query(String sql) {
        System.out.println("Ejecutando: " + sql);
        return "Resultado";
    }
}

// Cada cliente puede ejecutar cualquier query
// Sin control, caché, o auditoría
BaseDatos db = new BaseDatos();
db.query("DELETE FROM usuarios"); // ¡Peligroso!

Solución

/**
 * Sujeto: interfaz común.
 */
public interface AccesoBD {
    String query(String sql);
}

/**
 * Sujeto real: operación costosa.
 */
public class BaseDatosReal implements AccesoBD {
    @Override
    public String query(String sql) {
        System.out.println("[BD] Ejecutando: " + sql);
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        return "Resultado de: " + sql;
    }
}

/**
 * Proxy: añade autenticación, caché y logging.
 */
public class ProxyAccesoBD implements AccesoBD {
    private AccesoBDReal acceso;
    private Map<String, String> caché = new HashMap<>();
    private String usuarioActual;
    private boolean estaAutenticado = false;
    
    public ProxyAccesoBD(String usuario, String contraseña) {
        if ("admin".equals(usuario) && "123".equals(contraseña)) {
            this.usuarioActual = usuario;
            this.estaAutenticado = true;
            this.acceso = new BaseDatosReal();
            System.out.println("[PROXY] Usuario " + usuario + " autenticado");
        } else {
            System.out.println("[PROXY] ¡Acceso denegado!");
        }
    }
    
    @Override
    public String query(String sql) {
        if (!estaAutenticado) {
            throw new SecurityException("No autenticado");
        }
        
        // Verificar si es operación permitida
        if (sql.toUpperCase().contains("DELETE") || sql.toUpperCase().contains("DROP")) {
            throw new SecurityException("Operación no permitida: " + sql);
        }
        
        // Verificar caché
        if (caché.containsKey(sql)) {
            System.out.println("[PROXY] Retornando resultado en caché");
            return caché.get(sql);
        }
        
        // Registrar acceso
        System.out.println("[PROXY] Usuario " + usuarioActual + " ejecutando query");
        
        // Delegar al objeto real
        String resultado = acceso.query(sql);
        
        // Almacenar en caché
        caché.put(sql, resultado);
        
        return resultado;
    }
}

// ✅ Uso controlado
ProxyAccesoBD proxy = new ProxyAccesoBD("admin", "123");

// Exitoso
System.out.println(proxy.query("SELECT * FROM usuarios"));
System.out.println(proxy.query("SELECT * FROM usuarios")); // Del caché

// Rechazado
try {
    proxy.query("DELETE FROM usuarios");
} catch (SecurityException e) {
    System.out.println("[ERROR] " + e.getMessage());
}

Diagramas

Diagrama de Clases

Diagrama de Secuencia

Ejemplos

Ejemplo 1: Lazy Loading de Imagen

public interface Imagen {
    void mostrar();
}

public class ImagenReal implements Imagen {
    private String archivo;
    
    public ImagenReal(String archivo) {
        cargarImagen(archivo);
        this.archivo = archivo;
    }
    
    private void cargarImagen(String archivo) {
        System.out.println("[BD] Cargando imagen " + archivo);
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
    }
    
    @Override
    public void mostrar() {
        System.out.println("Mostrando: " + archivo);
    }
}

public class ProxyImagen implements Imagen {
    private String archivo;
    private ImagenReal imagenReal;
    
    public ProxyImagen(String archivo) {
        this.archivo = archivo;
    }
    
    @Override
    public void mostrar() {
        if (imagenReal == null) {
            System.out.println("[PROXY] Cargando imagen por primera vez...");
            imagenReal = new ImagenReal(archivo);
        }
        imagenReal.mostrar();
    }
}

// Uso: Imagen no se carga hasta que se llama mostrar()
Imagen img = new ProxyImagen("grande.jpg");
System.out.println("Imagen creada");  // Sin cargar
img.mostrar();  // Aquí se carga
img.mostrar();  // Ya está en caché

Variantes

Proxy Remoto (RPC):

public class ProxyServidor implements ServicioRemoto {
    private String url;
    
    @Override
    public String llamada(String param) {
        System.out.println("[PROXY] Conectando a " + url);
        // ... ejecutar RPC ...
        return "Respuesta del servidor";
    }
}

Ejercicios

Resumen

El patrón Proxy es fundamental para controlar acceso a objetos costosos o protegidos. Su versatilidad permite múltiples variantes (virtual, remoto, protector) adaptándose a diferentes necesidades. Aunque introduce indirección, sus beneficios en seguridad, performance y auditoría lo hacen indispensable en arquitecturas empresariales.

Próximo paso

Para seguir, conviene pasar a el material siguiente, donde el recorrido continúa sobre esta base.