SOLID Prensipleri

Nesneye Yönelik Programlamada SOLID prensipleri, yazılım tasarımını daha anlaşılır, esnek ve sürdürülebilir hale getirmek için kullanılan beş tasarım ilkesi içerir. SOLID kelimesi, Michael Feathers tarafından sunulan bu ilkelerin baş harfleridir. Bu ilkeler, Robert C. Martin tarafından desteklenen birçok ilkenin bir alt kümesidir.

5 SOLID prensibi:

  • Single-responsibility principle
  • Open-closed principle
  • Liskov substitution principle
  • Interface segregation principle
  • Dependency inversion principle

Single-Responsibility Principle (SRP)

Bu prensibe göre bir sınıfın yalnızca tek bir sorumluluğu olmalıdır. Başka bir deyişle, yazılımınız içinde birden fazla farkı görevi yapan kısım olmamalıdır.  Bu ilkede, yazılım kodunda her bir sınıfın, modülün veya metodun yalnızca bir iş yapması istenir. Örneğin, sınıf sadece konusu ile ilgili değişken ve yöntemleri içermelidir. Bu durumu basit bir örnek üzerinden anlatalım.

Yukarıdaki müşteri sınıfı, veritabanına müşteri ekleme için tasarımına başlanıyor. Müşteri Ekle() metodu bir müşterinin kaydını eklemeye çalışıyor. Eğer kodda hata çıkarsa “Hata.txt” adındaki bir dosyaya hata mesajı yazılıyor. İlk bakışta sorun yok gibi, ancak SRP’ye göre müşteri ve hata bölümleri farklı iki görevdir. Hatta, hata konusu loglama olarak düşünülebilir. SRP ilkesine göre bu iki bölümün ayrı ayrı yapılması gerekir. Bu durumda kodu aşağıdaki gibi tasarlayabiliriz.

Görüldüğü gibi kodu iki bölüme ayırdık. Bu sayede loglama ve musteri sınıfları ayrı ayrı geliştirilebilir. Hatta dosya loglama birçok sınıf tarafından kullanılabilir. Konuyu resimler üzerinden özetleyecek olursak:

 

Tüm yükü bir sınıfınıza, metodunuza vermeyin.

Bir sınıfınıza, metodunuza birden fazla sorumluluk yüklemeyin.

Bir sınıf veya metodu mümkün olduğunca kadar basit bir şekilde tutun.

 

Open-Closed Principle (OCP)

Yazılım varlıkları uzantı (eklenti) için açık ancak değişiklikler için kapalı olmalıdır. OCP, geniş çapta uyarlanabilen ancak aynı zamanda değişmeden kalan varlıkları gerektirir.  Bu noktada, çok şekillilik (polimorfizm) konusu ile  özel davranışlara sahip yinelenen varlıklar yaratmamız gerekir. Konuyu örnek kodumuz üzerinden inceleyelim.

MusType müşteri tipi için belirlenmiş bir property’dir. Yeni bir müşteri tipi geldiğinde tekrar kodu değiştirmeliyiz. Ancak, OCP ilkesine göre değişiklik için kapalı olmalı kuralını çiğnemiş oluruz. Bu ilke eklenti için açıktı. Bu durumu nasıl eklenti haline dönüştürmeliyiz. İşte bu noktada polimorfizm ve kalıtım çözümü düşünülebilir.

Musteri sınıfında indirim yoktur, ancak metot virtual bırakılmıştır. Bu metodu alt sınıflarda override edebiliriz. Örneğin, GumusMusteri için 50, AltinMusteri için 100 indirim uygulanır. Yeni bir müşteri tipi geldiğinde mevcut sınıflarda değişikliğe gerek yoktur. Direkt yeni bir sınıf oluşturup kalıtım sayesinde mevcut özellikleri alınıp gerekli özellikler override edilebilir.  Başka bir deyişle, benzersiz türetilmiş sınıf üzerinde çalışabiliriz ve üzerinde yaptığımız herhangi bir değişikliğin ebeveyni veya diğer türetilmiş sınıfı etkilemeyeceğinden emin olmalıyız.

 

Liskov Substitution Principle (LSP)

Bir programdaki nesneler, o programın doğruluğunu değiştirmeden alt türlerinin örnekleriyle değiştirilebilir olmalıdır. Başka bir deyişle, her alt sınıf, alt sınıfa özgü tüm yeni davranışlarla birlikte temel sınıftaki tüm davranışları korumalıdır. Alt sınıf, aynı istekleri işleyebilmeli ve üst sınıfıyla aynı görevleri tamamlayabilmelidir.

LSP’nin avantajı, aynı türdeki tüm alt sınıfların tutarlı bir kullanımı paylaştığı için yeni alt sınıfların geliştirilmesini hızlandırmasıdır. Yeni oluşturulan tüm alt sınıfların mevcut kodla çalışabilir. Yeni bir alt sınıfa ihtiyacınız olduğu zaman mevcut kodu yeniden çalışmadan oluşturabilmenize olanak sağlar.

Yukarıdaki kod üzerinden devam edelim. Potansiyel müşteriler indirim sorgulayabilsin fakat ekle metodu çalışmasın. Yani, ekleme metodu hata üretsin. Aşağıdaki gibi bir tasarım yapabiliriz.

Aşağıdaki gibi bir tasarım oluşur.

Bu tasarımın sorunlarına bakalım. Örneğin aşağıdaki gibi bir kodda sorun yaşarız.

Ne yazık ki bu kod, PotansiyelMusteri eklemesinde çalışma anında hata verir.

Çalıştırmadan önce hata vermesi için daha iyi bir tasarım yapmalıyız.

Bu durumu çözmek için LSP ilkesi, ebeveynin alt nesneyi kolayca değiştirmesi gerektiğini söyler. Bu durumu çözmek için indirim ve veritabanına ekleme için iki Interface oluşturabiliriz.

Potansiyel Musteri için sınıfımız aşağıdaki gibi olur.

Bu sayede potansiyel müşterilerimizin veritabanı ile ilişkisi kesilmiş oldu. Musteri sınıfı da aşağıdaki gibi yapabiliriz.

Bu tasarım sayesinde potansiyel dışındaki müşterilerin veritabanı ve indirimi kullanmasına olanak sağlandı. Bu durumda kod çalışmadan önce hata verir.

LSP ilkesinde interface’leri veya abstract sınıfları doğru kullanmak çok önemlidir.

 

Interface Segregation Principle (ISP)

İsteme özel birçok arayüz, tek bir genel amaçlı arayüzden daha iyidir. ISP’de sınıflar kullanmadıkları davranışları içermesi istenmez. Aslında, bu durum ilk SOLID ilkemizle de ilgilidir. Çünkü, bu ilke programa doğrudan katkıda bulunmayan tüm değişkenleri, metotları veya davranışları bir sınıftan çıkarır. ISP ise metotların daha spesifik metotlara dönüştürülmesidir. Bu sayede,

  • Daha az kod taşıyan metotlar elde edilir. Kodun ihtiyaç durumunda güncellemesi hızlanır.
  • Davranıştan bir metot sorumlu olduğu için davranışta karşılaşılan problem hızlı çözülür.

Bir örnek üzerinden olayı inceleyelim.

ICalisan Interface birçok metot içeriyor ve bu metotları hepsi sınıflara aktarılıyor ve iki farklı personel için metotlarda çakışıyor. Aslında bazı metotlar personel grupları için gereksizdir. Bu metotlar aşağıdaki gibi daha düzgün bir hale getirilebilir.

Ücret hesaplama metotları tam zamanlı ve sözleşmeli personel için ayrılmıştır. Bu sayede daha rahat okunur ve değiştirilebilir bir kod elde edilmiştir.

 

Dependency Inversion Principle (DIP)

Abstraction (Soyutlama) konusu sınıf ve doğru özelliklerin sınıfa eklenmesi açısından Nesneye Yönelik Programlamanın en önemli konularından biridir. DIP iki kısma sahiptir:

  • Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. Bunun yerine, her ikisi de soyutlamalara (Interface) bağlı olmalıdır.
  • Soyutlamalar ayrıntılara bağlı olmamalıdır. Ayrıntılar (somut uygulamalar gibi) soyutlamalara bağlı olmalıdır.

Yazılımcılar, konuyu parça parça öğrendikleri için sınıflarını yüklenirler. Bir anlamda yüksek seviyeli bileşenlere sahip programlar yazarlar. DIP ilkesinin amacı düşük ve yüksek seviyeli bileşenleri ayırıp her ikisini de soyutlamalara bağlamaktır. Bu durumda, yüksek ve düşük seviyeli bileşenler birbirinden yararlanabilir ama birindeki değişiklik doğrudan diğerini etkilememelidir. Konuyu bir örnek üzerinden inceleyelim.

Örneğimizde; bir interface, üst düzey, alt düzey ve ayrıntılı bileşenlerle genel bir program oluşturacağız. Öncelikle, müşteri adına ulaşmak için bir interface’de metodumuzu yazalım.

Şimdi, IMusteriDataAccess arayüzüne bağlı olacak ayrıntıları uygulayacağız. Bunu yapmak, DIP ilkesinin ikinci bölümünü gerçekleştirir.

Şimdi IMusteriDataAccess soyut arayüzünü uygulayan ve onu kullanılabilir bir biçimde döndüren bir fabrika sınıfı oluşturacağız. Döndürülen MusteriDataAccess sınıfı, düşük seviyeli bileşenimizdir.

Son olarak, IMusteriDataAccess arayüzünü de uygulayan üst düzey bir MusteriBusinessLogic bileşeni uygulayacağız. Üst düzey bileşenimizin düşük düzey bileşenimizi uygulamadığına ve yalnızca onu kullandığına dikkatinizi çekerim.

Sonuç

SOLID ilkeleri, kodunuzu geliştirmenin ve değişiklikleri kolaylaştırmanın mükemmel bir yoludur. Başlangıç aşamasında iseniz bu ilkeleri kullanmak zor olabilir, ama kod yazdıkça bu ilkelere ihtiyaç duyacaksınız. Özellikle framework’lerde sizi bu yönde kod geliştirmenizi sağlamaya çalışmaktadır. Bu konuyu kodlarınızda kullanmaya başladığınızda nesneye yönelik programlama dersinde gördüğünüz polimorfizm, abstraction, encapsulation ve inheritance gibi programınız içinde daha doğru şekilde kullanmış olacaksınız.

Bol kodlu günler 🙂

 

Kaynaklar: