SOLID Prensipleri

SOLID prensipleri, yazılım geliştirme sürecinde kodun anlaşılabilir, sürdürülebilir ve genişletilebilir olmasını sağlamak için kullanılan beş temel tasarım prensibidir. Bu prensipler, Robert C. Martin tarafından ortaya atılmıştır ve yazılım mühendisliğinde iyi bir tasarım ve kod kalitesi elde etmek için sıkça uygulanır.

SOLID, yazılım mühendisliğinde beş temel tasarım prensibini ifade etmek için kullanılan bir kısaltmadır. Her bir harf, bu prensipleri temsil eder. İşte SOLID'in açılımı:

  • S - Tek Sorumluluk Prensibi (Single Responsibility Principle - SRP)
  • O - Açık/Kapalı Prensibi (Open/Closed Principle - OCP)
  • L - Liskov Yerine Geçme Prensibi (Liskov Substitution Principle - LSP)
  • I - Arayüz Ayırma Prensibi (Interface Segregation Principle - ISP)
  • D - Bağımlılıkların Tersine Çevrilmesi Prensibi (Dependency Inversion Principle - DIP)

Her bir prensip, yazılım tasarımının belirli bir yönünü vurgular ve kodun daha temiz, esnek, sürdürülebilir ve genişletilebilir olmasını sağlamak için bir rehberlik sunar. SOLID prensipleri, yazılım geliştiricileri tarafından yaygın olarak benimsenen ve uygulanan tasarım prensipleridir.

Single Responsibility Principle (Tek Sorumluluk Prensibi)

Bu ilke, yazılımın daha anlaşılır, bakımı kolay ve yeniden kullanılabilir olmasını sağlamayı amaçlar. Bir bileşenin veya sınıfın birden fazla sorumluluğu olduğunda, bunların birbirinden bağımsız olarak değişebilmesi zorlaşır ve kod karmaşık hale gelir. Tek sorumluluk ilkesi, bu karmaşıklığı azaltarak daha esnek ve sürdürülebilir bir yazılım tasarımı sağlar.

Örneğin, bir kullanıcı kaydı sistemiyle ilgili bir sınıfı ele alalım. Bu sınıfın tek sorumluluğu, kullanıcı kayıtlarını oluşturmak, güncellemek veya silmek olmalıdır. Başka bir sorumluluğu, kullanıcının oturum açma işlemlerini yönetmek gibi başka bir görevi olmamalıdır. İki farklı sorumluluğu olan bir sınıf, kodun anlaşılmasını ve sınıfın kullanılmasını karmaşık hale getirebilir. Bu nedenle, tek sorumluluk ilkesine uygun bir şekilde, oturum açma işlemlerini yönetmek için ayrı bir sınıf veya bileşen kullanılması daha uygundur.

Tek sorumluluk ilkesi, yazılımın modülerlik, yeniden kullanılabilirlik ve bakım kolaylığı gibi önemli niteliklerini destekler. Bu ilkeye uyum sağlamak, daha temiz, düzenli ve esnek bir kod tabanı oluşturmayı amaçlar.

Open Closed (Açık/Kapalı İlkesi)

Açık/Kapalı İlkesi, yazılım tasarımı ve geliştirme sürecinde önemli bir prensiptir. Bu ilke, birimler arasındaki iletişimin mümkün olduğunca açık ve anlaşılır olması gerektiğini ifade eder. Açık/Kapalı İlkesi, SOLID prensiplerinden biridir ve genel olarak nesne yönelimli programlama (OOP) prensiplerine uygulanır.

Bu ilkenin temel fikri, mevcut birimlerin (sınıfların, modüllerin, bileşenlerin vb.) değiştirilmeden yeni işlevselliğin eklenmesi veya mevcut işlevselliğin değiştirilmesi gerektiğinde, kodun kapalı olmasını ve değiştirilmemesini sağlamaktır. Yani, bir birim (sınıf veya modül) bir kez oluşturulduğunda, bu birim değiştirilmemeli veya kırılmamalıdır.

Açık/Kapalı İlkesi'nin amacı, kodun yeniden kullanılabilirliğini ve sürdürülebilirliğini artırmaktır. Birimlerin değiştirilmeden kalması, kodun diğer birimler tarafından kullanılmasını engellemez ve yeni işlevselliğin eklenmesi, var olan kodu bozmadan gerçekleştirilebilir.

Bu ilkeyi uygulamak için genellikle soyutlama ve miras (inheritance) kullanılır. Soyutlama, birimlerin soyut bir arayüzle birbirine bağlanmasını sağlar ve bu arayüz üzerinden iletişim kurulmasını sağlar. Miras ise var olan bir birimi genişletmek veya değiştirmek için kullanılır, ancak mevcut kodu değiştirmeden yeni işlevler eklemeyi mümkün kılar.

Açık/Kapalı İlkesi, yazılım geliştirme sürecinde kodun daha esnek, değiştirilebilir ve genişletilebilir olmasını sağlar. Bu sayede, yazılımın bakımı ve ilerideki değişikliklere adapte edilmesi daha kolay hale gelir.

Liskov Substitution Principle (Liskov Yerine Geçme Prensibi)

Liskov Yerine Geçme Prensibi, bir yazılımın alt sınıflarının, üst sınıflarının yerine kullanılabilmesi gerektiğini ifade eder. Yani, bir üst sınıfın nesnesinin, alt sınıfın nesnesiyle değiştirildiğinde, sistemde herhangi bir hata, tutarsızlık veya beklenmeyen davranış olmamalıdır. Alt sınıf, üst sınıfın davranışlarını korumalı ve bu davranışları genişletebilir, ancak onları değiştirmemelidir. Bu, programın esnekliğini ve modülerliğini artırır ve kodun yeniden kullanılabilirliğini sağlar.

Liskov Yerine Geçme Prensibi, nesne yönelimli programlama (OOP) ve özellikle miras (inheritance) konseptiyle ilişkilidir. Alt sınıfların, üst sınıflarının arayüzünü ve davranışını korumaları, kodun daha iyi sürdürülebilir ve genişletilebilir olmasını sağlar. Bu prensip, yazılım projelerinde daha sağlam ve tutarlı kod oluşturmayı hedefler.

using System;

 public class Sekil

{

    public virtual void Ciz()

    {

        Console.WriteLine("Bir şekil çizildi.");

    }

}

 

public class Daire : Sekil

{

    public override void Ciz()

    {

        Console.WriteLine("Bir daire çizildi.");

    }

}

 

public class Kare : Sekil

{

    public override void Ciz()

    {

        Console.WriteLine("Bir kare çizildi.");

    }

}

 

public class Program

{

    public static void AnaFonksiyon(Sekil sekil)

    {

        sekil.Ciz();

    }

 

    public static void Main(string[] args)

    {

        Sekil sekil1 = new Daire();

        Sekil sekil2 = new Kare();

 

        AnaFonksiyon(sekil1);

        AnaFonksiyon(sekil2);

    }

}

Yukarıdaki örnekte, Sekil sınıfı bir üst sınıf olarak tanımlanmıştır ve Ciz() adında bir sanal (virtual) metodu bulunmaktadır. Bu metot, "Bir şekil çizildi." şeklinde bir çıktı basar.

Daire sınıfı, Sekil sınıfından türetilmiştir ve Ciz() metodu üzerine yazılmıştır. Bu şekilde, Daire sınıfı, üst sınıfın davranışını koruyarak kendi davranışını tanımlar.

Kare sınıfı da Sekil sınıfından türetilmiştir ve yine Ciz() metodu üzerine yazılmıştır.

AnaFonksiyon adlı bir metot, Sekil tipinde bir parametre alır ve bu parametrenin Ciz() metodunu çağırır. Bu sayede, AnaFonksiyon metodu, hem Daire nesnesini hem de Kare nesnesini kabul edebilir.

Main metodu içinde, Daire ve Kare nesneleri Sekil tipine atanarak AnaFonksiyon metoduyla çağrılır. Bu şekilde, Sekil tipindeki bir referans üzerinden alt sınıfların metotları çağrılabilir ve beklenen davranışlar sergilenir.

Bu örnekte Liskov Yerine Geçme Prensibi uygulanmıştır çünkü Daire ve Kare sınıfları, Sekil sınıfının yerine kullanıldığında beklenen davranışı sergilemektedir.

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

SOLID prensipleri, yazılım tasarımındaki iyi uygulamaları ve ilkelere dayanır. "I" SOLID prensiplerinin beşinci ilkesini ifade eder ve Interface Segregation Principle (Arayüz Ayrımı Prensibi) olarak adlandırılır.

Interface Segregation Principle (ISP), bir arayüzün mümkün olduğunca özelleştirilmiş ve bağımsız olması gerektiğini belirtir. Bu prensip, bir sınıfın ihtiyaç duymadığı metotları uygulamaktan kaçınarak, sınıfların fazla bağımlı olmadığı ve gereksiz karmaşıklığı önlediği bir tasarımı teşvik eder.

ISP'ye göre, bir arayüz, kullanıcıları için yeterli ve anlamlı bir işlevselliği sunmalıdır. Bir sınıfın kullanmadığı veya ihtiyaç duymadığı metotları içeren geniş arayüzler, sınıfların gereksiz yükümlülüklerle uğraşmasına neden olabilir. Bu durumda, sınıfların gereksinimlerini yansıtan daha özelleştirilmiş arayüzlerin tanımlanması daha uygun olur.

Bu prensip, aşırı bağımlılığı önler ve sınıfların bağımlılıklarını azaltır. Ayrıca, değişikliklerin sınırlı bir etki alanına sahip olmasını sağlar, çünkü her bir sınıfın kendi spesifik ihtiyaçlarına yönelik bir arayüzle bağlantılı olduğu durumlarda, değişiklikler sadece ilgili sınıfları etkiler.

ISP'nin bir örneğini şu şekilde düşünebiliriz:

public interface IWorker

{

    void Work();

    void Eat();

}

 

public class Manager : IWorker

{

    public void Work()

    {

        // Yönetici için çalışma işlemleri

    }

 

    public void Eat()

    {

        // Yönetici için yemek işlemleri

    }

}

 

public class Developer : IWorker

{

    public void Work()

    {

        // Geliştirici için çalışma işlemleri

    }

 

    public void Eat()

    {

        // Geliştirici için yemek işlemleri

    }

}

 

public class Robot : IWorker

{

    public void Work()

    {

        // Robot için çalışma işlemleri

    }

 

    public void Eat()

    {

        // Robot yemek yemez, bu metot boş bırakılır

        throw new NotImplementedException();

    }

}

"Robot" sınıfı için "Eat" metodu gereksizdir çünkü robotlar yemek yemez. Bu durumda, ISP'yi uygulamak için "IWorker" arayüzü üzerinde bir düzenleme yapabiliriz. İşte bu senaryoda ISP'yi uygulayan bir çözüm:

public interface IWorker

{

    void Work();

}

 

public interface IEater

{

    void Eat();

}

 

public class Manager : IWorker, IEater

{

    public void Work()

    {

        // Yönetici için çalışma işlemleri

    }

 

    public void Eat()

    {

        // Yönetici için yemek işlemleri

    }

}

 

public class Developer : IWorker, IEater

{

    public void Work()

    {

        // Geliştirici için çalışma işlemleri

    }

 

    public void Eat()

    {

        // Geliştirici için yemek işlemleri

    }

}

 

public class Robot : IWorker

{

    public void Work()

    {

        // Robot için çalışma işlemleri

    }

}

Yukarıdaki örnekte, "IWorker" arayüzü yalnızca "Work" metodu içermektedir. Ayrıca, "IEater" adında yeni bir arayüz tanımlanmıştır ve bu arayüz sadece "Eat" metodunu içermektedir.

Böylece, "Manager" ve "Developer" sınıfları hem "IWorker" hem de "IEater" arayüzlerini uygular. Ancak, "Robot" sınıfı sadece "IWorker" arayüzünü uygular çünkü robotlar yemek yemez.

Bu sayede, her sınıf kendi ihtiyaçlarına göre uygun arayüzleri uygulayabilir ve gereksiz metotları içermeyen daha özelleştirilmiş arayüzlerle çalışabilir.

ISP'nin amacı, bağımlılıkları minimize etmek ve daha modüler bir tasarım elde etmektir. Sınıfların ihtiyaç duymadığı metotları uygulamaktan kaçınarak, gereksiz karmaşıklığı ve bağımlılıkları ortadan kaldırır ve kodun daha temiz, esnek ve bakımı kolay olmasını sağlar.

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

Dependency Inversion Principle (DIP), yüksek seviyeli modüllerin düşük seviyeli modüllere doğrudan bağımlı olmamasını, bağımlılıkların soyutlamalar üzerinden yapılmasını önerir. Bu prensip, daha gevşek bir bağımlılık ilişkisi oluşturarak yazılımın esnekliğini ve genişletilebilirliğini artırır.

DIP'nin temel fikri, bir modülün, ona bağımlı olan diğer modülleri somut sınıflara bağımlı olmaktan kurtarmaktır. Bunun yerine, modüller arasındaki bağımlılıklar soyutlama yoluyla gerçekleştirilir. Yani, yüksek seviyeli bir modül, bir düşük seviyeli modülün somut sınıfına bağımlı olmak yerine, bir arayüz veya soyut sınıf üzerinden iletişim kurar.

Bu prensip sayesinde, yazılımın bileşenleri birbirinden daha bağımsız hale gelir ve değişikliklerin bir modül üzerindeki etkisi diğer modülleri etkilemez. Ayrıca, farklı implementasyonlar kullanma esnekliği sağlanır, çünkü yüksek seviyeli modüller, düşük seviyeli modüllerin iç detaylarına doğrudan erişmek yerine, soyutlama üzerinden iletişim kurarlar.

DIP, daha esnek, genişletilebilir ve bakımı kolay bir yazılım tasarlamak için modül arasındaki bağımlılıkları soyutlama üzerinden yönetmeyi önerir. Bu prensip, kodun daha anlaşılır, yeniden kullanılabilir ve test edilebilir olmasını sağlar.

DIP'ye göre, yazılımda sınıflar ve modüller arasındaki bağımlılıkların soyutlamalar üzerinden olması gerekmektedir. Bu, bir sınıfın veya modülün somut bir sınıfa veya modüle doğrudan bağımlı olmaması, bunun yerine bir arayüz veya soyutlama üzerinden bağımlılık kurması anlamına gelir. Böylece, bağımlılıkların somut uygulamalardan bağımsız olması sağlanır.

Bağımlılık enjeksiyonu, bir bileşene dışarıdan gerekli bağımlılıkların sağlanmasını ve bu bağımlılıkların bileşen tarafından kullanılmasını sağlar. Bu sayede, bileşenin hangi somut uygulamaya bağımlı olduğu, bileşenin kendisi tarafından değil, dışarıdan yönetilir.

Bağımlılık enjeksiyonu için genellikle üç farklı yöntem kullanılır:

Constructor Injection (Yapıcı Metot Enjeksiyonu): Bağımlılıklar, bileşenin yapıcı metodu aracılığıyla sağlanır. Bileşenin bir veya daha fazla parametreli bir yapıcı metodu vardır ve bağımlılıklar bu parametreler aracılığıyla enjekte edilir.

public class MyComponent

{

    private readonly IDependency _dependency;

     public MyComponent(IDependency dependency)

    {

        _dependency = dependency;

    }

     // ...

}

Property Injection (Özellik Enjeksiyonu): Bağımlılıklar, bileşenin özelliklerine (property) atanarak sağlanır. Bileşenin ilgili özellikleri, bağımlılıkların tipini temsil eden arayüz veya soyutlamalarla işaretlenir ve bu özelliklere bağımlılıklar atanır.

public class MyComponent

{

    public IDependency Dependency { get; set; }

     // ...

}

Method Injection (Metot Enjeksiyonu): Bağımlılıklar, bileşenin bir metodu aracılığıyla sağlanır. Bağımlılıkları alan bir metot tanımlanır ve bu metot bağımlılıkları enjekte eder.

public class MyComponent

{

    public void SetDependency(IDependency dependency)

    {

        // ...

    }

     // ...

}

Bağımlılık enjeksiyonu, bileşenler arasındaki bağımlılıkları azaltır, bileşenlerin daha test edilebilir ve yeniden kullanılabilir olmasını sağlar. Ayrıca, uygulama düzeyinde bağımlılıkların yönetimi daha esnek hale gelir ve bileşenlerin somut uygulamalara olan bağımlılıkları daha azdır. Böylece, SOLID prensiplerine uygun bir yazılım tasarımı elde edilmiş olur.

Telefon +90 505 747 42 84
Email info@devedijital.com
Adres
Tacettin Veli Mahallesi Halit Narin Caddesi Bahadır Plaza Kat:11 Daire:41 38230 Deve Dijital Melikgazi/Kayseri/Türkiye