Caner Tosuner

Leave your code better than you found it

Single Responsibility Principle

Daha önceki SOLID nedir yazısında bahsettiğim üzre sırasıyla SOLID prensiplerini örneklerle anlatmaya başlıyoruz. Bu yazıda SOLID'in S'si olan ilk prensip Single Responsibility 'den bahsedeceğim ve daha önce çalıştığım bir şirkette karşılaştığım Single Responsibility ile ilgili bir olaydan da bahsetmek istiyorum.

Bir gün şirkette bir bankacılık projesinde çalışırken Mobil tarafta geliştirme yapıyordum ve projedeki modüllerden birinin bitirilmesine 4-5 gün kalmıştı ancak WebService tarafı yetişemiyordu. WebService tarafı .Net ile geliştiriliyordu ve full-stack .Net'liğin vermiş olduğu sorumlulukla project manager'ımız benden 2-3 gün service tarafına destekte bulunmamı rica etti bende ok deyip tfs'ten projeyi çektim ve projeyi ayağa kaldırdıktan sonra ufaktan incelemeye başladım ve solution'da bulunan katmanlardan biri olan  "....Common" isimli projeyi açtım. Her projede olmazsa olmaz "CommonFunctions.cs" isimli class'ı açmamla kapatmam bir oldu. Class'ın tek suçu isminin başında "Common" olması ama o class'ı yazanlar sağ olsunlar aşağıya doğru scroll ederken VS resmen dondu. Class'ın içerisinde yanılmıyorsam 5 bin satıra yakın kod vardı.

Aslında böyle olmasının bence temel sebebi proje yaklaşık 3 yıllık bir projeydi ve her gelen tam anlamıyla projeye hakim olmadan geliştirmeye başlamış olsa gerek her şeyi bu class'a yazmışlar ve ortaya OCOP - One Class Oriented Programming (benim uydurmam) çıkmış. Bazı hesaplama metodları var, Resource manager metodları var, string format metodları var, obejct mapping metodları var... var oğlu var. İsminin başında "Common" olmasından kaynaklanan yetkiyle her şey bu class'ta.

Single Responsibility prensibi bu ve benzeri durumlar için var diyebiliriz. 

Tek bir soru ve cevap ile ;

  • Single Responsibility (tek sorumluluk) nedir ?
  • Her class'ın tek bir sorumluluk alanı olup  tek bir amaca hizmet etmesidir. 

şeklinde tanımlayabiliriz. Peki ne demek bu "Her class'ın tek bir sorumluluk alanı olup tek bir amaca hizmet etmesidir." cümlesi. 

Yazılmış olan her bir class kendi işinden sorumlu olup başka class'ların yapması gereken işlere karışmaması veya kendi sorumluluk alanına çıkmaması istenir. Çok küçük bir örnekle, toplama işlemlerini yapması beklenen class gidip de çıkartma işlemlerine yapmasın, çıkartma işlemlerini yapan başka bir class yazılır ve orda yapılması beklenir.

Daha güzel ve oop odaklı olan bir örnek üzerinden gidelim. Bir tane banka için proje geliştiriyor olalım (üstte bahsettiğim gibi değil tabi :) ) ve BankAccount adında bir class ve bu class içerisinde belli hesaplamalar sonucu tutulan field'lar bulunsun.

public class BankAccount
{
    public int AccountNumber { get; set; }
    public int AccountBalance { get; set; }
 
    public void SaveData()
    {
        //kayıt işlemlerini yapan metod
    }
}

Yukarıda ki class'a ilk baktığımızda sorunsuz güzel bir class gibi duruyor ancak SRP(Single Responsibility Principle) açısından baktığımızda SRP'yi ihlal ediyor. BankAccount class'ı içerisinde bulundurduğu methodlardan dolayı data management-calculations işlerinede karışmış durumda. Yani bu class içerisinde field'ları olan birbir obje görevimi görecek yoksa barındırdığı metodlar itibariyle bir nevi helper class'ımı olacak. Bu gibi sebeplerden dolayı class'ın aldığı sorumluluklar değişkenlik göstermektedir ve bu sorumlulukların bir arada karışıklıklara sebebiyet verebilir.

Peki ne yapacağız bu durum için ? Eğer BankAccount objemiz diğer bankalardaki hesaplar için de kullanılacağını varsayalım ve böyle bir durumda bir interface veya abstrack class oluşturmak en güzel çözüm olacaktır. 

public interface IBankAccount
{
    int AccountNumber { get; set; }
    int AccountBalance { get; set; }
}

 

Şimdi IBankAccount interface'inden implement olan BankAccount class'ını oluşturabiliriz.

public class CTBank : IBankAccount
{
    int _accountNumber;
    int _accountBalance;
 
    // IBankAccount Members
     public int AccountNumber
    {
        get
        {
            return _accountNumber;
        }
        set
        {
            _accountNumber = value;
        }
    }
 
    public int AccountBalance
    {
        get
        {
            return _accountBalance;
        }
        set
        {
            _accountBalance = value;
        }
    }
}

 

Kolay ve daha oop olacak şekilde class'ımızı oluşturduk. Şimdi sırada en başta BankAccount class'ında yazdğımız SaveData metodu için bir DataAccessLayer yazmak var. Bunun için ilk olarak yine bir interface yazalım ismi IDataService olsun.

public interface IDataService
{
    bool Save(IBankAccount account);
}

 

Bu noktaya kadar ne yaptık ? 

  1. BankAccount objesinde bulunan field'ları barındıran bir IBankAccount interface'i yazdık
  2. Bu interface'den implement olan BankAccount objesini tanımladık,
  3. DataAccess class'ı için IDataService adında bir interface tanımaldık,

Bu noktadan sonra geriye sadece DataService class'ını yazmak kalıyor. Bu class tahmin ettiğiniz gibi IDataService interfce'ini implemente edecek.

public class DataService : IDataService
{
    //IDataService Members
    public bool Save(IBankAccount account)
    {
        bool isSaved = false;
        try
        {
            //save data here 
            isSaved = true;
        }
        catch (Exception)
        {
            // error
            // log exception data
            isSaved = false;
        }
        return isSaved;
    }
}

 

Görüldüğü üzre projemiz ilk başta yazdığımız BankAccount class'ına kıyasla artık daha object-oriented ve daha yönetilip genişletilebilir hale gelmiş oldu. Tabi ki istenilen feature'a göre değişiklik gösterebilir ancak proje release olduktan sonra ki değişiklik isteklerine daha kolay ve bu değişiklerin daha hızlı entegre edilebilmesini sağlar hale geldi diyebiliriz.

Yorumlar (2) -

  • Koray

    30.10.2016 00:05:38 | Yanıtla

    Öncelikle bu bilgilendirici yazı için teşekkürler hocam. Solid prensiplerini kavramaya çalışan biri olarak bir soru sormak istiyorum. Örnek için IDataService interfaceinde tek bir method koymuşsunuz. Gerçek bir projedede tek bir method için interface açmak doğrumu yoksa örnek anlaşılır olsun diye tek bir methodmu koydunuz ? İnterface kullanımı hakkında bilgim var ama ne zaman kullanmam gerektiği konusunda kararsızlık yaşadığım çok oluyor. Tek bir method için interface kullanmak doğrumudur ? Teşekkürler.

    • caner

      30.10.2016 18:47:51 | Yanıtla

      Merhaba, interface içerisinde tanımlı tek bir metot olması tamamiyle örnek anlaşılır olsun diye. Seninde belirttiğin gibi tek bir metot için interface tanımlamak doğru değildir ama hani öyle bir an gelir ki mecbur kalırsın ve tek bir metodu bulunan bir interface tanımlamak zorunda da kalabilirsin Smile Ne zaman interface kullanılmalı sorusuna gelecek olursak; belirli kurallara uymasını istediğin nesnelerin var ve sadece tanımladığın işlemleri yapabilsin istiyorsan interface kullanabilirsin. Örneğin; bir futbol oyunu geliştiriyorsun ve hakemler oyun sırasında hangi işlemleri yapabilir ? "MaciBaslat", "MaciBitir", "DudukCal", vs... gibi işlemleri yapabilir.Peki hakem penaltı kurtarabilir mi ? penaltı atabilir mi ? Şut çekebilir mi ? Hayır, ve oyun sırasında yapmamasını bekleriz çünkü bunlar oyuncuların yapmasını istediğimiz işlemler. Bir şekilde hakem nesnesini tanımlarken yapabileceği işlemleri belirten bir contract & interface tanımlanmalı ve hareketleri sınırlanmalı. Bunu IHakem diye bir interface tanımayıp yukarıda belirttiğimiz metotları bu interface içerisine tanımlayıp Hakem.cs class'ının bu interface'den inherit olup metotları implement edildiğinde edildiğinde içleri doldurulup oyun içinde nasıl hareket edecekleri söylenebilir. Oyunculardan yapmasını beklediğimiz hareketler içinde yine IOyuncu adında bir interface tanımlayıp hareketlerini belirtebiliriz ve böylelikle uygulama içerisindeki nesnelerin kurallarını interface metotları ile belirtebiliriz.  
      Umarım bir kaç satırla da olsa anlatabilmişimdir, tekrar sorun olursa da her zaman yazabilirsin Smile

Pingbacks and trackbacks (1)+

Yorum ekle

Loading