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:
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!