Bloğa geri dön

SOLID: Nesne Yönelimli Tasarımın İlk 5 İlkesi?

SOLID: Nesne Yönelimli Tasarımın İlk 5 İlkesi?

Giriş

SOLID, popüler olarak Uncle Bob olarak bilinen Robert C. Martin tarafından ortaya atılan, nesne yönelimli tasarımın beş ilkesinin anımsatıcı bir kısaltmasıdır. Bu ilkeler; yazılım tasarımcıları, mimarları, mühendisleri ve geliştiricilerinin daha esnek, bakımı kolay ve ölçeklenebilir yazılımlar oluşturmasına yardımcı olmayı amaçlar. Bu ilkeleri takip ederek test edilmesi, yeniden yapılandırılması (refactor), yeniden kullanılması ve genişletilmesi daha kolay sınıflar tasarlayabilirsiniz.

SOLID kısaltmasının açılımı şudur:

S – Tek Sorumluluk İlkesi (Single Responsibility Principle)

O – Açık-Kapalı İlkesi (Open-Closed Principle)

L – Liskov'un Yerine Geçme İlkesi (Liskov Substitution Principle)

I – Arayüz Ayrımı İlkesi (Interface Segregation Principle)

D – Bağımlılığın Tersine Çevrilmesi İlkesi (Dependency Inversion Principle)

Bu makalede, daha iyi kod yazmanıza nasıl yardımcı olabileceğini anlamak için her bir ilkeyi ayrı ayrı açıklayacağız. Ayrıca, bunları kodlama yolculuğunuzda nasıl uygulayabileceğinizi ve temiz kod mimarisinde nelerden kaçınmanız gerektiğini göstermek için her bir ilke için kod parçacıkları ekleyeceğiz. Kavramları göstermek için, JetBrains ve açık kaynak katkıda bulunanlar tarafından geliştirilen Kotlin programlama dilini kullanacağız.

Tek Sorumluluk İlkesi (Single Responsibility Principle)

Tek Sorumluluk İlkesi (SRP), bir programdaki her sınıfın veya modülün tek ve iyi tanımlanmış bir sorumluluğu olması gerektiğini belirten bir yazılım tasarım ilkesidir. Bu, bir sınıfın değişmek için yalnızca tek bir nedeni olması ve programın işlevselliğinin yalnızca tek bir bölümünden sorumlu olması gerektiği anlamına gelir.

Bu ilkenin Kotlin:

Kotlin'deki data anahtar kelimesi, bu sınıfın bir veri sınıfı (data class) olduğunu belirtir; bu da veri tutmak amacıyla tasarlandığı ve karmaşık bir davranışa sahip olmadığı anlamına gelir. Bu veri sınıfını kullanarak, aşağıda gösterildiği gibi kullanıcıları yönetmek için bir UserService sınıfı oluşturabiliriz:

Bu örnekte, UserService sınıfının tek bir sorumluluğu vardır: veritabanındaki kullanıcıları yönetmek. Her kullanıcı, daha önce paylaşılan data class User kodundaki veri sınıfı ile temsil edilir. UserService sınıfındaki tüm metotlar bu sorumlulukla ilgilidir ve bu nedenle uyumludur (cohesive). Bu durum, sınıftaki tüm metotların tek ve iyi tanımlanmış bir görevle ilgili olduğu açık olduğundan, sınıfın anlaşılmasını ve bakımını kolaylaştırır.

Tek Sorumluluk İlkesi'ni (SRP) ihlal eden bir metot, UserService sınıfında, sınıfın birincil sorumluluğu olan veritabanındaki kullanıcıları yönetmekle ilgisi olmayan bir metot olacaktır. Örneğin, UserService sınıfının aşağıdaki varyasyonunu ele alalım:

Bu örnekte, sendEmail  metodu, UserService sınıfının bir veritabanındaki kullanıcıları yönetmek olan birincil sorumluluğuyla ilgili değildir. Bu metot, kullanıcıları yönetmekten ayrı bir konu olan e-posta göndermekten sorumludur. Sonuç olarak, bu metot UserService sınıfının değişmesi için ikinci bir neden ortaya çıkardığından SRP'yi ihlal eder.

Tek Sorumluluk İlkesine uymak için, e-posta gönderme işlevini EmailService  sınıfı gibi ayrı bir sınıfa ayırmak daha iyi olacaktır. Bu, UserService sınıfının kullanıcıları yönetme şeklindeki birincil sorumluluğuna odaklanmasını ve EmailService sınıfının e-posta gönderme sorumluluğuna odaklanmasını sağlayacaktır.

Tek Sorumluluk İlkesinin bir sınıfın sahip olduğu metot sayısı ile değil, metotların uyumu ve bir sınıf içindeki sorumlulukların net bir şekilde ayrılmasıyla ilgili olduğunu unutmamalısınız.

Açık-Kapalı İlkesi

Açık Kapalı İlkesi (OCP), yazılım varlıklarının (sınıflar, modüller veya fonksiyonlar gibi) genişletilmeye açık, ancak değiştirilmeye kapalı olması gerektiğini belirten bir yazılım tasarım ilkesidir. Bu, mevcut kodunu değiştirmeden bir sınıfa veya modüle yeni işlevsellik eklemenin mümkün olması gerektiği anlamına gelir.

Önceki örnekteki UserService sınıfını ele alalım. UserService sınıfına, kullanıcıları e-posta adresine göre aramamızı sağlayan yeni bir özellik eklemek istediğimizi varsayalım. Bunu yapmanın bir yolu, aşağıdaki kodda vurgulandığı gibi UserService sınıfına yeni bir metot eklemektir:

Bu yaklaşım çalışır, ancak yeni özelliği eklemek için mevcut UserService sınıfını değiştirmemiz gerektiğinden Açık Kapalı İlkesini ihlal eder. Daha iyi bir yaklaşım, UserService sınıfının kodunu değiştirmeden işlevselliğini genişletmek için kalıtım veya kompozisyon kullanmak olacaktır.

Bunu başarmak için, UserSearchService  adında, UserService sınıfını genişleten ve e-posta arama işlevini ekleyen yeni bir sınıf oluşturabiliriz:

Bu örnekte, UserSearchService sınıfı, tarafından sunulanın ötesinde ek işlevsellik sağladığı için genişletilmeye açıktır.UserService sınıfı. Aynı zamanda, UserService sınıfı değişikliğe kapalı kalır, çünkü e-posta arama özelliğini eklemek için kodunu değiştirmemiz gerekmedi.

Liskov İkame Prensibi

Liskov İkame Prensibi (LSP), bir üst sınıfın nesnelerinin, programın doğruluğunu etkilemeden bir alt sınıfın nesneleriyle değiştirilebilmesi gerektiğini belirten bir yazılım tasarım prensibidir. Bu, bir alt sınıfın kendi üst sınıfı için geçerli bir ikame olması ve aynı bağlamda kullanıldığında üst sınıfla aynı şekilde davranması gerektiği anlamına gelir.

Gösterime User ve UserService sınıflarını kullanarak devam edeceğiz. User sınıfının genişletilebilmesini sağlamak için Kotlin'de open anahtar kelimesini kullanırız:

İşte yukarıdaki UserService veri sınıfını kullanan orijinal User sınıfı:

Farz edelim ki User sınıfının, AdminUser adında yönetici yetkilerine sahip kullanıcıları temsil eden bir alt sınıfını oluşturmak istiyoruz. Bunu şu şekilde yapabiliriz:

Bu örnekte, AdminUser sınıfı, User sınıfı ile aynı şekilde davrandığı ve bir User nesnesinin beklendiği her yerde kullanılabileceği için User sınıfı için geçerli bir ikamedir. Örneğin, AdminUser sınıfını UserService sınıfı ile şu şekilde kullanabiliriz:

Bu kod doğrudur, çünkü AdminUser sınıfı, User sınıfı için geçerli bir ikamedir ve bir User nesnesiyle aynı şekilde kullanılabilir.

Liskov İkame Prensibi’nin sadece kalıtımdan ibaret olmadığını unutmamak önemlidir. Bu prensip, alt sınıfın nasıl uygulandığına bakılmaksızın, alt sınıf nesnelerinin üst sınıf nesneleriyle aynı şekilde davranmasını sağlamakla ilgilidir. Örneğin, eğer AdminUser sınıfı, bir şekilde User sınıfından farklı davranacak olsaydı, User sınıfı için geçerli bir ikame olmayacağından Liskov İkame Prensibi'ni ihlal ederdi.

Interface Segregation Principle

Arayüz Ayrımı Prensibi (ISP), istemcilerin kullanmadıkları interfaces bağımlı olmaya zorlanmaması gerektiğini belirten bir yazılım tasarım prensibidir. Bu, tek bir işi iyi yapan küçük, odaklanmış interfaces oluşturmanın, birçok şeyi yapmaya çalışan büyük, genel amaçlı interfaces oluşturmaktan genellikle daha iyi bir fikir olduğu anlamına gelir.

İşte önceki örneklerdeki User ve UserService sınıflarını yeniden yazarak bu prensibin nasıl uygulanabileceğine dair bir örnek:

Bu örnekte, UserService arayüzü, bir veritabanındaki kullanıcıları yönetmekle ilişkili dört yöntem tanımlar. DatabaseUserService  sınıfı bu arayüzü uygular ve bu yöntemler için somut uygulamalar sağlar.

Farz edelim ki UserService arayüzüne, kullanıcıları e-posta adresine göre aramamıza olanak tanıyan yeni bir özellik eklemek istiyoruz. Bunu yapmanın bir yolu, UserService arayüzüne yeni bir yöntem eklemek olacaktır:

Kodunuz, UserService arayüzünü uygulayan tüm sınıflarda bu yöntemi de uygulamadığınız sürece çalışmayacaktır:

Bu yaklaşım çalışsa da, DatabaseUserService  sınıfını, ihtiyaç duymayabileceği veya kullanmayabileceği bir yöntemi ( searchUsersByEmail ) uygulamaya zorladığı için Arayüz Ayrımı Prensibini (Interface Segregation Principle) ihlal eder.

Daha iyi bir yaklaşım, ayrı bir arayüz  oluşturmak olacaktır:

Artık ayrı, küçük ve odaklanmış arayüzlerimiz var, yani UserService  ve UserSearchServicetek bir sorumluluğa sahip olan. Bu arayüzlerin tüm işlevlerine ihtiyaç duyan bir sınıf, bunları aşağıdaki kod parçacığında gösterildiği gibi uygulayabilir:

Bu, istemcilerin (örneğin DatabaseUserService sınıfının) kullanmadıkları arayüzlere bağımlı olmaya zorlanmamasını sağladığı için Arayüz Ayrımı Prensibi'ne (Interface Segregation Principle) uygundur.

Bu kavramı daha iyi anlamak için, MemoryUserService  adında, UserService  arayüzünü uygulayan ancak e-posta arama işlevine ihtiyaç duymayan başka bir sınıfımız olduğunu varsayalım; kodu şu şekilde yazabiliriz:

Bu örnekte, MemoryUserService sınıfının yalnızca UserService arayüzünde tanımlanan yöntemleri uygulaması gerekir ve e-posta arama işlevi konusunda endişelenmesine gerek yoktur. Bu durum, MemoryUserService sınıfının, ilgisiz işlevleri uygulamaya zorlanmak yerine, bellekteki kullanıcıları yönetme şeklindeki birincil sorumluluğuna odaklanmasını sağlar.

Dependency Inversion Principle

Bağımlılığın Tersine Çevrilmesi İlkesi (Dependency Inversion Principle - DIP), üst düzey modüllerin alt düzey modüllere bağımlı olmaması gerektiğini, aksine her ikisinin de soyutlamalara bağımlı olması gerektiğini belirten bir yazılım tasarım ilkesidir. Bu, yazılımınızı genel olarak üst düzey bileşenlerin alt düzey bileşenlerin belirli uygulamalarına bağlı olmayacak, aksine farklı şekillerde uygulanabilen soyutlamalara (arayüzler veya soyut sınıflar gibi) bağımlı olacak şekilde tasarlamanın iyi bir fikir olduğu anlamına gelir.

Bu ilkenin, önceki kod parçacıklarında kullanılan User ve UserService sınıflarına nasıl uygulanabileceğine dair bir örneğe bakalım:

Bu örnekte, UserService sınıfı, kullanıcı deposunun belirli bir uygulamasına bağımlı olmak yerine UserRepository arayüzü (interface) adı verilen bir soyutlamaya bağımlıdır. Bu, UserRepository arayüzünü, UserService sınıfını etkilemeden veritabanı veya bellek içi depolama gibi farklı şekillerde uygulamamıza olanak tanır.

Örneğin, veritabanı kullanan UserRepository arayüzünün bir uygulaması şöyledir:

İşte bellek içi depolama kullanan başka bir UserRepository arayüzü uygulaması:

Bu, sistemin geri kalanını etkilemeden depo uygulamasını değiştirmemize olanak tanıdığı için sistemi daha esnek ve bakımı kolay hale getirir. Ayrıca testlerimizde UserRepository bağımlılığını taklit edebileceğimiz (mock) için UserService sınıfını test etmeyi de kolaylaştırır.

Sonuç

Bu makalede, SOLID'in beş prensibinden bahsettik.  her bir ilkeyi karşılayan kod ve paylaşılan kod parçacıkları. Bu ilkelere bağlı kalmak, daha esnek, bakımı kolay ve ölçeklenebilir yazılım sistemleri tasarlamanıza yardımcı olabilir. Ancak, bu ilkelerin kesin kurallar olmaktan ziyade kılavuz ilkeler olduğunu ve bunları kendi özel projeleri bağlamında ne zaman ve nasıl uygulayacağına karar vermenin geliştiriciye bağlı olduğunu unutmamak önemlidir. Daha derinlemesine ve güncel makaleler ve eğitimler üzerine bulut bilişim ve DevOps, yazılım tasarımı ve geliştirme, takip edilmesi gereken teknoloji trendleri ve daha fazlası.

Keyifli Kodlamalar!

author

Preslav Dobrev

Yazar · CloudSigma

Preslav Dobrev, CloudSigma'da Kreatif Tasarımcı olarak görev yapmakta olup geleneksel ve yenilikçi pazarlama kanallarını kullanarak tutarlı bir kurumsal kimlik oluşturmaya odaklanmaktadır. Sanatsal vizyonu stratejik pazarlamayla harmanlayarak etkili marka anlatıları oluşturma konusunda oldukça yeteneklidir.

Yorumlar

Henüz yorum yapılmamış. İlk siz olun.