Tek Sorumluluk Prensibi ve Açık/Kapalı Prensibi — SOLID (Kod Örnekleriyle)

Elif Şirin
5 min readJun 4, 2021

Medium’daki ilk yazımı yazmış olmaktan mutluluk duyuyorum. Bir yazılımcı olarak, bu konu hakkındaki görüşlerimi örneklerle harmanlayarak siz yazılımcı dostlarıma sunmak çok heyecan verici! Bu konunda sizlere yardımcı olabilirsem ne mutlu bana! Yazıma başlamadan önce bu yazı fikrini bana veren Türkay Ürkmez hocama sonsuz teşekkürler ediyorum.

SOLID, Ingilizce bir kelime olarak sağlam, sert, bütün anlamlarına gelmektedir. Bu kelime, aşağıda gördüğünüz üzere prensiplerimizin baş harflerinden meydana gelmektedir.

  • S : Single Responsibility Principle (Tek Sorumluluk Prensibi)
  • O : Open/Closed principle (Açık/Kapalı Prensibi)
  • L : Liskov substitution principle (Liskov Yerine Geçme Prensibi)
  • I : Interface segregation principle (Arayüz Ayrımı Prensibi)
  • D : Dependency Inversion Principle (Bağımlılığın Ters Çevrilmesi Prensibi)

Bir kodlama yaparken kod satırlarımızın sağlam bir bütünlük içinde olmasını bekleriz. Kodlarımızın temiz, anlaşılır, sürekli bir şekilde olmasını istediğimiz noktada “SOLID prensipleri” bize yön gösterir. Bu prensipler sayesinde kaliteli kodlar yazabiliriz.

Bu yazımda Tek Sorumluluk Prensibi (Single Responsibility Princible) ve Açık/Kapalı Prensibini (Open/Closed Princible) kod örnekleriyle açıklamaya çalışacağım.

Tek Sorumluluk Prensibi ( Single Responsibility Principle )

SOLID prensiplerinin ilki olan Single Responsibility Principle (Tek Sorumluluk Prensibi) temelde her bir sınıfın tek bir sorumluluk alarak karmaşıklığı azaltarak okunabilirliği kolaylaştırmaktır.

Bir nesneyi değiştirmek için tek bir neden olmalıdır.²

Örneğin, Kafe tarzında çok rağbet gören bir işletme düşünelim. Burada çalışan bir baristamız, bir garsonumuz ve bir de yöneticimiz olsun. Garsonun görevi siparişleri servis etmektir. Baristanın görevi istenen siparişi hazırlamak . Yöneticimizin görevi ise çalışanların iş saatlerini düzenlemek olsun diyelim. Eğer biz bütün bu görevleri bir kişiye yüklersek haliyle bütün işler yarım kalır. Ya da barista aynı zamanda sipariş almaya kalksa bu sefer de siparişler gecikir. Bu yüzden her rolün kendine ait sorumlulukları vardır. Bunu koda dökecek olursak, tek bir kişi yani bir sınıfa bütün sorumlulukları yüklenmeye kalkarsa kodumuzun vay haline!

public class Calisan 
{
public string Isim { get; set; }
public string SoyIsim { get; set; }
public void SiparisHazırla()
{
//Sipariş hazırlama işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Sipariş hazırlanmıştır.");
}
public void SiparisAl()
{
//Sipariş alma işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Sipariş alınmıştır.");
}
public void SaatleriDuzenle()
{
//Saatleri düzenleme işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Çalışan iş saatleri düzenlenmiştir.");
}

Yukarıdaki kodda gördüğünüz gibi bir sınıfa bütün işleri verdik. Eğer bir çalışan hem sipariş hazırlayıp, hem sipariş alır, bir de saatleri düzenlerse bu çok makul bir çalışma olmaz. Buradaki çalışma yorucu, yetersiz ve haliyle kalitesiz bir hizmet sağlar. Bu yüzden iş bölümü yapmak gerekir. Peki bu çalışmayı Tek Sorumluluk Presibiyle (Single Responsibility Principle) nasıl yapabiliriz?

Haydi gelin bakalım bu sorumuzun cevabına bakalım :

using System;namespace Single_Responsibility
{
public class Calisan
{
public string Isim { get; set; }
public string SoyIsim { get; set; }
}
}

İlk olarak, yukarıdaki gibi bir çalışan sınıfı oluşturduk. Bu sınıfa sadece çalışan bilgilerimizi tutma görevini verdik.

using System;namespace Single_Responsibility
{

public class Barista : Calisan
{
public void SiparisHazırla(){
//Sipariş hazırlama işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Sipariş hazırlanmıştır.");
}
}

Sonra, Barista sınıfımızı oluşturduk. Görüldüğü gibi bu sınıfa sadece sipariş hazırlamak görevini verdik.

using System;namespace Single_Responsibility
{
public class Garson : Calisan
{
public void SiparisAl(){
//Sipariş alma işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Sipariş alınmıştır.");
}
}

Daha sonra, Garson sınıfımızı oluşturduk. Görüldüğü gibi bu sınıfa sadece sipariş alma görevini verdik.

using System;namespace Single_Responsibility
{
public class Yonetici : Calisan
{
public void SaatleriDuzenle(){
//Saatleri düzenleme işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Çalışan iş saatleri düzenlenmiştir.");
}
}

En sonunda, Yonetici sınıfımızı oluşturduk. Görüldüğü gibi bu sınıfa sadece çalışanların iş saatlerini yani varidyalarını düzenleme görevi verdik.

Sonuç olarak, yukarıdaki örneğimizde de görüldüğü üzere Tek Sorumluluk Prensibine(Single Responsibility Prenciple) uyarak daha okunabilir, temiz ve sağlam bir kod yazmış olduk.

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

İşte geldik SOLID prensiplerimizin O kısmını anlamaya! “O” yani Open/Closed Principle, Açık/Kapalı Prensibi diye anlamlandırabiliriz. Yazdığımız kod, geliştirilmeye AÇIK, değişime KAPALI olmalıdır. Hepimiz şunu biliyoruz ki yaptığımız bir kodlamanın uzun bir süreçte ihtiyaçlarımızı karşılaması için yeni gereksinimler eklememiz gerekiyor. Bu gereksinimleri kodumuza entegre ederken yani yeni kod parçaları eklerken mevcut kodumuzda değişiklik yapmaya ihtiyaç duymamalıyız.

Demek ki öyle bir yapı tasarlamalısınız ki, gelişmeye AÇIK fakat kaynak kodun değiştirilmesine KAPALI olmalı. Bu kavramın mimarı Bertrand Meyer şöyle demiştir : “Yazılım varlıkları (classlar, modüller, fonksiyonlar vs.) gelişime AÇIK, kod değişimine KAPALI olmalıdır.” ³

Gerçek hayattan bir senaryo çizerek bunu kodlama üzerinde daha etkili bir şekilde anlatmak istiyorum. Örneğin, yayınevleri için internet üzerinden satış yapabilecekleri bir sistem düşünelim.

Gelin şu şekilde basit bir kodlama yapalım.

using System;namespace Open_Closed_Principle
{
public class Kitap
{
public string Isim { get; set; }
public string Yazar{ get; set; }
public string turu{ get; set; }
public decimal fiyat{ get; set; }

//Satış metodumuzu da buraya yazalım
public void KitabSat{
//Kitabı satma işlemleri burada gerçekleştiriliyor...
// ...
Console.WriteLine("Kitabı satma işlem başarılı!");
}
}

Bir süre sonra müşterimiz bizden bazı kitaplarının satışını e-kitap olarak internet üzerinden satmak istediğini söyledi. Biz de OCP’ye uymayarak tekrar aşağıdaki kodu yazdık.

public enum satis{      eKitap,      normalKitap,
}

İlk önce satış diye satış şekilleri ile ilgili sabitler oluşturduk ve bunu her kitap için tanımladık.

using System;namespace Open_Closed_Principle
{
public class Kitap
{
public string Isim { get; set; }
public string Yazar{ get; set; }
public string turu{ get; set; }
public decimal fiyat{ get; set; }
public satis satisSekli{ get; set; }


//Satış metodumuzu da buraya yazalım
public void KitabSat(Kitap kitap){
//Kitabı satma işlemleri burada gerçekleştiriliyor...
// ...
if ((int)kitap.satisSekli == 1) {
//Klasik satma işlemleri yapılır. Alicının adresi alınır.
//Belirlenen kitabı satma işlemleri gerçekleştirilir.
Console.WriteLine("Kitabı satma işlem başarılı!");
}
if ((int)kitap.turu == 2) {
//E-kitap satma işlemleri yapılır.
//Belirlenen e-kitabı satma işlemleri gerçekleştirilir.
Console.WriteLine("E-Kitabı satma işlem başarılı!");
}
}
}
}

Tekrar zaman geçti ve bu sefer müşterimiz sesli kitap satışı yapmak istediğini söyledi. Bu durumda yine Kitap sınıfımıza if ekleyip, sesli kitap ise bunları yap diyebiliriz. Ancak gördüğünüz gibi mevcut kodumuzu sürekli değiştiriyoruz. Bu da kodumuzun dayanıklılığını azaltıyor ve daha sonraki gelişimleri kaldırmasını zorlaştırıyor.

Hadi Gelin OCP’ye uygun olarak kodumuzu yeniden yazalım.

using System;namespace Open_Closed_Principle
{
public class Kitap
{
public string Isim { get; set; }
public string Yazar{ get; set; }
public string turu{ get; set; }
public decimal fiyat{ get; set; }
}
}

İlk olarak, her kitabın sahip olması gereken özellikleri olduğu için bir kitap sınıfı oluşturduk.

using System;namespace Open_Closed_Principle
{
public interface ISatis
{
public void KitapSat(Kitap kitap);
}
}

İkinci olarak, ISatis adını verdiğimiz KitapSat metoduna sahip bir inteface sınıfı oluşturduk.

using System;namespace Open_Closed_Principle
{
public class EKitap : Kitap, ISatis
{
public void KitapSat(Kitap kitap){
//E-kitap satma işlemleri burada yapılır.
//Belirlenen e-kitabı satma işlemleri ve
// yükleme işlemleri burada gerçekleştirilir.
Console.WriteLine("E-Kitabı satma işlem başarılı!");
}
}
}

Sonra da yukarıdaki gibi EKitap sınıfı oluşturduk. Bu sınıf Kitap sınıfının altsınıfı (subclass) ve ISatis yeteneğine sahiptir.

using System;namespace Open_Closed_Principle
{
public class NormalKitap : Kitap, ISatis
{
public void KitapSat(Kitap kitap){
//Klasik olarak satma işlemleri burada yapılır.
//Alıcının adresi alınır vs..
//Belirlenen normal kitabın satma işlemleri gerçekleştirilir.
Console.WriteLine("Normal Kitabı satma işlem başarılı!");
}
}
}

EKitap sınıfı gibi NormalKitap sınıfıda oluşturuldu. Çünkü bu kitapların satın alma ve gönderme işlemleri farklıdır. Son olarak, SesliKitap sınıfını oluşturalım. Bu kitabında satın alma ve yükleme özellikleri farklılıklar gösterir.

using System;namespace Open_Closed_Principle
{
public class SesliKitap : Kitap, ISatis
{
public void KitapSat(Kitap kitap){
//Sesli kitabı satma ve kullanıcının adresine yükleme
// işlemleri burada gerçekleştirilir.
Console.WriteLine("Sesli Kitabı satma işlem başarılı!");
}
}
}

İşte bu şekilde Open/Closed Prensibine uygun bir kod yazmış bulunmaktayız. Bu sayede müşterimiz bizden tekrar farklı bir kitap türünü satışının gerçekleştirilmesini isterse mevcut kodumuza dokunmadan gerekli geliştirmelerimizi yapabiliriz.

Umarım yazdıklarım ufak da olsa ufkunuzu açarak kafanızdaki soru işaretlerini gidermeye yardımcı olmuştur. :)

--

--