Physical Address

304 North Cardinal St.
Dorchester Center, MA 02124

Yazılım Tasarım Prensipleri

SOLID, yazılım tasarımında beş ana prensibi ifade eder. Single Responsibility prensibi, sınıfların yalnızca bir görevi olması gerektiğini; Open/Closed prensibi, sınıfların genişlemeye açık ancak değişikliğe kapalı olması gerektiğini; Liskov Substitution prensibi, türetilmiş sınıfların taban sınıflarının yerine geçebilir olması gerektiğini; Interface Segregation, bir sınıfın kullanmadığı arayüzleri uygulamaması gerektiğini; Dependency Inversion, yüksek seviyeli modüllerin düşük seviyeli modüllere bağımlı olmamaları gerektiğini belirtir. Bu prensipler yazılımı daha esnek, ölçeklenebilir ve yönetilebilir kılar.
Prensip Nedir? Yazılım Prensibi Ne Demektir?

Yazılım geliştirme sürecinde, kod kalitesini artırmak, sürdürülebilirliği sağlamak ve uzun vadede yazılımın yönetimini kolaylaştırmak için belirli prensiplere uyulması kritik öneme sahiptir. Bu prensipler, yazılım süreçlerindeki davranışlarımızı ve kararlarımızı yönlendiren evrensel ilkeler olarak düşünülebilir. Yazılım prensiplerinin anlaşılması, sadece daha iyi kod yazmayı değil, aynı zamanda kodun uzun vadede nasıl yönetileceğini de anlamayı sağlar.

Prensip Nedir?

Prensipler, genel anlamda bir bütünün, bir olgunun ahlaki sınırlarını çizen evrensel ilkelerdir. Prensipler, fiziki kurallara dayalı değildir; zaman, mekan ve koşullardan bağımsızdırlar. Prensipler, kuralların üzerinde yer alır ve daha soyut, daha evrensel niteliktedirler. Kurallar, belirli koşullara göre değişebilirken, prensipler zamandan ve mekandan bağımsız olarak sabit kalır.

Örneğin, “Bir insanın kalbini kırmamak” bir prensiptir ve bu ilkeye uyan bir kişi, koşullardan bağımsız olarak bu prensibi takip eder. Benzer şekilde, yazılım dünyasında da prensipler, kod yazarken belirli ahlaki sınırları belirler ve bu sınırlar içinde kalınarak kod yazılması, kodun sürdürülebilir ve yönetilebilir olmasını sağlar.

Yazılım Prensipleri

Yazılım prensipleri, yazılım geliştirme sürecinde en ideal kodlama ahlakını sağlayan ilkelerdir. Bu prensipler, yazılım süreçlerinde kodlama sırasında hangi durumlarda nasıl davranışlar sergilenmesi gerektiğini belirler. Yazılım prensiplerine uyularak inşa edilen kodlar, daha yönetilebilir, daha esnek ve gelecekteki ihtiyaçlara daha hızlı yanıt verebilir hale gelir.

Yazılım prensipleri, genel anlamda bir yazılım sisteminin sürdürülebilirliğini, esnekliğini ve bakımını kolaylaştırmak amacıyla geliştirilmiştir. Bu prensiplere uyulmaması durumunda, kodun karmaşıklaşması ve yönetiminin zorlaşması kaçınılmaz olur. Prensipler, kodun daha modüler, yeniden kullanılabilir ve okunabilir olmasını sağlar.

Örnek Prensipler

Aşağıda, yazılım geliştirmede sıkça kullanılan bazı temel prensipler listelenmiştir:

  1. Single Responsibility Principle (SRP): Her sınıfın veya modülün yalnızca bir sorumluluğu olmalıdır. Bu prensip, kodun daha modüler olmasını ve değişikliklerin daha kolay yönetilmesini sağlar.
  2. Open/Closed Principle (OCP): Yazılım varlıkları (sınıflar, modüller, fonksiyonlar) yeni işlevsellik eklemek için açık olmalı, ancak mevcut işlevselliği değiştirmek için kapalı olmalıdır. Bu, yeni özellikler eklenirken mevcut kodun bozulmamasını sağlar.
  3. Liskov Substitution Principle (LSP): Bir sınıf, temel sınıfın yerine geçebilmeli ve bu durumda programın davranışı değişmemelidir. Bu prensip, kalıtım hiyerarşisinin doğru bir şekilde kullanılmasını sağlar.
  4. Interface Segregation Principle (ISP): Büyük, genel amaçlı bir arayüz yerine, daha küçük ve spesifik arayüzler kullanılmalıdır. Bu, gereksiz bağımlılıkları azaltır ve kodun daha esnek olmasını sağlar.
  5. Dependency Inversion Principle (DIP): Yüksek seviye modüller, düşük seviye modüllere bağlı olmamalıdır; her iki modül de soyutlamalara (interfaces) bağımlı olmalıdır. Bu prensip, bağımlılıkların daha kolay yönetilmesini sağlar.
Yazılım Prensiplerinin Önemi

Yazılım prensipleri, kodun uzun vadede sürdürülebilir ve yönetilebilir olmasını sağlar. Bu prensiplere uyulmadığında, kod karmaşıklaşır, hataya açık hale gelir ve bakım maliyetleri artar. Prensipler, yazılım geliştiricilerin kod yazarken daha bilinçli ve sorumlu hareket etmelerini sağlar.

Örneğin, bir yazılım geliştirme projesinde, prensiplere uyulmadan yazılan kodlar zamanla karmaşıklaşabilir ve projeye yeni geliştiricilerin katılması durumunda, kodun anlaşılması zorlaşabilir. Bu da projenin sürdürülebilirliğini tehlikeye atar. Ancak prensipli bir şekilde yazılan kodlar, modüler, esnek ve yeniden kullanılabilir olduğundan, yeni özelliklerin eklenmesi veya mevcut kodun değiştirilmesi daha kolay ve daha güvenli olur.

Loose Coupling (Gevşek Bağlılık) Prensibi Nedir?

Loose Coupling (gevşek bağlılık), yazılım geliştirme süreçlerinde kullanılan temel prensiplerden biridir. Bu prensip, özellikle nesneye yönelik tasarım ilkelerinde sıkça vurgulanan, yazılımın daha esnek, sürdürülebilir ve bakımı kolay olmasını sağlayan önemli bir yaklaşımdır. Gevşek bağlılık prensibi, nesneler arasında bir bağımlılığın kaçınılmaz olduğunu kabul eder, ancak bu bağımlılığın mümkün olduğunca esnek ve yönetilebilir olmasını önerir.

Bağımlılık (Coupling) Nedir?

Bağımlılık, bir sınıfın ya da nesnenin başka bir sınıfın ya da nesnenin işlevselliğine ihtiyaç duyduğu durumlarda ortaya çıkar. Yazılım geliştirirken nesnelerin birbirleriyle iş birliği yapması gerekebilir. Ancak bu iş birliği sonucunda nesneler arasında kaçınılmaz olarak bir bağımlılık oluşur. Bu bağımlılığın seviyesi iki kategoriye ayrılabilir:

  1. Tight Coupling (Sıkı Bağlılık): Bir nesnenin diğer bir nesneye yüksek derecede bağımlı olduğu durumdur. Sıkı bağlılıkta, nesneler birbirlerine çok fazla detay bilir ve bu da onları birbirine çok sıkı bağlar. Bu durumda, bir nesnede yapılacak herhangi bir değişiklik, diğer nesneyi de etkiler. Sıkı bağlılık, yazılımın bakımı ve yönetimini zorlaştırır, çünkü küçük bir değişiklik bile birçok farklı sınıfın güncellenmesini gerektirebilir.
  2. Loose Coupling (Gevşek Bağlılık): Nesneler arasındaki bağımlılığın minimum düzeyde olduğu, esnek bir ilişki modelidir. Gevşek bağlılıkta, bir nesne diğer nesne hakkında minimum bilgiye sahiptir ve bu da bağımlılığı azaltır. Bu durumda, bir nesnede yapılan değişikliklerin diğer nesneleri etkileme olasılığı daha düşüktür.
Loose Coupling’in Amaçları

Loose coupling, nesneler arasında esnek bir bağımlılık kurmayı amaçlar. Bağımlılık kaçınılmazdır, ancak bu bağımlılığın sıkı olmaması gerekir. Bir nesnenin diğer bir nesne hakkında çok fazla bilgiye sahip olması, yazılımın esnekliğini azaltır ve bakımını zorlaştırır. Gevşek bağlılıkta amaç, nesnelerin sadece ihtiyaç duydukları minimum bilgilere sahip olmasını sağlamaktır.

Örnek: Telefon ve SIM Kart Senaryosu

Loose coupling prensibini açıklamak için verilen bir örnek, telefon ve SIM kart ilişkisiyle ilgilidir. Bir telefonun çalışması için bir SIM karta ihtiyacı vardır, bu da bir bağımlılık oluşturur. Ancak, telefon herhangi bir SIM kartla çalışabilir. Telefon, belirli bir SIM karta sıkı sıkıya bağlı değildir; farklı operatörlerden SIM kartlar kullanabilir ve bu SIM kartları değiştirmek için telefonda büyük bir değişiklik yapmaya gerek yoktur. Bu durum, gevşek bir bağımlılık örneğidir. Telefonun bir SIM karta ihtiyacı vardır (bağımlılık), ancak bu bağımlılık esnektir.

Eğer telefon, sadece tek bir operatörün SIM kartıyla çalışacak şekilde tasarlanmış olsaydı, bu durumda sıkı bağlılık oluşurdu. Bu tür bir durumda, operatör değiştiğinde telefonu değiştirmek gerekebilirdi. Loose coupling sayesinde, telefon birden fazla SIM kartı kabul edecek şekilde tasarlanmıştır.

Yazılımda Loose Coupling

Yazılımda loose coupling uygulamak için nesneler arasındaki bağımlılığı minimumda tutacak yapılar kullanılır. Bu yapılardan en önemlisi arayüzler (interfaces) ve soyut sınıflardır (abstract classes). Arayüzler ve soyut sınıflar, bir nesnenin başka bir nesneyle nasıl etkileşime gireceğini tanımlar, ancak bu nesnelerin birbirlerine sıkı sıkıya bağlı olmasını engeller.

Kod Örneği: Loose Coupling ile Mail Gönderimi

Bir mail gönderme sistemini ele alalım. Aşağıda, bu senaryoda gevşek bağlılığın nasıl uygulanabileceğini gösteren bir örnek bulunmaktadır:

Tight Coupling Durumu:

public class MailSender
{
    private GmailService _gmailService = new GmailService();

    public void SendMail()
    {
        _gmailService.Send("example@gmail.com", "Hello World");
    }
}

public class GmailService
{
    public void Send(string recipient, string message)
    {
        // Gmail API ile mail gönderme işlemi
    }
}

Bu örnekte, MailSender sınıfı doğrudan GmailService sınıfına bağlıdır. Eğer gelecekte MailSender sınıfı, Gmail yerine başka bir mail servisini kullanmak isterse, kodun içinde büyük değişiklikler yapmak zorunda kalacağız. Bu sıkı bağlılık örneğidir ve yazılımın esnekliğini kısıtlar.

Loose Coupling Durumu:

public interface IMailService
{
    void Send(string recipient, string message);
}

public class MailSender
{
    private IMailService _mailService;

    public MailSender(IMailService mailService)
    {
        _mailService = mailService;
    }

    public void SendMail()
    {
        _mailService.Send("example@gmail.com", "Hello World");
    }
}

public class GmailService : IMailService
{
    public void Send(string recipient, string message)
    {
        // Gmail API ile mail gönderme işlemi
    }
}

public class HotmailService : IMailService
{
    public void Send(string recipient, string message)
    {
        // Hotmail API ile mail gönderme işlemi
    }
}

Bu örnekte, MailSender sınıfı artık bir IMailService arayüzüne bağlıdır. Bu sayede, GmailService, HotmailService gibi farklı mail servisleri bu arayüzü implemente edebilir ve MailSender sınıfı herhangi bir mail servisinden bağımsız olarak çalışabilir. Artık sadece IMailService ile çalıştığı için, mail servisinin değişmesi durumunda MailSender üzerinde değişiklik yapmamıza gerek yoktur. Bu gevşek bağlılık örneğidir.

Loose Coupling’in Avantajları
  1. Esneklik: Gevşek bağlılık sayesinde, sistemdeki herhangi bir bileşeni değiştirmek veya güncellemek daha kolay hale gelir.
  2. Yönetilebilirlik: Nesneler arasındaki bağımlılık minimumda tutulduğunda, sistemin bakımı daha kolaydır.
  3. Yeniden Kullanılabilirlik: Gevşek bağlılık ile yazılan kodlar, başka projelerde veya başka yerlerde daha kolay bir şekilde yeniden kullanılabilir.
  4. Test Edilebilirlik: Bağımlılıklar arayüzlerle soyutlandığında, birim testlerde bağımlı sınıfların yerine sahte nesneler (mock objects) kullanılabilir. Bu, testlerin daha kolay yapılmasını sağlar.
SOLID – Single Responsibility Principle (SRP) Nedir?

Single Responsibility Principle (SRP), SOLID prensiplerinin ilki olan ve yazılım geliştirme sürecinde kritik bir öneme sahip olan bir prensiptir. Adından da anlaşılacağı gibi, SRP, her sınıfın veya modülün yalnızca bir tek sorumluluğu olması gerektiğini savunur. Bu prensip, kodun daha kolay yönetilebilir, esnek ve sürdürülebilir olmasını sağlar.

Prensiplerin Gerçek Hayattan Alınışı

SRP, sadece yazılım geliştirme sürecinde değil, aynı zamanda günlük hayatımızda da geçerli olan bir ilkedir. Gerçek hayatta bir kişi aynı anda birden fazla işi yapmaya çalıştığında, genellikle verim düşer ve hata yapma olasılığı artar. Örneğin, araba kullanırken aynı anda telefonla konuşmak, dikkati dağıtır ve kazaya neden olabilir. Aynı durum yazılım geliştirme için de geçerlidir: Bir sınıfın birden fazla sorumluluğa sahip olması, bu sınıfın yönetilmesini zorlaştırır ve hata yapma olasılığını artırır.

Single Responsibility Principle’in Temeli

SRP, bir sınıfın yalnızca tek bir sorumluluğa odaklanması gerektiğini belirtir. Eğer bir sınıfın değiştirilmesi için birden fazla neden varsa, bu sınıfın birden fazla sorumluluğu olduğu anlamına gelir ve bu, SRP’ye aykırıdır. SRP’ye göre, bir sınıfın yalnızca tek bir sorumluluğu olmalıdır ve bu sorumluluk, sınıfın varlık sebebini belirlemelidir.

Bir Sınıfın Tek Bir Sorumluluğa Sahip Olmasının Avantajları:
  1. Yönetilebilirlik: Tek sorumluluğa sahip sınıflar, daha kolay yönetilebilir ve anlaşılabilir.
  2. Esneklik: Sınıfların sorumlulukları net bir şekilde ayrıldığında, kodun modifiye edilmesi veya genişletilmesi daha kolay olur.
  3. Test Edilebilirlik: Sınıfların tek bir sorumluluğu olduğunda, bu sınıfların test edilmesi daha kolay ve güvenilir hale gelir.
SRP’nin Uygulanabilirliği

SRP’yi uygulamak, bir sınıfın sadece tek bir işi yapmasını sağlamak anlamına gelir. Bu, sınıfın sorumluluklarının net bir şekilde tanımlanması gerektiği anlamına gelir. Eğer bir sınıf, birden fazla sorumluluğa sahip olacak şekilde tasarlanmışsa, bu sınıfın bölünmesi ve her bir sorumluluğun ayrı bir sınıfa taşınması gerekir.

SRP’yi Uygularken Sorulabilecek Sorular:
  1. Bu sınıfın değiştirilmesi için birden fazla sebep var mı?
    • Eğer varsa, bu sınıfın birden fazla sorumluluğu olabilir ve SRP’ye aykırı çalışıyor olabilir.
  2. Bu sınıfın gerçekleştirdiği iş tek bir sorumluluğa mı odaklanıyor?
    • Eğer değilse, bu sorumlulukların ayrı sınıflara taşınması gerekebilir.
SRP’nin Örneklerle Açıklanması
Yanlış Uygulama (SRP’ye Aykırı):
public class Employee
{
    public string Name { get; set; }
    public void CalculateSalary() 
    {
        // Maaş hesaplama işlemleri
    }
    public void GenerateReport() 
    {
        // Rapor oluşturma işlemleri
    }
}

Bu örnekte, Employee sınıfı hem maaş hesaplama hem de rapor oluşturma işlemlerini yapmaktadır. Bu, SRP’ye aykırıdır çünkü Employee sınıfı birden fazla sorumluluğa sahiptir. Bu durum, sınıfın karmaşıklaşmasına ve yönetiminin zorlaşmasına neden olur.

Doğru Uygulama (SRP’ye Uygun):
public class Employee
{
    public string Name { get; set; }
}

public class SalaryCalculator
{
    public void CalculateSalary(Employee employee) 
    {
        // Maaş hesaplama işlemleri
    }
}

public class ReportGenerator
{
    public void GenerateReport(Employee employee) 
    {
        // Rapor oluşturma işlemleri
    }
}

Bu doğru uygulamada, maaş hesaplama ve rapor oluşturma sorumlulukları farklı sınıflara ayrılmıştır. Employee sınıfı yalnızca bir çalışanın özelliklerini barındırırken, maaş hesaplama işlemleri SalaryCalculator sınıfına, rapor oluşturma işlemleri ise ReportGenerator sınıfına taşınmıştır. Böylece her sınıfın tek bir sorumluluğu vardır ve SRP prensibine uyulmuştur.

SRP’nin Yazılım Geliştirmedeki Önemi

Bir sınıfın tek bir sorumluluğa sahip olması, kodun daha kolay anlaşılmasını, test edilmesini ve bakımını sağlar. SRP’ye uyulmadığında, bir sınıfta yapılan bir değişiklik, sınıfın diğer sorumluluklarını da etkileyebilir ve bu da yazılımın sürdürülebilirliğini tehlikeye atar. Bu yüzden, yazılım geliştirme sürecinde SRP’ye dikkat etmek, daha sağlam ve yönetilebilir bir yazılım elde etmek için kritik öneme sahiptir.

SRP’ye Uygun Kod Yazmanın İpuçları
  1. Sınıfları ve Metotları Küçük Tutun: Bir sınıf veya metodun çok fazla iş yaptığını fark ederseniz, sorumluluklarını bölmek için bu sınıfı veya metodu yeniden düzenleyin.
  2. Kod İncelemesi Yapın: Kodunuzu yazarken veya gözden geçirirken, her sınıfın tek bir sorumluluğa odaklandığından emin olun.
  3. Ayrı Sorumluluklar İçin Ayrı Sınıflar Kullanın: Farklı sorumluluklar için farklı sınıflar oluşturun ve bu sınıfları açıkça tanımlanmış sorumluluklarla ilişkilendirin.
SOLID – Open/Closed Principle (OCP) Nedir?

Open/Closed Principle (OCP), SOLID prensiplerinin ikinci ilkesidir ve yazılım geliştirme sürecinde kodun genişletilebilir olmasını, ancak değiştirilebilir olmamasını savunur. Bu prensip, yazılımın sürdürülebilirliğini ve esnekliğini artırarak, gereksinim değişikliklerine daha dayanıklı hale getirilmesini sağlar.

Open/Closed Principle’in Temel Kavramları

OCP, iki temel kavram üzerine kuruludur:

  1. Açık Olma (Open for Extension): Bir yazılım bileşeni (sınıf, modül, fonksiyon, vb.) yeni özellikler veya işlevler eklemek için açık olmalıdır. Bu, yeni davranışların yazılıma entegre edilmesine izin verir.
  2. Kapalı Olma (Closed for Modification): Aynı yazılım bileşeni, mevcut kodu değiştirmek zorunda kalmadan bu yeni davranışları desteklemelidir. Bu, yazılımın mevcut işlevselliğini korurken genişletilebilir olmasını sağlar.

Bu prensip, yazılımın gelişimi sırasında yeni gereksinimler ortaya çıktığında mevcut kodun değiştirilmeden genişletilmesine olanak tanır. Böylece, yazılımın bakım maliyetleri azalır ve hata yapma olasılığı en aza indirilir.

Kodun Değiştirilmesi ve Genişletilmesi

Kodun Değiştirilmesi: Bir yazılım bileşeninde yeni gereksinimler ortaya çıktığında mevcut kodun değiştirilmesi anlamına gelir. Bu, genellikle mevcut kodun yeniden düzenlenmesi, değiştirilmesi veya tamamen yeniden yazılması anlamına gelir.

Kodun Genişletilmesi: Yeni gereksinimler ortaya çıktığında mevcut kodun değiştirilmesine gerek kalmadan yeni davranışların yazılıma eklenmesi anlamına gelir. Bu, genellikle mevcut kodun üzerine yeni kod ekleyerek gerçekleştirilir.

OCP, kodun genişletilebilir olmasını, ancak değiştirilmemesini savunur. Bu sayede, yazılımın mevcut işlevselliği korunurken yeni özellikler eklenebilir.

Open/Closed Principle’in Uygulanması

OCP’yi uygulamak için yazılım bileşenlerinin genişletilebilir bir şekilde tasarlanması gerekir. Bu, genellikle arayüzler (interfaces) ve soyut sınıflar (abstract classes) kullanılarak yapılır. Arayüzler ve soyut sınıflar, yeni davranışların mevcut kodu değiştirmeden eklenmesine olanak tanır.

Örnek: Banka İşlemleri

Bir yazılım sistemi geliştirdiğimizi varsayalım ve bu sistemde farklı bankalar aracılığıyla para transferi işlemleri gerçekleştiriyoruz.

Yanlış Uygulama (OCP’ye Aykırı):
public class PaymentProcessor
{
    public void ProcessPayment(string bankType, double amount)
    {
        if (bankType == "Garanti")
        {
            // Garanti Bankası için ödeme işlemleri
        }
        else if (bankType == "Halkbank")
        {
            // Halkbank için ödeme işlemleri
        }
        // Yeni bankalar eklendiğinde bu metoda yeni if blokları eklenmesi gerekecek.
    }
}

Bu örnekte, yeni bir banka eklemek istediğimizde ProcessPayment metodunu değiştirmemiz gerekecektir. Bu durum, OCP’ye aykırıdır çünkü mevcut kodda değişiklik yapılması gerekir.

Doğru Uygulama (OCP’ye Uygun):
public interface IBank
{
    void ProcessPayment(double amount);
}

public class GarantiBank : IBank
{
    public void ProcessPayment(double amount)
    {
        // Garanti Bankası için ödeme işlemleri
    }
}

public class Halkbank : IBank
{
    public void ProcessPayment(double amount)
    {
        // Halkbank için ödeme işlemleri
    }
}

public class PaymentProcessor
{
    public void ProcessPayment(IBank bank, double amount)
    {
        bank.ProcessPayment(amount);
    }
}

Bu doğru uygulamada, yeni bir banka eklemek istediğimizde sadece yeni bir sınıf oluşturup IBank arayüzünü implemente etmemiz yeterli olacaktır. PaymentProcessor sınıfında herhangi bir değişiklik yapmamıza gerek kalmaz. Bu sayede, OCP prensibine uygun bir yapı elde edilmiş olur.

OCP’nin Yazılım Geliştirmedeki Önemi

OCP, yazılımın daha sürdürülebilir, genişletilebilir ve esnek olmasını sağlar. Bu prensip, yazılım bileşenlerinin mevcut işlevselliği koruyarak yeni gereksinimlere kolayca uyum sağlamasına olanak tanır. Kodun değiştirilmesi, genellikle yeni hatalara yol açabileceğinden, OCP’nin uygulanması bu riskleri en aza indirir.

OCP’nin Uygulanmasının Faydaları
  1. Sürdürülebilirlik: Mevcut kodu değiştirmeden yeni özellikler eklemek, yazılımın uzun vadede sürdürülebilir olmasını sağlar.
  2. Esneklik: Yeni gereksinimlere hızlı ve kolay bir şekilde yanıt vermek mümkün olur.
  3. Bakım Kolaylığı: Kodun değiştirilmesi gerekmemesi, bakım maliyetlerini azaltır ve hata yapma olasılığını düşürür.
  4. Yeniden Kullanılabilirlik: Kod, farklı gereksinimler için tekrar kullanılabilir hale gelir.
SOLID – Liskov Substitution Principle (LSP) Nedir?

Liskov Substitution Principle (LSP), SOLID prensiplerinin üçüncü ilkesidir ve nesne yönelimli programlama (OOP) süreçlerinde oldukça kritik bir rol oynar. LSP, bir sınıf hiyerarşisinde türetilen sınıfların (subclasses) temel sınıfın (base class) yerine geçebileceğini ve temel sınıfın yerini alırken programın işleyişinde herhangi bir bozulma meydana gelmemesi gerektiğini savunur. Bu, türetilen sınıfların temel sınıfın sunduğu davranışları bozmadan genişletebilmesi gerektiği anlamına gelir.

Liskov Substitution Principle’in Temel Kavramları

LSP’nin özünde şu iki ana kavram yatar:

  1. Yer Değiştirilebilirlik (Substitutability): Bir temel sınıfın nesnesi yerine, o sınıftan türetilen herhangi bir alt sınıfın nesnesi kullanılabilmelidir. Alt sınıfın, temel sınıfın tüm davranışlarını devralması ve bu davranışları bozmadan genişletebilmesi gerekir.
  2. Sözleşmeye Uyma (Contract Compliance): Alt sınıflar, temel sınıfın tanımladığı sözleşmeye (contract) tam anlamıyla uymalıdır. Bu, alt sınıfların temel sınıfın sunduğu tüm metotları anlamlı bir şekilde implemente etmesi gerektiği anlamına gelir. Eğer bir alt sınıf, temel sınıfın bir metodunu anlamsız bir şekilde implemente ediyorsa veya bu metodu boş bırakıyorsa, LSP ihlal edilmiş olur.
Liskov Substitution Principle’in Uygulanması

LSP’yi doğru bir şekilde uygulayabilmek için, temel sınıfların ve alt sınıfların doğru bir şekilde tasarlanması gerekir. Alt sınıflar, temel sınıfın davranışlarını genişletebilmeli, ancak bu davranışları bozacak şekilde değiştirmemelidir.

Örnek: Bulut Servisleri

Bir bulut hizmetleri sistemi geliştirdiğimizi varsayalım. Bu sistemde, farklı bulut sağlayıcıları (Amazon, Azure, Google Cloud) ile entegre olan bir yapı bulunuyor ve bu sağlayıcılar farklı hizmetler sunuyor.

Yanlış Uygulama (LSP’ye Aykırı):
public abstract class CloudService
{
    public abstract void Translate(string text);
    public abstract void MachineLearning(string data);
}

public class AmazonCloudService : CloudService
{
    public override void Translate(string text)
    {
        // Amazon Translate API çağrısı
    }

    public override void MachineLearning(string data)
    {
        // Amazon Machine Learning API çağrısı
    }
}

public class AzureCloudService : CloudService
{
    public override void Translate(string text)
    {
        // Azure Translate API yok, bu metot boş bırakılabilir ya da bir hata fırlatılabilir
        throw new NotImplementedException();
    }

    public override void MachineLearning(string data)
    {
        // Azure Machine Learning API çağrısı
    }
}

Bu örnekte, AzureCloudService sınıfı Translate metodunu implemente etmek zorunda bırakılmıştır. Ancak, Azure Translate hizmeti sunmadığı için bu metodun içi boş bırakılmış veya bir hata fırlatılmıştır. Bu, LSP’ye aykırı bir tasarımdır çünkü AzureCloudService, CloudService sınıfının tüm davranışlarını anlamlı bir şekilde devralmamaktadır.

Doğru Uygulama (LSP’ye Uygun):
public interface ICloudService
{
    void MachineLearning(string data);
}

public interface ITranslationService
{
    void Translate(string text);
}

public class AmazonCloudService : ICloudService, ITranslationService
{
    public void Translate(string text)
    {
        // Amazon Translate API çağrısı
    }

    public void MachineLearning(string data)
    {
        // Amazon Machine Learning API çağrısı
    }
}

public class AzureCloudService : ICloudService
{
    public void MachineLearning(string data)
    {
        // Azure Machine Learning API çağrısı
    }
}

Bu doğru uygulamada, Translate işlemini gerçekleştiren sınıflar sadece bu hizmeti sunan sınıflar tarafından implemente edilir. AzureCloudService, Translate metodunu implemente etmek zorunda kalmaz, çünkü Azure bu hizmeti sunmaz. Bu sayede, AzureCloudService sınıfı yalnızca gerçek davranışları barındırır ve LSP’ye uygun hale gelir.

Liskov Substitution Principle’in Yazılım Geliştirmedeki Önemi

LSP, yazılımın sürdürülebilirliğini ve genişletilebilirliğini artırır. Bu prensip, temel sınıflardan türeyen sınıfların, temel sınıfın davranışlarını değiştirmeden genişletmesine olanak tanır. Bu da kodun yeniden kullanılabilirliğini ve esnekliğini artırır.

LSP’nin Uygulanmasının Faydaları
  1. Yer Değiştirilebilirlik: Alt sınıflar, temel sınıfın yerine geçebilir ve bu geçiş sırasında herhangi bir hata oluşmaz.
  2. Kodun Tutarlılığı: Tüm alt sınıflar, temel sınıfın sözleşmesine uyar ve bu sözleşmeye uygun olarak davranır. Bu, yazılımın daha tutarlı ve öngörülebilir olmasını sağlar.
  3. Bakım Kolaylığı: Kod, LSP’ye uygun bir şekilde tasarlandığında, bakım sırasında hataların ortaya çıkma olasılığı azalır ve geliştirme süreci daha az maliyetli olur.
Liskov Substitution Principle’in Uygulanmasında Yaygın Hatalar

LSP’yi uygularken yapılan yaygın hatalar arasında, alt sınıfların temel sınıftan devralınan metotları anlamsız bir şekilde implemente etmesi veya bu metotları boş bırakması yer alır. Bu, alt sınıfların temel sınıfın sunduğu sözleşmeye uymadığını gösterir ve yazılımın işleyişinde hatalara yol açabilir.

SOLID – Interface Segregation Principle (ISP) Nedir?

Interface Segregation Principle (ISP), SOLID prensiplerinin dördüncü ilkesidir ve yazılım geliştirme sürecinde arayüzlerin (interface) nasıl tasarlanması gerektiğini ele alır. Bu prensip, bir arayüzün (interface) tek bir amaca hizmet etmesi gerektiğini savunur ve bir sınıfın sadece ihtiyaç duyduğu metotları içeren arayüzleri implemente etmesini önerir.

Interface Segregation Principle’in Temel Kavramları

ISP, yazılımda arayüzlerin daha spesifik ve odaklı olmasını savunur. Prensibin temel amacı, gereksiz metotları içeren büyük arayüzlerin (fat interfaces) kullanılmasından kaçınmaktır. ISP’ye göre, bir arayüz bir sınıfa yalnızca ihtiyaç duyduğu metotları sağlamalıdır; bu sayede sınıflar gereksiz metotları implemente etmek zorunda kalmaz.

Interface Segregation Principle’in Uygulanması

ISP’yi doğru bir şekilde uygulamak için, büyük ve çok amaçlı arayüzleri daha küçük, spesifik arayüzlere bölmek gerekir. Bu, her bir arayüzün yalnızca belirli bir davranışı temsil etmesini sağlar ve bir sınıfın yalnızca ihtiyaç duyduğu arayüzleri implemente etmesine olanak tanır.

Örnek: Yazıcı (Printer) Senaryosu

Bir yazıcı sistemi geliştirdiğimizi varsayalım. Bu sistemde, farklı yazıcı türleri (HP, Samsung, Lexmark) ile çalışan bir yapı bulunuyor ve bu yazıcılar farklı işlevlere sahip.

Yanlış Uygulama (ISP’ye Aykırı):
public interface IPrinter
{
    void Print();
    void Scan();
    void PrintDuplex();
}

public class HPPrinter : IPrinter
{
    public void Print()
    {
        // HP Yazıcı ile yazdırma işlemi
    }

    public void Scan()
    {
        // HP Yazıcı ile tarama işlemi
    }

    public void PrintDuplex()
    {
        // HP Yazıcı ile çift taraflı yazdırma işlemi
    }
}

public class SamsungPrinter : IPrinter
{
    public void Print()
    {
        // Samsung Yazıcı ile yazdırma işlemi
    }

    public void Scan()
    {
        // Samsung Yazıcı tarama işlemi
    }

    public void PrintDuplex()
    {
        throw new NotImplementedException(); // Samsung çift taraflı yazdırmayı desteklemiyor
    }
}

public class LexmarkPrinter : IPrinter
{
    public void Print()
    {
        // Lexmark Yazıcı ile yazdırma işlemi
    }

    public void Scan()
    {
        throw new NotImplementedException(); // Lexmark taramayı desteklemiyor
    }

    public void PrintDuplex()
    {
        throw new NotImplementedException(); // Lexmark çift taraflı yazdırmayı desteklemiyor
    }
}

Bu örnekte, IPrinter arayüzü her yazıcı için geçerli olmayabilecek metotları içerir. Örneğin, SamsungPrinter çift taraflı yazdırmayı desteklemez, ancak bu metodu implemente etmek zorunda kalır. Bu durum, ISP’ye aykırı bir tasarımdır çünkü SamsungPrinter gereksiz bir metodu implemente etmek zorunda bırakılır.

Doğru Uygulama (ISP’ye Uygun):
public interface IPrint
{
    void Print();
}

public interface IScan
{
    void Scan();
}

public interface IPrintDuplex
{
    void PrintDuplex();
}

public class HPPrinter : IPrint, IScan, IPrintDuplex
{
    public void Print()
    {
        // HP Yazıcı ile yazdırma işlemi
    }

    public void Scan()
    {
        // HP Yazıcı ile tarama işlemi
    }

    public void PrintDuplex()
    {
        // HP Yazıcı ile çift taraflı yazdırma işlemi
    }
}

public class SamsungPrinter : IPrint, IScan
{
    public void Print()
    {
        // Samsung Yazıcı ile yazdırma işlemi
    }

    public void Scan()
    {
        // Samsung Yazıcı ile tarama işlemi
    }
}

public class LexmarkPrinter : IPrint
{
    public void Print()
    {
        // Lexmark Yazıcı ile yazdırma işlemi
    }
}

Bu doğru uygulamada, her yazıcı yalnızca ihtiyaç duyduğu arayüzleri implemente eder. Örneğin, SamsungPrinter ve LexmarkPrinter, çift taraflı yazdırmayı desteklemedikleri için IPrintDuplex arayüzünü implemente etmek zorunda kalmaz. Bu sayede, her sınıf yalnızca ilgili olduğu işlevleri yerine getirir ve gereksiz metotları implemente etmez.

Interface Segregation Principle’in Yazılım Geliştirmedeki Önemi

ISP, yazılımın daha esnek ve sürdürülebilir olmasını sağlar. Gereksiz metotların implemente edilmesi zorunluluğunu ortadan kaldırarak, sınıfların yalnızca ihtiyaç duydukları işlevleri yerine getirmesini sağlar. Bu da yazılımın daha modüler, bakımının daha kolay ve genişletilebilir olmasına katkıda bulunur.

ISP’nin Uygulanmasının Faydaları
  1. Modülerlik: Arayüzlerin daha küçük ve odaklı olması, yazılımın daha modüler olmasını sağlar.
  2. Kolay Bakım: Gereksiz metotları içermeyen sınıflar, daha az karmaşıklığa sahiptir ve bu da bakım sürecini kolaylaştırır.
  3. Genişletilebilirlik: Her bir arayüzün belirli bir amaca hizmet etmesi, yazılımın gelecekteki genişletmelerine ve değişikliklerine uyum sağlamasını kolaylaştırır.
Interface Segregation Principle’in Uygulanmasında Yaygın Hatalar

ISP’yi uygularken yapılan yaygın hatalar arasında, büyük ve çok amaçlı arayüzler oluşturmak yer alır. Bu tür arayüzler, bir sınıfın implemente etmemesi gereken metotları implemente etmesini zorunlu kılar, bu da kodun karmaşıklığını artırır ve bakımını zorlaştırır.

SOLID – Dependency Inversion Principle (DIP) Nedir?

Dependency Inversion Principle (DIP), SOLID prensiplerinin beşincisidir ve yazılım geliştirmede bağımlılıkların nasıl ele alınması gerektiğini açıklar. Bu prensip, yüksek seviye modüllerin düşük seviye modüllere bağımlı olmaması gerektiğini, aksine her iki seviyenin de soyutlamalara (abstractions) bağımlı olması gerektiğini savunur. DIP, yazılım sistemlerinin daha esnek, sürdürülebilir ve ölçeklenebilir olmasını sağlayan bir prensiptir.

Dependency Inversion Principle’in Temel Kavramları

DIP, yazılım geliştirme süreçlerinde bağımlılıkları yönetme şeklini yeniden yapılandırır. Yüksek seviyeli modüllerin, belirli düşük seviyeli modüllere doğrudan bağımlı olması yerine, bu modüllerin soyutlamalar üzerinden birbirleriyle iletişim kurmalarını önerir. Bu, bağımlılıkların tersine çevrilmesi olarak adlandırılır.

Dependency Inversion Principle’in Uygulanması

DIP’in uygulanması, yazılım geliştirme sürecinde sınıfların birbirine bağımlı olmasını engellemek için soyutlamalar kullanmayı içerir. Bu soyutlamalar, genellikle arayüzler (interfaces) veya soyut sınıflar (abstract classes) olarak karşımıza çıkar. Yüksek seviyeli bir modül, düşük seviyeli bir modülle doğrudan etkileşime girmek yerine, aralarındaki iletişimi bir arayüz veya soyut sınıf üzerinden gerçekleştirir.

Örnek: Mail Servisi Senaryosu

Bir mail servisi uygulaması geliştirdiğimizi düşünelim. Bu uygulama, çeşitli e-posta servis sağlayıcılarını (Gmail, Yahoo, Outlook) desteklemelidir. DIP’e uygun olmayan bir yapı, mail servisini doğrudan Gmail gibi belirli bir servise bağımlı hale getirir. Bu yapı, mail servisinin esnekliğini kısıtlar ve değişiklik yapmayı zorlaştırır.

Yanlış Uygulama (DIP’e Aykırı):
public class MailService
{
    private GmailService _gmailService;

    public MailService()
    {
        _gmailService = new GmailService();
    }

    public void SendEmail(string message)
    {
        _gmailService.Send(message);
    }
}

public class GmailService
{
    public void Send(string message)
    {
        // Gmail üzerinden e-posta gönderme işlemi
    }
}

Bu yanlış uygulamada, MailService sınıfı doğrudan GmailService sınıfına bağımlıdır. Bu yapı, başka bir e-posta servis sağlayıcısını eklemek istediğimizde MailService sınıfında değişiklik yapmamızı gerektirir ve bu durum DIP’e aykırıdır.

Doğru Uygulama (DIP’e Uygun):
public interface IEmailService
{
    void SendEmail(string message);
}

public class GmailService : IEmailService
{
    public void SendEmail(string message)
    {
        // Gmail üzerinden e-posta gönderme işlemi
    }
}

public class YahooService : IEmailService
{
    public void SendEmail(string message)
    {
        // Yahoo üzerinden e-posta gönderme işlemi
    }
}

public class MailService
{
    private IEmailService _emailService;

    public MailService(IEmailService emailService)
    {
        _emailService = emailService;
    }

    public void SendEmail(string message)
    {
        _emailService.SendEmail(message);
    }
}

Bu doğru uygulamada, MailService sınıfı herhangi bir spesifik e-posta servisine bağımlı değildir. Bunun yerine, IEmailService adlı bir arayüz üzerinden tüm e-posta servis sağlayıcıları ile iletişim kurar. Bu, MailService sınıfının esnekliğini artırır ve gelecekte farklı e-posta servis sağlayıcıları eklenmesi gerektiğinde sınıfta herhangi bir değişiklik yapılmasını gerektirmez.

Dependency Inversion Principle’in Yazılım Geliştirmedeki Önemi

DIP, yazılımın esnekliğini, sürdürülebilirliğini ve ölçeklenebilirliğini artırır. Yüksek seviyeli modüllerin düşük seviyeli modüllere doğrudan bağımlı olmaması, sistemde yapılacak değişikliklerin etkilerini minimize eder ve kodun bakımını kolaylaştırır.

DIP’in Uygulanmasının Faydaları
  1. Esneklik: Modüller arasında gevşek bir bağ kurarak, değişikliklerin etkisini minimize eder.
  2. Kolay Bakım: Bağımlılıkların soyutlamalar üzerinden yönetilmesi, sistemde yapılacak değişikliklerin etkilerini izole eder.
  3. Genişletilebilirlik: Yeni modüller eklemek, var olan soyutlamaları implemente etmekle kolaylaşır.
Dependency Inversion Principle’in Uygulanmasında Yaygın Hatalar

DIP’i uygularken yapılan yaygın hatalar arasında, sınıfların doğrudan başka sınıflara bağımlı hale getirilmesi yer alır. Bu, sistemin esnekliğini azaltır ve bakımını zorlaştırır. Soyutlamaların doğru kullanılması, DIP’in başarılı bir şekilde uygulanmasını sağlar.

Kaynakça

Aynı bölümü tekrar tekrar okumaya devam edersen hayatının bir sonraki bölümüne başlayamazsın.

Anonim

Bir sonraki yazıda görüşmek dileğiyle!”

Leave a Reply

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir


2 + 6 = ?