• Cum. May 3rd, 2024

Arif GÖKÇE

Senior Software Engineer

SOLID Prensipleri: Yazılımın Temel Taşları

Solid PrensipleriSolid Prensipleri

Yazılım geliştirmenin karmaşıklığı arttıkça, kodun sürdürülebilir, esnek ve genişletilebilir olması gerekliliği ortaya çıkmıştır. Bu nedenle, yazılım mühendisleri SOLID prensiplerini kullanarak kodlarını daha kaliteli hale getirmeye çalışmışlardır. SOLID prensipleri, yazılım geliştirmede sürdürülebilirlik, esneklik, genişletilebilirlik, bakım kolaylığı ve kodun anlaşılırlığı gibi temel hedeflere ulaşmak için kullanılan prensiplerdir.

SOLID, beş farklı prensibin baş harflerini temsil eder ve her biri belirli bir ilkeyi ifade eder:

Single Responsibility PrincipleSRP (Tek Sorumluluk Prensibi):

Bir sınıfın veya modülün sadece bir sorumluluğu olmalıdır ve o sorumluluğu başarıyla yerine getirmelidir. Başka bir deyişle, bir sınıfın veya modülün birden fazla sebeple değiştirilmesi gerekmek yerine, sadece bir sebeple değiştirilebilir olmalıdır. Bir postacı, aynı anda posta dağıtımı, posta toplama ve posta sıralama gibi birden fazla görevi üstlenmemelidir. Her bir görev, ayrı bir sınıf veya fonksiyon tarafından gerçekleştirilmelidir.

Tek Sorumluluk Prensibi, bir sınıfın veya modülün sadece bir görevi yerine getirmesi gerektiğini vurgular. Bu prensibi uygulamak, kodun daha anlaşılır, sürdürülebilir ve değiştirilebilir olmasını sağlar. Örneğin, bir .NET uygulamasında, bir kullanıcının kimlik doğrulama ve yetkilendirme işlemleri ile ilgili kodlarını ayrı bir sınıf veya modülde bulundurmak, Tek Sorumluluk Prensibini uygulamak anlamına gelir. Böylece, kullanıcı yönetimi ile ilgili değişiklikler yapmak istediğinizde, sadece bu sınıfı veya modülü değiştirmeniz yeterlidir.

public class AuthenticationManager
{
    public bool AuthenticateUser(string username, string password)
    {
        // Kullanıcıyı doğrula
        // Doğrulama işlemleri burada gerçekleştirilir
    }
}
public class AuthorizationManager
{
    public bool AuthorizeUser(string username, string role)
    {
        // Kullanıcının yetkilendirilip yetkilendirilmediğini kontrol et
        // Yetkilendirme işlemleri burada gerçekleştirilir
    }
}

Yukarıdaki örnekte, AuthenticationManager sınıfı kullanıcı doğrulama işlemleriyle ilgili kodları içerirken, AuthorizationManager sınıfı ise kullanıcı yetkilendirme işlemleriyle ilgili kodları içermektedir. Her iki sınıf da ayrı ayrı bir sorumluluğu yerine getirmektedir ve değiştirildiğinde birbirini etkilememektedir.

Open/Closed Principle – OCP( Açık/Kapalı Prensibi ):

Bir sınıfın veya modülün kodu değiştirmeye kapalı (closed) ve uzantılara açık (open) olmalıdır. Yani, mevcut kod değiştirilmeden, yeni özellikler veya davranışlar eklemek mümkün olmalıdır. Bir otomobil üreticisi, yeni bir araba modeli eklemek istediğinde mevcut kodu değiştirmeden, sadece yeni bir sınıf ekleyerek veya mevcut sınıfları genişleterek yeni modeli sisteme ekleyebilmelidir.

Açık/Kapalı Prensibi, kodun değişime kapalı olmasını ve yeni özellikler eklenirken kodun değiştirilmesine gerek olmamasını vurgular. Bu prensibi uygulamak, kodun daha genişletilebilir ve bakımı daha kolay hale getirir. Örneğin, bir .NET Core uygulamasında, yeni bir veritabanı sürücüsü ekleyerek mevcut veritabanı erişim kodunu değiştirmek yerine, yeni bir sınıf ekleyerek ve gerekli arayüzü uygulayarak solid prensiplerinden Açık/Kapalı Prensibini uygulayabilirsiniz.

public abstract class Shape
{
    public abstract double CalculateArea();
}
public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }
    public override double CalculateArea()
    {
        return Width * Height;
    }
}
public class Circle : Shape
{
    public double Radius { get; set; }
    public override double CalculateArea()
    {
        return Math.PI * Radius * Radius;
    }
}

Yukarıdaki örnekte, Shape adlı soyut bir sınıf, herhangi bir şeklin alanını hesaplamak için kullanılır. Rectangle ve Circle sınıfları ise bu soyut sınıfı genişleterek farklı şekillerin alanını hesaplamaktadır. Herhangi bir yeni şekil eklemek istediğinizde, Shape sınıfını değiştirmenize gerek olmadan yeni bir sınıf ekleyerek Açık/Kapalı Prensibini uygulayabilirsiniz.

Liskov Substitution Principle – LSP (Liskov Yerine Geçme Prensibi):

Bir sınıfın, türetilen sınıflar tarafından yerine geçebilir olması gerekmektedir. Yani, bir sınıfın türetilen sınıfları, temel sınıfın yerine geçerek aynı davranışları sergilemelidir. Bir kargo şirketi, bir kurye ve bir kamyon şoförünü aynı arayüzü (örneğin, IDeliveryDriver arayüzü) implemente ederek, her ikisini de aynı şekilde kullanabilmelidir. Kurye ve kamyon şoförü, aynı temel davranışları sergileyerek, teslimat süreçlerini gerçekleştirmeli ve sistemi kullanıcılar için tutarlı ve güvenilir kılmaktadır.

Liskov Yerine Geçme Prensibi, kodda hiyerarşik olarak ilişkili sınıflar arasında tutarlılığı sağlar. Bu prensip, türetilen sınıfların temel sınıfın yerine geçebilir olması gerektiğini vurgular. Örneğin, bir .NET Core uygulamasında, birden fazla veri kaynağı kullanıyorsanız, farklı veri kaynaklarını temsil eden sınıfların, aynı arayüzü uygulayarak solid prensipleri Liskov Yerine Geçme Prensibini uygulayabilirsiniz.

public abstract class Animal
{
    public virtual void MakeSound()
    {
        Console.WriteLine("Animal makes sound.");
    }
}
public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Dog barks.");
    }
}
public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Cat meows.");
    }
}

Yukarıdaki örnekte, Animal adlı soyut bir sınıf, hayvanların ortak davranışlarını temsil eder ve MakeSound adlı bir metodu bulunur. Dog ve Cat sınıfları, Animal sınıfını genişleterek kendi MakeSound metotlarını uygular. Her iki sınıf, Animal sınıfının yerine geçebilir ve aynı davranışları sergiler. Bu sayede, Solid prensipleri nden Liskov Yerine Geçme Prensibi uygulanmış olur.

Interface Segregation Principle – ISP (Arayüz Ayrımı Prensibi ):

Bir sınıfın, kullanmadığı arayüzleri implemente etmemesi gerekmektedir. Yani, bir sınıf, ihtiyacı olmayan metotları içeren arayüzleri uygulamamalıdır.

Arayüz Ayrımı Prensibi, sınıfların gereksiz yere karmaşık ve fazla metotlara sahip arayüzleri uygulamamasını vurgular. Bu prensip, sınıfların sadece kendi ihtiyaçlarını karşılayan arayüzleri uygulamasını sağlayarak, daha az bağımlılık ve daha az karmaşıklık elde etmeyi hedefler. Örneğin, bir .NET uygulamasında, birden fazla servis arayüzü kullanıyorsanız, her sınıfın sadece kendi ihtiyaçlarına yönelik arayüzleri uygulamasını sağlayarak Arayüz Ayrımı Prensibini uygulayabilirsiniz.

public interface ILoggable
{
    void Log(string message);
}
public interface ICacheable
{
    void Cache(string key, object data);
}
public class Logger : ILoggable
{
    public void Log(string message)
    {
        Console.WriteLine($"Logging: {message}");
    }
}
public class Cacher : ICacheable
{
    public void Cache(string key, object data)
    {
        Console.WriteLine($"Caching data with key '{key}': {data}");
    }
}
public class DataService : ILoggable, ICacheable
{
    private Logger _logger;
    private Cacher _cacher;
    public DataService()
    {
        _logger = new Logger();
        _cacher = new Cacher();
    }
    public void Log(string message)
    {
        _logger.Log(message);
    }
    public void Cache(string key, object data)
    {
        _cacher.Cache(key, data);
    }
    // ...
}

Yukarıdaki örnekte, ILoggable ve ICacheable adlı iki ayrı arayüz bulunmaktadır. Logger sınıfı, ILoggable arayüzünü, Cacher sınıfı ise ICacheable arayüzünü uygulamaktadır. DataService sınıfı ise her iki arayüzü de kullanmaktadır. Böylece, DataService sınıfı sadece ihtiyaç duyduğu arayüzleri uygulayarak Arayüz Ayrımı Prensibini uygulamış olur.

Dependency Inversion Principle – DIP (Bağımlılık Tersine Çevirme Prensibi)

Yüksek seviyeli modüller, düşük seviyeli modüllere bağlı olmamalıdır. Her ikisi de soyutlamalara bağlı olmalıdır. Soyutlamalar ise ayrıntılara bağlı olmamalıdır.

Bağımlılık Tersine Çevirme Prensibi, kodda bağımlılıkların soyutlamalara doğru olması gerektiğini vurgular. Yüksek seviyeli modüllerin, düşük seviyeli modüllere bağımlı olmaması, her ikisinin de soyutlamalara bağlı olması gerektiğini ifade eder. Bu sayede, kodun değiştirilebilirliği ve bakımı kolaylaşır. Örneğin, bir .NET Core uygulamasında, yüksek seviyeli modüllerin soyutlamalara (arayüz veya soyut sınıflar) bağımlı olduğunu, düşük seviyeli modüllerin ise soyutlamaları uygulayan sınıflara bağımlı olduğunu sağlayarak Bağımlılık Tersine Çevirme Prensibini uyglamış oluruz.

Solid prensipleri, yazılım tasarımında kodun sürdürülebilirliğini, esnekliğini ve genişletilebilirliğini artırmak için kullanılan önemli prensiplerdir. Bu prensiplere uygun bir kod tasarımı, daha az hata, daha kolay bakım ve güncelleme imkanı, daha yüksek kod kalitesi ve daha iyi anlaşılabilirlik gibi avantajlar sağlar. Her bir prensip, yazılımın farklı yönlerini ele alarak, sağlam, esnek ve genişletilebilir bir kod tabanı oluşturmayı hedefler.

Umarım bu makalede SOLID prensiplerini anlatırken sizi sıkmadım ve kod dünyasında daha sağlam bir temel oluşturmanıza yardımcı oldum. Eğer kodunuzda “S” harfi kadar “SOLID” bir tasarım kullanıyorsanız, hatasız çalışan, bakımı kolay, güncellemeye hazır ve kod polislerinin rüyası bir uygulamanız olduğundan emin olabilirsiniz. Şimdi, tüm postacı-kapıcı-manav kombinasyonlarından uzak durarak, kodunuzu SOLID prensiplerine göre düzenlemenin keyfini çıkarın! Eğer daha fazla yazılım geliştirme ipuçları ve danışmanlık isterseniz, Arif Gökçe olarak buradayım, yazılım geliştirme danışmanı olarak size yardımcı olmaktan mutluluk duyarım.

Arif Gökçe

SOFTWARE DEVELOPMENT CONSULTANT