Proxy (Helyettesítő)
Kategória: Strukturális minta (Structural Pattern)
Lényege és célja
A Proxy minta célja, hogy egy objektum elé egy "helyettesítőt" vagy "képviselőt" állítson, amely kontrollálja a hozzáférést az eredeti (valódi) objektumhoz.
A Proxy és a Valódi Objektum ugyanazt az interfészt implementálja, így a kliens (felhasználó) számára teljesen észrevétlen marad, hogy nem az eredeti objektummal, hanem annak helyettesítőjével beszélget. Ez lehetővé teszi, hogy a Proxy a háttérben extra logikát (pl. jogosultság-ellenőrzés, késleltetés, cache-elés) hajtson végre, mielőtt vagy miután továbbadja (delegálja) a kérést a valódi objektumnak.
A Proxy leggyakoribb típusai:
A Proxy minta egy gyűjtőfogalom, a gyakorlatban több specifikus fajtáját különböztetjük meg a feladatától függően: 1. Virtual Proxy (Virtuális helyettesítő - Lazy Initialization): Késlelteti egy memória- vagy erőforrás-igényes objektum létrehozását egészen addig, amíg ténylegesen szükség nem lesz rá (ezt kódoltuk le az adatbázis-kapcsolattal). 2. Protection Proxy (Védelmi helyettesítő): Jogosultságokat ellenőriz. Csak akkor engedi tovább a hívást a valódi objektumnak, ha a kliensnek megvan hozzá a megfelelő jogosultsága. 3. Caching Proxy (Gyorsítótárazó helyettesítő): Eltárolja a drága műveletek eredményét (pl. hálózati lekérdezés), és a következő azonos kérésnél a cache-ből adja vissza, anélkül, hogy a valódi objektumot zavarná. 4. Remote Proxy (Távoli helyettesítő): Egy másik szerveren vagy memóriaterületen lévő objektumot képvisel lokálisan (pl. gRPC, RMI).
Mikor használjuk?
- Amikor az eredeti objektum létrehozása túl drága (lassú), és csak akkor akarjuk példányosítani, ha feltétlenül muszáj.
- Amikor hozzáférési kontrollt (biztonsági réteget) szeretnénk tenni egy meglévő objektum elé anélkül, hogy az eredeti kódját módosítanánk.
- Amikor logolni szeretnénk az eredeti objektum felé menő kéréseket (és azok eredményeit).
Mermaid Diagram
Az ábrán jól látható a minta lelke: a kliens csak a DatabaseConnector interfészt ismeri. A ProxyDatabaseConnector elfogja a hívást, megvizsgálja a saját belső állapotát, és csak akkor hozza létre a RealDatabaseConnector-t (kompozíció), ha arra tényleg szükség van.
classDiagram
class DatabaseConnector {
<<interface>>
+query(sql: String) void
}
class RealDatabaseConnector {
+RealDatabaseConnector()
+query(sql: String) void
}
class ProxyDatabaseConnector {
-realConnector: RealDatabaseConnector
+query(sql: String) void
}
class Client
Client --> DatabaseConnector : "használja"
RealDatabaseConnector ..|> DatabaseConnector : "implementálja"
ProxyDatabaseConnector ..|> DatabaseConnector : "implementálja"
%% A Proxy felel a valódi objektum élettartamáért és eléréséért
ProxyDatabaseConnector o--> RealDatabaseConnector : "tartalmazza (delegál neki)" Forráskód
package structural.proxy;
import java.util.Objects;
interface DatabaseConnector {
void query(String sql);
}
class RealDatabaseConnector implements DatabaseConnector {
public RealDatabaseConnector() {
System.out.println("Csatlakozás a szerverhez...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public void query(String sql) {
System.out.println("Lekérdezés futtatása: " + sql);
}
}
class ProxyDatabaseConnector implements DatabaseConnector {
private RealDatabaseConnector realDatabaseConnector;
@Override
public void query(String sql) {
if (Objects.isNull(realDatabaseConnector)) {
realDatabaseConnector = new RealDatabaseConnector();
}
realDatabaseConnector.query(sql);
}
}
class Main {
static void main() {
System.out.println("Itt még nem kezd azonnal kapcsolódni.");
var databaseConnector = new ProxyDatabaseConnector();
System.out.println("Csak akkor fog kapcsolódni a DB-hez amikor meghívjuk a query metódust.");
databaseConnector.query("SELECT * FROM orders");
System.out.println("Itt már nem kapcsolódik mert cacheből dolgozik.");
databaseConnector.query("SELECT * FROM orders");
}
}