SSOLID Prensipleri — SOLID Principles

Emre Büşlü
Fiba Tech Lab
Published in
6 min readNov 1, 2022

Bu yazımda nesneye yönelimli programlamanın ilkelerinden olan SOLID prensipler bütününden bahsedeceğim. SOLID prensipleri, programlama dillerinden bağımsızdır. Temel ve önemli olan bu prensipler bütününün bilinmesi ve kod yazarken göz önünde bulundurulması, yazılıma ve yazılımcıya kalite katar. Bu prensiplere uygun hareket etmemiz; bize daha temiz, yönetmesi-geliştirmesi-okunması daha kolay ve tekrara düşmeyen kodlar yazmamızı sağlar.

5 temel prensibin baş harflerinden oluşan SOLID kelimesinin “S”sinden başlayarak tek tek inceleyelim. Olabildiğince kodlarla örneklendirmeye çalışacağım. Yazının sonunda örneklerin verildiği projeyi bulabilirsiniz.

Single Responsibility Principle — Tek Sorumluluk Prensibi

“Every class should have only one responsibility.”

Adından da anlaşılacağı üzere bu prensip; her bir sınıfın, fonksiyonun, hatta değişkenin bile tek bir amaca hizmet etmesi gerektiğini savunur. Şu soruyu sorarak bu prensip ihlallerini tespit etmeye çalışabiliriz. “Bu sınıfı değiştirmek için kaç farklı sebebim var ?” Cevap 1 olmalı, aksi durumda bu prensibi ihlal etmişiz demektir. Aşağıdaki örnekleri inceleyerek ana fikri kavramaya çalışalım.

Bir kişinin e-posta, ad ve soyad bilgilerini tutmak için Person nesnesi yaratabiliriz. Aynı zamanda e-postanın geçerli olup olmadığı, nesne yaratılırken kontrol ediliyor. Bu durum tek sorumluluk prensibini ihlal eder. Nesne birden fazla amaca hizmet ediyor. İhlali aşmak için çözüm üretelim.

Person sınıfında e-postayı temsil eden değişkenin tipini Email olarak değiştirdik.

Email sınıfı yaratılırken adresin geçerli olup olmadığı kontrol ediliyor.

( Eğer Email sınıfımızın farklı sorumlulukları olsaydı, SRP’yi ihlal etmemek adına e-posta adresi validasyonu için EmailAddressValidator gibi bir sınıf yaratıp Email sınıfının içerisinde çağırabilirdik. )

Open/Closed Principle — Açıklık/Kapalılık Prensibi

Software entities should be open for extension, but closed for modification.

Bir yazılım varlığının (sınıf, metot, modül vs.) özelliklerinin genişletilmeye açık, değişime kapalı olmasını savunur. Ne demek istendiğini örneklerle anlamaya çalışalım.

Calculator sınıfının Area metodu ile Circle ve Square nesnelerinin alanları hesaplanıyor. Fakat uygulamaya yeni nesneler eklendiği vakit Area metodunun gövdesinde değişiklik(modification) yapmamız gerekiyor. Bu aşamada OCP’yi ihlal etmiş oluyoruz “…closed for modification”.

Prensibi göz önünde bulundurarak çözüm üretmeye çalışalım ;

Shape abstract sınıfı ile Area metodunu Circle ve Square sınıfları içerisine aldık. Calculator sınıfında ise bu metodu çağırıyoruz. Uygulamaya yeni nesneler eklendiğinde değişiklik(modification) yapmamız gereken bir yer olmayacak, ve uygulamamız genişleyecek “…open for extension”.

Liskov’s Substitution Principle — Liskov’un Yerine Koyma Prensibi

Subtypes must be substitutable for their base types.

Alt sınıflar, üst sınıflara göre (aralarındaki hiyerarşik yapıdan ötürü) daha fazla özelleşmiştir. Bu yapılar için LSP şunu savunur ; “üst sınıfın özellikleri, kalıtımı alan alt sınıflarda sağlıklı olarak kullanılabilmelidir.” Aksi durumda bu prensip ihlal edilir.

Frog sınıfı Amphibians sınıfından kalıtım almış ve Amphibians sınıfına ait metotları sağlıklı bir şekilde kullanıyor. Buraya kadar sorun yok.

Fakat yukarıdaki gibi bir kalıtım uygun değildir. Dolphin sınıfının Walk metodunun olması anormal/istenmeyen/beklenmeyen bir durumdur ve bu metot atıl kalmıştır. Kodlama yaparken de hataya sebebiyet verebilir. LSP’yi ihlal etmiş olduk.

Amphibians ve Frog sınıflarımızda herhangi bir değişiklik yapmadık.

Swimmers isminde, Swim metodu olan bir sınıf yaratalım. Ve Dolphin sınıfı da bu sınıftan kalıtım almış olsun. Ana fikrimizi hatırlayalım ; “Subtypes must be substitutable for their base types.”. Yunuslar birer amfibi (yüzergezer) olmadığından bu sınıftan kalıtım alması (Walk metoduna sahip olması) doğru olmayacaktı. Son değişikliklerle prensip ihlalini ortadan kaldırdık.

Interface Segregation Principle — Interface Ayrıştırma Prensibi

Many client-specific interfaces are better than one general-purpose interface.

Bildiğimiz gibi Interface’ler sınıflara implementasyon yapmaya zorlar. Fakat bazı durumlarda sınıflara uygulanan Interface’in bazı implementasyonları o sınıf için anlamlı olmayabilir. Bu prensip; bu tarz durumlar yaşandığında Interface’leri ayrıştırmak gerektiğini savunuyor. Örneklendirelim.

Taşıt niteliklerini temsil eden bir Interface’imiz olsun.

Airplane sınıfımıza bu Interface’i implemente ettik fakat uçaklar denize açılamaz. Interface’in zorladığı Sail metodu atıl kalmış oldu.

Benzer şekilde Car sınıfına implemente edilen Fly ve Sail metodları da atıl durumda. Son bir örnek verelim.

Yelkenlinin de motorunun olmadığını varsayalım. Ve eğer Flying Dutchman değil ise uçamaz 🙂.

Gördüğünüz gibi araçların birçok özelliği tek bir Interface’te toplanmış. Dolayısıyla Interface ayrıştırma prensibi ihlal edilmiş. Metodların atıl kalma durumu her ne kadar çalışma ve derleme zamanında (eğer kodlama dikkatli yapıldıysa) sorun çıkartmasa da bize hata yapma ihtimali kazandırıyor, bunu istemeyiz. Ayrıca mantıklı da değil. Bir otomobilin Fly metodu neden olsun ki ?

Prensip ihlalini ortadan kaldırmaya çalışalım.

Bir önceki örnekteki IVehicle Interface’ini parçalara ayrıştırarak(segregation) 4 adet Interface elde ettik. Airplane , Car ve Sailboat sınıflarına bu Interface’leri ihtiyaçlar doğrultusunda uygulayacağız.

Sınıflarda atıl metotlar oluşmadı. ISP ihlalini ortadan kaldırmış olduk.

Dependency Inversion Principle — Bağımlılığı Tersine Çevirme Prensibi

Depend upon abstractions, not concretions.

Bu prensip sınıflar arası ilişkileri ele alır. Üst seviye sınıflar, alt seviye sınıflara somut olarak bağlı olmamalıdır. Abstract sınıflar veya Interface’ler ile sınıflar arası ilişkinin soyutlaştırılması gerektiğini savunur.

2 farklı veritabanımız olduğunu ve çeşitli durumlara göre veritabanlarına log yazdırmamız gerektiğini varsayalım.

LogWriter sınıfını logları yazdırmak için kullanalım. Gördüğünüz üzere yukarıdaki kod bloğu geçerli ve sağlıklı çalışabilir. Fakat yukarıda basit bir örnek verdik. N adet farklı log yazdırma yöntemi ile karşılaştığımızda bunların tamamını LogWriter sınıfında mı yönetmeye çalışmalıyız ? Peki ya MysqlLogger sınıfının veritabanı bağlantısı için girdilere ihtiyacı olursa ? Birçok benzer soru sorulabilir... OracleLogger ve MysqlLogger sınıflarının LogWriter sınıfına tight coupling (sıkı/somut bağlı) olduğunu görüyoruz. Bu durum DIP’i ihlal eder. Aşağıdaki örnekte Interface kullanarak bu durumu loose coupling (gevşek/soyut bağlı) durumuna geçireceğiz.

Log yazdırmak için kullanacağımız sınıflarımıza uygulamak üzere bir Interface yaratalım.

Bu Interface’i ilgili sınıflarımıza uygulayalım.

Son olarak LogWriter sınıfımızı düzenleyelim. Sınıfımızın constructor metodunda ILogger tipinde bir parametre aldık, yani bu sınıfa bir bağımlılık enjekte etmiş olduk. Artık LogWriter sınıfının MysqlLogger veya OracleLogger sınıflarıyla direkt olarak bir bağlantısı kalmadı. Kullanımdaki farklılığı görebilmeniz için Program sınıfını inceleyebilirsiniz.

S.O.L.I.D prensiplerinin tamamından bahsetmeye ve örneklendirmeye çalıştık. Bu prensipler hakkında bolca kaynak ve örnek var. Mühendislik fakültelerinde de muhakkak bahsediliyor. Fakat kod yazarken veya yeniden düzenleme yaparken bu prensipleri gözden kaçabiliyor. Bazen bazı şeyleri “teknik olarak biliyor” olmamıza rağmen “pratikte bilmiyoruz”. Zihnimize, parmaklarımıza sirayet etmesi için bu prensipleri tekrar etmeli ve projelerimizde uygulamalıyız. Gelişmeye ve geliştirmeye devam edelim. Bir sonraki yazımızda görüşmek dileğiyle…

--

--