Το Adapter Pattern (Μοτίβο Προσαρμογέα) ανήκει στα δομικά μοτίβα σχεδίασης (Structural Design Patterns). Η κύρια λειτουργία του είναι να επιτρέπει σε δύο μη συμβατά interfaces (διεπαφές) να συνεργαστούν μεταξύ τους. Μπορείτε να το σκεφτείτε σαν έναν οποιονδήποτε προσαρμογέα ή μετασχηματιστή.
Όταν θέλουμε να χρησιμοποιήσουμε μια υπάρχουσα κλάση, αλλά το interface της δεν ταιριάζει με το υπόλοιπο σύστημα. Όταν δημιουργούμε ένα επαναχρησιμοποιήσιμο component που πρέπει να συνεργαστεί με μελλοντικές ή απρόβλεπτες κλάσεις που δεν μοιράζονται το ίδιο interface. Συνήθως χρησιμοποιείται κατά την ενσωμάτωση legacy κώδικα (παλιού κώδικα) ή βιβλιοθηκών τρίτων (3rd party libraries).
Τα βασικά συστατικά:
Ας υποθέσουμε ότι έχουμε ένα σύστημα πληρωμών που λειτουργεί μέσω ενός interface που ονομάζεται PaymentProcessor και δέχεται πληρωμές σε ευρώ. Θέλουμε να ενσωματώσουμε μια νέα υπηρεσία πληρωμών (έστω την “X-Pay”), η οποία δέχεται ποσά μόνο σε Δολλάρια ($) και οι μέθοδοί της έχουν διαφορετικά ονόματα. Θα φτιάξουμε έναν Adapter που θα αναλάβει και τη μετάφραση των μεθόδων, αλλά και τη μετατροπή του νομίσματος.
// Target Interface (Λειτουργεί με Ευρώ) public interface PaymentProcessor { void processPayment(double amountInEuro); }
Η κλάση X-Pay την οποία θέλουμε να προσαρμόσουμε:
// Adaptee Class (Λειτουργεί με Δολλάρια) public class XPayGateway { public void chargeInUSD(double amountInUSD) { System.out.println("Η X-Pay χρέωσε επιτυχώς: $" + amountInUSD); } }
Ο προσαρμογέας υλοποιεί το PaymentProcessor (άρα το E-shop τον βλέπει σαν κανονικό processor), αλλά εσωτερικά καλεί την XPayGateway, αφού πρώτα μετατρέψει τα Ευρώ σε Δολλάρια.
// Adapter public class XPayAdapter implements PaymentProcessor { private XPayGateway xPayGateway; private static double euro2usd; // Ισοτιμία public XPayAdapter(XPayGateway xPayGateway, double conversion_rate) { this.xPayGateway = xPayGateway; euro2usd = conversion_rate; } @Override public void processPayment(double amountInEuro) { // 1. Μετατροπή του ποσού από Ευρώ σε Δολλάρια double amountInUSD = amountInEuro * euro2usd; System.out.printf("Adapter: Μετατροπή %.2f€ σε %.2f$%n", amountInEuro, amountInUSD); // 2. Κλήση της μεθόδου του Adaptee xPayGateway.chargeInUSD(amountInUSD); } }
Τελικά με τη χρήση του Adapter το σύστημα πληρωμών μπορεί να επωφεληθεί από την εν λόγω υπηρεσία.
public class PaymentService { public static void main(String[] args) { // 1. Ο πελάτης αγοράζει κάτι που κοστίζει 100 Ευρώ double orderTotal = 100.00; // 2. Δημιουργούμε το instance της ξένης βιβλιοθήκης (Adaptee) XPayGateway xPaymentService = new XPayGateway(); // 3. "Κλειδώνουμε" τη βιβλιοθήκη μέσα στον Adapter PaymentProcessor paymentProcessor = new XPayAdapter(xPaymentService); // 4. Το E-shop εκτελεί την πληρωμή κανονικά σε Ευρώ, χωρίς να ξέρει τι γίνεται από πίσω System.out.println("E-shop: Εκκίνηση πληρωμής για την παραγγελία..."); paymentProcessor.processPayment(orderTotal); } }
Το Adapter Pattern είναι εξαιρετικά δημοφιλές και η ίδια η Java το χρησιμοποιεί εκτενώς στις δικές της ενσωματωμένες βιβλιοθήκες (JDK). Όποτε βλέπετε μια κλάση που παίρνει ένα αντικείμενο ενός τύπου και το “μεταμορφώνει” ώστε να μπορεί να χρησιμοποιηθεί από ένα API που απαιτεί έναν άλλον τύπο, έχεις να κάνεις με έναν Adapter. Ακολουθούν κάποια κλασικά παραδείγματα:
Arrays.asList() λειτουργεί ως προσαρμογέας. Παίρνει τον πίνακα και επιστρέφει μια List η οποία “κάθεται” πάνω από τον πίνακα. Εδώ έχουμε μια κλασική περίπτωση όπου πρέπει να γεφυρωθούν δύο εντελώς διαφορετικοί τρόποι διαχείρισης δεδομένων: τα Bytes (δυαδικά δεδομένα) και οι Characters (χαρακτήρες/κείμενο).
Αυτή είναι μια ελαφρώς διαφορετική παραλλαγή που ονομάζεται Convenience Adapter.
MouseListener έχει 5 μεθόδους (mouseClicked, mousePressed, mouseReleased, mouseEntered, mouseExited). Αν ο προγραμματιστής θέλει να γράψει κώδικα μόνο για το click, η Java θα τον ανάγκαζε να κάνει override και τις 5 μεθόδους, αφήνοντας τις 4 κενές, γεμίζοντας τον κώδικα με “θόρυβο”.MouseAdapter. Αυτή η κλάση υλοποιεί το interface MouseListener και δίνει κενές (empty) υλοποιήσεις για όλες τις μεθόδους. Κληρονομείς τον MouseAdapter και κάνεις override μόνο τη μέθοδο που σε ενδιαφέρει, αγνοώντας τις υπόλοιπες.// Αντί για "implements MouseListener" (που θέλει 5 μεθόδους) // χρησιμοποιούμε τον MouseAdapter και γράφουμε μόνο μία button.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { System.out.println("Left mouse button clicked!"); } });