Dependency Inversion Principle (Bağımlılığı Tersine Çevirme İlkesi)

Ana Sayfa Blog Dependency Inversion Principle (Bağımlılığı Tersine Çevirme İlkesi)

Dependency Inversion Principle (Bağımlılığı Tersine Çevirme İlkesi)

Bu makalede SOLID prensiplerinin beşinci ve son maddesinden söz edeceğiz. "Neydi bu prensipler?" diye mi sordunuz, buyurun, özet makaleyi inceleyin.

Beş SOLID tasarım ilkesini gözden geçirmemizde son hedefimize ulaştık! Bu son ilke Bağımlılığı Tersine Çevirme ilkesidir ve yüksek düzey kodun düşük düzey koda dayanmaması gerektiğini söyler. Yüksek düzey kod bunun yerine yüksek ve düşük düzey kod arasındaki "aradaki-adam" olarak hizmet eden bir soyutlama katmanına bağlı olmalıdır. Bu ilkenin ikinci bir yönü soyutlamaların ayrıntılara bağımlı olmaması, ayrıntıların soyutlamalara bağlı olmasıdır. Eğer tüm bunlar kafa karıştırıcı geliyorsa, endişelenmeyin. Aşağıda bu ilkenin her iki yönünü de keşfedeceğiz.

Bağımlılığı Tersine Çevirme İlkesi

Bu ilke yüksek düzey kodun düşük düzey koda bağlı olmaması ve soyutlamaların ayrıntılara bağlı olmaması gerektiğini belirtir.

İlkeyi göstermek için, aşağıdaki sınıfı ele alalım:

class Authenticator {

    public function __construct(DatabaseConnection $db)
    {
        $this->db = $db;
    }

    public function findUser($id)
    {
        return $this->db->exec('select * from users where id = ?', [$id]);
    }

    public function authenticate($credentials)
    {
        // Kullanıcı kimliğini doğrula...
    }

}

Tahmin edebileceğiniz gibi, Authenticator sınıfı kullanıcıların bulunması ve kimliğinin doğrulanmasından sorumludur. Bu sınıfın inşa edicisini inceleyelim. Bir DatabaseConnection olgusunu type-hint ettiğimizi göreceksiniz. Dolayısıyla kimlik doğrulayıcımızı veritabanına sıkıca bağlıyoruz ve esasında kullanıcıların her zaman için sadece ilişkisel bir SQL veritabanından sağlanacağını söylüyoruz. Üstelik, bizim yüksek düzey kodumuz (yani, Authenticator) doğrudan düşük düzey koda (yani, Database\Connection) dayanıyor.

Öncelikle "yüksek düzey" ve "düşük düzey" kodu tartışalım. Düşük düzey kod bir diskten dosyalar okunması veya bir veritabanıyla etkileşim gibi temel operasyonları implemente eder. Yüksek düzey kod, karmaşık mantığı enkapsüle eder ve işlev görmek için düşük düzey koda bağımlıdır fakat ona doğrudan bağımlı olmamalıdır. Bunun yerine, yüksek düzey kod düşük düzey kodun tepesinde oturan bir soyutlamaya, örneğin bir interface'e bağlı olmalıdır. Sadece bu kadar değil, aynı zamanda düşük düzeyli kod da bir soyutlamaya dayanmalıdır. Bu yüzden, Authenticator sınıfımız içinde kullanabileceğimiz bir interface yazalım:

interface UserProviderInterface {
    public function find($id);
    public function findByUsername($username);
}

Sonra da bu interface'in bir implementasyonunu Authenticator sınıfımıza enjekte edelim:

class Authenticator {

    public function __construct(UserProviderInterface $users,
                                HasherInterface $hash)
    {
        $this->hash = $hash;
        $this->users = $users;
    }

    public function findUser($id)
    {
        return $this->users->find($id);
    }

    public function authenticate($credentials)
    {
        $user = $this->users->findByUsername($credentials['username']);

        return $this->hash->make($credentials['password']) == $user->password;
    }

}

Bu değişiklikleri yaptıktan sonra, Authenticator sınıfımız şimdi iki adet yüksek düzey soyutlamaya dayanıyor: UserProviderInterface ve HasherInterface. Authenticator içine bu interface'lerin herhangi bir implementasyonunu enjekte edebiliriz. Örneğin, kullanıcılarımız şimdi Redis'te depolanıyorsa, UserProvider sözleşmesini implemente eden bir RedisUserProvider yazabiliriz. Authenticator sınıfı artık düşük düzey depolama operasyonlarına doğrudan bağımlı değildir.

Ayrıca, şimdi düşük düzey kodumuz da yüksek düzey UserProviderInterface soyutlamasına dayanıyor çünkü bu arayüzü kendisi implemente ediyor:

class RedisUserProvider implements UserProviderInterface {

    public function __construct(RedisConnection $redis)
    {
        $this->redis = $redis;
    }

    public function find($id)
    {
        $this->redis->get('users:'.$id);
    }

    public function findByUsername($username)
    {
        $id = $this->redis->get('user:id:'.$username);

        return $this->find($id);
    }

}

Tersine Düşünme

Bu ilkenin uygulanması bir çok geliştiricinin uygulama tasarım yolunu tersine çevirir. Yüksek düzey kodu "yukarıdan aşağıya" biçiminde daha düşük koda doğrudan bağlamak yerine, bu ilke yüksek ve düşük düzey kodun ikisinin de yüksek düzey soyutlamaya bağlı olmasını söyler.

Authenticator sınıfımızın bağımlılıklarını "tersine çevirmeden" önce, bu sınıf bir veritabanı depolama sistemi dışında hiçbir şeyle kullanılamazdı. Eğer depolama sistemimizi değiştirseydik, Authenticator sınıfımızda da değişiklik yapmamız gerekecekti, bu da Açık Kapalı ilkesini ihlal edecekti. Daha önce gördüğümüz gibi, yine birden çok ilke birlikte ayakta duruyor veya birlikte düşüyor.

Authenticator sınıfını depolama katmanı üzerinde bir soyutlamaya bağlı olmaya zorladıktan sonra, onu UserProviderInterface sözleşmemizi implemente eden herhangi bir depolama sistemi ile kullanabiliriz ve Authenticator sınıfının kendisinde hiçbir değişiklik yapmamıza gerek olmaz. Geleneksel bağımlılık zinciri tersine çevrilmiştir ve bu değişiklikle kodumuz çok daha esnek ve hoş bir hale gelmiştir!

Sinan Eldem

Fullstack Web Developer

Laravel Framework ile PHP ve MySQL üzerine özel ders, danışmanlık ve web programcılığı hizmetleri veriyorum.

Danışmak istedikleriniz ile ilgili benimle irtibat kurabilirsiniz.

Benzer Yazılar

Solid İlkeleri (Solid Principles)

Merhaba arkadaşlar,

Daha sonra her biri için ayrı ayrı sayfalar oluşturarak detaylandıracağım Solid Prensiplerine değinmek istiyorum.

Liskov Substitution Principle (Liskov İkame İlkesi)

Bu ilke, bir soyutlamanın herhangi bir implementasyonunu o soyutlamayı kabul eden her yerde kullanabileceğinizi ifade eder ama bunu biraz basitleştirelim.

Open–Closed Principle (Açık/Kapalı İlkesi)

Bir uygulamanın ömrü boyunca, sürekli olarak sıfırdan yeni özellikler eklemekten ziyade mevcut kod temeline ekleme yapmak için daha çok zaman harcanır.

Yorumlar