Merhabalar, bu yazıda sizlere yazılım geliştiricileri olarak bilmemiz ve mümkün olduğunca uygulamamız gereken bir prensipler dizisinden bahsedeceğim.

S.O.L.I.D prensipleri geliştirdiğimiz yazılımlarda kodun daha kaliteli, daha okunaklı, daha performanslı olmasını amaçlayan prensiplerdir. Şimdi bu 5 prensipin neler olduğuna birlikte bakalım.

1. S -> Single Responsibility Principle (Tekil Sorumluluk Prensibi) : Single Responsibility Principle yani Tek Sorumluluk Prensibi. Adından da anlayacağınız gibi bu prensip belirli bir parça kodun sadece bir işten sorumlu olmasını şart koşar. Bu isterseniz sınıf olur, isterseniz interface ve onun implementasyonu olur isterseniz de method olur. Şimdi https://www.intertech.com/Blog/the-single-responsibility-principle-with-c-examples/ adresindeki şu örneğe bir bakalım:

public interface IMessage
    {
        IList<String> ToAddresses { get; set; }
        string MessageBody { get; set; }
        bool Send();
    }

    public interface IEmailMessage:IMessage
    {
        string Subject { get; set; }
        IList<String> BccAddresses { get; set; }
    }

    public class SmtpMessage : IEmailMessage
    {
        public IList<String> ToAddresses { get; set; }
        public IList<String> BccAddresses { get; set; }
        public string MessageBody { get; set; }
        public string Subject { get; set; }
        public bool Send()
        {
            //Do the real work here
            return true;
        }
    }

    public class SmsMessage : IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public string MessageBody { get; set; }
        public bool Send()
        {
            //Do the real work here
            return true;
        }
    }

Yukarıdaki kod bloğu içerisinde bir kullanıcıya gönderilecek mesaj için temel bir interface olarak IMessage interface’i oluşturulmuş ve e-posta mesajları için de IMessage’ı kalıtan IEmailMessage interface’i oluşturulmuş. SmtpMessage ve SmsMessage sınıfları da ilgili interface’leri implement ederek işlemleri gerçekleştiriyor. Fakat yukarıdaki kodda IMessage interface’i içerisinde mailin gönderilmesi işleminden sorumlu olacak send() metodunu da implement edilmek üzere barındırıyor. Fakat IMessage ya da IMailMessage interface’lerini implement eden sınıflar mecburi olarak send() metodunu da implement ediyor. Bu durumda bir sınıfın sadece kendi işi ile sorumlu olma durumu ortadan kalkıyor çünkü mailin gönderilmesi olayı mesaj nesnesi ve mesajı sınıfları ile ilgili bir olay değil. send() metodunu gönderim işleminden sorumlu bir interface içerisinde tanımlayıp kullanmak tekil sorumluluk prensibine uygun bir yaklaşım olacaktır. Aşağıdaki kod prensibe uygun haldeki koddur:

    public interface IMessageServer
    {
        bool Send(IMessage message);
    }

    public interface IMessage
    {
        IList<String> ToAddresses { get; set; }
        string MessageBody { get; set; }
    }

    public interface IEmailMessage : IMessage
    {
        string Subject { get; set; }
        IList<String> BccAddresses { get; set; }
    }

    public class SmtpMessage : IEmailMessage
    {
        public IList<String> ToAddresses { get; set; }
        public IList<String> BccAddresses { get; set; }
        public string MessageBody { get; set; }
        public string Subject { get; set; }
    }

    public class SmsMessage : IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public string MessageBody { get; set; }
    }

    public class SmsMessageServer:IMessageServer
    {
        public bool Send(IMessage message)
        {
            //Do the real work here
            return true;
        }
    }

    public class SmtpMessageServer : IMessageServer
    {
        public bool Send(IMessage message)
        {
            //Do the real work here
            return true;
        }
    }

IMessageServer interface’i ve onun içerisinde send() metodu tanımlı ve SmsMessageServer ile SmtpMessageServer sınıfları IMessageServer interface’ini implement ederek işlerini görüyorlar. Böylece herkes kendi sorumluluğundaki işi yapıyor.

2. O -> Open/Closed (Açık/Kapalı) Prensibi: Bu presip bile öğütlenen davranış kodun değişime kapalı fakat geliştirmeye açık olmasıdır. Bu prensip bir bakımdan da kodun if-else’ler, switch-case’ler ya da aynı işi yapan farklı isimdeki metodlar ile dolmasını engellemek amacı ile öğütlenmiştir. Bu prensibi uygulamanın en güzel yolu kodunuzu interface ya da abstract class’lar üzerine inşa etmektir. http://www.gencayyildiz.com/blog/acik-kapali-prensibiopen-closed-principle-ocp/ adresindeki güzel bir örneği burada paylaşmak istiyorum.

class AIslemi
{
    public bool Islem() { Console.WriteLine("AIslemi"); return true; }
}
class BIslemi
{
    public bool Islem() { Console.WriteLine("BIslemi"); return true; }
}
class CIslemi
{
    public bool Islem() { Console.WriteLine("CIslemi"); return true; }
}
enum IslemTipi
{
    AIslemi,
    BIslemi,
    CIslemi
}
 
class IslemYapici
{
    AIslemi aIslemi;
    BIslemi bIslemi;
    CIslemi cIslemi;
 
    public IslemYapici()
    {
        aIslemi = new AIslemi();
        bIslemi = new BIslemi();
        cIslemi = new CIslemi();
    }
 
    public void IslemiYap(IslemTipi islemTipi)
    {
        switch (islemTipi)
        {
            case IslemTipi.AIslemi:
                aIslemi.Islem();
                break;
            case IslemTipi.BIslemi:
                bIslemi.Islem();
                break;
            case IslemTipi.CIslemi:
                cIslemi.Islem();
                break;
        }
    }
}

Yukarıdaki kodda AIslemi,BIslemi ve CIslemi sınıfları içerilerinde Islem() adında bir metot barındırıyorlar. IslemYapici sınıfı içerisindeki IslemiYap metodu da gelen parametre ile IslemTipi enum’ındaki değerlere göre Switch Case ile Islem() metodunu çağırmakta. Peki bu kodda sıkıntı ne? Diyelim ki sizden DIslemi diye bir işlemi daha koda eklemenizi istediler. Bu durumda ne yapmanız geekiyor? Önce DIslemi diye class oluşturursunuz (bu şart), sonra IslemTipi enum’ına eklersiniz(gereksiz), sonra da IslemiYap metoduna bir case daha eklersiniz (gereksiz). Böylece kodunuzda 3 adet değişiklik yapmanız gerekti fakat prensip bize kodun gereksiz değişimlere kapalı olması gerektiğini söylüyor. Şimdi bu kod prensibe uygun nasıl yazılır ona bakalım:

interface Islem
{
    bool Islem();
}
class AIslemi : Islem
{
    public bool Islem() { Console.WriteLine("AIslemi"); return true; }
}
class BIslemi : Islem
{
    public bool Islem() { Console.WriteLine("BIslemi"); return true; }
}
class CIslemi : Islem
{
    public bool Islem() { Console.WriteLine("CIslemi"); return true; }
}
class IslemYapici
{
    Islem islem;
 
    public IslemYapici(Islem islem)
    {
        this.islem = islem;
    }
 
    public void IslemiYap()
    {
        islem.Islem();
    }
}

Yukarıdaki kodda ise Islem adında bir interface tanımlı ve içerisinde Islem() adında bir interface tanımlı. AIslemi,BIslemi ve CIslemi sınıfları da Islem interface’ini implement ediyor. IslemYapici sınıfımız da Islem interface tipinden bir nesne tutup constructor’ı ile bu nesneyi doldurup IslemiYap() metodu ile işini tamamlıyor. Böylece DIslemi’nin koda eklenmesi gerektiği zaman DIslemi sınıfını Islem interface’inden implement ederek oluşturursak kullanıma hazır hale getirmiş olacağız. Direk iş yapan kodumuz içerisinde değişiklik yapmamış ve kodu geliştirmeye açık tutmuş olduk.

3. L -> Liskov’un Yerine Geçme Prensibi(Liskov Substitution Principle – LSP) : L prensibine göre eğer siz bir türden başka bir tür türetiyorsanız, üst türdeki şeylerin tamamını alt türde de kullanıyor olmanız gerekmekte. Böylece nesneler yer değiştirdiğinde yani birbirleri yerine kullanılmaya çalışıldığında da bir hata alınmamalı. Örneğin sizin KitapMetotları adında bir interface’iniz var ve içerisinde getElektonikId diye bir metodu var. Siz bu Kitap interface’ini implement ederek bir ElektronikKitap ve bir de BasılıKitap adında 2 sınıf türetiyorsunuz. ElektronikKitap için her zaman getElektronikId metodu bir değer dönebilecekken BasılıKitap için bu değer zaman boş dönülecektir. Bu durumda da biz ElektronikKitap ve BasılıKitap nesnelerini birbiri yerine kullanmayız. Bu sefer de kod içerisinde bir if ile BasılıKitap değil ise getElektronikId metodunu kullanmaya çalışırız ve bu durumda da Açık/Kapalı prensibini ihlal etmiş oluruz. Kodu prensiplere uygun yazmak için KitapMetotları interface’inin ElektronikKitapMetotları ve BasılıKitapMetotları adında iki interface’e bölünmesi, getElektronikId metodunun da sadece ElektronikKitapMetotları interface’inde tanımlanması gerekmektedir.

4. I -> Interface Segregation Principle (Arayüz Ayrımı) : Aslında bu maddeyi L maddesini örneklerken biraz açıklamış oldum. Arayüz Ayrımı prensibine göre sizin oluşturacağınız interface’ler gereğinden fazla metotlar tutmamalılar. Yani sınıflarımız kullanmayacakları metotları implement etmeye zorlanmamalıdır. Yukarıdaki örnekten devam edersek, BasılıKitap içindeki interface implementation’ından dolayı zorla ezdiğimiz ve sürekli boş değer dönecek olan getElektronikId metodu Dummy Code olacaktır ve I prensibine aykırı kod yazmış olacağız. Kodu I prensibine uygun hale getirmek için KitapMetotları Interface’ini ElektronikKitapMetotları ve BasılıKitapMetotları adında iki interface’e bölmek gerekecektir ve getElektronikId’yi sadece ElektronikKitapMetotları interface’inde tanımlamak gerekecektir. Böylece iki sınıfı iki interface’den implement ederek gereksiz override’ın önüne geçmi oluruz.

5. D -> Dependency Inversion Principle (Bağımlılık Değişimi) : Bu prensibe göre de bir sınıf diğer bir sınıfa doğrudan bağımlı olmamalıdır. Aralarındaki bağ soyut bir kavram üzerinden sağlanmalıdır. Bu soyut kavram interface de olabilir abstract class da olabilir.

Örneğin bir bir PDFBook sınıfımız olsun:

package com.ilkaygune.solid;

public class PDFBook {
	public String read() {
		return "Reading a PDF Book!";
	}
}

Şöyle de bir EBookReader sınıfımız olsun:

package com.ilkaygune.solid;

public class EBookReader {
	protected PDFBook book;

	public EBookReader(PDFBook book) {
		this.book = book;
	}

	public void read() {
		System.out.println(book.read());
	}
}

EBookReader sınıfımızın yapılandırıcısı bir PDFBook nesnesi alıyor ve sınıf içerisindeki nesne de PDFBook türünden. Bu durumda EBookReader sınıfı PDFBook sınıfına hiçbir soyut kavram olmadan doğrudan bağlanmış oluyor. Bu durum D prensibinin ihlal edilmesi demektir. Şimdi bu hatayı nasıl düzeltebileceğimize bakalım şimdi de.

Öncelikle bir EBook bir interface’i oluştururuz:

package com.ilkaygune.solid;

public interface EBook {
	public String read();
}

Akabinde PDFBook’u EBook’dan implement ederiz:

package com.ilkaygune.solid;

public class PDFBook implements EBook {

	@Override
	public String read() {
		return "Reading a PDF Book!";
	}
}

Son olarak da EBookReader sınıfı içindeki PDFBook’a olan doğrudan bağlantıyı kaldırıp soyut kavram olan EBook üzerinden bağlantı kurdururuz:

package com.ilkaygune.solid;

public class EBookReader {
	protected EBook book;

	public EBookReader(EBook book) {
		this.book = book;
	}

	public void read() {
		System.out.println(book.read());
	}
}

Böylece D kuralını da kodumuz için işletmiş oluruz.

Bu yazıda anlatacaklarım bu kadar arkadaşlar, SOLID prensiplerine değinmiş olduk. Yazı içerisinde kullandığım kaynakları aşağıda listeliyorum, dileyen arkadaşlar o yazıları da okuyabilir.

Başka yazıda görüşene kadar sağlıcakla kalın.

Selam ve Sevgilerimle

  1. https://medium.com/@techmostal/solid-yaz%C4%B1l%C4%B1m-geli%C5%9Ftirme-prensipleri-86a236f6e961
  2. http://www.ethemkizil.com/2017/03/16/nedir-bu-solid-prensipleri/
  3. http://selahattinunlu.com/seriler/solid-prensipleri/
  4. http://www.canertosuner.com/post/solid-prensipleri
  5. http://www.gokhan-gokalp.com/solid-nedir-ve-single-responsibility-principle-srp/
  6. http://www.gencayyildiz.com/blog/tag/solid-prensipleri/
  7. https://mertmtn.blogspot.com/2018/03/solid-prensipleri-nedir.html
  8. https://www.intertech.com/Blog/the-single-responsibility-principle-with-c-examples/