
SOLID Prensipleri Nedir? Örneklerle Anlatım

SOLID prensipleri, yazılım geliştirirken daha anlaşılır, esnek ve bakımı kolay kodlar yazmayı sağlayan beş temel tasarım ilkesidir. Nesne yönelimli programlamada sıkça kullanılan bu prensipler, kod tekrarını azaltmaya ve uygulamaların geliştirilmesini kolaylaştırmaya yardımcı olur. Peki SOLID prensipleri nelerdir ve nasıl uygulanır?
SOLID Prensipleri Nedir?
SOLID, nesne yönelimli programlamada kullanılan beş tasarım prensibinin baş harflerinden oluşur. Bu prensipler, yazılım bileşenlerinin sorumluluklarını düzenlemeyi ve bileşenler arasındaki bağımlılıkları daha yönetilebilir hâle getirmeyi amaçlar.
SOLID yaklaşımı, özellikle büyüyen yazılım projelerinde önem kazanır. Yeni bir özellik eklendiğinde mevcut kodun birçok farklı bölümünü değiştirmek zorunda kalmamak, yazılım geliştirme sürecini kolaylaştırır.
SOLID prensipleri şunlardır:
- S – Single Responsibility Principle: Tek Sorumluluk Prensibi
- O – Open/Closed Principle: Açık/Kapalı Prensibi
- L – Liskov Substitution Principle: Liskov Yerine Geçme Prensibi
- I – Interface Segregation Principle: Arayüz Ayrımı Prensibi
- D – Dependency Inversion Principle: Bağımlılıkların Tersine Çevrilmesi Prensibi
Bu ilkeler, Robert C. Martin tarafından yaygınlaştırılan yazılım tasarımı yaklaşımının önemli bir parçasıdır. Her biri farklı bir probleme odaklanır ancak birlikte kullanıldıklarında daha sürdürülebilir bir kod yapısı oluşturmaya yardımcı olurlar.
1. Single Responsibility Principle (Tek Sorumluluk Prensibi) Nedir?
Tek Sorumluluk Prensibi, bir sınıfın değişmek için yalnızca bir temel nedene sahip olması gerektiğini ifade eder. Başka bir deyişle, bir sınıf birbiriyle ilgisiz birçok işi aynı anda üstlenmemelidir.
Örneğin bir e-ticaret uygulamasında sipariş oluşturan, ödeme alan ve e-posta gönderen bütün işlemleri tek bir sınıfta toplamak, kodun zamanla karmaşıklaşmasına neden olabilir.
Bu sorumlulukları ayrı sınıflara bölmek daha düzenli bir yapı sağlar:
class OrderService:
def create_order(self, order):
print("Sipariş oluşturuldu.")
class PaymentService:
def process_payment(self, amount):
print("Ödeme gerçekleştirildi.")
class EmailService:
def send_confirmation(self, email):
print("Onay e-postası gönderildi.")Bu örnekte her sınıf farklı bir sorumluluğu yerine getirir. Ödeme süreci değiştiğinde sipariş oluşturma veya e-posta gönderme kodunu değiştirmek gerekmez.
Ne zaman kullanılır? Bir sınıfın çok sayıda farklı iş yaptığını, sık sık değiştiğini veya test edilmesinin zorlaştığını fark ettiğinde sorumlulukları ayırmayı değerlendirebilirsin.
2. Open/Closed Principle (Açık/Kapalı Prensibi) Nedir?
Açık/Kapalı Prensibi, yazılım bileşenlerinin yeni davranışlar eklemeye açık, mevcut ve çalışan kodu değiştirmeye ise mümkün olduğunca kapalı olması gerektiğini söyler.
Örneğin bir uygulamanın farklı ödeme yöntemlerini desteklediğini düşünelim. Her yeni ödeme yöntemi eklendiğinde mevcut ödeme sınıfına yeni koşullar eklemek, kodun karmaşıklaşmasına yol açabilir.
Bunun yerine ortak bir arayüz tanımlanabilir:
class PaymentMethod:
def pay(self, amount):
raise NotImplementedError
class CreditCardPayment(PaymentMethod):
def pay(self, amount):
print("Kredi kartı ile ödeme yapıldı.")
class BankTransferPayment(PaymentMethod):
def pay(self, amount):
print("Banka transferi ile ödeme yapıldı.")Yeni bir ödeme yöntemi gerektiğinde mevcut sınıfları değiştirmek yerine PaymentMethod yapısını uygulayan yeni bir sınıf oluşturulabilir.
Bu yaklaşım, yeni özelliklerin eklenmesini kolaylaştırırken mevcut davranışların bozulma ihtimalini de azaltır. Ancak her değişiklik için yeni sınıf oluşturmak gerekmez; amaç, gerçekten genişlemeye ihtiyaç duyulan alanlarda gereksiz değişiklikleri önlemektir.
3. Liskov Substitution Principle (Liskov Yerine Geçme Prensibi) Nedir?
Liskov Yerine Geçme Prensibi, bir üst sınıfın yerine kullanılan alt sınıfların programın beklenen davranışını bozmaması gerektiğini belirtir. Bir alt sınıf, üst sınıfın sunduğu özellikleri devraldığında bu özelliklerin anlamını da korumalıdır.
Örneğin bir uygulamada bütün kuşların uçabildiğini varsayan bir Bird sınıfı tasarlamak sorun yaratabilir. Çünkü penguenler gibi bazı kuşlar uçamaz.
class Bird:
def move(self):
print("Kuş hareket ediyor.")
class Sparrow(Bird):
def move(self):
print("Serçe uçarak hareket ediyor.")
class Penguin(Bird):
def move(self):
print("Penguen yürüyerek hareket ediyor.")Bu örnekte move() metodu, bütün kuşların hareket edebilmesini sağlayan genel bir davranış sunar. Alt sınıflar ise bu davranışı kendi özelliklerine göre gerçekleştirir.
Eğer üst sınıfta fly() gibi bütün kuşların uçmasını zorunlu kılan bir metot bulunsaydı penguen sınıfı bu tasarıma uygun davranamazdı. Böyle durumlarda sınıf hiyerarşisini yeniden tasarlamak veya uçma davranışını ayrı bir arayüzde tanımlamak daha doğru olabilir.
Ne zaman kullanılır? Kalıtım kullanırken alt sınıfların üst sınıfların beklentilerini karşılayıp karşılamadığını kontrol etmek için bu prensipten yararlanabilirsin.
4. Interface Segregation Principle (Arayüz Ayrımı Prensibi) Nedir?
Arayüz Ayrımı Prensibi, bir sınıfın kullanmadığı metotları uygulamak zorunda bırakılmaması gerektiğini ifade eder. Bunun için büyük ve kapsamlı arayüzler yerine daha küçük, belirli amaçlara hizmet eden arayüzler tercih edilir.
Örneğin bir yazılımda yazdırma, tarama ve faks işlemlerini içeren tek bir cihaz arayüzü olduğunu düşünelim. Yalnızca yazdırma yapabilen bir cihaz, desteklemediği tarama ve faks metotlarını da uygulamak zorunda kalabilir.
Bu tasarım yerine yetenekler ayrı arayüzlere bölünebilir:
class Printable:
def print_document(self):
raise NotImplementedError
class Scannable:
def scan_document(self):
raise NotImplementedError
class SimplePrinter(Printable):
def print_document(self):
print("Belge yazdırılıyor.")
class MultiFunctionPrinter(Printable, Scannable):
def print_document(self):
print("Belge yazdırılıyor.")
def scan_document(self):
print("Belge taranıyor.")Bu yapıda SimplePrinter yalnızca desteklediği yazdırma davranışını uygular. MultiFunctionPrinter ise hem yazdırma hem de tarama özelliklerini kullanabilir.
Böylece sınıflar ihtiyaç duymadıkları davranışlara bağımlı olmaz. Bu prensip özellikle çok sayıda farklı yeteneğe sahip sistemlerde kodun daha anlaşılır olmasını sağlar.
5. Dependency Inversion Principle (Bağımlılıkların Tersine Çevrilmesi Prensibi) Nedir?
Bağımlılıkların Tersine Çevrilmesi Prensibi, üst seviyedeki iş mantığının doğrudan belirli alt seviye uygulamalara bağımlı olmaması gerektiğini belirtir. Her iki taraf da soyutlamalara, yani belirli bir uygulamaya bağlı kalmadan davranışı tanımlayan yapılara dayanmalıdır.
Örneğin bir bildirim sistemi yalnızca e-posta gönderen belirli bir sınıfa bağımlıysa ileride SMS bildirimi eklemek zorlaşabilir. Bildirim davranışını ortak bir arayüz üzerinden tanımlamak, farklı uygulamaların kullanılmasını kolaylaştırır.
class Notification:
def send(self, message):
raise NotImplementedError
class EmailNotification(Notification):
def send(self, message):
print(f"E-posta gönderildi: {message}")
class SMSNotification(Notification):
def send(self, message):
print(f"SMS gönderildi: {message}")
class OrderService:
def __init__(self, notification):
self.notification = notification
def complete_order(self):
self.notification.send("Siparişiniz tamamlandı.")OrderService, bildirimin e-posta veya SMS ile gönderildiğini bilmek zorunda değildir. Kendisine verilen bildirim nesnesinin send() metodunu kullanır.
Bu sayede bildirim türü değiştirildiğinde sipariş hizmetinin iş mantığını yeniden yazmak gerekmez. Ayrıca testlerde gerçek bildirim gönderen sınıflar yerine sahte (mock) nesneler kullanmak kolaylaşır.
Ne zaman kullanılır? Bir bileşenin belirli bir veri tabanına, bildirim hizmetine veya harici servise sıkı biçimde bağlandığı durumlarda bağımlılıkları soyutlamalar üzerinden kurmayı değerlendirebilirsin.
SOLID Prensipleri Neden Önemlidir?
SOLID prensipleri, kodun yalnızca ilk geliştirildiği anda değil, sonraki değişikliklerde de anlaşılır ve yönetilebilir kalmasına yardımcı olur. Özellikle birden fazla geliştiricinin aynı proje üzerinde çalıştığı durumlarda sorumlulukların net olması iş birliğini kolaylaştırır.
Bu prensiplerin sağladığı başlıca avantajlar şunlardır:
- Bakım kolaylığı: Bir değişiklik gerektiğinde ilgili kod bölümünü bulmak ve güncellemek kolaylaşır.
- Test edilebilirlik: Bileşenler birbirinden ayrıldığında birim testleri yazmak daha kolay olabilir.
- Genişletilebilirlik: Yeni özellikler, mevcut kodun tamamını değiştirmeden eklenebilir.
- Kodun yeniden kullanılabilirliği: Belirli bir sorumluluğu yerine getiren sınıflar farklı bölümlerde kullanılabilir.
- Ekip içi anlaşılabilirlik: Kodun hangi işlemi neden gerçekleştirdiği daha kolay anlaşılır.
Bununla birlikte SOLID prensipleri her projede mümkün olan en fazla sayıda sınıfı veya arayüzü oluşturmak anlamına gelmez. Gereğinden fazla soyutlama, küçük projelerde kodun okunmasını ve geliştirilmesini zorlaştırabilir. Bu nedenle tasarım kararları uygulamanın ihtiyaçlarına göre verilmelidir.
SOLID prensiplerini öğrenirken her ilkeyi ayrı ayrı anlamak ve küçük örneklerle uygulamak faydalıdır. Zamanla bu prensiplerin birlikte nasıl çalıştığını görmek, daha kapsamlı yazılım projelerinde doğru tasarım kararları vermeyi kolaylaştırır.



