Bu makalede SOLID prensiplerinin üçüncü maddesinden söz edeceğiz. "Neydi bu prensipler?" diye mi sordunuz, buyurun, özet makaleyi inceleyin.
Merak etmeyin, Liskov İkame İlkesini anlamak, göründüğünden çok daha kolaydır. Bu ilke, bir soyutlamanın herhangi bir implementasyonunu o soyutlamayı kabul eden her yerde kullanabileceğinizi ifade eder ama bunu biraz basitleştirelim. Düz bir ifadeyle, bu ilke eğer bir sınıf bir interface'in bir implementasyonunu kullanıyorsa, bu sınıfta herhangi bir değişiklik yapılmaksızın o interface'in herhangi bir implementasyonunu kullanabilmesi gerektiğini söyler.
Liskov İkame İlkesi
Bu ilke programın doğruluğunu değiştirmeden, nesnelerin yerine bunların alt tiplerinin olgularından birinin konabileceğini belirtir.
Bu ilkeyi göstermek için, önceki bölümdeki OrderProcessor örneğimizi kullanmaya devam edelim. Bu metoda bir göz atalım:
public function process(Order $order)
{
// Siparişi doğrula...
$this->orders->logOrder($order);
}
Dikkat ederseniz Order doğrulandıktan sonra, OrderRepositoryInterface implementasyonunu kullanarak siparişi günlüğe kaydediyoruz. Diyelim ki, bizim sipariş işleme sürecimiz çok küçükken siparişlerimizin hepsini dosya sisteminde CSV formatında depoluyorduk. Tek OrderRepositoryInterface implementasyonumuz CsvOrderRepository idi. Şimdi, sipariş hızımız büyüdüğü için, onları saklamak için ilişkisel bir veritabanı kullanmak istiyoruz. Bu yüzden, yeni depomuz için olası bir implementasyona bir bakalım:
class DatabaseOrderRepository implements OrderRepositoryInterface {
protected $connection;
public function connect($username, $password)
{
$this->connection = new DatabaseConnection($username, $password);
}
public function logOrder(Order $order)
{
$this->connection->run('insert into orders values (?, ?)', [
$order->id, $order->amount,
]);
}
}
Şimdi bu implementasyonu nasıl kullanmak zorunda olduğumuzu inceleyelim:
public function process(Order $order)
{
// Siparişi doğrula...
if ($this->repository instanceof DatabaseOrderRepository)
{
$this->repository->connect('root', 'password');
}
$this->respository->logOrder($order);
}
Fark ettiğiniz gibi, işleyici alıcı sınıfımızın içinden şimdiki OrderRepositoryInterfaceimizin bir veritabanı implementasyonu olup olmadığını kontrol etmeye zorlanıyoruz. Eğer öyleyse, veritabanına bağlanmamız gerekiyor. Bu, çok küçük bir uygulama için bir problem olarak görünmeyebilir ancak OrderRepositoryInterface şayet düzinelerce başka sınıflar tarafından kullanılıyorsa ne olur? Her bir alıcı sınıfta bu "bootstrap" kodunu implemente etmeye zorlanmış olacağız. Bunun sürdürülmesi bir baş ağrısı olacaktır ve hatalara yatkın olacaktır ve tek bir alıcı sınıfı güncellemeyi unutursak uygulamamız kırılacaktır.
Yukarıdaki örnek Liskov İkame İlkesini açıkça bozmaktadır. Alıcı sınıfta connect metodunu çağıran değişikliği yapmadan interface'imizin bir implementasyonunu enjekte edemiyoruz. Peki, problemi tespit ettiğimize göre düzeltelim. İşte yeni DatabaseOrderRepository implementasyonumuz:
class DatabaseOrderRepository implements OrderRepositoryInterface {
protected $connector;
public function __construct(DatabaseConnector $connector)
{
$this->connector = $connector;
}
public function connect()
{
return $this->connector->bootConnection();
}
public function logOrder(Order $order)
{
$connection = $this->connect();
$connection->run('insert into orders values (?, ?)', [
$order->id, $order->amount,
]);
}
}
Artık veritabanına bağlantıyı DatabaseOrderRepository sınıfımız yönetiyor ve alıcı OrderProcessor'den "bootstrap" kodumuzu çıkartabiliriz:
public function process(Order $order)
{
// Siparişi doğrula...
$this->respository->logOrder($order);
}
Bu değişikliği yapmakla, OrderProcessor alıcı sınıfında değişiklik yapmaksızın CsvOrderRepository veya DatabaseOrderRepository implementasyonlarını kullanabiliyoruz. Kodumuz Liskov İkame İlkesine uyuyor! Tartıştığımız mimari kavramların bir çoğunun bilgi ile ilişkili olduğuna dikkat ediniz. Özel olarak, bir sınıfın sahip olduğu bilgi onun "etrafındakilerdir", örneğin periferik kodu ve bir sınıfın işini yapmasına yardım eden bağlımlıklar gibi. Sağlam bir uygulama mimarisi yapmaya çalışırken, sınıf bilgisinin sınırlandırılması tekrar karşımıza çıkan ve önemli bir konu olacaktır.
Ayrıca, Liskov İkame ilkesinin ihlal edilmesinin, öğrendiğimiz diğer ilkelerle ilgili sonuçlar doğurduğuna da dikkat ediniz. Bu ilkenin bozulmasıyla, Açık Kapalı ilkesi de bozulmak durumundadır çünkü eğer alıcı sınıf çeşitli çocuk sınıflarının olgularını kontrol etmek zorundaysa, ne zaman yeni bir çocuk sınıf söz konusu olsa alıcı sınıfın değiştirilmesi gerekecektir.
Sızıntıları İzleyin
Bu ilkenin, önceki bölümde konuştuğumuz "sızdıran soyutlamaların" önlenmesiyle yakından ilgili olduğunu büyük ihtimalle farketmişsinizdir. Bizim veritabanı deposunun sızdıran soyutlaması Liskov İkame İlkesinin bozuluyor olduğunun ilk işaretiydi. Bu sızıntılar yönünden göz kulak olun!