Table of Contents
Observer Pattern
Το Observer Pattern (Πρότυπο Παρατηρητή) είναι ένα από τα πιο διαδεδομένα πρότυπα στη σχεδίαση λογισμικού. Χρησιμοποιείται για να ορίσει μια σχέση ένα-προς-πολλά μεταξύ αντικειμένων, έτσι ώστε όταν η κατάσταση ενός αντικειμένου αλλάζει, όλα τα εξαρτημένα αντικείμενα να ενημερώνονται αυτόματα.
Πρόκειται για ένα subscription based σύστημα, όπου σε κάθε αλλαγή του observable αντικειμένου ενημερώνονται για την αλλαγή όλα τα αντικείμενα που έχουν κάνει εγγραφή.
Τα βασικά συστατικά του προτύπου είναι τα εξής:
- Subject (Υποκείμενο): Διατηρεί μια λίστα με παρατηρητές και παρέχει μεθόδους για προσθήκη (attach), αφαίρεση (detach) και ειδοποίηση (notify).
- Observer (Παρατηρητής): Μια διεπαφή (interface) που ορίζει τη μέθοδο ενημέρωσης (update).
- Concrete Subject: Η πραγματική κλάση που παρακολουθούμε. Όταν αλλάζει η κατάστασή της, καλεί τη notify.
- Concrete Observer: Οι κλάσεις που “αντιδρούν” στην ενημέρωση.
| Κλάση/Interface | Λειτουργία |
|---|---|
| Subject (κλάση) | List<Observer>, attach(o), detach(o), notify() |
| Observer (Interface) | update(data) |
| ConcreteSubject (κλάση) | Γνωρίζει την κατάσταση (state) του observable αντικειμένου. Μόλις αλλάξει η κατάσταση, καλεί τη notify(). |
| ConcreteObserver (κλάση) | Υλοποιεί την update() για να εκτελέσει μια ενέργεια. |
Σχηματικός κώδικας σε Java
Παρακάτω δίνεται μία γενική σχηματική υλοποίηση του Observer Pattern σε γλώσσα Java.
- ObserverSchema.java
// ========================================== // 1. ΤΟ INTERFACE ΤΟΥ ΠΑΡΑΤΗΡΗΤΗ (OBSERVER) // ========================================== interface Observer { // Η μέθοδος που καλείται από το Subject για την ενημέρωση void update(String data); } // ========================================== // 2. ΤΟ INTERFACE ΤΟΥ ΥΠΟΚΕΙΜΕΝΟΥ (SUBJECT) // ========================================== interface Subject { void attach(Observer o); // Προσθήκη παρατηρητή void detach(Observer o); // Διαγραφή παρατηρητή void notifyObservers(); // Ενημέρωση όλων των παρατηρητών } // ========================================== // 3. Η ΣΥΓΚΕΚΡΙΜΕΝΗ ΚΛΑΣΗ ΥΠΟΚΕΙΜΕΝΟΥ // (CONCRETE SUBJECT) // ========================================== class ConcreteSubject implements Subject { // Η λίστα των παρατηρητών private List<Observer> observers = new ArrayList<>(); // Η κατάσταση του παρατηρούμενου αντικειμένου private String state; @Override public void attach(Observer o) { observers.add(o); } @Override public void detach(Observer o) { observers.remove(o); } @Override public void notifyObservers() { for (Observer o : observers) { o.update(state); // Ενημέρωση κάθε παρατηρητή } } // Η μέθοδος που αλλάζει την κατάσταση (state) public void setState(String newState) { this.state = newState; notifyObservers(); // Πυροδότηση ειδοποιήσεων } } // ========================================== // 4. ΣΥΓΚΕΚΡΙΜΕΝΟΙ ΠΑΡΑΤΗΡΗΤΕΣ // (CONCRETE OBSERVERS) // ========================================== class ConcreteObserverA implements Observer { @Override public void update(String data) { // Αντίδραση στην αλλαγή (π.χ. Logging) System.out.println("Observer A: State changed to: " + data); } } class ConcreteObserverB implements Observer { @Override public void update(String data) { // Διαφορετική αντίδραση (π.χ. αποστολή e-mail) System.out.println("Observer B: Sending e-mail about state change: " + data); } }
- Interfaces (Observer, Subject): Ορίζουν την αφηρημένη δομή και τις μεθόδους επικοινωνίας.
- Concrete Classes: Υλοποιούν τη λογική. Η ConcreteSubject κρατάει τη λίστα των παρατηρητών.
- ConcreteObserver υλοποιούν την ειδική συμπεριφορά που θέλουμε να εκτελεστεί κατά την ενημέρωση.
- Loose Coupling: Παρατηρήστε ότι η ConcreteSubject δεν γνωρίζει τίποτα για την ConcreteObserverA ή την ConcreteObserverB. Γνωρίζει μόνο το interface Observer. Αυτή είναι η ουσία του προτύπου.
Παράδειγμα - Stock Observer
Παρακάτω δίνουμε ένα παράδειγμα που προσομοιάζει ένα σύστημα παρατήρησης τιμών μετοχών. Έχουμε μία σειρά από παραητήρητές (email observer, mobile app observer), οι οποίοι ενημερώνονται μέσω της κλάσης TechMarket (υλοποιεί το interface Stock Subject). Παρατηρήστε ότι σε κάθε αλλαγή τιμής ενημερώνονται μόνο οι Observers που έχουν κάνει attach στο TechMarket και ότι το TechMarket δεν γνωρίζει τίποτα για την εσωτερική δομή του κάθε Observer, εκτός από το ότι θα τον ενημερώσει μέσω της μεθόδου update.
- ObservableMarket.java
import java.util.ArrayList; import java.util.List; // 1. Το Interface του Παρατηρητή interface StockObserver { void update(String stockName, double price); } // 2. Το Interface του Υποκειμένου (Subject) interface StockSubject { void attach(StockObserver observer); void detach(StockObserver observer); void notifyObservers(String stockName, double price); } // 3. Η Συγκεκριμένη Κλάση Υποκειμένου (Concrete Subject) class TechMarket implements StockSubject { private List<StockObserver> observers = new ArrayList<>(); @Override public void attach(StockObserver observer) { observers.add(observer); } @Override public void detach(StockObserver observer) { observers.remove(observer); } @Override public void notifyObservers(String stockName, double price) { for (StockObserver observer : observers) { observer.update(stockName, price); } } // Μέθοδος που αλλάζει την κατάσταση και πυροδοτεί την ειδοποίηση public void setPrice(String stockName, double price) { System.out.println("\n[Market] Νέα τιμή για τη μετοχή " + stockName + ": " + price + "€"); notifyObservers(stockName, price); } } // 4. Συγκεκριμένοι Παρατηρητές (Concrete Observers) class MobileApp implements StockObserver { @Override public void update(String stockName, double price) { System.out.println("[Mobile App] Ειδοποίηση: Η τιμή της " + stockName + " ενημερώνθηκε σε " + price + "€"); } } class EmailAlert implements StockObserver { @Override public void update(String stockName, double price) { System.out.println("[Email] Ενημέρωση: Η τιμή της " + stockName + " ενημερώθηκε σε " + price + "€"); } } // --- Κύρια Κλάση Εκτέλεσης --- public class ObservableMarket { public static void main(String[] args) { TechMarket market = new TechMarket(); StockObserver app = new MobileApp(); StockObserver email = new EmailAlert(); // Εγγραφή παρατηρητών market.attach(app); market.attach(email); // Αλλαγή τιμής market.setPrice("TSLA", 750.50); // Αφαίρεση ενός παρατηρητή και νέα αλλαγή market.detach(email); market.setPrice("MSFT", 310.20); } }
