Ana içeriğe geç

Open/Closed Principle (OCP)

Sınıflar genişlemeye açık, değişikliğe kapalı olmalıdır; yeni gereksinimler mevcut kodu değiştirmeden eklenebilmelidir.


1. Switch-Case ile Genişleme

Yanlış Kullanım: Her yeni tip eklendiğinde mevcut kodu değiştirmek.

public class ShippingCostCalculator
{
    public decimal Calculate(string method, decimal weight)
    {
        switch (method)
        {
            case "standard": return weight * 5;
            case "express": return weight * 10;
            case "overnight": return weight * 20;
            // Yeni kargo yöntemi eklemek için bu sınıfı değiştirmek gerekir
            default: throw new ArgumentException("Bilinmeyen yöntem");
        }
    }
}

İdeal Kullanım: Polymorphism ile genişlemeye açık yapı kurun.

public interface IShippingCostStrategy
{
    string Method { get; }
    decimal Calculate(decimal weight);
}

public class StandardShipping : IShippingCostStrategy
{
    public string Method => "standard";
    public decimal Calculate(decimal weight) => weight * 5;
}

public class ExpressShipping : IShippingCostStrategy
{
    public string Method => "express";
    public decimal Calculate(decimal weight) => weight * 10;
}

public class ShippingCostCalculator
{
    private readonly Dictionary<string, IShippingCostStrategy> _strategies;

    public ShippingCostCalculator(IEnumerable<IShippingCostStrategy> strategies)
    {
        _strategies = strategies.ToDictionary(s => s.Method);
    }

    public decimal Calculate(string method, decimal weight)
        => _strategies[method].Calculate(weight);
}

2. Doğrudan Sınıf Değiştirme

Yanlış Kullanım: Mevcut sınıfa yeni davranış eklemek için kodu değiştirmek.

public class NotificationService
{
    public void Send(string type, string message, string recipient)
    {
        if (type == "email")
        {
            // E-posta gönder
        }
        else if (type == "sms")
        {
            // SMS gönder
        }
        // Push notification eklemek için burayı değiştirmek gerekir
    }
}

İdeal Kullanım: Yeni bildirim kanalını ayrı sınıf olarak ekleyin.

public interface INotificationChannel
{
    Task SendAsync(string message, string recipient);
}

public class EmailChannel : INotificationChannel
{
    public Task SendAsync(string message, string recipient) { /* ... */ }
}

public class SmsChannel : INotificationChannel
{
    public Task SendAsync(string message, string recipient) { /* ... */ }
}

// Yeni kanal eklemek için sadece yeni sınıf oluşturulur
public class PushChannel : INotificationChannel
{
    public Task SendAsync(string message, string recipient) { /* ... */ }
}

// DI'da tüm kanalları kaydedin
builder.Services.AddScoped<INotificationChannel, EmailChannel>();
builder.Services.AddScoped<INotificationChannel, SmsChannel>();
builder.Services.AddScoped<INotificationChannel, PushChannel>();

3. Validation Kurallarını Sınıf İçine Gömmek

Yanlış Kullanım: Validasyon kurallarını sabit kodlamak.

public class OrderValidator
{
    public bool Validate(Order order)
    {
        if (order.Items.Count == 0) return false;
        if (order.TotalPrice <= 0) return false;
        if (order.TotalPrice > 10000) return false;
        // Yeni kural eklemek için bu metodu değiştirmek gerekir
        return true;
    }
}

İdeal Kullanım: Kural tabanlı validasyon ile genişletilebilir yapı kurun.

public interface IOrderRule
{
    bool IsSatisfiedBy(Order order);
    string ErrorMessage { get; }
}

public class OrderMustHaveItems : IOrderRule
{
    public bool IsSatisfiedBy(Order order) => order.Items.Count > 0;
    public string ErrorMessage => "Sipariş en az bir ürün içermelidir.";
}

public class OrderMaxAmountRule : IOrderRule
{
    public bool IsSatisfiedBy(Order order) => order.TotalPrice <= 10000;
    public string ErrorMessage => "Sipariş tutarı 10.000 TL'yi geçemez.";
}

public class OrderValidator
{
    private readonly IEnumerable<IOrderRule> _rules;

    public OrderValidator(IEnumerable<IOrderRule> rules) => _rules = rules;

    public (bool IsValid, List<string> Errors) Validate(Order order)
    {
        var errors = _rules
            .Where(r => !r.IsSatisfiedBy(order))
            .Select(r => r.ErrorMessage)
            .ToList();

        return (errors.Count == 0, errors);
    }
}

4. Middleware Pipeline ile OCP

Yanlış Kullanım: Request işleme mantığını tek bir metoda toplamak.

public async Task HandleRequest(HttpContext context)
{
    // Logging
    _logger.LogInformation("Request: {Path}", context.Request.Path);
    // Authentication
    if (!context.User.Identity.IsAuthenticated) { context.Response.StatusCode = 401; return; }
    // Rate Limiting
    if (IsRateLimited(context)) { context.Response.StatusCode = 429; return; }
    // İş mantığı
    await ProcessAsync(context);
}

İdeal Kullanım: Middleware pipeline ile her concern’ü ayrı katman olarak ekleyin.

app.UseMiddleware<RequestLoggingMiddleware>();
app.UseAuthentication();
app.UseMiddleware<RateLimitingMiddleware>();
app.UseAuthorization();
app.MapControllers();

// Yeni middleware eklemek mevcut kodu değiştirmez
app.UseMiddleware<CorrelationIdMiddleware>();

5. Decorator ile OCP

Yanlış Kullanım: Mevcut servise loglama eklemek için kodu değiştirmek.

public class PaymentService : IPaymentService
{
    public async Task<PaymentResult> ProcessAsync(Payment payment)
    {
        _logger.LogInformation("Ödeme başlatıldı"); // Yeni eklendi - mevcut kod değişti
        var result = await _gateway.ChargeAsync(payment);
        _logger.LogInformation("Ödeme tamamlandı"); // Yeni eklendi
        return result;
    }
}

İdeal Kullanım: Decorator ile mevcut servisi değiştirmeden davranış ekleyin.

public class PaymentService : IPaymentService
{
    public async Task<PaymentResult> ProcessAsync(Payment payment)
    {
        return await _gateway.ChargeAsync(payment); // Değişmedi
    }
}

public class LoggingPaymentDecorator : IPaymentService
{
    private readonly IPaymentService _inner;
    private readonly ILogger _logger;

    public LoggingPaymentDecorator(IPaymentService inner, ILogger logger)
    {
        _inner = inner;
        _logger = logger;
    }

    public async Task<PaymentResult> ProcessAsync(Payment payment)
    {
        _logger.LogInformation("Ödeme başlatıldı");
        var result = await _inner.ProcessAsync(payment);
        _logger.LogInformation("Ödeme tamamlandı");
        return result;
    }
}