Adaptör Kalıbı
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 97 / 104.
Adapter deseni, uyumsuz arayüzlerin birlikte çalışmasına olanak tanır. Bir sınıfın arayüzünü istemcilerin beklediği bir arayüz haline dönüştürerek iki sınıf arasında bir köprü görevi görür. Bu, özellikle sisteminizin arayüzüyle eşleşmeyen eski kodları veya üçüncü taraf kütüphaneleri entegre ederken kullanışlıdır.
İhtiyacınız olanı yapan mevcut bir sınıfınız olduğunu, ancak arayüzünün kodunuzun beklediğiyle eşleşmediğini hayal edin. Her iki tarafı da değiştirmek yerine, mevcut sınıfı sarmalayan ve çağrıları çeviren bir adaptör oluşturursunuz:
#include <iostream>
#include <string>
// Uyumsuz arayüze sahip mevcut sınıf
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// İstemcinin beklediği hedef arayüz
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// Adaptör LegacyPrinter'ı sarmalar ve Printer arayüzünü uygular
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Çağrıyı dönüştür
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// İstemci beklenen arayüzü kullanır
adapter.print("Hello World");
}Adaptör, LegacyPrinter'a bir referans tutar ve Printer arayüzünü uygular. print() çağrıldığında, işlemi printMessage()'a devreder. İstemci kodu, alttaki eski uygulamayı bilmeden Printer arayüzü ile çalışır.
Mevcut bir sınıfı kullanmak istediğinizde ancak arayüzü ihtiyacınız olanla eşleşmediğinde veya uyumsuz arayüzlere sahip sınıflarla işbirliği yapan yeniden kullanılabilir bir sınıf oluşturmanız gerektiğinde Adapter kullanın.
Görev
KolayFarklı ses kütüphanesi arayüzlerini birbirine bağlayan bir Medya Oynatıcı Adaptörü sistemi kuralım. Eski bir ses kütüphanesini modern bir medya oynatıcı uygulamasına entegre ettiğinizi hayal edin; eski kütüphane mükemmel çalışıyor ancak arayüzü oynatıcınızın beklediğiyle eşleşmiyor. İşte bu, Adaptör (Adapter) deseninin parladığı yerdir.
Kodunuzu dört dosya halinde düzenleyeceksiniz:
MediaPlayer.h: Modern medya oynatıcınızın beklediği hedef arayüzü tanımlayın.Şu saf sanal (pure virtual) metotlara sahip soyut bir
MediaPlayersınıfı oluşturun:play(const std::string& filename)— belirtilen dosyayı oynatırstop()— oynatmayı durdurursetVolume(int level)— ses seviyesini ayarlar (0-100)
Sanal bir yıkıcı (virtual destructor) ekleyin.
LegacyAudioLib.h: Uyumsuz bir arayüze sahip mevcut "eski" ses kütüphanesini oluşturun.LegacyAudioLibsınıfınız, oynatıcınızın beklediğinden farklı çalışan eski bir kütüphaneyi temsil eder. Şunlara sahip olmalıdır:loadAudio(const std::string& path)—Legacy: Loading [path]yazdırırstartPlayback()—Legacy: Playback startedyazdırırstopPlayback()—Legacy: Playback stoppedyazdırıradjustVolume(double percentage)— ondalık bir değer (0.0 ile 1.0 arası) alır veLegacy: Volume set to [percentage]yazdırır (ondalık değeri gösterin)
AudioAdapter.h: İki arayüzü birbirine bağlayan adaptörü oluşturun.AudioAdaptersınıfınızMediaPlayersınıfından miras almalı ve birLegacyAudioLibnesnesine referans tutmalıdır. Adaptör, modern arayüzden gelen çağrıları eski arayüze çevirir:play(), eski kütüphane üzerinde hemloadAudio()hem destartPlayback()metotlarını çağırmalıdırstop(),stopPlayback()metoduna delege edilmelidirsetVolume()bir tam sayı (0-100) alır ancak eski kütüphane ondalık bir değer (0.0-1.0) bekler, bu yüzden bunu dönüştürmeniz gerekecektir
main.cpp: Adaptörün çalışmasını gösterin.İki girdi okuyun:
- Oynatılacak bir dosya adı (string)
- Bir ses seviyesi (tam sayı, 0-100)
Bir
LegacyAudioLibörneği ve onu sarmalayan birAudioAdapteroluşturun. Ardından adaptörüMediaPlayerarayüzü üzerinden (birMediaPlayerişaretçisi olarak saklayarak) şunları yapmak için kullanın:- Ses seviyesini girdi olarak alınan seviyeye ayarlayın
- Girdi olarak alınan dosya adını oynatın
- Oynatmayı durdurun
Örneğin, song.mp3 ve 75 girdileriyle:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedpodcast.wav ve 50 girdileriyle:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedİstemci kodunun tamamen MediaPlayer arayüzüyle nasıl çalıştığına dikkat edin; arka planda asıl işi eski bir kütüphanenin yaptığından haberi bile yoktur. Adaptör, tam sayı ses seviyesini ondalık sayıya dönüştürmek de dahil olmak üzere tüm çeviri işlemini gerçekleştirir. Adaptör deseninin özü budur: uyumsuz arayüzlerin her iki tarafı da değiştirmeden birlikte çalışmasını sağlamak.
Kopya kağıdı
Adapter deseni, iki sınıf arasında bir köprü görevi görerek uyumsuz arayüzlerin birlikte çalışmasını sağlar. Bir sınıfın arayüzünü, istemcilerin beklediği bir arayüze dönüştürür.
Mevcut bir sınıfı kullanmak istediğinizde ancak arayüzü ihtiyacınız olanla eşleşmediğinde veya eski kodları ya da üçüncü taraf kütüphaneleri entegre ederken Adapter desenini kullanın.
Yapı
Desen üç ana bileşenden oluşur:
- Hedef Arayüz (Target Interface): İstemcinin beklediği arayüz
- Adapte Edilen (Adaptee): Uyumsuz arayüze sahip mevcut sınıf
- Adaptör (Adapter): Adapte edileni sarmalar ve hedef arayüzü uygulayarak aralarındaki çağrıları çevirir
Örnek
#include <iostream>
#include <string>
// Uyumsuz arayüze sahip mevcut sınıf (Adaptee)
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// İstemcinin beklediği hedef arayüz
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// Adapter, LegacyPrinter'ı sarmalar ve Printer arayüzünü uygular
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Çağrıyı çevir
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// İstemci beklenen arayüzü kullanır
adapter.print("Hello World");
}Adaptör, adapte edilenin bir referansını tutar ve hedef arayüzü uygular. Adaptör üzerindeki metotlar çağrıldığında, bu çağrıları gerektiği gibi çevirerek adapte edilenin metotlarına devreder. İstemci kodu, eski uygulamadan haberdar olmadan hedef arayüz ile çalışır.
Kendin dene
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// Girdileri oku
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: Bir LegacyAudioLib örneği oluşturun
// TODO: Eski kütüphaneyi sarmalayan bir AudioAdapter oluşturun
// TODO: Adaptörü bir MediaPlayer işaretçisi (pointer) olarak saklayın
// TODO: MediaPlayer arayüzünü şunları yapmak için kullanın:
// 1. Ses seviyesini giriş seviyesine ayarlayın
// 2. Giriş dosya adını oynatın
// 3. Oynatmayı durdurun
return 0;
}
Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.
Nesne Yönelimli Programlama bölümündeki tüm dersler
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı11İleri Düzey OOP Kavramları
Kompozisyon ve Kalıtım KarşılaştırmasıCRTP ile Mixin YapılarıPimpl İdiyomuTür Silme (Type Erasure)Enum Sınıfları ve Güçlü TiplendirmeOOP'de İstisna YönetimiÖzel İstisna Hiyerarşileri14Tasarım Kalıpları Bölüm 2
Komut KalıbıAdaptör KalıbıDekoratör KalıbıŞablon Metot KalıbıDurum KalıbıKompozit KalıbıBir Kalıp Olarak RAII3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner