Το Bridge Pattern (Μοτίβο Γέφυρας) ανήκει στα δομικά σχεδιαστικά μοτίβα (Structural Design Patterns). Ο κύριος σκοπός του είναι να διαχωρίσει μια αφαίρεση (Abstraction) από την υλοποίησή της (Implementation), ώστε αυτά τα δύο να μπορούν να μεταβάλλονται και να επεκτείνονται ανεξάρτητα.
Αντί να χρησιμοποιούμε κληρονομικότητα που «δένει» την αφαίρεση με την υλοποίηση κατά τη μεταγλώττιση (compile-time), το Bridge Pattern χρησιμοποιεί σύνθεση (composition).
Πότε Χρησιμοποιείται;
Το μοτίβο αποτελείται από τέσσερα (4) βασικά στοιχεία:
Ας υποθέσουμε ότι έχουμε ένα σύνολο από συσκευές multimedia (Devices), όπως Τηλεόραση (TV) και Ραδιόφωνο (Radio), οι οποίες ελέγχονται από Τηλεχειριστήρια (Remotes). Θέλουμε να κατασκευάσουμε δύο είδη χειριστηρίων, το Απλό Τηλεχειριστήριο (Remote Control) και το Προηγμένο Τηλεχειριστήριο (AdvancedRemoteControl). Αντί να φτιάξουμε κλάσεις όπως TvNormalRemote, TvAdvancedRemote, RadioNormalRemote κ.λπ., θα γεφυρώσουμε τις Συσκευές με τα Τηλεχειριστήρια.
// Η διεπαφή για όλες τις συσκευές public interface Device { boolean isEnabled(); void enable(); void disable(); int getVolume(); void setVolume(int percent); int getChannel(); // Νέα μέθοδος void setChannel(int channel); // Νέα μέθοδος }
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); } } }
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) δίνεται παρακάτω:
// Το βασικό τηλεχειριστήριο διατηρεί μία αναφορά προς τη συσκευή 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); } }
Η κλάση ενός αναβαθμισμένου τηλεχειριστηρίου με επιπλέον λειτουργίες, επίσης δίνεται παρακάτω:
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!"); } }
Συνοψίζοντας, τα οφέλη αυτής της αρχιτεκτονικής είναι τα εξής:
RemoteControl με τις βασικές μεθόδους, και την κλάση AdvancedRemoteControl με σύνθετες «macro» λειτουργίες (nextChannel, cinemaMode), χωρίς να αλλάξουμε τον τρόπο που λειτουργούν εσωτερικά τα υπόλοιπα δομικά στοιχεία.