Türkay Ürkmez

Adapter Design Pattern

Adapter Design Pattern

Merhaba sevgili dostlar!

Tasarım desenleri incelediğimiz makale serimize devam ediyoruz. Eğer önceki makaleleri de takip etmişseniz, Yaratımsal Tasarım Desenleri (Creational Design Patterns) kategorimizi tamamladığımızı biliyorsunuz demektir. Peki o halde, sırada hangi kategori var dersiniz?

Efendim sıradaki tasarım deseni kategorimiz Yapısal (Structural) Tasarım Desenleri ismini alıyor. Bu ada bakarak söz konusu kategorideki desenleri, nesnelerin birbiriyle olan fiziksel ilişkilerini düzenlemek amacıyla kullandığımızı söyleyebiliriz. Bu kısa tanımdan sonra, bu kategorinin ilk tasarım deseni olarak; Adapter Design Pattern’i incelemeye başlayalım dilerseniz.

Biliyorsunuz ki problemi anlamadan, herhangi bir tasarım deseninin nasıl çalıştığını anlamak zor oluyor. O zaman gelin, ilk olarak problemin üzerinde biraz duralım.

Problem

Bir uygulamayı geliştirirken, tüm ihtiyaçlarımızı baştan aşağıya biz karşılamıyoruz doğal olarak. Bir aygıttan veri okumak ya da çıktıyı başka bir uygulamaya göndermek gibi ihtiyaçlarımız için üçüncü parti bileşenlerden faydalanabiliyoruz. Bu bileşenler, bize bazen bir API (Application Programming Interface) aracılığı ile sunuluyor, bazen de bir COM nesnesi olarak bazen de Web Service aracılığı ile. Hangi yöntemle sunulursa sunulsun bizim tek yapacağımız, ihtiyaç duyduğumuz nesnenin ilgili metodunu kullanmaktan ibaret oluyor.

Bu metotların dönüş değerleri ya da aldığı parametreler, yani kısaca arayüzleri, bizim geliştirdiğimiz istemcinin beklediği arayüzden farklı ise ne yapacağız?

Bu soruya ilk olarak şöyle yanıt verebilirsiniz; üçüncü parti bileşenin arayüzü neyse, biz de o arayüze göre bir istemci yazarız. Ancak bu her zaman mümkün olmayabilir. Örneğin, aynı işleve sahip fakat arayüzleri farklı, birden fazla nesne varsa ne yapacaksınız?

Gelin buna daha somut bir örnek verelim; uygulamanız, bir optik okuyucu cihazından gelen verileri alsın. Bu cihazın API’ sini kullanarak söz konusu veri okuma işlemini yapıyorsunuz. Eğer istemcinizi bu API’ ye göre yazarsanız, o zaman uygulamanız sabit bir cihaza göre yazılmış olacaktır. Bunun pek de istenen bir şey olmadığını düşünüyorum.

Demek ki, üçüncü parti bileşenin arayüzünü, istemcinin beklediği arayüze dönüştürmemiz çok daha mantıklı olacaktır.

Kavramlar soyut olduğunda, üzerinde düşünülmesi de zor olabiliyor haliyle. O zaman bu problemi en basit haliyle günlük hayatımıza uyarlayalım bakalım.

Bir tatil için, İngiltere’ye gittiniz. Otelinize girdiniz ve telefonunuzun şarjının bitmek üzere olduğunu fark ettiniz hemen çantanızdan şarj aletini çıkarttınız. Tam prize takacaktınız ki! O da ne! Priz beklediğiniz gibi değil.


Bu durumda, ne yapabilirsiniz? Duvardaki prizi değiştiremezsiniz. E şarj aletini değiştirmek de mantıklı değil (Diğer elektrikli cihazların mesela tıraş makinesinin fişi ne olacak?) . O halde, prizin arayüzünü sizin beklediğiniz arayüze çeviren bir adaptöre ihtiyacınız var.

İşte bu! Uygulama geliştirirken yaşayabileceğiniz problemi, günlük hayatta da yaşıyoruz ve çözüm olarak adaptör nesneler kullanıyoruz. O halde aynı çözümü kendi mimarimiz için neden uygulamayalım?

Çözüm

Öncelikle, geliştirdiğimiz istemcinin beklediği arayüzü tasarlamam gerekiyor (fişinizi nereye takabiliyorsunuz ?) . Sonra da adapte edilecek nesneye erişen bir sınıfa ihtiyacım olacak (duvardaki priz ne istiyor?). İşte bu kadar! Hadi o zaman bunları koda bir senaryo dâhilinde uygulayalım. Farz edelim ki, POS cihazından gelen veriyi okumak üzere bir uygulama yazıyorsunuz. Cihazın API’si tarafından nesnemiz de aşağıdaki gibi olsun:

Daha önce de belirttiğim gibi, bu örnekte de düşünmem gereken şeylerden biri şu olmalı: uygulama sadece bir POS cihazına (ve onun API’sine) bağlı olmamalı. Madem öyle, tüm POS cihazları ile çalışabilecek bir adaptör arayüzü tasarlamamız gerekiyor. İşte o arayüz geliyor:

İstemci, işte bu arayüz üzerinden cihaz API’si ile iletişim kurabilecek. POS cihazı ile çalışacak olan her sınıfı bu interface’den implemente etmem gerekiyor. Şu anda elimde bulunan X Bankın POS cihazı için gereken sınıfımı aşağıdaki gibi yazıyorum:

Bu sınıfa lütfen dikkat edin. Tanımladığımız interface’den gelen ReadCardData() metodu, adapte edilecek nesnenin (XBankPOSReader) ilgili metodunu (ReadFromCard()) çağırıyor yalnızca.

Yani istemci, XBankCardReaderAdapter adaptör nesnesine erişecek (şarj aletini adaptöre takacak). Adaptör de, asıl nesne üzerindeki ihtiyacımız olan metodu çağıracak (adaptörü duvardaki prize takacak). Hadi o zaman, istemcimizin kodlarını görelim:

İşte bu kadar! Kodun geliştirilebilirliği ortada duruyor. Bu uygulamanın başka bir kart okuyucu API’siyle çalışması gerektiğinde, geliştiricinin yapması gereken tek bir şey var. Yeni bir sınıfı Interface’den üretmek ve yukarıdaki 13. Satırda, bu sınıfın örneğini almak. Bu kadar…

Ha unutmadan, adapter tasarım deseninin UML diyagramı da burada dursun:

Evet, sevgili tasarım deseni severler… Bir yazımızın daha sonuna gelmiş bulunuyoruz. Bir sonrakine dek kendinize iyi bakın…

Kodla kalın…