Tarık Kaygusuz

.Net sever bir yazılımcının kod rehberi.


Friends don't let friends __doPostBack();

SOLID Prensipleri

Günümüzde birçoğumuz C#, Java gibi nesne yönelimli programlama dilleri kullanıyoruz. Peki kullandığımız dillerin gücünden ne kadar faydalanabiliyoruz? Geliştirdiğimiz uygulamalar, zaman içerisinde değişebilecek ihtiyaçlara ne kadar güçlü karşılık verebiliyor? Eğer object oriented programlama yapıyor isek, dünya üzerinde standart kabul edilen 5 temel prensibi bilmemiz gerekiyor. 

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

Solid'e ek olarak Kiss, Yangi, Dry, Reuse Release Equivalence, Common Closure prensipleri de bulunmaktadır.

Şimdi kısaca bu prensiplerin neler olduğundan bahsedelim:

Single Responsibility Principle

Her ne kadar kaliteli kod yazmak için özen göstersek de, çalışma hayatında önümüze sadece kendi geliştirmiş olduğumuz projeler gelmiyor. Tek bir class içerisinde yazılmış binlerce satır kodu okuyup anlamaya çalışmak (belki sadece küçük bir revizyon için) zorunda kalabiliyoruz. Böyle durumlarda ekip arkadaşlarınızla aranızda şöyle konuşmalar geçtiği olur:

+Şu projede biraz optimizasyon yapsak mı?
- Abi çalışıyorsa hiç dokunmayalım.

Projenin içerisindeki her bir yapı, diğer yapılara o kadar bağımlıdır ve yapılan işler o kadar iç içe geçmiştir ki; küçük bir değişikliğin neleri etkileyeceğini kestirmeniz çok zordur ve genelde böyle projeler çöp proje olarak görülür. İçerisindeki class'ları methodları alıp başka bir projede kullanamazsınız. 

Eğer tek sorumluluk prensibine uyarsanız bu şekilde binlerce satırlık class'larınız methodlarınız olmaz. Her class'ın, her mothodun sadece tek bir yaptığı iş vardır, böylece bir değişiklik yapmak için sadece bir nedeniniz olmuş olur. Genişleyebilir, tekrar kullanılabilir ve test edilebilir yapılar kurmak için tek sorumluluk ilkesini dikkate almamız gerekir.

Open/Closed Principle 

Açık kapalı prensibi, yazılım geliştirirken kullandığımız varlıkların (class, method vs.) gelişime açık, kodların ise değişime kapalı olması ilkesidir. Örneğin; bir loglama altyapısı oluşturduğunuzu düşünün, Veritabanına ve XML'e kayıt tutuyorsunuz. Daha sonradan Eventloglara da log tutma ihtiyacı hissettiğinizde, sadece Eventloglara kayıt tutan kodları yazmanız yetecek, kodunuzda hiçbir değişiklik yapmadan bu yapı sisteme entegre olacak. Bunun için uygulayacağımız çözüm şu şekilde olabilir:





Burada LogTo methoduna ILogger interface'inden implemente olmuş bir class veriyoruz. Yani; daha sonrasında gene ILogger interface'i üzerinden implemente olmuş EventLog isimli bir class yazarsak Logger sınıfı üzerinde hiçbir değişiklik yapmamız gerekmeden, sisteme n sayıda log tutan yapı entegre edebileceğiz. 


Liskov ‘s Substitution Principle

Liskov'un yerine geçme prensibi alt sınıflardan oluşturulan nesnelerin üst sınıfların nesneleriyle yer değiştirdiklerinde aynı davranışı göstermek zorunda olduklarını söyler. Yani; türetilen sınıflar, türeyen sınıfların tüm özelliklerini kullanmak zorundadır. Eğer kullanmaz ise ortaya işlevsiz, dummy kodlar çıkacaktır. Bu durumda üst sınıfta if else blokları kullanarak tip kontrolü yapmamız gerekebilir ve böylelikle Açık Kapalı prensibine de ters düşmüş oluruz. 


Burada IDeveloper interface'ini implemente eden Junior Developer ve Senior Developer class'ları olduğunu görüyoruz. Ancak proje geliştirilirken bir sorun olduğunu farkettik. Junior Developer'ımız solid ilkelerini kullanamıyor  Yapıyı bu şekilde kurmaya devam edersek ya bu methodun içini boş bırakıcaz ya da  NotImplementedException throw edicez ve ClientApp uygulamamız IDeveloper interface'i üzerinden, IDeveloper'dan implemente olan tiplerini kullanıp Açık Kapalı prensibine bağlı kalabilecekken eğer JuniorDeveloper değil ise SolidIlkeleriniKullan() diyen bir if else bloğuna sahip olucak.

Peki böyle bir durumda çözüm nasıl olabilirdi? Bu yanlış tasarımın önüne nasıl geçeriz? 


Class Diagramından da anlaşıldığı gibi Developer sınıfını soyut yaptık ve ISolidKullanabilir isimli bir Interface oluşturduk. Böylelikle Liskov prensibine bağlı kaldığımız gibi, oluştuduğum dll'i kullanan Client Uygulaması da if else bloğuna gerek kalmadan Açık Kapalı prensibine uygun olabilecek.

Interface Segregation Principle

Arayüz ayırım prensibi, bir arayüze gerektiğinden fazla yetenek eklemememiz gerektiği söyler. 


Yukarıdaki Diagrama baktığımızda IArac Interface'inde Calistir, Durdur ve KlimayiAc method imzalarının tanımlanmış olduğunuz görüyoruz. Ancak burada bir sorun var. BMV class'ı bütün methodları implemente edebilirken Murat131 de klima özelliği yok. 

Böyle bir durumda Arayüz ayırım prensibinin bize önerdiği çözüm şu: Eğer interface'iniz implemente olacağı tüm sınıflarda desteklenemiyorsa Interface'inizi bölün. Bu ilkeye uyarak yapıyı tekrar kuralım:


Burada IKlimaliArac interface'ini IArac interface'inden türettik. BMV sınıfı IKlimaliArac, Murat131 ise IArac interface'ini implemente ederek Arayüz Ayırma Prensibine uygun bir tasarıma sahip olmuş oldu.

Dependency Inversion Principle

Bağımlılığın ters çevirilmesi ilkesine göre üst seviye sınıflar, modüller, methodlar vs. alt seviyeli sınıflara bağımlı olmamalıdır. Alt sınıflarda yapılan değişiklikler üst sınıfları etkilememelidir.


Oluşturduğum örnekte üst sınıf olan Encryption, alt sınıfları olan XMLReader ve TextReader'dan belirli bir formatta dönen veriyi şifrelemektedir. Reader sınıfları birer nesne olarak Encryption sınıfının içinde kullanılmakta/bağımlı olarak çalışmaktadır. Alt sınıf olan Reader sınıflarından birini uygulama dışında tuttuğumuz anda Encryption sınıfı hata verecektir. Reader sınıflarının ReadAll methodlarında yapacağımız bir kod değişikliği Encryption sınıfının işlevselliğini/kurgusunu rahatlıkla bozabilecektir.

Burada, üst sınıfın alt sınıflara olan bağımlılığı nasıl ters çevirebiliriz? Örneğini verdiğimde ters çevirmekten neyi kast ettiğimizi daha rahat anlayabileceğiz.



Yeni oluşturmuş olduğumuz yapıda, Encryption sınıfının XMLReader ve TextReader sınıflarına olan bağımlılığı, araya IReader interface'i koyularak ters çevrilmiştir. Alt seviyeli olan Reader sınıfları IReader interface'ine bağımlı durumdadır ve içlerinde yapılan değişiklikler Encryption sınıfını kesinlikle etkilemeyecektir. IReader interface'ini implemente edecek FileReader gibi bir sınıf, Encryption üzerinde hiçbir değişiklik yapılmadan kullanılabileceği bir tasarıma da uygun hale gelmiştir.


Diğer Prensipler : 

Kiss Principle : Keep it Simple Stupid
Bir problemin birçok durumda birden fazla çözümü vardır ve biz yazılımcılar kendi bilgi/birikimlerimizi en iyi yansıtan, en karmaşık kod yapısını kullanmaya eğilimli olabiliyoruz. Kiss prensibine göre çözümlerimiz mümkün oldunca sade/anlaşılır olmalı. En basit ve en optimum çözüm yöntemini seçerek uygulama geliştirmeliyiz.

Yagni Principle : You Aren't Gonna Need It!
Yagni prensibine göre, "o an" ihtiyacımız olmayan kodları sisteme dahil etmemeliyiz, ileride eklenebilecek özellikler hakkında öngürüde bulunup ek geliştirmeler yapmamalıyız.

Dry Principle : Don't Repeat Yourself
Proje içerisinde aynı kodu tekrar tekrar yazıyorsanız dry prensibine uymuyorsunuz demektir. Tekrarlı kod yapısında değişiklik olması, kullandığınız her yerde tek tek değiştirmenizi gerektirebilir ve ayrıca sistemi gereksiz yere karmaşık hale getirecektir. Böyle bir durumda, tekrarlı kodları merkezileştirecek bir çözüm üretmemiz gerekmektedir.

Reuse-Release Equivalence Principle
Sistemde kullanılan paketler/namespace'ler arasındaki bağımlılıkları yönetmek "tekrar kullanılabilir" yapılar kurmakla mümkün olur.

Common Closure Principle
Ortak Kapama Prensibi, Single Responsibility'nin namespace'ler için uyarlanmış halidir. Aynı sebepten değişebilecek sınıflar aynı namespace altında bulunmalıdır. Böylilikle sistemde oluşabilecek değişikliklerin tüm sistemi etkilemesinin önüne geçilmesi amaçlanır. 

Bu yazımla birlikte, nesne yönelimli programlama geliştiren herkesin öğrenmesi gereken 5 temel prensibi ve yaygın olarak uygulanan diğer prensipleri incelemiş olduk. Bir başka yazımda görüşmek üzere, mutlu günler dilerim. 

Yorumlar (8) -

  • Mehmet

    8.6.2016 02:40:21 | Yanıtla

    Gerçekten çok güzel ve anlaşılır bir yazi olmuş, paylasimiz icin cok tesekkurler.

  • Halit

    20.6.2016 21:13:15 | Yanıtla

    Anlatım gayet başarılı. Tek tek ve örneklerle açıklaman çok iyi olmuş.
    Teşekkürler

  • KAYA

    30.4.2017 09:49:29 | Yanıtla

    Harika

  • Muammer

    2.5.2017 20:53:40 | Yanıtla

    Çok iyi bir makale olmuş ellerinize sağlık

  • Emre

    19.6.2017 01:05:55 | Yanıtla

    Güzel paylaşım için teşekkürler.

  • Kübra Köken

    1.11.2017 09:55:07 | Yanıtla

    Teşekkür ederim,örnekler çok güzel olmuş.

  • İsmini vekmek istemeyen biri :)

    20.11.2017 12:12:51 | Yanıtla

    Gerçekten açık ve net olmuş.
    Teşekkürler
    Anlatmak sanattır.

  • hakan

    13.2.2018 16:17:26 | Yanıtla

    Tebrik ederim çok sade ve açıklayıcı olmuş

Loading