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
| Roll | Ansvar |
|---|---|
| Service Interface | Gemensamt gränssnitt för Service och Proxy. |
| Service | Det verkliga objektet med affärslogiken. |
| Proxy | Hå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