Adapter Pattern Nedir? Nasıl Kullanılır?

Adapter Pattern, mevcut sınıfların arayüzünü değiştirmeden uyumluluk sağlamayı amaçlayan bir tasarım desenidir. Bu desen, özellikle var olan kodlarla yeni sistemler arasında uyum sağlamak için kullanışlıdır. Adapter Design Pattern, iki farklı sınıfın birlikte çalışabilmesi için aralarında bir köprü görevi gören bir “adapter” sınıfı oluşturarak gerçekleştirilir.

Adapter Pattern’in temel amacı, bir sınıfın arayüzünü, başka bir sınıfın beklediği bir arayüze dönüştürmektir. Bu sayede, birlikte çalışamayan sınıflar arasında uyum sağlanır. Adapter Pattern, hem nesne tabanlı (object adapter) hem de sınıf tabanlı (class adapter) olarak uygulanabilmektedir. Java, çoklu kalıtımı desteklemediğinden genellikle nesne tabanlı adaptör kullanımı daha yaygındır.

Adapter Pattern Kullanımının Avantajları ve Dezavantajları

Avantajları

  1. Uyumlu Hale Getirme: Farklı arayüzlere sahip sınıfları bir araya getirerek uyumsuzluğu giderir. Bu sayede farklı yapıda geliştirilmiş sınıfları birlikte kullanabiliriz.
  2. Yeniden Kullanım: Adaptörler, mevcut sınıfları değiştirmeden yeni bir arayüzle kullanmamızı sağlar. Bu, kodun yeniden kullanılabilirliğini artırır.
  3. Esneklik: Adaptörler, sistemi esnek hale getirir. Yeni sınıfların eklenmesi veya mevcut sınıfların değiştirilmesi gerektiğinde kolayca adapte edilebilirler.

Dezavantajları

  1. Kod Karmaşıklığı: Adaptörler, ekstra bir katman eklediği için kod karmaşıklığını artırabilir. Bu da, bakımı zorlaştırır.
  2. Performans Kaybı: Adaptörler, arayüz dönüşümleri nedeniyle performans kaybına yol açabilir. Ancak bu genellikle önemsizdir ve optimize edebiliriz.

Adapter Pattern Örnek Senaryo

Bir medya oynatıcı uygulamamız olduğunu varsayalım. Bu uygulama, MediaPlayer arayüzünü kullanarak ses dosyalarını çalabiliyor. Ancak, elimizde sadece MP3 dosyalarını çalabilen bir sınıf (Mp3Player) var. Yeni bir gereksinim olarak, uygulamanın MP4 ve VLC formatındaki dosyaları da çalabilmesi gerekiyor. Bunu sağlamak için Adapter Pattern kullanarak mevcut sistemi genişletebiliriz.

MediaPlayer Arayüzü

public interface MediaPlayer {
    void play(String audioType, String fileName);
}

Mp3Player Sınıfı

public class Mp3Player implements MediaPlayer {
    @Override
    public void play(String audioType, String fileName) {
        if(audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing mp3 file. Name: " + fileName);
        } else {
            System.out.println("Invalid media. mp3 format supported only.");
        }
    }
}

AdvancedMediaPlayer Arayüzü

public interface AdvancedMediaPlayer {
    void playVlc(String fileName);
    void playMp4(String fileName);
}

VlcPlayer ve Mp4Player Sınıfları

public class VlcPlayer implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        System.out.println("Playing vlc file. Name: " + fileName);
    }

    @Override
    public void playMp4(String fileName) {
        // Do nothing
    }
}

public class Mp4Player implements AdvancedMediaPlayer {
    @Override
    public void playVlc(String fileName) {
        // Do nothing
    }

    @Override
    public void playMp4(String fileName) {
        System.out.println("Playing mp4 file. Name: " + fileName);
    }
}

MediaAdapter Sınıfı

public class MediaAdapter implements MediaPlayer {
    AdvancedMediaPlayer advancedMusicPlayer;

    public MediaAdapter(String audioType) {
        if(audioType.equalsIgnoreCase("vlc")) {
            advancedMusicPlayer = new VlcPlayer();
        } else if(audioType.equalsIgnoreCase("mp4")) {
            advancedMusicPlayer = new Mp4Player();
        }
    }

    @Override
    public void play(String audioType, String fileName) {
        if(audioType.equalsIgnoreCase("vlc")) {
            advancedMusicPlayer.playVlc(fileName);
        } else if(audioType.equalsIgnoreCase("mp4")) {
            advancedMusicPlayer.playMp4(fileName);
        }
    }
}

AudioPlayer Sınıfı

public class AudioPlayer implements MediaPlayer {
    MediaAdapter mediaAdapter;

    @Override
    public void play(String audioType, String fileName) {
        if(audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing mp3 file. Name: " + fileName);
        } else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
            mediaAdapter = new MediaAdapter(audioType);
            mediaAdapter.play(audioType, fileName);
        } else {
            System.out.println("Invalid media. " + audioType + " format not supported");
        }
    }
}

Kullanım

public class AdapterPatternDemo {
    public static void main(String[] args) {
        AudioPlayer audioPlayer = new AudioPlayer();

        audioPlayer.play("mp3", "beyond the horizon.mp3");
        audioPlayer.play("mp4", "alone.mp4");
        audioPlayer.play("vlc", "far far away.vlc");
        audioPlayer.play("avi", "mind me.avi");
    }
}

Örneğin açıklanması

Bu örnekte, AudioPlayer sınıfı MediaPlayer arayüzünü kullanarak farklı formatlardaki medya dosyalarını çalabiliyor. MediaAdapter sınıfı, gerekli adaptasyonu sağlayarak VlcPlayer ve Mp4Player sınıflarının kullanılmasını mümkün kılıyor. Bu sayede, yeni medya formatları eklemek ve mevcut sistemi genişletmek çok daha kolay hale geliyor.

Adapter Pattern Kullanım Alanları

1. Eski ve Yeni Kodun Entegrasyonu

Adapter Pattern, mevcut eski kodun (legacy code) yeni sistemlerle entegre edilmesi gerektiğinde sıkça kullanılmaktadır. Eski kodun değiştirilmesi zor veya riskliyse, adapter kullanarak yeni sistemin eski kodla uyumlu hale getirilmesi sağlanır.

2. Farklı Arayüzlerin Uyumluluğu

Farklı arayüzleri kullanan bileşenlerin birlikte çalışması gerektiğinde Adapter Pattern devreye girer. Örneğin, bir API’nin sunduğu arayüzün uygulamanızın beklediği arayüze dönüştürülmesi gerektiğinde bu desen kullanışlıdır.

3. Üçüncü Parti Kütüphaneler

Üçüncü parti kütüphanelerin, mevcut uygulamanın arayüzüne uygun hale getirilmesi gerektiğinde Adapter kalıbı kullanılmalıdır. Bu, kütüphane sağlayıcılarının sunduğu arayüzlerin uygulamanızın ihtiyaçlarını karşılaması için uyarlanması anlamına gelir.

4. Çoklu Arayüz Desteği

Tek bir sınıfın birden fazla arayüzü desteklemesi gerektiğinde Adapter kalıbı kullanılmaktadır. Bu, özellikle farklı istemcilerin farklı arayüzleri beklediği durumlarda yararlıdır.

5. Sistem Bağımlılıklarının Azaltılması

Adapter tasarım deseni, bağımlılıkları azaltmak için kullanılır. Bir sınıfın doğrudan başka bir sınıfa bağımlı olmasını engelleyerek, sistemin daha esnek ve sürdürülebilir hale gelmesini sağlar.

6. Platform Bağımsızlık

Farklı platformlar arasında taşınabilirliği sağlar. Örneğin, farklı işletim sistemlerinde çalışan yazılımların uyumluluğunu artırmak için bu deseni kullanabiliriz.

7. Değişken Formatlar ve Protokoller

Farklı veri formatları veya iletişim protokollerinin kullanılması gerektiğinde Adapter tasarım kalıbı devreye girer. Örneğin, bir uygulamanın XML ve JSON formatlarını desteklemesi gerekiyorsa, bu desen kullanılabilir.

8. İstemci Uyumluluğu

Farklı istemcilerin aynı hizmeti farklı yollarla talep ettiği durumlarda Adapter Pattern kullanabiliriz. Bu, istemcilerin beklediği spesifik arayüzlerin sağlanması için adaptörlerin kullanılmasını içerir.

Adapter Pattern Kullanım Örnekleri

  • Veri Dönüşümleri: Farklı veri kaynaklarından gelen verilerin tek bir formatta işlenmesi gerektiğinde.
  • Grafik Kütüphaneleri: Farklı grafik kütüphanelerinin birlikte kullanılması gerektiğinde.
  • Dosya Sistemleri: Farklı dosya sistemlerinin veya veri tabanlarının aynı uygulama içinde kullanılması gerektiğinde.
  • Ağ Protokolleri: Farklı ağ protokollerinin desteklenmesi gerektiğinde, örneğin HTTP ve FTP.

Kod Örneği: Üçüncü Parti Kütüphane Entegrasyonu

Şimdi bir örnek de gerçek dünya da karşılaşabileceğimiz bir sorun ile ilgili paylaşmak istedim. Diyelim ki bir e-ticaret uygulamanız var ve yeni bir ödeme sağlayıcısı eklemeniz gerekiyor. Ancak, mevcut sisteminiz PaymentProcessor arayüzünü kullanıyor ve yeni sağlayıcı bu arayüzü desteklemiyor. Adapter Pattern kullanarak bu sorunu çözebiliriz.

PaymentProcessor Arayüzü

public interface PaymentProcessor {
    void processPayment(double amount);
}

Eski Ödeme Sınıfı

public class OldPaymentProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment of $" + amount + " using OldPaymentProcessor.");
    }
}

Yeni Ödeme Sağlayıcı Sınıfı

public class NewPaymentService {
    public void makePayment(double amount) {
        System.out.println("Processing payment of $" + amount + " using NewPaymentService.");
    }
}

PaymentAdapter Sınıfı

public class PaymentAdapter implements PaymentProcessor {
    private NewPaymentService newPaymentService;

    public PaymentAdapter(NewPaymentService newPaymentService) {
        this.newPaymentService = newPaymentService;
    }

    @Override
    public void processPayment(double amount) {
        newPaymentService.makePayment(amount);
    }
}

Kullanım

public class AdapterPatternExample {
    public static void main(String[] args) {
        PaymentProcessor oldProcessor = new OldPaymentProcessor();
        oldProcessor.processPayment(100.0);

        NewPaymentService newService = new NewPaymentService();
        PaymentProcessor adapter = new PaymentAdapter(newService);
        adapter.processPayment(200.0);
    }
}

Bu örnekte, PaymentAdapter sınıfı, NewPaymentService sınıfını PaymentProcessor arayüzü ile uyumlu hale getirir. Bu sayede, mevcut sisteminizi değiştirmeden yeni ödeme sağlayıcısını kullanabilirsiniz. Adapter Pattern, bu tür senaryolarda esneklik ve uyumluluk sağlar.

Sonuç

Adapter tasarım deseni, bu esnekliği ve genişletilebilirliği sağlayarak yazılım geliştirme sürecinde önemli bir rol oynar.

Burada anlatılan tasarım deseninin kodlarına GitHub linki üzerinden ulaşabilirsiniz.

Paylaşmak ister misin?

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir