Inversion of Control (IoC) Nedir?

Inversion Of Control (Denetimin Tersine Çevrilmesi) (IoC), yazılım mühendisliğinde modülerliği artırmak ve bileşenlerin birbirinden ayrılmasını sağlamak için kullanılan bir tasarım prensibidir. Bir sınıfın bağımlılıklarını kontrol etmesi yerine, kontrol tersine çevrilir, yani bu sınıfın bağımlılıklarını harici bir varlık yönetir. IoC, Spring (Java) ve ASP.NET Core (C#) gibi frameworklerde temel bir kavramdır.

IoC’nin Temel Kavramları

  1. Dependency Injection (DI): IoC’yi gerçekleştirmenin en yaygın yolu. Bağımlılıklar bir sınıfa sağlanır, sınıf kendi bağımlılıklarını oluşturmaz.
  2. Service Locator Pattern: Dependency injection’a alternatif olarak, merkezi bir kayıt (servis bulucu) gerektikçe bağımlılıkları sağlar.

IoC’nin Faydaları

  • Decoupling (Bağımsızlık): Sınıflar ve bağımlılıkları arasındaki sıkı bağımlılığı azaltır.
  • Testability (Test Edilebilirlik): Bağımlılıkların sahte versiyonlarını kullanarak birim testleri yazmayı kolaylaştırır.
  • Flexibility (Esneklik): Bağımlılıkları değiştirmeyi, bu bağımlılıkları kullanan sınıfları değiştirmeden kolaylaştırır.
  • Maintainability (Bakım Kolaylığı): Kodun okunabilirliğini ve bakımını artırır.

Dependency Inversion Principle Nedir?

Dependency Inversion (Bağımlılıkların Tersine Çevrilmesi İlkesi) (DIP), SOLID prensiplerinden biridir ve yüksek seviyeli modüllerin (iş mantığı) düşük seviyeli modüllere (veritabanı erişimi, ağ iletişimi gibi) bağımlı olmamasını, her ikisinin de soyutlamalara bağımlı olmasını savunur. Bu ilke, bağımlılıkların kontrolünü sınıfın dışına taşıyarak daha esnek, modüler ve test edilebilir bir yapı sağlar.

DIP’nin İki Temel Kuralı

  1. Yüksek seviyeli modüller, düşük seviyeli modüllere bağımlı olmamalıdır. Her ikisi de soyutlamalara bağımlı olmalıdır.
  2. Soyutlamalar detaylara bağımlı olmamalıdır. Detaylar soyutlamalara bağımlı olmalıdır.

DIP’nin Faydaları

  • Modülerlik: Modüller arasında sıkı bağımlılıkları azaltarak kodun daha modüler olmasını sağlar.
  • Esneklik: Uygulamanın farklı bileşenlerini daha kolay değiştirmeyi ve genişletmeyi mümkün kılar.
  • Test Edilebilirlik: Birim testleri yazmayı ve bağımlılıkları sahte nesnelerle değiştirmeyi kolaylaştırır.

DIP Örneği

DIP’yi uygulamak için, bağımlılıkları doğrudan sınıflar yerine arayüzler veya soyut sınıflar aracılığıyla tanımlayarak gerçekleştirebiliriz.

Kötü Uygulama (DIP’ye Uygun Değil)

public class FileLogger
{
    public void Log(string message)
    {
        // Log message to a file
    }
}

public class OrderProcessor
{
    private FileLogger _logger = new FileLogger();

    public void Process(Order order)
    {
        // Process the order
        _logger.Log("Order processed.");
    }
}

Bu örnekte, OrderProcessor sınıfı doğrudan FileLogger sınıfına bağımlıdır, bu da DIP’ye uygun değildir.

İyi Uygulama (DIP’ye Uygun)

// Logger Arayüzü
public interface ILogger
{
    void Log(string message);
}

// FileLogger Sınıfı
public class FileLogger : ILogger
{
    public void Log(string message)
    {
        // Log message to a file
    }
}

// OrderProcessor Sınıfı
public class OrderProcessor
{
    private readonly ILogger _logger;

    // Yapıcı Enjeksiyonu
    public OrderProcessor(ILogger logger)
    {
        _logger = logger;
    }

    public void Process(Order order)
    {
        // Process the order
        _logger.Log("Order processed.");
    }
}

// Kullanım Örneği
class Program
{
    static void Main(string[] args)
    {
        ILogger logger = new FileLogger();
        OrderProcessor processor = new OrderProcessor(logger);

        processor.Process(new Order());
    }
}

Bu örnekte, OrderProcessor sınıfı doğrudan FileLogger sınıfına bağımlı değildir, bunun yerine ILogger interface’ine bağımlıdır. Böylece farklı loglama yöntemlerini kolayca değiştirebiliriz ve bağımlılıklar soyutlamalara dayanır.


Dependency Injection Nedir?

Dependency Injection (DI), Inversion of Control (IoC) prensibini uygulamak için kullanılan bir tekniktir. DI, bir sınıfın ihtiyaç duyduğu bağımlılıkların (başka sınıfların veya servislerin) dışarıdan sağlanmasını ifade eder. Bu, sınıflar arasında daha gevşek bağlılıklar sağlar, kodun daha modüler, test edilebilir ve bakımının kolay olmasına katkıda bulunur.

Dependency Injection Türleri

  1. Constructor Injection : Bağımlılıklar, sınıfın yapıcı metodu (constructor) aracılığıyla enjekte edilir.
  2. Properties Injection : Bağımlılıklar, sınıfın özellik (properties) aracılığıyla enjekte edilir.
  3. Method Injection : Bağımlılıklar, method yöntemleri aracılığıyla enjekte edilir.

DI’nin Faydaları

  • Bağımsızlık: Sınıflar ve bağımlılıkları arasındaki sıkı bağlılıkları azaltır.
  • Test Edilebilirlik: Birim testleri yazmayı kolaylaştırır, çünkü bağımlılıklar kolayca sahte (mock) nesnelerle değiştirilebilir.
  • Esneklik: Bağımlılıkların değiştirilmesini kolaylaştırır.
  • Bakım Kolaylığı: Kodun okunabilirliğini ve bakımını artırır.

Constructor Injection Örneği

// Servis Interface
public interface IMessageService
{
    void SendMessage(string message);
}

// Servis Uygulaması
public class EmailMessageService : IMessageService
{
    public void SendMessage(string message)
    {
        Console.WriteLine($"Email gönderildi: {message}");
    }
}

// Client Sınıfı
public class NotificationSender
{
    private readonly IMessageService _messageService;

    // Yapıcı Enjeksiyonu
    public NotificationSender(IMessageService messageService)
    {
        _messageService = messageService;
    }

    public void Notify(string message)
    {
        _messageService.SendMessage(message);
    }
}

// Kullanım Örneği
class Program
{
    static void Main(string[] args)
    {
        // Bağımlılığı manuel olarak enjekte etme
        IMessageService messageService = new EmailMessageService();
        NotificationSender sender = new NotificationSender(messageService);

        sender.Notify("Merhaba, Dünya!");
    }
}

Properties Injection Örneği

public interface IMessageService
{
    void SendMessage(string message);
}

public class EmailMessageService : IMessageService
{
    public void SendMessage(string message)
    {
        Console.WriteLine($"Email gönderildi: {message}");
    }
}

public class NotificationSender
{
    public IMessageService _messageService { get; set; }

    public void Notify(string message)
    {
        _messageService.SendMessage(message);
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        IMessageService messageService = new EmailMessageService();
        NotificationSender sender = new NotificationSender();
        sender._messageService = messageService;

        sender.Notify("Merhaba, Dünya!");
    }
}

Method Injection Örneği

public interface IMessageService
{
    void SendMessage(string message);
}

public class EmailMessageService : IMessageService
{
    public void SendMessage(string message)
    {
        Console.WriteLine($"Email gönderildi: {message}");
    }
}

public class NotificationSender
{
    public void Notify(string message, IMessageService _messageService)
    {
        _messageService.SendMessage(message);
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        IMessageService messageService = new EmailMessageService();
        NotificationSender sender = new NotificationSender();

        sender.Notify("Merhaba, Dünya!", messageService);
    }
}

Service Locator Pattern Nedir?

Service Locator Pattern, yazılım geliştirme süreçlerinde bağımlılıkların yönetilmesi için kullanılan bir tasarım desenidir. Bu desen, bağımlılıkları doğrudan sınıflara enjekte etmek yerine, merkezi bir “servis bulucu” kullanarak bağımlılıkların sağlanmasını sağlar. Bu, uygulamanın farklı bölümlerindeki bağımlılıkları yönetmek için merkezi bir yer sağlar.

Service Locator Pattern’in Özellikleri

  • Merkezi Bağımlılık Yönetimi: Tüm bağımlılıkların merkezi bir yerden sağlanmasını ve yönetilmesini sağlar.
  • Bağımlılıkların Geç Çözülmesi: Bağımlılıkların ihtiyaç duyulduğunda çözülmesini ve sağlanmasını mümkün kılar.
  • Esneklik: Farklı bağımlılıkları dinamik olarak sağlama esnekliği sunar.

Service Locator Pattern’in Kullanımı

C# ile Service Locator Pattern’in nasıl kullanılacağını bir örnekle açıklayalım.

Adımlar

  1. Servislerin Tanımlanması
  2. Service Locator’ın Tanımlanması
  3. Service Locator Kullanımı

Örnek

Servislerin Tanıtılması

public interface IMessageService
{
    void SendMessage(string message);
}

public class EmailMessageService : IMessageService
{
    public void SendMessage(string message)
    {
        Console.WriteLine($"Email gönderildi: {message}");
    }
}

public class SmsMessageService : IMessageService
{
    public void SendMessage(string message)
    {
        Console.WriteLine($"SMS gönderildi: {message}");
    }
}

Service Locator’ın Tanımlanması

public static class ServiceLocator
{
    private static readonly Dictionary<Type, object> _services = new Dictionary<Type, object>();

    public static void RegisterService<T>(T service)
    {
        var type = typeof(T);
        if (!_services.ContainsKey(type))
        {
            _services.Add(type, service);
        }
    }

    public static T GetService<T>()
    {
        var type = typeof(T);
        if (_services.ContainsKey(type))
        {
            return (T)_services[type];
        }

        throw new Exception("Servis bulunamadı.");
    }
}

Service Locator Kullanımı

public class NotificationSender
{
    public void Notify(string message)
    {
        var messageService = ServiceLocator.GetService<IMessageService>();
        messageService.SendMessage(message);
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Servisleri kayıt etme
        ServiceLocator.RegisterService<IMessageService>(new EmailMessageService());

        // NotificationSender'ı kullanma
        var sender = new NotificationSender();
        sender.Notify("Merhaba, Dünya!");
    }
}

Açıklama

  • Service Locator: Servisleri kaydeden ve sağlayan merkezi bir yer.
  • RegisterService: Belirtilen servisi kaydeder.
  • GetService: Kayıtlı servisleri döner.

Bu örnekte, ServiceLocator bağımlılıkların merkezi olarak yönetilmesini sağlar. NotificationSender sınıfı, ServiceLocator aracılığıyla IMessageService bağımlılığını alır ve bu bağımlılığı kullanarak mesaj gönderir.

Service Locator’ın Dezavantajları

  • Bağımlılıkların Saklanması: Bağımlılıklar açıkça sınıfların dışından sağlanmadığı için, bağımlılıklar gizlenmiş olur ve bu da kodun anlaşılmasını zorlaştırabilir.
  • Test Edilebilirlik: Bağımlılıkların doğrudan enjekte edilmemesi, birim testlerinin yazılmasını ve bağımlılıkların sahte nesnelerle değiştirilmesini zorlaştırabilir.
  • Tersine Bağımlılıklar: Service Locator kullanımı, sınıfların merkezi bir bileşene bağımlı olmasına neden olabilir ve bu da SOLID prensiplerine aykırı olabilir.

Ne Zaman Kullanılmalı?

Service Locator Pattern, özellikle bağımlılıkların yönetilmesinin zor olduğu büyük ve karmaşık projelerde faydalı olabilir. Ancak, Dependency Injection (DI) gibi diğer bağımlılık yönetim tekniklerinin tercih edilmesi, daha iyi bir bağımlılık yönetimi ve test edilebilirlik sağlar.