SOLID Prensipleri

Sevda Gül Baran
4 min readSep 23, 2022

--

Object Orinted Programming de 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, temel olarak bu prensiplerin oluşturduğu kombinasyonun baş harflerinden oluşan bir kavramdır. Bu ilkeler, Robert C. Martin tarafından geliştirilen prensipler bütünüdür.

Bu prensipler:

  • Single-Responsibility Principle ( Tek Sorumluluk Prensibi)
  • Open-Closed Principle (Açık-Kapalı Prensibi)
  • Liskov Substitution Principle (Liskov’un yerine geçme Prensibi)
  • Interface Segregation Principle (Arayüz Ayırma Prensibi)
  • Dependency Inversion Principle (Bağımlılığı Tersine Çevirme)

Doğru ve temiz kod standartlarını belirleyen bu prensipleri daha detaylı bir şekilde açıklamaya başlayalım.

S- Single Responsibility Principle

A CLASS SHOULD HAVE ONLY ONE REASON TO CHANGE.

Bir sınıfın veya metodun gerçekleştirebileceği tek bir sorumluluğu olmalıdır. Bu durumda hem görev paylaşımı doğru bir şekilde yapılmış olur hem de programın ilerleyen zamanlarda değiştirilmesi, bakıma alınması çok daha kolay olur.

Swift kodunda SRP kullanmadan nasıl gözüktüğünü inceleyelim. Basit bir mesaj yanıtlama senaryosu oluşturalım.

SRP’nin savunduğu görüş: Bir sınıfın değişmesi için asla birden fazla neden olmamalıdır. Bu, birden fazla metod olamayacağı anlamına gelmez. Ancak tek koşul, tek bir amacı olması gerektiğidir.

Burada, ManageMessageReceiver sınıfı aşağıdakileri yapar:

  • Mesajı alır.
  • Mesajı arayüzde gösterir.

Senaryomuzda iki ayrı uygulama için ManageMessageReceiver sınıfını kullanıyoruz:

  • Bir mesajlaşma uygulaması, mesajı almak için bu sınıfı kullanır.
  • Grafiksel bir uygulama, mesajı kullanıcı arayüzünde çizmek için bu sınıfı kullanır.

SRP’yi ihlal ettiğini düşünüyor musunuz?

EVET, ManageMessageReceiver sınıfı aslında iki farklı şey gerçekleştiriyor. Bunlardan biri mesajlaşmayı yönetirken diğeri mesajı UI’de görüntüler.

İki farklı sınıf oluşturup: MessageReceiver sınıfı mesajlaşma uygulaması tarafından, MessageDisplay sınıfı ise arayüz uygulaması tarafından kullanılacaktır. Hatta sınıfları iki ayrı dosyaya ayırabiliriz ve bu şekilde birinde değişiklik yapılması gerektiğinde diğerine dokunmamamızı sağlar.

O- Open-Closed Principle

SOFTWARE ENTITIES (CLASSES, MODULES, FUNCTIONS, ETC.) SHOULD BE OPEN FOR EXTENSION, BUT CLOSED FOR MODIFICATION.

Bir sınıf veya metod yeni özelliklere ve gelişmeye açık lakin değişmeye kapalı olmalıdır. Var olan davranışını değiştirmeden yeni özellikler kazanabilir. Bu noktada, polimorfizm konusu ile özel davranışlara sahip yinelenen varlıklar yaratmamız gerekir.

Kodun yapısından bahsedecek olursak ilk olarak şekilleri tuttuğumuz bir enum yapısı var. Şöyle ki, enum bir şeyleri organize etmek için kullanılabilecegimiz güçlü bir araç. DrawGraphic sınıfı içerisinde bulunan drawShape fonksiyonu gelen caseye göre yapacağı işi switch-case yapısı ile ekranda göstermektedir.

Zaman geçtikçe proje içine yeni metod ya da şekiller dahil etmek istediğimizde; drawShape fonksiyonu büyüyecek ve işlevselligini kaybetmiş olacak. Aynı zamanda DrawGraphic sınıfını değiştirilmiş olacak, ki bu OCP’ye aykırı bir tutum.

Peki nasıl çözülebilir?

Doğru tasarımda ise Draw adında bir protocol tanımlanıp draw() adında fonksiyon oluşturuldu. Draw protocol’unden türeyen her bir sınıf kendi draw() fonksiyonunu implemente edebilmelidir. Böylelikle projemize geliştirilebilir fakat değişemeyecek bir base oluşturabiliriz.

L- Liskov Substitution Principle

FUNCTIONS THAT USE POINTERS OR REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT.

Alt sınıflardan oluşan nesnelerin üst sınıfın nesneleri ile yer değiştirdikleri zaman, aynı davranışı sergilemesini beklemektir. Bu kural, Object Oriented Programming ile doğrudan ilişkilidir.

Bu prensip kalıtım mantığının doğru bir şekilde anlaşılması ve kullanılması konusunda bizi yönlendirmek için vardır. Kalıtım alınan sınıfın içerisindeki özellikler kendisinden kalıtılan sınıfta da doğru ve eksiksiz bir şekilde kullanılmalıdır. Aksi durumlar prensibe aykırıdır.

Planet sınıfında, güneş yörüngesinde bulunan gezegenler için bir fonksiyon içerir. Earth, güneş yörüngesinde yer alır ve bir gezegendir. Pluto ise güneş yörüngesinde bulunur fakat gezegen değildir, cüce gezegen olarak tanımlanır. (Etrafını temizlememiş cisimler cüce gezegen olarak isimlendirilir.)

Bu sorunu araya bir sınıf daha koyarak ya da interface (Swift dilinde Protocol olarak geçer.) ile çözebiliriz.

LSP uygulanarak, Plüton Planet sınıfını miras aldı ancak cüce ve normal gezegeni ayıran notClearedNeigbourhood() fonksiyonu eklendi.

Swift, kalıtımın düzgün yapılıp yapılmadığını hiyerarşiyi test etmek için nesneler tasarlayabilir ve LSP’yi bir doğrulama aracı olarak uygulayabilir.

I- Interface Segregation Principle

CLIENTS SHOULD NOT BE FORCED TO DEPEND UPON INTERFACES THAT THEY DO NOT USE.

Mantık olarak isminden anlaşılacağı gibi oluşturduğumuz modeller de kullanılan arayüzlerin birbirinden ayrılarak kullanmasına dayanır. Yani her farklı sınıf yapısı için kendine özgü bir arayüz olması gerekli. Bundan önceki prensibimiz LSP deki gibi ortak arayüz olup kullanılmayan fonksiyonlar var ise bu prensibe ters düşer.

Şimdi interPlanetCommunication’ı kullanmak isteyen herkes için, gerekli olmasa da beş fonksiyonun tümünü uygulaması gerekiyor.

ISP, protocollerin her birinin kendi sorumluluğuna sahip olacak şekilde geliştirilmesini ve böylece spesifik, kolay anlaşılır ve yeniden kullanılabilir olmasını sağlar.

D- Dependency Inversion Principle

HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS.

Bir sınıfın, fonksiyonun ya da özelliğin, onu kullanan diğer sınıflara karşı olan bağımlılığı en aza indirgenmelidir. 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.

Yukarıdaki örnekte üst seviye sınıfımız LogManager sınıfıdır, alt seviye sınıflar ise DBLogger ve FileLogger sınıflarıdır. Bu durumda çok net bir şekilde üst seviye sınıfımız LogManager, alt seviye sınıflarımıza DBLogger ve FileLogger sınıflarımıza doğrudan bağımlıdır.

Çözüm ise üst ve alt seviye sınıf ilişkilerini Logger protocol ile yönetmektir.

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

Sonuç

SOLID prensipleri, geliştirilen proje için ileride istenecek değişimlere açık olan ve bu değişimlere en az eforla uyum sağlayacak yapı ve mimaride kod yazmayı sağlayan mükemmel yaklaşımlardır. Başlangıç aşamasında iseniz bu ilkeleri kullanmak zor olabilir, ama kod yazdıkça bu ilkelere ihtiyaç duyacaksınız.

Bu süreçte bana destek olup, ayrıca yazımı review eden mentorum Anıl taşkıran‘ a teşekkür ederim.

--

--