8 Kasım 2019 Cuma

GoF - Adapter Örüntüsü

Giriş
Adapter bir Yapısal Örüntüdür. Adapter iki standart arayüzü birbirine dönüştürmek için kullanılır. Burada dikkat edilmesi gereken kelime arayüz kelimesi. Örneğin VGA girişini DVI çıkışına çeviren fiziksel nesne de adaptördür. Adapter örüntüsü de fiziksel nesne ile aynı işlevi yapar.

Kavramlar
1. Target Interface - Client tarafından bilinen arayüzdür.
2. Adaptee Interface - Kullanılmak istenen ancak Target Interface'e uymayan arayüzdür
3. Adapter - Bizim yazdığımız kod. Adaptee nesnesini Target Interface ile uyumlu hale getirir.

Adapter Gerçekleştirimleri
Açıklaması şöyle. Tüm Adapter örüntüleri kalıtım kullanmak zorundadır.
The GoF lists three different approaches to design a Target API for adaptation. The first two are recognizable as a couple of their Behavioral design patterns.

1. Template Method
2. Strategy
3. Closures (what Smalltalk calls code blocks)
Template Method
Normalde Eğer Strategy kullanıyorsak Adapter sınıfımızın constructor metoduna belli bir tip geçmek gerekir. Yani bir adapter bir target ile çalışır. Ancak elimizde daha fazla target nesne varsa template kullanabiliriz.
Örnek
Şöyle yaparız. Burada amaç Point bekleyen metoda örneğin Line geçebilmek. GenericLineAdapter kullanarak hangi tipte Line olursa olsun onu Point bekleyen metoda geçebiliriz.
template<class T>
struct GenericLineAdapter : Point {
  T&      m_line;
  GenericLineAdapter(T &line) : m_line(line) {}
  void draw(){ m_line.draw(); }
};
Strategy
Bu örüntünün iki çeşidi olsa da her iki yöntemde de Adapter, ihtiyaç duyduğumuz Target arayüzünü gerçekleştirmek zorundadır.
- Eğer Target bir sınıf ise Adapter mecburen kalıtır.
- Eğer Target bir arayüz ise Adapter bu arayüzü gerçekleştirir.

1. Object Adapter Türevi - Composition
Adapter sınıfı Target arayüzünden kalıtır/gerçekleştirir ve verilen Adaptee nesnesini sarmalar. Yani klasik composition çözümü uygulanır. Eğer Target sınıfının gerçekleştirilmesi gereken çok sayıda metodu varsa, zor bir çözüm halini alabilir.

Örnek
Şöyle yaparız. Mevcut kod yani draw_point() metodu Point nesnesi alıyor. Ancak biz Line nesnesi kullanmak istiyoruz. LineAdapter ile bu iş halledilebilir.
struct Point {
  int32_t     m_x;
  virtual void draw(){ cout<<"Point\n"; }
};


struct Point2D : Point {
  int32_t     m_y;
  void draw(){ cout<<"Point2D\n"; }
};


void draw_point(Point &p) {
  p.draw();
}


struct Line {
  Point2D     m_start;
  Point2D     m_end;
  void draw(){ cout<<"Line\n"; }
};

struct LineAdapter : Point {
  Line&       m_line;
  LineAdapter(Line &line) : m_line(line) {}
  void draw(){ m_line.draw(); }
};

int main() {
  Line l;
  LineAdapter lineAdapter(l);
  draw_point(lineAdapter);
  return EXIT_SUCCESS;
}
1.1 Pluggable Adapter
Bu aslında Closure veya Functional Interfaces ile kurulan bir yapı. Açıklaması şöyle.
A distinguishing feature of pluggable adapters is that the name of a method called by the client and that existing in the ITarget interface can be different. The adapter must be able to handle the name change. In the previous adapter variations, this was true for all Adaptee methods, but the client had to use the names in the ITarget interface. (...)

The pluggable adapter sorts out which object is being plugged in at the time. Once a service has been plugged in and its methods have been assigned to the delegate objects, the association lasts until another set of methods is assigned. What characterizes a pluggable adapter is that it will have constructors for each of the types that it adapts. In each of them, it does the delegate assignments (one, or more than one if there are further methods for rerouting).
Bir başka açıklama şöyle
The pluggable adapter pattern is a technique for creating adapters that doesn't require making a new class for each adaptee interface you need to support.
Örnek
Elimizde şöyle bir kod olsun. Bu örnekte Composition kullanılıyor ancak Target arayüzün Estimate() metodunu kullanmak yerine yeni bir Request metodu tanımlanıyor. Request metodu istenirse Adaptee veya istenirse Target arayüzünü çağırabiliyor.
class Adaptee
{
  public double Precise(double a, double b)
  {
    ...;
  }
}

// New standard for requests
class Target
{
  public string Estimate(int i)
  {
    ...
  }
}    

// Implementing new requests via old
class Adapter : Adaptee
{
    public Func<int, string> Request;    
    // Different constructors for the expected targets/adaptees    
    // Adapter-Adaptee
    public Adapter(Adaptee adaptee)
    {
      // Set the delegate to the new standard
      Request = x => Math.Round(Precise(x, 3));
    }

    // Adapter-Target
    public Adapter(Target target)
    {
      // Set the delegate to the existing standard
      Request = target.Estimate;
    }
}
Kullanmak için şöyle yaparız.
Adapter adapter1 = new Adapter (new Adaptee(  ));
//Here, it will call the Adaptee's abstracted method. 
adapter1.Request(5);

//The only way to call the Target's method is to instantiate a new adapter with the
//target    
Adapter adapter2 = new Adapter (new Target(  ));
Console.WriteLine(adapter2.Request(5));
Örnek
Şöyle yaparız. Burada Adapter Beverage ile ilklendirilirse Beverage gibi davranır. Eğer JuiceMaker ile ilklendirilirse JuiveMaker gibi davranır.
/* Legacy code -------------------------------------------------------------- */
struct Beverage {
  virtual void getBeverage() = 0;
};

struct CoffeeMaker : Beverage {
  void Brew() { cout << "brewing coffee" << endl;}
  void getBeverage() { Brew(); }
};


void make_drink(Beverage &drink){
  drink.getBeverage();                // Interface already shipped & known to client
}

/* --------------------------------------------------------------------------- */
struct JuiceMaker {                     // Introduced later on
  void Squeeze() { cout << "making Juice" << endl; }
};

struct Adapter : Beverage {              // Making things compatible
  function<void()>    m_request;
  Adapter(CoffeeMaker* cm) { m_request = [cm] ( ) { cm->Brew(); }; }
  Adapter(JuiceMaker* jm) { m_request = [jm] ( ) { jm->Squeeze(); }; }
  void getBeverage() { m_request(); }

};

int main() {
  Adapter adp1(new CoffeeMaker());
  make_drink(adp1);
  Adapter adp2(new JuiceMaker());
  make_drink(adp2);
  return EXIT_SUCCESS;
}
2. Class Adapter - Multiple Inheritance
Adapter sınıfı Target ve Adaptee sınıflarından multiple inheritance kullanarak aynı anda kalıtır. C++ gibi diller tarafından kullanılabilen bir yöntem. Java gibi multiple inheritance desteklemeyen diller tarafından bu yöntem biraz değiştirilerek kullanılır.

Örnek
C# kodunda şöyle yaparız.
// Required standard for requests
interface ITarget {
  // Rough estimate required
  string Request (int i);
}


// Existing way requests are implemented
class Adaptee {

  // Provide full precision
  public double SpecificRequest (double a, double b) {
    ...;
  }
}


// Implementing the required standard via Adaptee
class Adapter : Adaptee, ITarget {

   public string Request (int i) {
    return "Rough estimate is " + (int) Math.Round(SpecificRequest (i,3));
  }
}

2.1 . Two Way Adapter
Two Way Adapter yazısına taşıdım.

Adapter'ın Hangi Mesajı Sarmaladığını Nasıl Anlarız
1. Adapter'a bir getAdapteeMessageType() metodu yazılır. Bu metoda bakarak gerçek nesneye cast edilir.


Örnek - Yanlış İsimlendirme
Normalde Adapter örüntüsü için iki tane farklı arayüz gerekir. Bunlar Adaptee ve Target Interface Elimizde Callback şeklinde Client'ı tetikleyen bir kod parçası vardı. Bu callback'lere bir filtre eklenmesi gerekti. Böylece Client her zaman tetiklenmeyecekti. Filtre koduna Adapter ismi verildi.

Filtre kodu EventSource'u ve IFilter nesnesini sarmaladı ve EventSource'a kendisi abone oldu. Gelen event'i süzdü ve gerekiyorsa Client'a verdi. Burada bir arayüzü diğerine çevirme olayı olmamasına rağmen sınıfa XFilterAdapter ismi verilmesi bence yanlıştı

Örnek - Doğru İsimlendirme
Elimizde iki tane mesaj seti vardı (A seti ve B seti) ve bu mesajlar farklı hiyererşilere sahipti. Client normalde A seti ile çalışıyordu ancak daha sonra B setine ne de çevirip farklı bir  sisteme de mesaj gönderme ihtiyacı çıktı. ABAdapter yazılarak A mesajını B mesajına çeviren ve gönderen bir sınıf yazıldı.

Diğer
Adapter,Decorator ve Facade davranış açısından bir başka nesneyi sarmaladıkları için birbirlerine benzerler. Açıklaması şöyle.
A decorator makes it possible to add or alter behavior of an interface at run-time. Alternatively, the adapter can be used when the wrapper must respect a particular interface and must support polymorphic behavior, and the Facade when an easier or simpler interface to an underlying object is desired
Adapter ve Decorator arasındaki fark nedir
Decorator arayüzü değiştirmez, sadece sınıfa yeni davranış ekler.

Facade ve Adapter arasındaki fark nedir?
Facade ise sadece kullanım kolaylığı için vardır. Arayüzler çok önemli değildir

Hiç yorum yok:

Yorum Gönder