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.

Bridge tasarım deseninin amacı

Bridge, Türkçe karşılığı ile köprü büyük sınıfları veya birbiriyle yakın ilişkili sınıfları, birbirinden bağımsız olarak geliştirilebilecek iki ayrı hiyerarşiye bölmenizi sağlayan bir tasarım desenidir.

Sorun

Abstraction (Soyutlama) ve Implementation (Uygulama) kavramları korkutucu mu geliyor. Paniğe gerek yok, hemen basit iki örnek düşünelim;

Diyelimki Circle (Çember) ve Square (Kare) gibi bir kaç alt sınıfı olan Shape (Şekil) adlı bir sınıfınız olsun. Bu sınıf hiyerarşisini renkleri de dahile decek şekilde genişletmek istiyorsunuz ve bu amaçla Red (Kırmızı) ve Blue mavi şekil alt sınıflarını oluşturdunuz. Fakat zaten iki şekil alt sınıfınız olduğu için MaviDaire, KırmızıKare gibi dört farklı kombinasyon hazırlamanız gerekecek.

Sınıf kombinasyonları geometrik olarak artacaktır.

Yeni şekil tür veya renkleri eklemek hiyerarşinizi eksponensiyel olarak büyütecektir. Örneğin bir üçgen eklemek istediğinizde her iki renk için de ayrı alt sınıflar oluşturmanız gerekecek. Ve daha sonra yeni bir renk eklemek isterseniz her şekil için bir tane olmak üzere üç alt sınıf daha eklemeniz gerekecek. Daha fazlasını ekledikçe bu durum daha da kötüye gidecek.

Çözüm

Bu sorunla karşılaşmamızın nedeni şekil alt sınıfını iki bağımsız boyutta genişletmeye çalışmamız. Bu sınıf türetme (class inheritance) yaparken maalesef karşınıza sık çıkacak bir sorundur.

Köprü deseni (Bridge pattern) bu kalıtsal türetme (inheritance) işini nesne bileşimi haline getirerek çözüyor. Bunun anlamı bu iki boyuttan birini ayrı bir sınıf haline getiriyor ve orijinal sınıf içerisinden buna referans veriyorsunuz, böylece orijinal sınıf diğer sınıfın bütün metod ve durumlarını içermek zorunda kalmıyor.

Sınıf hiyerarşinizin gereksiz büyümesini engellemek için birden fazla ilişkili sınıflara ayırabilirsiniz.

Bu yaklaşımı uygulayarak renkle ilgili kodları Kırmızı ve Mavi alt sınıfları içine koyabiliriz. Böylece şekil sınıfının sadece ilgili renk sınıfına bir referans tutması yeterli olur.

Uygulanabilirlik

Belirli bir işlevin çeşitli varyasyonlarını içeren monoloitik bir sınıfı parçalara bölmek ve yönetmek istediğinizde köprü (Bridge) tasarım desenini kulllanın.

Sınıf büyüdükçe nasıl çalıştığını anlamak daha zor olacak, gerektiğinde değişiklik yapmak daha fazla vakit alacaktır. Varyasyonların birinde yapılan bir değişiklik tüm sınıfta değişiklik yapmayı gerektirebilir. Böyle bir değişiklik genelde hataya açık veya farkedilmeyen kritik yan etkilere neden olabilir.

Köprü deseni monolotilik bir sınıfı bir kaç sınıf hiyerarşisine döndürmenize olanağ sağlar. Bunu yaptıktan sonra hiyerarşideki sınıfları diğerlerinden bağımsız olarak değiştirebilirsiniz. Bu yaklaşım kodun bakımını kolaylaştırırken mevcut kodda hatalara neden olma riskini de azaltır.

Bir sınıfı bağımsız birden fazla boyuta genişletecekseniz bu sınıfı kullanın.

Köprü tüm boyutlar için ayrı sınıflar oluşturmayı önerir. Orijinal sınıf tüm işi kendi yapmaktansa bu alt sınıflara yükler.

Çalışma anında uygulama yöntemlerini değiştirmek için köprü desenini kullanın.

Her ne kadar isteğe bağlı da olsa, köprü deseni abstraction (soyutlama) içerisindeki uygulanışı (implementation) değiştirme olanağı sağlar. Basitçe ilgili alana yeni bir değer atayarak bunu sağlayabilirsiniz.

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.
  • Bridge ile birlikte Abstract Factory kullanabilirsiniz. Köprünün (bridge) tanımladığı bazı soyutlamalar (abstraction) sadece belirli uygulanışlar (implementation) için geçerli olduğunda bu iş birliği kullanışlıdır. Bu durumda Abstract Factory bu ilişkileri kapsamına alır ve kompleks yapıyı istemci koddan gizlemiş olur.
  • Builder ve Bridge‘i birleştirebilirsiniz. Yönetici (director) sınıf soyutlama (abstraction) rolünü oynarken, farklı builder’lar uygulanışı (implementation) belirler.

Bridge Tasarım Deseni Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Bridge\Conceptual;

/**
 * The Abstraction defines the interface for the "control" part of the two class
 * hierarchies. It maintains a reference to an object of the Implementation
 * hierarchy and delegates all of the real work to this object.
 */
class Abstraction
{
    /**
     * @var Implementation
     */
    protected $implementation;

    public function __construct(Implementation $implementation)
    {
        $this->implementation = $implementation;
    }

    public function operation(): string
    {
        return "Abstraction: Base operation with:\n" .
            $this->implementation->operationImplementation();
    }
}

/**
 * You can extend the Abstraction without changing the Implementation classes.
 */
class ExtendedAbstraction extends Abstraction
{
    public function operation(): string
    {
        return "ExtendedAbstraction: Extended operation with:\n" .
            $this->implementation->operationImplementation();
    }
}

/**
 * The Implementation defines the interface for all implementation classes. It
 * doesn't have to match the Abstraction's interface. In fact, the two
 * interfaces can be entirely different. Typically the Implementation interface
 * provides only primitive operations, while the Abstraction defines higher-
 * level operations based on those primitives.
 */
interface Implementation
{
    public function operationImplementation(): string;
}

/**
 * Each Concrete Implementation corresponds to a specific platform and
 * implements the Implementation interface using that platform's API.
 */
class ConcreteImplementationA implements Implementation
{
    public function operationImplementation(): string
    {
        return "ConcreteImplementationA: Here's the result on the platform A.\n";
    }
}

class ConcreteImplementationB implements Implementation
{
    public function operationImplementation(): string
    {
        return "ConcreteImplementationB: Here's the result on the platform B.\n";
    }
}

/**
 * Except for the initialization phase, where an Abstraction object gets linked
 * with a specific Implementation object, the client code should only depend on
 * the Abstraction class. This way the client code can support any abstraction-
 * implementation combination.
 */
function clientCode(Abstraction $abstraction)
{
    // ...

    echo $abstraction->operation();

    // ...
}

/**
 * The client code should be able to work with any pre-configured abstraction-
 * implementation combination.
 */
$implementation = new ConcreteImplementationA();
$abstraction = new Abstraction($implementation);
clientCode($abstraction);

echo "\n";

$implementation = new ConcreteImplementationB();
$abstraction = new ExtendedAbstraction($implementation);
clientCode($abstraction);

Örnek Python Kodu

from __future__ import annotations
from abc import ABC, abstractmethod


class Abstraction:
    """
    The Abstraction defines the interface for the "control" part of the two
    class hierarchies. It maintains a reference to an object of the
    Implementation hierarchy and delegates all of the real work to this object.
    """

    def __init__(self, implementation: Implementation) -> None:
        self.implementation = implementation

    def operation(self) -> str:
        return (f"Abstraction: Base operation with:\n"
                f"{self.implementation.operation_implementation()}")


class ExtendedAbstraction(Abstraction):
    """
    You can extend the Abstraction without changing the Implementation classes.
    """

    def operation(self) -> str:
        return (f"ExtendedAbstraction: Extended operation with:\n"
                f"{self.implementation.operation_implementation()}")


class Implementation(ABC):
    """
    The Implementation defines the interface for all implementation classes. It
    doesn't have to match the Abstraction's interface. In fact, the two
    interfaces can be entirely different. Typically the Implementation interface
    provides only primitive operations, while the Abstraction defines higher-
    level operations based on those primitives.
    """

    @abstractmethod
    def operation_implementation(self) -> str:
        pass


"""
Each Concrete Implementation corresponds to a specific platform and implements
the Implementation interface using that platform's API.
"""


class ConcreteImplementationA(Implementation):
    def operation_implementation(self) -> str:
        return "ConcreteImplementationA: Here's the result on the platform A."


class ConcreteImplementationB(Implementation):
    def operation_implementation(self) -> str:
        return "ConcreteImplementationB: Here's the result on the platform B."


def client_code(abstraction: Abstraction) -> None:
    """
    Except for the initialization phase, where an Abstraction object gets linked
    with a specific Implementation object, the client code should only depend on
    the Abstraction class. This way the client code can support any abstraction-
    implementation combination.
    """

    # ...

    print(abstraction.operation(), end="")

    # ...


if __name__ == "__main__":
    """
    The client code should be able to work with any pre-configured abstraction-
    implementation combination.
    """

    implementation = ConcreteImplementationA()
    abstraction = Abstraction(implementation)
    client_code(abstraction)

    print("\n")

    implementation = ConcreteImplementationB()
    abstraction = ExtendedAbstraction(implementation)
    client_code(abstraction)

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)