Nedir Bu SOLID Prensipleri ?
Bugün yazılım sektöründe çalışan herkesin sık sık karşılaştığı konulardan biri olan solid prensipleri konusundan bahsedeceğim.Özellikle sektöre yeni başlayan arkadaşlara iş ortamlarında sık sık aktardığımız bu prensiplerin yazılı olarak dijital ortamda da bulunmasını istediğim için bu yazıyı kaleme alıyorum.
Öncelikle SOLİD nedir ? Bu sorunun cevabını verelim.
Robert C. Martin tarafından öne sürülen prensipler Robert C. Martin tarafından Desingn Principles and Desing Patterns kitabında tanıtılmıştır.
Geliştirilen bir yazılımın esnek, yeniden kullanılabilir, sürdürülebilir ve anlaşılır olmasını sağlayan, ayrıca kod tekrarını önleyen prensiplerdir.Kodun esnek, sürdürülebilir ve geliştirilebilir tasarlanmaması kodu kırılganlaştırır ve yazılım ürününün gelişmesini etkiler. SOLID 5 farklı prensipten oluşur ve her birinin baş harfini alır.
1. (S)ingle Responsibility Principle
2. (O)pen-Closed Principle
3. (L)iskov ‘s Substitution Principle
4. (I)nterface Segregation Principle
5. (D)ependency Inversion Principle
Şimdi örneklerle beraber bu prensiplerin neler olduğundan bahsedelim.
1. Single Responsibility Principle
Single Responsibility Principle’a göre her class’ın,methodun tek bir görevi ve sorumluluğu olmak zorundadır.
“Bir şeyi yapabiliyor olmanız onu mutlaka yapmanız gerektiği anlamına gelmiyor.”
Kısaca özetlemek gerekirse resimde de gördüğünüz gibi kod yazarken bir isviçre çakısı olmamamız gerektiğini bizlere anlatıyor.Her şeyi bir class’ın,fonksiyonun veya methodun içine yüklemememiz gerektiğini, her şeyin bir tane sorumluluğunun olması gerektiğini bizlere anlatan bir yaklaşımdır. Bu yaklaşımı size basit bir Mercedes Benz örneği açıklamaya devam edeceğim.
Öncelikle BaseCar isimli base class’a bir göz gezdirelim.Mercedes Benz isimli class’ımızı bu BaseCar isimli class’dan türetelim.
Yukarıdaki kod parçacığında kullanıcıya bilgilendirme için sms ve email gönderimi yapıldığını görmekteyiz. Buradaki problem SendTripToDriver methodunun içerisinde birden fazla işlem yapılıyor olmasıdır.
Single Responsibility Principle’e göre bir method içerisinde birden fazla iş yapılmamalıdır.Yukarıdaki kod’u bu prensibe göre düzenlersek eğer aşağıdaki gibi bir yapı uygun olabilir.
2. Open-Close Principle
Open-closed priciple’a göre yazdığımız kodların geliştirmeye açık ancak değiştirmeye kapalı olması gerekmektedir.Var olan fonksiyonları,modelleri değiştirmek yerine bunları geliştirilebiliyor olmamız gerekmektedir.Tek cümle ile özetlemek gerekirse ;
“Değişime kapalı, gelişime açık.”
Bu yaklaşımı da Mercedes Benz ve BMW örneği üzerinden açıklamaya devam edelim ve yaratmış olduğumuz Mercedes Benz ve BMW class’ları için aracın yakıt giderini hesaplayan bir tane daha class yazalım.
Daha sonra bu class’ımızı Calculate isimli bir methodda kullanalım.
Mercedes Benz için yukarıdaki methodu kullanarak aracın yakıt giderini hesapladık.Daha sonra BMW için de yakıt giderini hesaplamak istediğimizde doğal olarak artık Calculate isimli methodumuzda aşağıdaki gibi değişiklik yapmamız gerekecektir.
Bu durumda methodumuzda değişiklik yaptık ve var olan bir fonksiyonalitenin bazı özelliklerini değiştirmiş olduk.Ayrıca Mercedes Benz ve BMW gibi her yeni araç geldiğinde buradaki kodu değiştirmek zorunda kalacağız.
Değiştirmek zorunda kalıyorsak open-closed principle’da değişime kapalı olan close tarafını ihlal etmiş oluyoruz.Burada önemli olan nokta her yeni araç geldiğinde Calculator methoduna gidip değişiklik yapmamamız gerekliliğidir.
Bunun çözümü için abstraction’ı kullanabiliriz.Öncelikle BaseCar isimli class’mızı abstract bir class haline getiriyoruz ve içine GetCostPerKM isimli bir method yapalım.Daha sonra her araç kendi içerisinde o fonsiyonu doldursun.
BaseCar class’ımızı abstract bir class haline getirdiğimiz için bu class’ı inherent eden class’lar içerisinde override etmek zorundayız.
Gördüğümüz gibi artık yeni bir araç geldiği zaman veya methodta bir geliştirme yapmak istediğimiz zaman FuelCostCalculator’ı değiştirmemiz gerekmeyecektir.
Kısaca özetlemek gerekirse uygulamamızı geliştirme yapmak istediğimizde methodlarımızda veya class’larımızda değişiklik yapmıyor var olan özellikleri de değiştirmiyor olmamız gerekiyor.
3. Liskov ‘s Substitution Principle
Liskovs Substitution Priciple’a göre bir class inherit edildiği class gibi davranamıyorsa burada bir ihlal vardır.Yukarıdaki resimde de ifade edildiği gibi;
“Eğer bir araç ise araç gibi gidebiliyor,araç gibi farlarını yakabiliyor ancak ekstradan uçmaya ihtiyacı varsa bir yerlerde yanlış abstraction gerçekleştirmişizdir.”
MercedesBenz class’ı üzerinden ifade etmeye devam edelim.
MercedesBenz class’ı BaseCar gibi görünüyor ve içerisindeki bütün methodlara sahipse bir problem yok.MercedesBenz inherit edildiği class’ların içerisinde bütün fonksiyonları kullanabiliyor olması gerekmektedir.
Bir senaryo üzerinden konuşalım.Örneğin BMV firması bir süre sonra driver’lara artık sms atmayacağını sadece e-mail atmak istediğini söylediği zaman bizim hazırladığımız örnekte liskovs substitution priciple ihlal edilmiş oluyor.Bunun çözümü için aşağıdaki gibi bir yol izleyebiliriz.
Öncelikle email ve sms methodlarımızı BaseClass içerisinden silerek ayrı ayrı birer interface seviyesinde ayırarak bu interfacelerin implementasyonlarını class’ımız üzerinde yapalım.
BMV firması artık driverlara sadece e-mail atmak istediğini için BMV class’ı aşağıdaki gibi IEmailSendable interface’ini implemente etmesi yeterli olacaktır.
MercedesBenz firmasının ise driverlarına her iki işlemine yapmak istediğini düşünelim.
Son düzenlemeler ile kodumuzu Liskovs Substitution Priciple’a uygun bir hale getirmiş olduk. Son kez tekrarlamak gerekirse inherit edilen class inherit ettiği classların tamamının bütün özelliklerini kullanabiliyor ve onların yerine geçebiliyor olması gerekir.
4. Interface Segregation Principle
Interface Segregation Principle’a göre bir interface içerisine o interface’i implemente eden bütün class’larda kullanılmayacak bir method eklemememiz gerekiyor.Tek cümle ile özetlemek gerekirse;
“Nesneler, ihtiyaç duymadıkları metotların bulunduğu interface’lere bağlı olmaya zorlanmamalıdır.”
IEmailSendable interface’miz üzerinden açıklamaya devam edelim.Şuan IEmailSendable içerisinde SendTripInfoEmailToDriver isimli methodumuz var.Aşağıdaki gibi kodumuza birden fazla driver’a email gönderme işlemi yapan başka bir method daha ekleyelim.
MercedesBenz ve BMW class’larımızda düzenlemelerimizi yapalım.
Ancak bir süre sonra Mercedes firması bizim yazmış olduğumuz bu fonksiyonu desteklemeyeceğini söylemiş olsun.Bu durumda bizim kodumuz interface segregation principle’i ihlal etmiş oluyor.Peki bu sorunu nasıl çözebiliriz ?
Bir önceki prensipteki gibi yeni bir interface oluşturarak bu sorunu çözebiliriz.Öncelikle IMultipleEmailSendable isimli bir yeni interface oluşturalım ve aşağıdaki gibi SendTripInfoEmailToDrivers isimli methodumuzu buraya taşıyıp,sadece o özelliği destekleyen class’lara eklememiz yeterli olacaktır.
Bu şekilde interface’mizin içerisinde kullanması zorunlu olmayan methodları,özellikleri ve foksiyonları çıkararak ayrı bir özellik seti olarak kullandık.
5.Dependency Inversion Principle
Dependency Inversion Principle’a göre daha üst seviyedeki bir class daha alt seviyede bir class’a bağımlı olmamalıdır.
Bu prensip ile ilgili araçta oluşan seyahat,arıza gibi bilgileri loglamak için TripInfoLogger isimli bir class oluşturalım.
Bu class’ımızı aşağıdaki gibi methodumuzda bir hata oluştuğu zaman loglama yapmak için kullanalım.
Daha sonra aşağıdaki gibi bir senaryomuz oluştuğunu düşünürsek;
Aracımızda motor arızası gibi büyük hatalar olduğu zaman Almanya’ya hata durumu yollayalım ancak daha küçük bir hata(lastik basıncı gibi) olduğu zaman bunun bilgisini Almanya’ya göndermeyelim de başka bir ülkeye hata bilgilendirmesi yapalım.
Bizim bu talep üzerine kullanmış olduğumuz Logger mekanizmasina aşağıdaki gibi yeni bir fonsiyonolite eklememiz gerecekti.
Daha sonra methodumuzu aşağıdaki gibi düzenleyebilirdik.
Burada en önemli sorun her bir yeni logger eklendiği zaman sisteme bizim TripInfoLogger içerisinde yeni bir method yazmamız gerekecekti.Bunun sebebi de TripInfoLogger class’ının sistemdeki loggerlara bağımlı olması.Bu yüzden Dependency Inversion Principle prensibi burada ihlal edilmiş oluyor.
Bu sorunun çözümü de sürekli yeni yeni methodlar eklememek için TripInfoLogger içerisinde bağımlı olduğumuz class’lardan kurturup bir abstraction yaratmak ve sorunu çözüyor olmak.
Öncelikle aşağıdaki gibi ILogger isimli bir interface oluşturalım ve TripInfoLogger class’ımızı aşağıdaki gibi düzenleyelim.
Artık TripInfoLogger class’ı kendi içerisinde daha alt seviye bir class’a depend(bağımlı) değil ve Dependency Inversion Principle’a uygun bir şekilde yazılmış oldu.Bu şekilde son prensibimizi de açıklamış olduk.
SOLID Prensiplerinin uygulamamızın genel amacı;
a) Temiz kod yazmayı sağlamak ve gereksiz, karmaşık kodlamadan kaçınmak.
b) Kodu esnek bir şekilde kullanmak.
c) Kullandığımız yazılım dilinin ve OOP mantığının bize kazandırdığı tüm sınırları ve güçleri bilmek ve kullanmak.
d) Kodumuzun güncelleme ve düzenlemelere, yeni sürümlere kolayca adapte olmasını sağlamak.
e) Kolay adapte olmasını sağlayarak kod değişikliğini en kısa ve en kolay seviyeye indirmek.
f) Yazılan kodu okuyan her kişi tarafından yeterince anlaşılır kılmak gibi amaçlara sahip olduğunu söyleyebiliriz.
Aklınıza takılan sorular olursa bana websitemdeki e-mail hesabım üzerinden ulaşabilirsiniz :)