User Tools

Site Tools


oop:adapter_pattern

Adapter Pattern

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

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

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

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

  • Target (Στόχος): Το interface που περιμένει και καταλαβαίνει ο Client (ο τρέχων κώδικάς μας).
  • Client (Πελάτης): Η κλάση που χρησιμοποιεί το Target interface.
  • Adaptee (Προσαρμοζόμενος): Η υπάρχουσα κλάση/βιβλιοθήκη που έχει το ασύμβατο interface, αλλά περιέχει τη συμπεριφορά που χρειαζόμαστε.
  • Adapter (Προσαρμογέας): Η κλάση που υλοποιεί το Target interface και εσωτερικά κρατάει μια αναφορά (reference) στον Adaptee, μεταφράζοντας τις κλήσεις.

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

Ας υποθέσουμε ότι έχουμε ένα σύστημα πληρωμών που λειτουργεί μέσω ενός 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()

  • Το πρόβλημα: Έχεις έναν κλασικό πίνακα (Array), αλλά θέλεις να τον περάσεις σε μια μέθοδο που δέχεται μια λίστα (List), για να μπορείς να χρησιμοποιήσεις μεθόδους των Collections (όπως αναζήτηση, streams κλπ).
  • Η λύση (Adapter): Η μέθοδος Arrays.asList() λειτουργεί ως προσαρμογέας. Παίρνει τον πίνακα και επιστρέφει μια List η οποία “κάθεται” πάνω από τον πίνακα.

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

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

  • Το πρόβλημα: Έχεις ένα stream που διαβάζει ωμά bytes (π.χ. FileInputStream ή System.in), αλλά εσύ θέλεις να το διαβάσεις ως κείμενο/χαρακτήρες (Reader).
  • Η λύση (Adapter): Ο InputStreamReader λειτουργεί ως Adapter. Δέχεται ως παράμετρο στον κατασκευαστή του ένα InputStream (που παράγει bytes) και το προσαρμόζει στο interface ενός Reader (που παράγει χαρακτήρες), κάνοντας εσωτερικά τη μετάφραση (decoding) με βάση κάποιο encoding (π.χ. UTF-8). Υπάρχουν παραλαγές του κατασκευαστή που ορίζουν το char-set που θα χρησιμοποιηθεί αν δεν θέλουμε το default του συστήματος.

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

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

  • Το πρόβλημα: Το interface MouseListener έχει 5 μεθόδους (mouseClicked, mousePressed, mouseReleased, mouseEntered, mouseExited). Αν ο προγραμματιστής θέλει να γράψει κώδικα μόνο για το click, η Java θα τον ανάγκαζε να κάνει override και τις 5 μεθόδους, αφήνοντας τις 4 κενές, γεμίζοντας τον κώδικα με “θόρυβο”.
  • Η λύση (Adapter): Η Java παρέχει την κλάση 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!");
    }
});
oop/adapter_pattern.txt · Last modified: 2026/05/24 21:08 by gthanos