Bu yazı Design Patterns/Tasarım Desenleri nedir? başlıklı yazı dizisinin bir parçasıdır.

Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.

Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.

Adapter deseninin amacı

Adapter birbiriyle uyumlu olmayan arayüzlere (interface) sahip nesnelerinbirlikte çalışabilmelerini sağlayan yapısal bir tasarım desenidir.

Sorun

Hisse senedi piyasalarını takip etmeyi sağlayan bir uygulama geliştirdiğinizi düşünün. Uygulama piyasa verilerini farklı kaynaklardan XML formatında çekiyor ve daha sonra güzel görünümlü grafikler ve diagramlar ile kullanıcıya sunuyor olsun.

Günün birinde üçüncü parti bir analiz kütüphanesi entegre ederek uygulamayı daha da geliştirmeye karar verdiniz diyelim. Yalnız bir sorun var, bu analiz kütüphanesi yalnızca JSON formatı ile çalışabiliyor.

Analiz kütüphanesini olduğu gibi kullanamazsınız çünkü beklediği veri formatı ugylamanıza uyumlu değil.

Kütüphaneyi XML ile çalışabilecek hale getirmeniz mümkün. Ama bu kütüphaneye bağlı başka kodları da değiştirmeniz gerekir. Daha da kötüsü kütüphanenin kaynak koduna erişiminiz olmadığı için böyle bir seçeneğiniz de olmayabilir. Ayrıca gelecekte bu kütüphaneye gelecek güncellemeleri de almanız zorlaşacaktır.

Çözüm

Bir adaptör (adapter) yazabilirsiniz. Bu özel nesne bir nesnenin arayüzünü (interface) değiştirerek diğer nesnenin kullanabileceği hale getirir.

İş yapmak isteyen bir Japon ve bir Alman arasında her iki dili bilen bir kişinin tercümanlık yaptığını düşünün, işte adaptörde buna benzer bir iş yapıypr.

Adapter arka tarafta dönen kompleks dönüştürme işlemlerini gizlemek için nesnelerden birini tamamen sarar. Örneğin feet ve mil gibi emperyal birimlerle işlem yapan bir kütüphaneyi bir adaptör ile sararak tüm veriyi kilometre ve metreye dönüştürebilirsiniz.

Adaptörler sadece veriyi farklı formatlara dönüştürmeyi değil, aynı zamanda farklı arayüzlerin birlikte çalışmasını sağlar. Bir örnekle açıklarsak;

  1. Adaptör mevcut nesnelerden biriyle uyumlu bir arayüzü (interface) alır.
  2. Mevcut nesne bu arayüzü kullanarak adaptörün metodlarını güvenli bir şekilde çağırabilir.
  3. Bir istek geldiğinde adaptör isteği ikinci bir nesneye onun beklediği bir formatda aktarır.

Duruma göre iki yönlü istekleri birbirine çeviriebilecek bir adaptör bile oluşturulabilir.

Tekrar hisse piyasası uygulamamıza geri dönelim. Uyumsuz format sorununu çözmek için analitik kütüphaesinin tüm sınıfları için bir XML-JSON çevirici adaptörü yazar ve kodunuz içerisinde bu adaptörü kullanırsınız. Adaptör bir çağrı aldığında gelen XML verisini JSON yapısına dönüştürür ve çağrıyı uygun metodlarla sarmaladığı analiz nesnenine aktarır.

Uygulanabilirlik

Mevcut bir sınıfı (class) kullanmak istediğiniz fakat o sınıfın arayüzünün (interface) kodunuzun geri kalanı ile uyumlu olmadığı zamanlarda bir adaptör sınıfı kullanabilirsiniz.

Adaptör tasarım deseni, kodunuzla doğrudan uyumlu olmayan mevcut bir sınıf veya üçüncü parti kütüphane arasında bir nevi tercüman olarak hizmet eden bir ara katman oluşturmanızı sağlar.

Diğer tasarım desenleri/kalıpları ile ilişkisi

  • Köprü (Bridge) uygulamanın farklı bölümlerini birbirinden bağımsız olarak geliştirmeniz için henüz o bölümler kodlanmadan önce geliştirilir. Adaptör ise mevcut bir uygulamada kullanılarak birbiri ile uyumsuz sınıfların birlikte çalışmasını sağlar.
  • Adaptör mevcut bir nesnenin arayüzünü değiştirirken, Decorator nesneyi arayüzünü değiştirmeden genişletir. Decorator ile özyinelemeli (recursive) kompozisyonlar oluşturulabilirken, adaptörlerde bu mümkün değildir.
  • Adaptör sarmaladığı nesne için farklı bir arayüz sunar. Proxy bunu aynı arayüzle yaparken, Decorator geliştirilmiş bir arayüzle yapar.
  • Facade mevcut nesneler için yeni bir arayüz tanımlarken Adaptör mevcut bir arayüzü kullanılabilir hale getirmeye çalışır. Adaptör genellikle tek bir nesne sararken, Facade geniş bir alt nesne sistemi ile çalışır.

Adapter Tasarım Deseni Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Adapter\Conceptual;

/**
 * The Target defines the domain-specific interface used by the client code.
 */
class Target
{
    public function request(): string
    {
        return "Target: The default target's behavior.";
    }
}

/**
 * The Adaptee contains some useful behavior, but its interface is incompatible
 * with the existing client code. The Adaptee needs some adaptation before the
 * client code can use it.
 */
class Adaptee
{
    public function specificRequest(): string
    {
        return ".eetpadA eht fo roivaheb laicepS";
    }
}

/**
 * The Adapter makes the Adaptee's interface compatible with the Target's
 * interface.
 */
class Adapter extends Target
{
    private $adaptee;

    public function __construct(Adaptee $adaptee)
    {
        $this->adaptee = $adaptee;
    }

    public function request(): string
    {
        return "Adapter: (TRANSLATED) " . strrev($this->adaptee->specificRequest());
    }
}

/**
 * The client code supports all classes that follow the Target interface.
 */
function clientCode(Target $target)
{
    echo $target->request();
}

echo "Client: I can work just fine with the Target objects:\n";
$target = new Target();
clientCode($target);
echo "\n\n";

$adaptee = new Adaptee();
echo "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";
echo "Adaptee: " . $adaptee->specificRequest();
echo "\n\n";

echo "Client: But I can work with it via the Adapter:\n";
$adapter = new Adapter($adaptee);
clientCode($adapter);

Örnek Python Kodu

class Target:
    """
    The Target defines the domain-specific interface used by the client code.
    """

    def request(self) -> str:
        return "Target: The default target's behavior."


class Adaptee:
    """
    The Adaptee contains some useful behavior, but its interface is incompatible
    with the existing client code. The Adaptee needs some adaptation before the
    client code can use it.
    """

    def specific_request(self) -> str:
        return ".eetpadA eht fo roivaheb laicepS"


class Adapter(Target, Adaptee):
    """
    The Adapter makes the Adaptee's interface compatible with the Target's
    interface via multiple inheritance.
    """

    def request(self) -> str:
        return f"Adapter: (TRANSLATED) {self.specific_request()[::-1]}"


def client_code(target: "Target") -> None:
    """
    The client code supports all classes that follow the Target interface.
    """

    print(target.request(), end="")


if __name__ == "__main__":
    print("Client: I can work just fine with the Target objects:")
    target = Target()
    client_code(target)
    print("\n")

    adaptee = Adaptee()
    print("Client: The Adaptee class has a weird interface. "
          "See, I don't understand it:")
    print(f"Adaptee: {adaptee.specific_request()}", end="\n\n")

    print("Client: But I can work with it via the Adapter:")
    adapter = Adapter()
    client_code(adapter)

Diğer Tasarım Kalıpları/Design Patterns

Yaratımsal Kalıplar (Creational Patterns)

Yapısal Kalıplar (Structural Patterns)

Davranışsal Kalıplar (Behavioral Patterns)