====== Proxy Pattern ====== Το Proxy Pattern (Μοτίβο Μεσολαβητή) ανήκει στα Δομικά Μοτίβα Σχεδίασης (Structural Design Patterns). Ο κύριος σκοπός του είναι να παρέχει ένα υποκατάστατο ή έναν εκπρόσωπο (proxy) για ένα άλλο αντικείμενο, ώστε να ελέγχει την πρόσβαση σε αυτό. Σκεφτείτε το σαν μια χρεωστική κάρτα. Η κάρτα είναι ο εκπρόσωπος των μετρητών στο λογαριασμό σας. Όταν πληρώνετε με αυτήν, η κάρτα (proxy) μεσολαβεί για να ελέγξει αν υπάρχει το ποσό και να κάνει τη συναλλαγή με τον πραγματικό τραπεζικό λογαριασμό (real subject). ===== Η δομή του Proxy Pattern ===== * **Subject (Διεπαφή/Interface):** Ορίζει την κοινή διεπαφή μεταξύ του Client και του Proxy, ώστε ο Proxy να μπορεί να χρησιμοποιηθεί οπουδήποτε αναμένεται το πραγματικό αντικείμενο. * **RealSubject (Πραγματικό Αντικείμενο):** Το πραγματικό αντικείμενο που εκτελεί την ουσιαστική λογική, το οποίο όμως μπορεί να είναι "βαρύ" ή να χρειάζεται έλεγχο πρόσβασης. * **Proxy (Μεσολαβητής):** Διατηρεί μια αναφορά (reference) στο RealSubject. Υλοποιεί την ίδια διεπαφή, ελέγχει την πρόσβαση και προωθεί τα αιτήματα στο RealSubject όταν χρειάζεται. ===== Περιπτώσεις που χρησιμοποιείται ===== * **Virtual Proxy (Εικονικός Μεσολαβητής):** Όταν το πραγματικό αντικείμενο είναι "βαρύ" (π.χ. φόρτωμα μιας τεράστιας εικόνας ή σύνδεση σε βάση δεδομένων). Ο Proxy καθυστερεί τη δημιουργία του μέχρι να χρειαστεί πραγματικά (Lazy Initialization). * **Protection Proxy (Μεσολαβητής Προστασίας):** Όταν θέλουμε να ελέγξουμε τα δικαιώματα πρόσβασης (Authorization) ενός χρήστη πριν του επιτρέψουμε να καλέσει μια μέθοδο. * **Remote Proxy (Απομακρυσμένος Μεσολαβητής):** Όταν το πραγματικό αντικείμενο βρίσκεται σε διαφορετικό reference space (π.χ. σε άλλον server) και ο Proxy αναλαμβάνει τη δικτυακή επικοινωνία (RMI, RPC). * **Logging/Caching Proxy:** Για να κρατάμε ιστορικό (logs) των κλήσεων ή για να αποθηκεύουμε αποτελέσματα (cache) ώστε να μην ξαναεκτελείται μια κοστοβόρα λειτουργία. ===== Παράδειγμα - Caching Proxy ===== Θα περιγράψουμε το κλασσικό και χρήσιμο παράδειγμα του Proxy caching μηχανισμού μιας βάσης δεδομένων. Σε αυτό το σενάριο, ο Proxy μεσολαβεί ανάμεσα στον Client και την πραγματική Βάση Δεδομένων (RealDatabase). Διατηρεί στη μνήμη του έναν [[https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html|HashMap]] που λειτουργεί ως Cache. Ακολουθεί η σχηματική υλοποίηση σε Java. ==== Το Interface (Subject) ==== Το //interface// ορίζει την κοινή λειτουργία αναζήτησης δεδομένων. public interface Database { String query(String sqlQuery); } ==== Η Bάση Δεδομένων (RealSubject) ==== Προσομοιώνει μια "βαριά" βάση δεδομένων που χρειάζεται χρόνο για να απαντήσει (π.χ. λόγω δίσκου ή δικτύου). 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!}"; } } ==== H Proxy Cache Database ==== Αυτή η κλάση έχει ως πεδίο την πραγματική βάση δεδομένων και ένα HashMap για την προσωρινή αποθήκευση των αποτελεσμάτων (μηχανισμός cache). import java.util.HashMap; import java.util.Map; public class ProxyCacheDatabase implements Database { // Ο Proxy έχει ως πεδίο την πραγματική βάση private RealDatabase realDatabase; // Η μνήμη Cache για γρήγορη ανάκτηση private Map 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; } } ==== O Client ==== Θα κάνουμε το ίδιο ερώτημα δύο φορές για να δούμε τη διαφορά στην ταχύτητα. 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"); } } * **Διαφάνεια:** Ο Client καλεί απλά την db.query(). Δεν τον νοιάζει αν τα δεδομένα ήρθαν από τη μνήμη RAM του Proxy ή από τον σκληρό δίσκο της RealDB. * **Έλεγχος Ροής:** Ο Proxy προστατεύει τη βαριά βάση από το να δέχεται συνεχώς τα ίδια "κουραστικά" ερωτήματα, βελτιώνοντας δραματικά την ταχύτητα της εφαρμογής.