SOLID Yazılım Geliştirme Prensipleri
Başarılı bir yazılım geliştirme pratiği, sadece kodun çalışır durumda olmasından daha fazlasını gerektirir. Kodun okunabilir, genişletilebilir ve bakımı kolay olması, uzun vadede projenin başarısını sağlayacak unsurlardan biridir. İşte bu noktada SOLID prensipleri devreye girer.
SOLID, yazılım geliştirme dünyasında yaygın olarak kabul gören beş temel prensibi ifade eder. Her bir prensip, yazılım tasarımında belirli bir ilkeyi temsil eder ve uygulandığında daha esnek, daha anlaşılır ve daha sürdürülebilir kodlar elde edilmesini sağlar.
SOLID, Robert Cecil Martin (Bob Amca) tarafından ortaya atılan, nesne yönelimli programlamada yazılım tasarımının esnek , kolay anlaşılabilir ve sürdürülebilir hale getirmek için uyulması gereken kurallar bütünüdür.
Yazılım geliştirirken SOLID prensiplerine uymanız, Clean (Temiz) Kod yazma alışkanlığı sağlar.
SOLID Prensipleri
(S)ingle Responsibility Principle (SRP: Tek Sorumluluk Prensibi )
(O)pen/Closed Principle (OCP: Açık Kapalı Prensibi)
(L)iskov ‘s Substitution Principle (LSP: Liskov’un Yerine Geçme Prensibi )
(I)nterface Segregation Principle (ISP: Arayüz Ayrıştırma Prensibi )
(D)ependency Inversion Principle (DIP: Bağımlılık Ters Çevirme Prensibi )
(S)ingle Responsibility Principle (SRP: Tek Sorumluluk Prensibi )
Single Responsibility Principle (Tek sorumluluk Prensibi) 'ne göre bir sınıfın , fonksiyonun yalnızca tek bir sorumluluğu olmalıdır. Bir method'a birden fazla iş vermek , o methodun sorumluluğunu arttırır. Sınıfı bir çok işten sorumlu tutmak yerine tüm methodları parçalara ayırıp bağımsız methodlar ile yapılmasını söyleyen bir prensiptir. Bazen uğraşmamak için bu prensibi göz ardı edebiliyoruz , bu küçük bir proje için belki sorun yaratmayabilir fakat on binlerce kodunuz olduğunu düşünürsek kod anlaşılamaz ve sürdürülemez hale gelecektir.
Tek bir sınıfta tek bir sorumluluk daha az bağımlılık sağlayacaktır.
Daha az sorumluluk daha yalın ve küçük yapılar oluşmasını sağlar. Bu sayede kod daha anlaşılabilir/okunabilir olacaktır.
Yukarıdaki kullanım ne kadar normal gözükse bile yanlış bir kullanımdır. ChangeUserName
ve ChangeEmailAddress
, User
sınıfının sorumlulukları olabilir ama SendEmail
metodu sorumluluğun içinde değildir.
Bu şekilde bir kullanım ile SendEmail
metodunun başka yerlerde kullanılabilirliğini engelledik.
Yukarıdaki kod bloğu doğru bir kullanımdır. User sınıfına sadece kendi sorumluluğunu verdik ve email işlemlerini EmailHelper
adındaki bir sınıfa verdik. Bu sayede EmailHelper
başka yerlerde kolayla kullanılabilir ve daha anlaşılır ve kodumuzun kontrolü daha kolay hale geldi.
(O)pen/Closed Principle (OCP: Açık Kapalı Prensibi)
Geliştirmeye açık , Değiştirmeye kapalı
Sınıflarımız , fonksiyonlarımız değişikliğe kapalı ama yeni davranışların eklenmesine açık olmalıdır. Yani siz yeni bir davranış ekleyeceğiniz zaman daha önceki yazdığınız kodunuz üzerinde değişiklik yapmamanız gerekir.
Örnek verecek olursak siz şuan müşterilerinize SMS
ile bilgilendirme mesajı atıyorsunuz fakat belli bir süre zaman geçiyor ve siz artık SMS
yerine Email
yoluyla bilgilendirme yapmanız gerekiyor. Yukarıdaki koda göre bu değişikliği yapmak için SMS
kodlarını değiştirip Email
göndermek için gerekli kodları yazmanız gerekir ve bu yaklaşım Open Closed prensibine aykırı bir durumdur.
Kod bloğumuzu yukarıdaki gibi interface düzeyinde yazarak , önceki kod bloğunu değiştirmeden geliştirmeye açık hale getirebiliriz.
Yukarıdaki mantıkla yaklaşarak geliştirmesi gereken yerleri önceki kod bloğunu değiştirmeden yapabiliriz. Şuan SMS
ile mesaj gönderiyoruz fakat bundan sonra Email
ile mesajları göndermemiz gerekiyor , ne yapmalıyız ? Yapmamız gereken sadece new'lenen sınıfı değiştirmektir.
(L)iskov ‘s Substitution Principle (LSP: Liskov’un Yerine Geçme Prensibi )
Aynı objeden (class, interface) türeyen tüm sınıflar, birbirlerinin yerine kullanılabiliyor olmalıdır. Eğer böyle bir durum söz konusu değilse, nesneler birbirlerinin yerine geçtiğinde hatalar meydana gelebilir.
Yukarıda bir interface tanımlanmış ve bu interface'i implemente eden sınıfların Walk
ve Fly
fonksiyonlarını implemente etmesi istenmiş. Her kuş yürüyebilir fakat her kuş uçabilir mi?
Yukarıdaki kodda da göründüğü gibi serçe yürüyebilir ve uçabilir. Serçe
, IBird
interface'ini implemente ettiğinde hiçbir sorun çıkmadı.
Fakat DeveKusu
sınıfı IBird
interface'ini implemente ettiğinde hata fırlatır çünkü deve kuşu uçamaz.
Göründüğü gibi serçe için her şey yolundayken serçe yerine deve kuşunu kullandığımızda patlama meydana geliyor. Bu gibi durumlar Liskov Substitution (Yerine Geçme) Prensibi'ne aykırı bir durumdur.
Olması gereken durum, kuşlar için methodu ayırarak interface düzeyinde yazmaktır. Aslında bu prensibi ihlal edince Single Responsibility prensibini de ihlal etmiş oluyorsunuz. Çünkü tüm kuşlara birden fazla sorumluluk vermek yanlış bir kullanım olarak düşünülebilir. Bu methodları parçalamamız gerekir.
Düzeltilmiş kod yukarıdaki gibidir. Interface'in içini parçalayarak her interface'e kendi sorumluluğunu verdik. Böylece bu interfaceleri implemente eden sınıflar yer değiştirebilir duruma geliyor.
(I)nterface Segregation Principle (ISP: Arayüz Ayrıştırma Prensibi )
Interface Segregation Prensibi'ne göre interface öyle bir şekilde tasarlanmalı ki bu interface'i kullanan sınıfların sadece ihtiyaç olan methotları uygulamaları gereksin, ihtiyaçları olmayan metotları barındırmak zorunda olmasın.
Interfacelerin doğru parçalanmasıdır.
IAnimal
interface'inde Fly()
, Run()
, Swim()
methotları olması gibi. Bu yanlış bir parçalamadır çünkü her hayvan uçamaz , yüzemez. Dolayısıyla bu methodların içi boş kalacaktır. Bu Interface Segregation Prensibi'ne aykırı bir durumdur.
Interface'ler yukarıdaki gibi parçalanmalı ve sadece ortak kullanabilecek metotları barındırmalıdır. Bu şekilde Hiçbir Animal
yapamadığı gereksiz bir özelliği barındırmak zorunda kalmaz.
Aslında bakacak olursak çözüm yolları Liskov Prensibi ile oldukça benzerdir. Bu noktada kafanız karışmış olabilir.
Liskov Prensibi'nin amacı tutarlılıktır. Aynı nesneden türeyen sınıflar birbiriyle yer değiştirebilir olmalıdır.
Interface Segragation Prensibi'nin amacı ise bir interface'lerin tasarımında gereksiz bağımlılıkları ortadan kaldırır ve sınıfların sadece ihtiyaç duydukları metotları içeren interface'leri implement etmelerini sağlar.
(D)ependency Inversion Principle (DIP: Bağımlılık Ters Çevirme Prensibi )
Dependency Inversion Prensibi yüksek seviye modüllerin düşük seviye modüllere bağımlı olmaması gerektiğini, bunun yerine ikisinin de soyutlamalara (abstractions) bağlı olması gerektiğini söyler.
Bu prensip sayesinde esnek, bakımı kolay ve daha kolay test edilebilir bir koda sahip oluyorsunuz.
Örnek vermek gerekirse, mesaj göndermek için tasarladığımız bir sistem olduğunu düşünelim. MessageSender
sınıfımız olsun ve bu sınıfın içerisinde SMS gönderilsin ve bu gönderim işlemi SmsService
ile olsun. Burada üst seviye sınıf MessageSender
, alt seviye sınıfımız ise SmsService
'tir. Bu yapıda MessageSender
, SmsService
'e bağımlı durumdadır. Doğrudan MessageSender
'ın içinde SmsService
'i kullandığımız için MessageSender
, SmsService
'e bağımlı hale geliyor ve bu durum Dependency Inversion Prensibi'ne aykırı bir durumdur.
Yukarıda görüldüğü gibi MessageSender
içerisinde SmsService
doğrudan new'lenmesi (new'lenmesinden ziyade class içerisinde başka class new'lenmesi), MessageService
sınıfının SmsService
sınıfına doğrudan bağımlı hale gelmesine sebep olur. Bu, sınıfın sadece SmsService
'i kullanmak için tasarlanmış olmasına ve bu nedenle SmsService
dışında başka bir şekilde kullanılamaz hale gelmesine yol açar. Bu durum, Dependency Inversion Prensibi'ne aykırıdır çünkü yüksek seviye modül olan MessageSender
'ın düşük seviye modül olan SmsService
'e doğrudan bağımlı olması gerekmeyip, soyutlamalara bağımlı olması gerekmektedir.
Yukarıdaki kod bloğunda Dependency Inversion Prensibi'nin doğru bir şekilde kullanımı yer almıştır. MessageSender
'da kullanılacak sınıf soyutlaştırarak kullanılmış ve SmsService
'e hiçbir bağımlılığı kalmamıştır.
Last updated