Table of Contents

Adapter Pattern

Το Adapter Pattern (Μοτίβο Προσαρμογέα) ανήκει στα δομικά μοτίβα σχεδίασης (Structural Design Patterns). Η κύρια λειτουργία του είναι να επιτρέπει σε δύο μη συμβατά interfaces (διεπαφές) να συνεργαστούν μεταξύ τους. Μπορείτε να το σκεφτείτε σαν έναν οποιονδήποτε προσαρμογέα ή μετασχηματιστή.

Πότε το χρησιμοποιούμε;

Όταν θέλουμε να χρησιμοποιήσουμε μια υπάρχουσα κλάση, αλλά το interface της δεν ταιριάζει με το υπόλοιπο σύστημα. Όταν δημιουργούμε ένα επαναχρησιμοποιήσιμο component που πρέπει να συνεργαστεί με μελλοντικές ή απρόβλεπτες κλάσεις που δεν μοιράζονται το ίδιο interface. Συνήθως χρησιμοποιείται κατά την ενσωμάτωση legacy κώδικα (παλιού κώδικα) ή βιβλιοθηκών τρίτων (3rd party libraries).

Τα βασικά συστατικά:

Παράδειγμα - Σύνδεση με σύστημα πληρωμών

Ας υποθέσουμε ότι έχουμε ένα σύστημα πληρωμών που λειτουργεί μέσω ενός interface που ονομάζεται PaymentProcessor και δέχεται πληρωμές σε ευρώ. Θέλουμε να ενσωματώσουμε μια νέα υπηρεσία πληρωμών (έστω την “X-Pay”), η οποία δέχεται ποσά μόνο σε Δολλάρια ($) και οι μέθοδοί της έχουν διαφορετικά ονόματα. Θα φτιάξουμε έναν Adapter που θα αναλάβει και τη μετάφραση των μεθόδων, αλλά και τη μετατροπή του νομίσματος.

PaymentProcessor.java
// Target Interface (Λειτουργεί με Ευρώ)
public interface PaymentProcessor {
    void processPayment(double amountInEuro);
}

Η κλάση X-Pay την οποία θέλουμε να προσαρμόσουμε:

XPayGateway.java
// Adaptee Class (Λειτουργεί με Δολλάρια)
public class XPayGateway {
    public void chargeInUSD(double amountInUSD) {
        System.out.println("Η X-Pay χρέωσε επιτυχώς: $" + amountInUSD);
    }
}

Ο προσαρμογέας υλοποιεί το PaymentProcessor (άρα το E-shop τον βλέπει σαν κανονικό processor), αλλά εσωτερικά καλεί την XPayGateway, αφού πρώτα μετατρέψει τα Ευρώ σε Δολλάρια.

XPayAdapter.java
// 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 το σύστημα πληρωμών μπορεί να επωφεληθεί από την εν λόγω υπηρεσία.

PaymentService.java
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

Το Adapter Pattern είναι εξαιρετικά δημοφιλές και η ίδια η Java το χρησιμοποιεί εκτενώς στις δικές της ενσωματωμένες βιβλιοθήκες (JDK). Όποτε βλέπετε μια κλάση που παίρνει ένα αντικείμενο ενός τύπου και το “μεταμορφώνει” ώστε να μπορεί να χρησιμοποιηθεί από ένα API που απαιτεί έναν άλλον τύπο, έχεις να κάνεις με έναν Adapter. Ακολουθούν κάποια κλασικά παραδείγματα:

java.util.Arrays#asList()

java.io.InputStreamReader και java.io.OutputStreamWriter

Εδώ έχουμε μια κλασική περίπτωση όπου πρέπει να γεφυρωθούν δύο εντελώς διαφορετικοί τρόποι διαχείρισης δεδομένων: τα Bytes (δυαδικά δεδομένα) και οι Characters (χαρακτήρες/κείμενο).

WindowAdapter, MouseAdapter (στο java.awt.event)

Αυτή είναι μια ελαφρώς διαφορετική παραλλαγή που ονομάζεται Convenience Adapter.

// Αντί για "implements MouseListener" (που θέλει 5 μεθόδους)
// χρησιμοποιούμε τον MouseAdapter και γράφουμε μόνο μία
button.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseClicked(MouseEvent e) {
        System.out.println("Left mouse button clicked!");
    }
});