Table of Contents
Bridge Pattern
Το Bridge Pattern (Μοτίβο Γέφυρας) ανήκει στα δομικά σχεδιαστικά μοτίβα (Structural Design Patterns). Ο κύριος σκοπός του είναι να διαχωρίσει μια αφαίρεση (Abstraction) από την υλοποίησή της (Implementation), ώστε αυτά τα δύο να μπορούν να μεταβάλλονται και να επεκτείνονται ανεξάρτητα.
Αντί να χρησιμοποιούμε κληρονομικότητα που «δένει» την αφαίρεση με την υλοποίηση κατά τη μεταγλώττιση (compile-time), το Bridge Pattern χρησιμοποιεί σύνθεση (composition).
Πότε Χρησιμοποιείται;
- Όταν θέλουμε να αποφύγουμε έναν μόνιμο δεσμό μεταξύ μιας αφαίρεσης και μιας υλοποίησης.
- Όταν τόσο οι αφαιρέσεις όσο και οι υλοποιήσεις τους πρέπει να επεκτείνονται με υποκλάσεις (subclasses).
- Όταν οι αλλαγές στην υλοποίηση μιας αφαίρεσης δεν πρέπει να επηρεάζουν τον κώδικα του πελάτη (client).
- Όταν θέλουμε να αποφύγουμε τις πολύπλοκες ιεραρχίες κλάσεων. Για παράδειγμα, αν έχουμε 3 είδη σχημάτων και 3 χρώματα, χωρίς το Bridge θα χρειαζόμασταν 3×3 = 9 κλάσεις (π.χ. RedCircle, BlueCircle, RedSquare κ.λπ.). Με το Bridge χρειαζόμαστε μόλις 3+3 = 6 κλάσεις.
Το μοτίβο αποτελείται από τέσσερα (4) βασικά στοιχεία:
- Abstraction (Αφαίρεση): Ορίζει τη διεπαφή της αφαίρεσης και διατηρεί μια αναφορά σε ένα αντικείμενο τύπου Implementor.
- Refined Abstraction (Εμπλουτισμένη Αφαίρεση): Επεκτείνει τη διεπαφή που ορίζει η Abstraction.
- Implementor (Υλοποιητής): Ορίζει τη διεπαφή για τις κλάσεις υλοποίησης. Αυτή η διεπαφή δεν χρειάζεται να ταιριάζει ακριβώς με την Abstraction. Συνήθως, η Implementor παρέχει πρωτογενείς λειτουργίες, ενώ η Abstraction ορίζει λειτουργίες υψηλότερου επιπέδου που βασίζονται σε αυτές τις πρωτογενείς.
- Concrete Implementors (Συγκεκριμένοι Υλοποιητές): Υλοποιούν τη διεπαφή του Implementor.
Παράδειγμα
Ας υποθέσουμε ότι έχουμε ένα σύνολο από συσκευές multimedia (Devices), όπως Τηλεόραση (TV) και Ραδιόφωνο (Radio), οι οποίες ελέγχονται από Τηλεχειριστήρια (Remotes). Θέλουμε να κατασκευάσουμε δύο είδη χειριστηρίων, το Απλό Τηλεχειριστήριο (Remote Control) και το Προηγμένο Τηλεχειριστήριο (AdvancedRemoteControl). Αντί να φτιάξουμε κλάσεις όπως TvNormalRemote, TvAdvancedRemote, RadioNormalRemote κ.λπ., θα γεφυρώσουμε τις Συσκευές με τα Τηλεχειριστήρια.
- Device.java
// Η διεπαφή για όλες τις συσκευές public interface Device { boolean isEnabled(); void enable(); void disable(); int getVolume(); void setVolume(int percent); int getChannel(); // Νέα μέθοδος void setChannel(int channel); // Νέα μέθοδος }
- Tv.java
public class Tv implements Device { private boolean on = false; private int volume = 30; private int channel = 1; // Νέο πεδίο για το τρέχον κανάλι @Override public boolean isEnabled() { return on; } @Override public void enable() { on = true; System.out.println("TV: Ενεργοποιήθηκε"); } @Override public void disable() { on = false; System.out.println("TV: Απενεργοποιήθηκε"); } @Override public int getVolume() { return volume; } @Override public void setVolume(int percent) { // Περιορίζουμε την ένταση μεταξύ 0 και 100% if (percent < 0) percent = 0; if (percent > 100) percent = 100; this.volume = percent; System.out.println("TV: Η ένταση ορίστηκε στο " + this.volume + "%"); } // Υλοποίηση της νέας μεθόδου ανάκτησης καναλιού @Override public int getChannel() { return channel; } // Υλοποίηση της νέας μεθόδου αλλαγής καναλιού @Override public void setChannel(int channel) { if (channel > 0) { // Απλή επικύρωση ότι το κανάλι είναι θετικός αριθμός this.channel = channel; System.out.println("TV: Αλλαγή στο κανάλι " + this.channel); } } }
- Radio.java
public class Radio implements Device { private boolean on = false; private int volume = 15; private int channel = 1; // Νέο πεδίο για τον τρέχοντα σταθμό @Override public boolean isEnabled() { return on; } @Override public void enable() { on = true; System.out.println("Radio: Ενεργοποιήθηκε"); } @Override public void disable() { on = false; System.out.println("Radio: Απενεργοποιήθηκε"); } @Override public int getVolume() { return volume; } @Override public void setVolume(int percent) { if (percent < 0) percent = 0; if (percent > 100) percent = 100; this.volume = percent; System.out.println("Radio: Η ένταση ορίστηκε στο " + this.volume + "%"); } // Υλοποίηση της νέας μεθόδου ανάκτησης σταθμού @Override public int getChannel() { return channel; } // Υλοποίηση της νέας μεθόδου αλλαγής σταθμού @Override public void setChannel(int channel) { if (channel > 0) { this.channel = channel; System.out.println("Radio: Συντονισμός στον σταθμό " + this.channel); } } }
Τώρα θέλουμε να εισάγουμε την έννοια του τηλεχειριστήριου. Η κλάση του βασικού τηλεχειριστηρίου (Remote Control) δίνεται παρακάτω:
- RemoteControl.java
// Το βασικό τηλεχειριστήριο διατηρεί μία αναφορά προς τη συσκευή public class RemoteControl { protected Device device; // Αυτή είναι η Γέφυρα (Bridge) public RemoteControl(Device device) { this.device = device; } public void togglePower() { if (device.isEnabled()) { device.disable(); } else { device.enable(); } } public void volumeDown() { device.setVolume(device.getVolume() - 10); } public void volumeUp() { device.setVolume(device.getVolume() + 10); } public int getChannel() { int currentChannel = device.getChannel(); System.out.println("Remote: Channel is: " + currentChannel); return currentChannel; } // Επιτρέπει στο απλό τηλεχειριστήριο να αλλάξει απευθείας κανάλι (π.χ. πατώντας ένα νούμερο) public void setChannel(int channel) { System.out.println("Remote: Setting channel to: " + channel); device.setChannel(channel); } }
Η κλάση ενός αναβαθμισμένου τηλεχειριστηρίου με επιπλέον λειτουργίες, επίσης δίνεται παρακάτω:
- AdvancedRemoteControl.java
public class AdvancedRemoteControl extends RemoteControl { public AdvancedRemoteControl(Device device) { super(device); } // 1. Η υπάρχουσα λειτουργία σίγασης public void mute() { System.out.println("Advanced Remote: Setting device to silent mode."); device.setVolume(0); } // 2. Νέα λειτουργία: Επόμενο κανάλι public void nextChannel() { int currentChannel = getChannel(); setChannel(currentChannel + 1); System.out.println("Advanced Remote: Next Channel."); } // 3. Νέα λειτουργία: Προηγούμενο κανάλι public void previousChannel() { int currentChannel = getChannel(); if (currentChannel > 1) { setChannel(currentChannel - 1); System.out.println("Advanced Remote: Previous Channel."); } } // 4. Νέα Macro λειτουργία: Cinema Mode (Συνδυασμός ενεργειών) public void cinemaMode() { System.out.println("Advanced Remote: Enabling Cinema Mode..."); if (!device.isEnabled()) { device.enable(); } device.setVolume(80); // Δυνατός ήχος για ταινίες device.setChannel(10); // Υποθέτουμε ότι το κανάλι 10 είναι για ταινίες System.out.println("Advanced Remote: Cinema Mode is Ready!"); } }
Συνοψίζοντας, τα οφέλη αυτής της αρχιτεκτονικής είναι τα εξής:
- Αποφυγή πολύπλοκων ιεραρχιών κλάσεων: Αν είχαμε χρησιμοποιήσει παραδοσιακή κληρονομικότητα θα δημιουργούσαμε πολλές συνδυαστικές κλάσεις (TvBasicRemote, TvAdvancedRemote, RadioBasicRemote, RadioAdvancedRemote). Με το Bridge, κρατήσαμε τον κώδικα διαχωρισμένο σε δύο ανεξάρτητους άξονες: Τηλεχειριστήρια και Συσκευές.
- Ανεξάρτητη Εξέλιξη (Orthogonal Extension):
- Στην πλευρά της υλοποίησης, προσθέσαμε τις πρωτογενείς μεθόδους στο interface Device και τις κλάσεις Tv/Radio, χωρίς να επηρεαστεί η υπάρχουσα λογική των τηλεχειριστηρίων.
- Στην πλευρά της αφαίρεσης, εμπλουτίσαμε την κλάση
RemoteControlμε τις βασικές μεθόδους, και την κλάσηAdvancedRemoteControlμε σύνθετες «macro» λειτουργίες (nextChannel, cinemaMode), χωρίς να αλλάξουμε τον τρόπο που λειτουργούν εσωτερικά τα υπόλοιπα δομικά στοιχεία.
- Επαναχρησιμοποίηση Κώδικα και Καθαρή Ιεραρχία: Το AdvancedRemoteControl μπόρεσε να χρησιμοποιήσει τις μεθόδους getChannel() και setChannel() της γονικής του κλάσης (RemoteControl). Δεν χρειάστηκε να ξαναγράψουμε κώδικα που μιλάει απευθείας με τη συσκευή. Η λογική συγκεντρώθηκε σε ένα σημείο.
- Επεκτασιμότητα: Η αρχιτεκτονική αυτή είναι πλέον εξαιρετικά ευέλικτη. Αν αύριο θελήσεις να προσθέσεις
- Μια νέα συσκευή, π.χ. SmartProjector, απλά υλοποιείς το Device. Όλα τα τηλεχειριστήρια θα δουλέψουν μαζί της αμέσως.
- Ένα νέο τηλεχειριστήριο, π.χ. VoiceRemoteControl, απλά κληρονομείς το RemoteControl. Θα μπορεί να ελέγξει όλες τις υπάρχουσες συσκευές αμέσως.
