Kihagyás

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");
    }
}