SOLID kavramı nedir?
SOLID Prensipleri: Kodunu Daha Yönetilebilir Hale Getirme Sanatı
SOLID, yazılım geliştirme dünyasında her ciddi programcının bilmesi gereken temel bir prensipler bütünü. Robert C. Martin (Uncle Bob) tarafından popülerleştirilen bu beş prensip, yazdığın kodun zamanla daha sürdürülebilir, anlaşılır ve esnek olmasını sağlar. Kısacası, kodunun "ömrünü uzatır".
- Single Responsibility Principle (SRP) - Tek Sorumluluk Prensibi
Bu prensip diyor ki, bir sınıfta veya modülde yalnızca tek bir değişiklik nedeni olmalı. Yani, bir sınıfın yapması gereken tek bir işi olmalı. Örneğin, bir `Kullanici` sınıfın varsa, hem kullanıcı bilgilerini tutmalı hem de veritabanına kaydetme işini yapmamalı. Veritabanı işlemleri için ayrı bir sınıf (`KullaniciRepository` gibi) oluşturmak, SRP'ye uymaktır.
Deneyimlerime göre, bu prensibe uymayan kodlarda değişiklik yapmak kabusa dönüşür. Bir fonksiyonda küçük bir değişiklik yapmak istersin ama aynı fonksiyonda ilgisiz başka işler de yapıldığı için farkında olmadan başka yerleri bozarsın. Kodun modülerleşmesi, test yazmayı da inanılmaz kolaylaştırır. Her sınıfın tek bir sorumluluğu olduğunda, o sınıfı izole edip kolayca test edebilirsin.
Pratik Öneri: Bir sınıfın ne iş yaptığını tarif etmeye çalıştığında "ve" kelimesini kullanıyorsan, muhtemelen o sınıf birden fazla sorumluluğa sahip demektir. Örneğin, "Bu sınıf kullanıcı bilgilerini yönetir ve e-posta gönderir." yerine, "Bu sınıf kullanıcı bilgilerini yönetir." ve "Bu sınıf e-posta gönderme işlerini yapar." şeklinde ayırmak daha doğru olur.- Open/Closed Principle (OCP) - Açıklık/Kapalılık Prensibi
Bu prensip der ki, bir yazılım varlığı (sınıf, modül, fonksiyon vb.) genişletmeye açık olmalı, ancak değişikliğe kapalı olmalı. Yani, mevcut kodu değiştirmeden yeni özellikler ekleyebilmelisin. Bunun en yaygın yolu soyutlamalar (interface, abstract class) kullanmaktır.
Diyelim ki bir `RaporOlusturucu` sınıfın var ve başlangıçta sadece `PDFRaporu` üretiyor. Daha sonra `ExcelRaporu` da eklemek istediğinde, mevcut `RaporOlusturucu` sınıfını değiştirmeden, sadece yeni bir rapor sınıfı (`ExcelRaporu`) oluşturup, bir soyutlama (`IRapor`) üzerinden bu raporu ekleyebilirsin. Mevcut çalışan kodunu bozmadan yeni özellikler ekleyebilmek, sistemin kararlılığını artırır.
Pratik Öneri: Bir sınıfın `if-else` veya `switch-case` bloklarıyla farklı türdeki nesnelere göre farklı davranışlar sergilediğini görüyorsan, OCP ihlali olabilir. Bu tür durumları soyutlama ve polimorfizm ile çözmeyi düşün. Örneğin, bir `OdemeServisi` içinde farklı ödeme yöntemleri için uzun bir `switch` bloğu varsa, her ödeme yöntemi için ayrı bir sınıf (örn: `KrediKartiOdeme`, `HavaleOdeme`) oluşturup, ortak bir arayüz (`IOdemeYontemi`) ile yönetebilirsin.- Liskov Substitution Principle (LSP) - Liskov Yerine Geçme Prensibi
Barbara Liskov'un adını taşıyan bu prensip, bir sınıfta (alt sınıf), onun ana sınıfının (üst sınıf) yerine geçtiğinde programın doğruluğunu bozmamalıdır. Kısacası, alt sınıflar üst sınıfların özelliklerini beklendiği gibi taşımalıdır. Bir üst sınıfın bir nesnesini, alt sınıfın bir nesnesiyle değiştirdiğinde, programın davranışı değişmemeli.
Örneğin, bir `Kus` ana sınıfın ve `Suri` ile `Penguen` alt sınıfların varsa, `Suri` `ucmak` fiilini yerine getirebilirken, `Penguen` uçamaz. Eğer `Kus` sınıfında zorunlu bir `uc` metodu varsa ve `Penguen` bu metodu uygulayamıyor veya boş bırakıyorsa, LSP ihlal edilmiş olur. Çünkü bir `Kus` nesnesini beklediğin yere bir `Penguen` nesnesi koyduğunda beklenmedik bir hata alırsın.
Pratik Öneri: Bir alt sınıf oluştururken, üst sınıfın metotlarını geçersiz kılma (override) ihtiyacı duyduğunda veya zorunlu metotları uygularken mantıksız hale geldiğinde, büyük olasılıkla LSP'yi ihlal ediyorsun. Belki de alt sınıf, ana sınıfın bir "is-a" (bir türüdür) ilişkisi yerine "has-a" (sahiptir) ilişkisi kurmalı. Örneğin, "Penguen bir kuştur" yerine "Penguen bir kuştur ancak uçamaz" gibi bir durum varsa, bu ilişkiyi farklı bir şekilde modellemeyi düşün.- Interface Segregation Principle (ISP) - Arayüz Ayrıştırma Prensibi
Bu prensip, istemcilerin (yani bir arayüzü kullanan sınıfların) kullanmadıkları metotlara bağımlı olmaya zorlanmamaları gerektiğini söyler. Büyük, tek bir arayüz yerine, küçük ve özel arayüzler oluşturmak daha iyidir.
Diyelim ki bir `IPrinter` arayüzün var ve içinde `print()`, `scan()` ve `fax()` metotları bulunuyor. Eğer bir makinen sadece `print()` yapabiliyorsa, o makineyi `IPrinter` arayüzüne bağladığında, `scan()` ve `fax()` metotlarını da uygulamak zorunda kalır ki bu mantıksızdır. Bunun yerine, `IPrinter`, `IScanner`, `IFax` gibi ayrı ayrı arayüzler oluşturursan, her sınıf sadece ihtiyaç duyduğu arayüzlere bağımlı olur.
Pratik Öneri: Bir arayüzü uygulayan bir sınıfta, o arayüzün metotlarının çoğunun kullanılmadığını veya boş bir implementasyonla doldurulduğunu fark ediyorsan, ISP'yi gözden geçirmelisin. Arayüzlerini daha küçük parçalara bölerek, sınıfların sadece ihtiyaç duydukları fonksiyonelliğe erişmesini sağla. Bu, gereksiz bağımlılıkları azaltır ve kodunu daha temiz tutar.- Dependency Inversion Principle (DIP) - Bağımlılık Tersine Çevirme Prensibi
Bu prensip iki kısımdan oluşur:
- Üst seviye modüller alt seviye modüllere bağımlı olmamalıdır. Her ikisi de soyutlamalara (abstactions) bağımlı olmalıdır.
- Soyutlamalar detaylara (concrete implementations) bağımlı olmamalıdır. Detaylar soyutlamalara bağımlı olmalıdır.
Kısacası, kodunun yüksek seviyeli modüllerinin, düşük seviyeli modüllerine değil, soyutlamalarına (interface veya abstract class) bağımlı olması gerekir. Ve bu soyutlamalar da, concrete (somut) implementasyonlarına değil, soyutlamaların kendisine bağımlı olmalıdır.
Bir `ServisKatmani` sınıfın var ve bu sınıf doğrudan `VeriErisimKatmani` somut sınıfına bağlıysa, bu DIP ihlalidir. Bunun yerine, `IVeriErisim` gibi bir arayüz tanımlayıp, `ServisKatmani`'nın bu arayüze bağımlı olmasını sağlarsın. `VeriErisimKatmani` ise bu arayüzü uygular. Bu, "Injection" (Enjeksiyon) dediğimiz yöntemiyle (örneğin Constructor Injection ile) sağlanır. Böylece `ServisKatmani`, hangi somut veri erişim sınıfının kullanılacağını bilmek zorunda kalmaz, bu kontrol başka bir yere (genellikle bir IoC container'a) bırakılır.
Pratik Öneri: Sınıfların birbirine doğrudan somut sınıflar üzerinden bağımlı olduğunu görüyorsan, DIP'i düşünme vakti gelmiş demektir. Bağımlılıkları soyutlamalar üzerinden yönetmek ve bu bağımlılıkları dışarıdan (genellikle bir framework veya IoC container aracılığıyla) sağlamak, kodunun esnekliğini ve test edilebilirliğini inanılmaz derecede artırır. Dependency Injection, DIP'i hayata geçirmek için kullanılan en güçlü araçlardan biridir.