Sunday, November 22, 2015

C++11'de Uyumlayıcı Örüntüsünün (=Adapter Pattern) Genel (=Generic) Çözümü

Nesneyi dayalı programlamada gevşek bağlı bir çözüm için hizmet alan ve veren sınıf arasına ya soyut sınıf ya da arayüz yerleştiriyoruz. Böylelikle hizmet alan ve hizmet veren sınıflar biri birlerini doğrudan görmezler: #include (C++) ya da import (Java) ifadelerinde hizmet veren sınıfın adı geçmez. Bazen farklı kaynaklardan (kütüphane, çatı, geliştirici) gelen sınıfların arayüzleri uyuşmaz. Bu durumda hizmet alacağımız sınıfın arayüzü  ile hizmet alan sınıfın hizmet alırken kullandığı arayüzü uyumlamak gerekir. Bu amaçla Gang of Four (GOF) örüntülerinden biri olan Uyumlayıcı Örüntüsü (=Adapter Pattern) kullanılır. 
Uyumlayıcı Örüntüsü iki farklı yaklaşımla gerçeklenebilir: 
  • Nesne Uyumlaştırma
Uyumlaştırmaya çalıştığımızı sınıftan bir nesneyi içerecek (=composition) şekilde bir uyumlayıcı sınıf oluşturulur (Şekil-1).
  • Sınıf Uyumlaştırma
Uyumlayıcı sınıf, uyumlaştırmaya çalıştığımızı sınıftan türetilerek oluşturulur (Şekil-2).
Şekil-1 Nesne Uyumlaştırma
Şekil-2 Sınıf Uyumlaştırma
Her iki çözümde de her farklı LegacyInterface için yeni bir LegacyAdapter sınıfı tasarlamamız gerekecektir. Acaba sadece tek bir Uyumlayıcı sınıf yazarak tüm ihtiyaçları karşılayabilir miyiz? Bu sorunun yanıtını  C++11'de arayalım. C++11'de önemli yeniliklerle tanıştık. Bu yeniliklerden biri, değişken parametreli şablon tanımlamamıza izin veren variadic templates:
template <class S,class... R>
class Executor {
  public:
    virtual ~Executor(){}
    virtual S execute(R... x) = 0;
}; 
C++'da daha önce değişken parametreli metodlar tanımlanabiliyordu. Şimdi ise benzer bir gösterimi şablon tanımında kullanabiliyoruz. Burada Executor istemcinin kullandığı arayüzü modelliyor. Genel uyumlayıcıyı Şekil-1'de verildiği şekilde gerçekleştirelim:
#pragma once 

template <class T,class S,class... R>
class GenericAdapter : public Executor<S,R...> {
  public:
    GenericAdapter(T *target, S(T:: *method)(R...)) {
      this->target= target;
      this->method = method;
    }
~GenericAdapter() {
      delete target;
    }

    S execute(R... x) {  
      return (target->*method)(x...);
    }
private:
    T *target; 
    S(T:: *method)(R...); 
}; 
Şimdi ise bu genel uyumlayıcının kullanımını bir örnek üzerinde inceleyelim:
A.h:
class A {
   public: 
       ~A(){
          cout << "A::A()" << endl;
       }
       int fun(int x){
          cout << "A::fun(int)" << endl ;
          return x;
       }
};
B.h:
class B {
   public: 
       ~B(){
          cout << "B::B()" << endl;
       }
       int gun(int x,int y){
          cout << "B::gun(int,int)" << endl ;
          return x+y  ;
       }
}; 
C.h:
class C {
   public: 
       ~C(){
          cout << "C::C()" << endl;
       }
       int run(int x,int y,int z){
          cout << "C::run(int,int,int)" << endl ;
          return x+y+z;
       }
};
App.cpp:
#include "A.h" 
#include "B.h" 
#include "C.h" 
#include "Executor.h" 
#include "GenericAdapter.h" 

int main(){
    Executor<int,int> *p = new GenericAdapter<A,int,int>(new A(),& A::fun);
    Executor<int,int,int> *q= new GenericAdapter<B,int,int,int>(new B(),& B::gun);
    Executor<int,int,int,int> *r= new GenericAdapter<C,int,int,int,int>(new C(),& C::run);

    cout << p->execute(4) << endl ;
    cout << q->execute(4,8) << endl ;
    cout << r->execute(4,8,15) << endl ;

    return 0;
}
Uygulama çalıştırıldığında ekran görüntüsü aşağıdaki şekilde gerçekleşir:
A::fun(int)
4
B::gun(int,int)
12
C::run(int,int,int)
27
Her ne kadar biz tek bir uyumlayıcı sınıf kodlamış olsak da derleyici her farklı T, S ve R tipleri için ayrı bir sınıf üretecektir. Yukarıdaki örnek kod için derleyici GenericAdapter sınıfı için üç adet ve Executor sınıfı için üç adet kod üretecektir. Biz bu sınıfları bir kere yazıyoruz, derleyici her farklı tip için ihtiyaç duydukça kod üretiyor. Buna üretken programlama diyoruz.

No comments:

Post a Comment