Proxy (Ombud)

Introduktion

Proxy är ett strukturmönster som tillhandahåller ett substitut eller en platshållare för ett annat objekt. En proxy kontrollerar åtkomsten till originalobjektet och kan utföra något före eller efter begäran når det.


Problem

Du har ett tungt objekt — t.ex. en databasanslutning eller ett stort dokument — som är dyrt att skapa och initialisera. Du vill inte skapa det förrän det faktiskt behövs, men du vill heller inte ändra i all klientkod som använder det.

Eller: du vill lägga till åtkomstkontroll, caching eller loggning runt ett befintligt objekt utan att ändra det.

Lösning

Proxy implementerar samma gränssnitt som originalobjektet. Klientkoden märker ingen skillnad — den arbetar mot gränssnittet. Proxyn hanterar det extra arbetet: lazy initialization, behörighetskontroll, caching eller loggning.

När ska Proxy användas?

  • Lazy initialization (Virtual Proxy) — Skapa tunga objekt först när de verkligen behövs.
  • Åtkomstkontroll (Protection Proxy) — Kontrollera om anroparen har rätt behörighet.
  • Caching (Caching Proxy) — Cacha resultat för kostsamma operationer.
  • Loggning (Logging Proxy) — Logga alla anrop transparent.

Struktur

RollAnsvar
Service InterfaceGemensamt gränssnitt för Service och Proxy.
ServiceDet verkliga objektet med affärslogiken.
ProxyHåller en referens till Service; kontrollerar åtkomst och utför extra arbete.

Exempel — Dokumentlagring med caching och behörighetskontroll (Java)

Scenariot: En DocumentService som är dyr att anropa. En proxy hanterar caching av resultat och kontrollerar behörighet — helt transparent för klientkoden.

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

// ── Service Interface ─────────────────────────────────────

public interface DocumentService {
    String getDocument(String documentId);
    void   saveDocument(String documentId, String content);
    void   deleteDocument(String documentId);
}

// ── Real Service ──────────────────────────────────────────

public class RealDocumentService implements DocumentService {
    private final Map<String, String> storage = new HashMap<>();

    public RealDocumentService() {
        // Simulerar dyr initialisering
        System.out.println("📂 DocumentService initialiserad (dyr operation)");
        storage.put("doc-1", "Innehåll i dokument 1");
        storage.put("doc-2", "Innehåll i dokument 2");
        storage.put("doc-3", "Konfidentiellt dokument 3");
    }

    @Override
    public String getDocument(String documentId) {
        System.out.println("  [DB] Hämtar " + documentId + " från databasen...");
        return storage.getOrDefault(documentId, null);
    }

    @Override
    public void saveDocument(String documentId, String content) {
        System.out.println("  [DB] Sparar " + documentId + " i databasen...");
        storage.put(documentId, content);
    }

    @Override
    public void deleteDocument(String documentId) {
        System.out.println("  [DB] Tar bort " + documentId + " från databasen...");
        storage.remove(documentId);
    }
}

// ── Proxy — Lazy init + caching + behörighetskontroll ────

public class DocumentServiceProxy implements DocumentService {
    private RealDocumentService         realService;   // Lazy — skapas vid behov
    private final Map<String, String>   cache        = new HashMap<>();
    private final String                currentUser;
    private final Set<String>           adminUsers   = Set.of("admin", "superuser");

    public DocumentServiceProxy(String currentUser) {
        this.currentUser = currentUser;
        System.out.println("🔐 Proxy skapad för användare: " + currentUser);
    }

    private RealDocumentService getService() {
        if (realService == null) {
            realService = new RealDocumentService(); // Lazy initialization
        }
        return realService;
    }

    private boolean isAdmin() {
        return adminUsers.contains(currentUser);
    }

    @Override
    public String getDocument(String documentId) {
        // Kontrollera cache först
        if (cache.containsKey(documentId)) {
            System.out.println("  ⚡ Cache-träff för " + documentId);
            return cache.get(documentId);
        }

        String content = getService().getDocument(documentId);

        if (content != null) {
            cache.put(documentId, content); // Cacha resultatet
        }

        return content;
    }

    @Override
    public void saveDocument(String documentId, String content) {
        System.out.println("  🔍 Kontrollerar behörighet för " + currentUser + "...");

        if (!isAdmin()) {
            System.out.println("  ❌ Åtkomst nekad: " + currentUser + " har inte skrivbehörighet.");
            return;
        }

        cache.remove(documentId); // Ogiltigförklara cache
        getService().saveDocument(documentId, content);
        System.out.println("  ✅ Sparad av " + currentUser);
    }

    @Override
    public void deleteDocument(String documentId) {
        System.out.println("  🔍 Kontrollerar behörighet för " + currentUser + "...");

        if (!isAdmin()) {
            System.out.println("  ❌ Åtkomst nekad: Borttagning kräver adminbehörighet.");
            return;
        }

        cache.remove(documentId);
        getService().deleteDocument(documentId);
        System.out.println("  ✅ Borttaget av " + currentUser);
    }
}

// ── Klientkod ─────────────────────────────────────────────

public class Application {
    public static void main(String[] args) {
        System.out.println("=== Vanlig användare ===");
        DocumentService userProxy = new DocumentServiceProxy("anna");

        // Första anropet — går till databasen
        System.out.println("\nHämtar doc-1:");
        String doc1 = userProxy.getDocument("doc-1");
        System.out.println("  Innehåll: " + doc1);

        // Andra anropet — cachar
        System.out.println("\nHämtar doc-1 igen:");
        String doc1Again = userProxy.getDocument("doc-1");
        System.out.println("  Innehåll: " + doc1Again);

        // Försöker spara — nekas
        System.out.println("\nFörsöker spara doc-4:");
        userProxy.saveDocument("doc-4", "Nytt innehåll");

        System.out.println("\n=== Admin-användare ===");
        DocumentService adminProxy = new DocumentServiceProxy("admin");

        System.out.println("\nSparar doc-4:");
        adminProxy.saveDocument("doc-4", "Admins nya dokument");

        System.out.println("\nHämtar doc-4:");
        System.out.println("  Innehåll: " + adminProxy.getDocument("doc-4"));

        System.out.println("\nTar bort doc-2:");
        adminProxy.deleteDocument("doc-2");
    }
}

Output:

=== Vanlig användare ===
🔐 Proxy skapad för användare: anna

Hämtar doc-1:
  [DB] Hämtar doc-1 från databasen...
📂 DocumentService initialiserad (dyr operation)
  Innehåll: Innehåll i dokument 1

Hämtar doc-1 igen:
  ⚡ Cache-träff för doc-1
  Innehåll: Innehåll i dokument 1

Försöker spara doc-4:
  🔍 Kontrollerar behörighet för anna...
  ❌ Åtkomst nekad: anna har inte skrivbehörighet.

=== Admin-användare ===
🔐 Proxy skapad för användare: admin

Sparar doc-4:
  🔍 Kontrollerar behörighet för admin...
  [DB] Sparar doc-4 i databasen...
📂 DocumentService initialiserad (dyr operation)
  ✅ Sparad av admin

Hämtar doc-4:
  [DB] Hämtar doc-4 från databasen...
  Innehåll: Admins nya dokument

Tar bort doc-2:
  🔍 Kontrollerar behörighet för admin...
  [DB] Tar bort doc-2 från databasen...
  ✅ Borttaget av admin

Fördelar

  • Single Responsibility Principle — Caching, behörighet och loggning separeras från affärslogiken.
  • Open/Closed Principle — Lägg till nya proxyer utan att ändra Service-klassen.
  • Lazy initialization skjuter upp dyr initialisering tills den faktiskt behövs.

Nackdelar

  • Svarstiden kan öka med ett extra indirektionslager.
  • Koden kan bli mer komplex med flera proxylager.

Av Victor Hernandez från Bytebase.se