Ana içeriğe geç

Single Responsibility Principle (SRP)

Her sınıfın yalnızca bir sorumluluğu ve değişmek için yalnızca bir nedeni olmalıdır; aksi halde bakımı zor, test edilemez kod ortaya çıkar.


1. God Class (Her Şeyi Yapan Sınıf)

Yanlış Kullanım: Tek sınıfta birden fazla sorumluluk barındırmak.

public class UserService
{
    public void Register(User user)
    {
        // Validasyon
        if (string.IsNullOrEmpty(user.Email)) throw new Exception("Email boş olamaz");

        // Veritabanı kaydı
        _context.Users.Add(user);
        _context.SaveChanges();

        // E-posta gönderimi
        var smtp = new SmtpClient("smtp.server.com");
        smtp.Send(new MailMessage("noreply@app.com", user.Email, "Hoşgeldiniz", "..."));

        // Log kaydı
        File.AppendAllText("log.txt", $"Kullanıcı kaydedildi: {user.Email}");
    }
}

İdeal Kullanım: Her sorumluluğu ayrı sınıfa taşıyın.

public class UserService
{
    private readonly IUserRepository _repository;
    private readonly IEmailService _emailService;
    private readonly ILogger<UserService> _logger;

    public UserService(IUserRepository repository, IEmailService emailService, ILogger<UserService> logger)
    {
        _repository = repository;
        _emailService = emailService;
        _logger = logger;
    }

    public async Task RegisterAsync(User user)
    {
        await _repository.AddAsync(user);
        await _emailService.SendWelcomeAsync(user.Email);
        _logger.LogInformation("Kullanıcı kaydedildi: {Email}", user.Email);
    }
}

2. Fat Controller

Yanlış Kullanım: Controller içinde iş mantığı yazmak.

[HttpPost]
public async Task<IActionResult> CreateOrder(OrderDto dto)
{
    if (dto.Items.Count == 0) return BadRequest("Sipariş boş olamaz");

    var order = new Order { CustomerId = dto.CustomerId };
    decimal total = 0;
    foreach (var item in dto.Items)
    {
        var product = await _context.Products.FindAsync(item.ProductId);
        if (product.Stock < item.Quantity) return BadRequest("Stok yetersiz");
        product.Stock -= item.Quantity;
        total += product.Price * item.Quantity;
    }
    order.TotalPrice = total;
    _context.Orders.Add(order);
    await _context.SaveChangesAsync();
    return Ok(order);
}

İdeal Kullanım: Controller’ı ince tutun, iş mantığını servis katmanına taşıyın.

[HttpPost]
public async Task<IActionResult> CreateOrder(OrderDto dto)
{
    var result = await _orderService.CreateAsync(dto);
    return Ok(result);
}

3. Birden Fazla Değişim Nedeni Olan Sınıf

Yanlış Kullanım: Raporlama sınıfının hem veri çekme hem formatlama hem gönderme yapması.

public class ReportGenerator
{
    public Report FetchData(DateTime from, DateTime to)
    {
        // Veritabanından veri çek
    }

    public string FormatAsHtml(Report report)
    {
        // HTML formatla
    }

    public void SendByEmail(string html, string to)
    {
        // E-posta gönder
    }
}

İdeal Kullanım: Her sorumluluğu kendi sınıfına ayırın.

public class ReportDataFetcher
{
    public Task<Report> FetchAsync(DateTime from, DateTime to) { /* ... */ }
}

public class ReportFormatter
{
    public string FormatAsHtml(Report report) { /* ... */ }
}

public class ReportSender
{
    public Task SendByEmailAsync(string content, string to) { /* ... */ }
}

4. Model Sınıfında İş Mantığı ve Persistence

Yanlış Kullanım: Entity sınıfına veritabanı işlemleri eklemek.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public void Save()
    {
        using var context = new AppDbContext();
        context.Products.Add(this);
        context.SaveChanges();
    }

    public decimal CalculateDiscount() => Price * 0.10m;
}

İdeal Kullanım: Entity sadece domain mantığını barındırsın, persistence ayrı olsun.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public decimal CalculateDiscount() => Price * 0.10m;
}

public class ProductRepository : IProductRepository
{
    private readonly AppDbContext _context;

    public async Task AddAsync(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
    }
}

5. Servis İçinde Validation ve Mapping

Yanlış Kullanım: Validasyon ve mapping işlemlerini servis içine gömmek.

public class InvoiceService
{
    public async Task<InvoiceDto> CreateAsync(CreateInvoiceDto dto)
    {
        // Validasyon
        if (dto.Amount <= 0) throw new ArgumentException("Tutar pozitif olmalı");
        if (string.IsNullOrEmpty(dto.CustomerName)) throw new ArgumentException("Müşteri adı boş olamaz");

        var invoice = new Invoice
        {
            Amount = dto.Amount,
            CustomerName = dto.CustomerName,
            CreatedAt = DateTime.UtcNow
        };

        await _context.Invoices.AddAsync(invoice);
        await _context.SaveChangesAsync();

        return new InvoiceDto { Id = invoice.Id, Amount = invoice.Amount };
    }
}

İdeal Kullanım: Validasyon ve mapping işlemlerini ayrı sorumluluk olarak tanımlayın.

public class CreateInvoiceValidator : AbstractValidator<CreateInvoiceDto>
{
    public CreateInvoiceValidator()
    {
        RuleFor(x => x.Amount).GreaterThan(0);
        RuleFor(x => x.CustomerName).NotEmpty();
    }
}

public class InvoiceService
{
    private readonly IInvoiceRepository _repository;
    private readonly IMapper _mapper;

    public async Task<InvoiceDto> CreateAsync(CreateInvoiceDto dto)
    {
        var invoice = _mapper.Map<Invoice>(dto);
        await _repository.AddAsync(invoice);
        return _mapper.Map<InvoiceDto>(invoice);
    }
}