Το Proxy Pattern (Μοτίβο Μεσολαβητή) ανήκει στα Δομικά Μοτίβα Σχεδίασης (Structural Design Patterns). Ο κύριος σκοπός του είναι να παρέχει ένα υποκατάστατο ή έναν εκπρόσωπο (proxy) για ένα άλλο αντικείμενο, ώστε να ελέγχει την πρόσβαση σε αυτό.
Σκεφτείτε το σαν μια χρεωστική κάρτα. Η κάρτα είναι ο εκπρόσωπος των μετρητών στο λογαριασμό σας. Όταν πληρώνετε με αυτήν, η κάρτα (proxy) μεσολαβεί για να ελέγξει αν υπάρχει το ποσό και να κάνει τη συναλλαγή με τον πραγματικό τραπεζικό λογαριασμό (real subject).
Θα περιγράψουμε το κλασσικό και χρήσιμο παράδειγμα του Proxy caching μηχανισμού μιας βάσης δεδομένων. Σε αυτό το σενάριο, ο Proxy μεσολαβεί ανάμεσα στον Client και την πραγματική Βάση Δεδομένων (RealDatabase). Διατηρεί στη μνήμη του έναν HashMap που λειτουργεί ως Cache. Ακολουθεί η σχηματική υλοποίηση σε Java.
Το interface ορίζει την κοινή λειτουργία αναζήτησης δεδομένων.
public interface Database { String query(String sqlQuery); }
Προσομοιώνει μια “βαριά” βάση δεδομένων που χρειάζεται χρόνο για να απαντήσει (π.χ. λόγω δίσκου ή δικτύου).
public class RealDatabase implements Database { @Override public String query(String sqlQuery) { // Προσομοίωση καθυστέρησης της πραγματικής βάσης System.out.println("[RealDB] Searching on database for: \"" + sqlQuery + "\"..."); try { Thread.sleep(1500); // Καθυστέρηση 1.5 δευτερολέπτου } catch (InterruptedException e) { e.printStackTrace(); } // Επιστροφή υποτιθέμενων δεδομένων ανάλογα με το ερώτημα if (sqlQuery.contains("id = 1")) { return "{id: 1, name: 'Mickey Mouse'}"; } return "{No Results found!}"; } }
Αυτή η κλάση έχει ως πεδίο την πραγματική βάση δεδομένων και ένα HashMap για την προσωρινή αποθήκευση των αποτελεσμάτων (μηχανισμός cache).
import java.util.HashMap; import java.util.Map; public class ProxyCacheDatabase implements Database { // Ο Proxy έχει ως πεδίο την πραγματική βάση private RealDatabase realDatabase; // Η μνήμη Cache για γρήγορη ανάκτηση private Map<String, String> cache; public ProxyCacheDatabase() { this.realDatabase = new RealDatabase(); this.cache = new HashMap<>(); } @Override public String query(String sqlQuery) { System.out.println("[ProxyCache] Checking cache first..."); // 1. Αν το αποτέλεσμα υπάρχει στην Cache, το επιστρέφει ΚΑΤΕΥΘΕΙΑΝ if (cache.containsKey(sqlQuery)) { System.out.println("[ProxyCache] HIT! Found in Cache."); return cache.get(sqlQuery); } // 2. Αν ΔΕΝ υπάρχει στην Cache (Cache Miss) System.out.println("[ProxyCache] MISS! No data found in Cache. Connecting to RealDB..."); // Ρωτάει την πραγματική βάση String result = realDatabase.query(sqlQuery); // 3. Αποθηκεύει το νέο αποτέλεσμα στην Cache για το επόμενο request cache.put(sqlQuery, result); System.out.println("[ProxyCache] Updated cache."); return result; } }
Θα κάνουμε το ίδιο ερώτημα δύο φορές για να δούμε τη διαφορά στην ταχύτητα.
public class ClientDB { public static void main(String[] args) { // Ο Client χρησιμοποιεί τη βάση μέσω του Caching Proxy Database db = new ProxyCacheDatabase(); String queryStr = "SELECT * FROM users WHERE id = 1"; // 1η Κλήση: Η cache είναι άδεια, θα πάει στη RealDB (Θα κάνει 1.5 δευτερόλεπτο) System.out.println("\n=== 1st Attempt (Empty Cache) ==="); long startTime = System.currentTimeMillis(); String result1 = db.query(queryStr); long endTime = System.currentTimeMillis(); System.out.println("Result: " + result1); System.out.println("Exec time: " + (endTime - startTime) + " ms\n"); // 2η Κλήση: Τα δεδομένα υπάρχουν ήδη, θα απαντήσει ο Proxy ακαριαία (0 ms) System.out.println("=== 2nd Attempt (Found in Cache) ==="); startTime = System.currentTimeMillis(); String result2 = db.query(queryStr); endTime = System.currentTimeMillis(); System.out.println("Result: " + result2); System.out.println("Exec time: " + (endTime - startTime) + " ms"); } }