Liskov Substitution Principle (LSP) : Daha Sağlam ve Esnek Kodlama
Liskov Substitution Principle (LSP), S.O.L.I.D. prensiplerinin üçüncüsü olarak, nesne yönelimli programlamada önemli bir yer tutar. Bu prensip, alt sınıfların üst sınıfların yerine geçebilmesi ve programın beklenmeyen davranışlar sergilemeden çalışmasını garanti altına almayı hedefler.
Bu makalede LSP’nin neden önemli olduğunu ve nasıl uygulanabileceğini inceleyeceğiz. Ayrıca, LSP’ye uygun olmayan ve uygun olan kod örneklerini ele alarak, projelerimizde nasıl daha tutarlı ve güvenilir bir tip hiyerarşisi elde edebileceğimizi göreceğiz.
Liskov Substitution Principle (LSP), Nesne Yönelimli Programlama’nın (OOP) temel ilkelerinden biridir ve özellikle polimorfizm kavramıyla yakından ilişkilidir. Polimorfizm, üst sınıfların referanslarını kullanarak farklı alt sınıf nesnelerini çağırabilme yeteneğini sağlar. LSP’nin amacı, bu polimorfik yapının doğru ve tutarlı bir şekilde uygulanmasını garanti altına almaktır.
LSP’ye göre:
- Alt sınıflar, üst sınıfın sunduğu tüm işlevleri korumalıdır.
- Alt sınıflar, üst sınıfın davranışlarını bozmadan veya beklenmeyen sonuçlar doğurmadan uygulamalıdır.
Eğer bu prensip ihlal edilirse, polimorfizmin faydaları ortadan kalkabilir ve sistemde beklenmeyen hatalar ortaya çıkabilir. Bu nedenle, LSP’ye uygun tasarım yapmak, kodun esnekliğini artırır ve bakımını kolaylaştırır.
LSP’yi Java İle Uygulama
Şimdi LSP’yi Java ile nasıl uygulayabileceğimize bakalım. Öncelikle bu prensibe uymayan bir örnek üzerinden gidip, ardından bu örneği LSP’ye uygun hale getireceğiz.
LSP’ye Uymayan Örnek
Kredi Kartı ve Bitcoin ile ödeme sistemini modelleyen sınıflarımız olduğunu varsayalım:
- Üst sınıf:
Payment
- Alt sınıflar:
CreditCardPayment
veBitcoinPayment
Ortak kodlarımızın olduğu ve override edilmesi beklenen metodları olan abstract (soyut) bir Payment sınıfı oluşturalım. İçinde ödeme ve iade olmak üzere iki abstract metodumuz olsun.
public abstract class Payment {
// Ortak kodlar bu sınıfta olmalı.
public abstract void processPayment(BigDecimal amount);
public abstract void refund(BigDecimal amount);
}
Kredi kartı işlemlerini yapabileceğimiz, Payment sınıfından türeyen CreditCardPayment sınıfımızı oluşturalım:
public class CreditCardPayment extends Payment {
@Override
public void processPayment(BigDecimal amount) {
System.out.println("Kredi kartı ile " + amount + " TL ödeme yapıldı.");
}
@Override
public void refund(BigDecimal amount) {
System.out.println("Kredi kartı ile " + amount + " TL iade yapıldı.");
}
}
Bitcoin ile ödeme işlemlerini yapabileceğimiz, Payment sınıfından türeyen BitcoinPayment sınıfımızı oluşturalım:
public class BitcoinPayment extends Payment {
@Override
public void processPayment(BigDecimal amount) {
System.out.println("Bitcoin ile " + amount + " BTC ödeme yapıldı.");
}
@Override
public void refund(BigDecimal amount) {
// Bitcoin için iade işlemi desteklenmiyor
throw new UnsupportedOperationException("Bitcoin ödemelerinde iade yapılamaz.");
}
}
Teknik olarak Bitcoin iadesi yapamayacağımız için, refund metodunda exception fırlatıldığına dikkat edin.
Şimdi main metodunda bu sınıfları aşağıdaki gibi örnekleyip kullanalım.
public static void main(String[] args) {
Payment creditCardPayment = new CreditCardPayment();
creditCardPayment.processPayment(BigDecimal.valueOf(100));
creditCardPayment.refund(BigDecimal.valueOf(100));
Payment bitcoinPayment = new BitcoinPayment();
bitcoinPayment.processPayment(BigDecimal.valueOf(0.0025));
bitcoinPayment.refund(BigDecimal.valueOf(0.0025));
}
Kodu çalıştırdığımızda aşağıdaki gibi bir çıktı alırız.
Kredi kartı ile 100 TL ödeme yapıldı.
Kredi kartı ile 100 TL iade yapıldı.
Bitcoin ile 0.0025 BTC ödeme yapıldı.
Exception in thread "main" java.lang.UnsupportedOperationException:
Bitcoin ödemelerinde iade yapılamaz.
at bad.payment.BitcoinPayment.refund(BitcoinPayment.java:14)
at bad.Application.main(Application.java:17)
Kredi kartı için hem ödeme hem de iade yapılabilirken, Bitcoin için sadece ödeme yapılabildi, iade yapılamadığı için bir exception fırlatıldı. Bu sınıfı kullanacak kişilerin refund metodunu çağırmasını bu yapıda engelleyemiyoruz. Payment sınıfının tüm özelliklerini sağlayamadığımız için LSP ihlal edilmiş oldu.
Aşağıdaki diyagram, yukarıdaki kodların yapısını özetlemektedir. BitCoinPayment sınıfının refund metodunu desteklemediğini görebilirsiniz:
LSP’ye Uygun Çözüm
Bu sorunu çözmek için iade işlemlerinin yapıldığı refund metodunu, Payment’tan ayırıp, farklı bir abstract sınıfa taşıyabiliriz.
Payment sınıfımızdan refund metodunu silerek aşağıdaki gibi güncelleyelim.
public abstract class Payment {
// Ortak kodlar bu sınıfta olmalı.
public abstract void processPayment(BigDecimal amount);
}
İade işlemlerini destekleyen bir abstract sınıf oluşturalım. Adına RefundablePayment diyebiliriz, Payment sınıfından türediğine dikkat edelim.
public abstract class RefundablePayment extends Payment {
public abstract void refund(BigDecimal amount);
}
Kredi kartı ile ödeme işleminde iade olabileceği için RefundablePayment sınıfından türeteceğiz.
public class CreditCardPayment extends RefundablePayment {
@Override
public void processPayment(BigDecimal amount) {
System.out.println("Kredi kartı ile " + amount + " TL ödeme yapıldı.");
}
@Override
public void refund(BigDecimal amount) {
System.out.println("Kredi kartı ile " + amount + " TL iade yapıldı.");
}
}
Bitcoin ile ödeme işleminde iade olamayacağı için sadece Payment sınıfından türeteceğiz.
public class BitcoinPayment extends Payment {
@Override
public void processPayment(BigDecimal amount) {
System.out.println("Bitcoin ile " + amount + " BTC ödeme yapıldı.");
}
}
BitcoinPayment sınıfında refund metodu olmadığına dikkat edin.
Sınıflarımız hazır olduğuna göre kodlarımızı çalıştıralım.
public static void main(String[] args) {
RefundablePayment creditCardPayment = new CreditCardPayment();
creditCardPayment.processPayment(BigDecimal.valueOf(100));
creditCardPayment.refund(BigDecimal.valueOf(100));
Payment bitcoinPayment = new BitcoinPayment();
bitcoinPayment.processPayment(BigDecimal.valueOf(0.0025));
}
BitcoinPayment sınıfında zorunlu olarak olması gereken bir refund metodu olamayacağı için, daha geliştirme aşamasında hatalı bir kullanımın önüne geçmiş olduk. Kodu çalıştırdığımızda hata almayız, çıktımız aşağıdaki gibi olur.
Kredi kartı ile 100 TL ödeme yapıldı.
Kredi kartı ile 100 TL iade yapıldı.
Bitcoin ile 0.0025 BTC ödeme yapıldı.
Bu tasarımda, sadece kredi kartı ile ödemede RefundablePayment sınıfından türetirken, bitcoin ile ödeme, bu işlevi yerine getirmek zorunda kalmaz. Bu sayede BitcoinPayment, Payment sınıfının yerine geçebilir, ve LSP’yi ihlal etmemiş oluruz.
Yeni bir ödeme tipi geldiğinde, örneğin Paypal ile ödeme, teknik olarak iade edilebilecekse RefundablePayment, iade edilemeyecek işlemse Payment’tan türetmemiz yeterli olacak.
Aşağıdaki diyagram, yukarıdaki kodların yapısını özetlemektedir.
Yukarıdaki örneği bir bütün halinde incelemek isterseniz aşağıdaki github linkine tıklayabilirsiniz.
Liskov Substitution Principle (LSP), hem yeni bir uygulama geliştirirken hem de mevcut bir uygulamayı geliştirirken veya değiştirirken akılda tutulması gereken çok faydalı bir fikirdir. Bu prensip, yazılım geliştirmenin temel prensiplerinden biri olarak, alt sınıfların üst sınıfların yerine geçebilmesini garanti altına alır.