Tasarım Desenleri Rehberi: Yazılım Mimarisinde Düzen ve Verimlilik
Yazılım geliştirme dünyasında, karşılaşılan problemleri çözmek için birçok farklı yaklaşım bulunmaktadır. Bu yaklaşımlar arasında en sistematik ve etkili olanlarından biri tasarım desenleridir. Bu blog yazısında, tasarım desenlerinin ne olduğunu, nasıl kategorize edildiklerini ve MVC Core üzerinden pratik uygulamalarını inceleyeceğiz.
Tasarım Desenleri Nedir?
Tasarım desenleri, yazılım geliştirme sürecinde karşılaşılan yaygın problemlere yönelik tekrar kullanılabilir çözüm yöntemleridir. Bunlar, yazılım mühendislerinin deneyimlerinden ortaya çıkmış ve zaman içinde standartlaşmış olan en iyi pratiklerdir. Tasarım desenleri, kodun okunabilirliğini artırır, bakımını kolaylaştırır ve yazılımın kalitesini yükseltir.
Tasarım desenleri, kesin kod parçaları değil, belirli bir problemi çözmeye yönelik genel çözüm şablonlarıdır. Bu şablonları kendi spesifik ihtiyaçlarınıza göre uyarlayabilirsiniz.
Tasarım Desenlerinin Kategorizasyonu
Tasarım desenleri, temelde üç ana kategoriye ayrılır:
1. Yaratımsal (Creational) Desenler
Yaratımsal desenler, nesnelerin nasıl oluşturulacağına odaklanır. Bu desenler, nesne oluşturma sürecini soyutlaştırarak sistemin hangi nesneleri nasıl oluşturduğundan bağımsız hale gelmesini sağlar.
Önemli Yaratımsal Desenler:
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
2. Yapısal (Structural) Desenler
Yapısal desenler, sınıfların ve nesnelerin nasıl bir araya getirilerek daha büyük yapılar oluşturacağına odaklanır. Bu desenler, farklı arayüzlerin birlikte çalışmasını sağlar ve kompleks yapıları daha anlaşılır hale getirir.
Önemli Yapısal Desenler:
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
3. Davranışsal (Behavioral) Desenler
Davranışsal desenler, nesneler arasındaki iletişime ve sorumlulukların dağıtımına odaklanır. Bu desenler, algoritmaların ve nesneler arasındaki iletişimin yapısını tanımlar.
Önemli Davranışsal Desenler:
- Observer
- Strategy
- Command
- Template Method
- Iterator
- State
- Visitor
- Chain of Responsibility
- Mediator
- Memento
MVC Core Üzerinden Tasarım Deseni Örnekleri
ASP.NET Core MVC, birçok tasarım desenini bir arada kullanarak modern web uygulamaları geliştirmeyi sağlayan güçlü bir framework’tür. İşte MVC Core’da kullanılan bazı tasarım deseni örnekleri:
MVC (Model-View-Controller) Deseni
MVC’nin kendisi de bir tasarım desenidir ve davranışsal kategori altında değerlendirilebilir. Bu desen, bir uygulamayı üç ana bileşene ayırır:
- Model: Veri ve iş mantığını içerir.
- View: Kullanıcı arayüzünü temsil eder.
- Controller: Model ve View arasında köprü görevi görür, kullanıcı isteklerini işler.
// Model örneği
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// Controller örneği
public class ProductController : Controller
{
private readonly IProductRepository _repository;
public ProductController(IProductRepository repository)
{
_repository = repository;
}
public IActionResult Index()
{
var products = _repository.GetAllProducts();
return View(products);
}
public IActionResult Details(int id)
{
var product = _repository.GetProductById(id);
if (product == null)
return NotFound();
return View(product);
}
}
Dependency Injection (DI) Deseni
ASP.NET Core, Dependency Injection desenini çekirdek yapısına entegre etmiştir. Bu, yaratımsal bir desendir ve sınıfların bağımlılıklarını dışarıdan almalarını sağlayarak daha esnek ve test edilebilir kod yazmanızı sağlar.
// Servis tanımı
public interface IProductService
{
IEnumerable<Product> GetAllProducts();
Product GetProductById(int id);
}
// Servis implementasyonu
public class ProductService : IProductService
{
private readonly ApplicationDbContext _context;
public ProductService(ApplicationDbContext context)
{
_context = context;
}
public IEnumerable<Product> GetAllProducts()
{
return _context.Products.ToList();
}
public Product GetProductById(int id)
{
return _context.Products.FirstOrDefault(p => p.Id == id);
}
}
// Startup.cs'de servislerin kayıt edilmesi
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IProductService, ProductService>();
services.AddControllersWithViews();
}
Repository Deseni
Repository deseni, veri erişim katmanını soyutlaştırarak uygulamanın geri kalanını veritabanı işlemlerinden izole eder. Bu yapısal bir desendir ve MVC Core uygulamalarında sıkça kullanılır.
// Repository arayüzü
public interface IProductRepository
{
IEnumerable<Product> GetAll();
Product GetById(int id);
void Add(Product product);
void Update(Product product);
void Delete(int id);
}
// Repository implementasyonu
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
public ProductRepository(ApplicationDbContext context)
{
_context = context;
}
public IEnumerable<Product> GetAll()
{
return _context.Products.ToList();
}
public Product GetById(int id)
{
return _context.Products.Find(id);
}
public void Add(Product product)
{
_context.Products.Add(product);
_context.SaveChanges();
}
public void Update(Product product)
{
_context.Products.Update(product);
_context.SaveChanges();
}
public void Delete(int id)
{
var product = _context.Products.Find(id);
if (product != null)
{
_context.Products.Remove(product);
_context.SaveChanges();
}
}
}
Factory Deseni
Factory deseni, nesne oluşturma mantığını istemci kodundan ayırarak farklı nesnelerin yaratılmasını sağlar. ASP.NET Core MVC’de, özellikle karmaşık nesnelerin oluşturulmasında kullanılabilir.
// Farklı türde ürün tipleri
public abstract class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
public abstract string GetProductDetails();
}
public class PhysicalProduct : Product
{
public decimal Weight { get; set; }
public override string GetProductDetails()
{
return $"{Name} - ${Price} - Weight: {Weight}kg";
}
}
public class DigitalProduct : Product
{
public string DownloadLink { get; set; }
public override string GetProductDetails()
{
return $"{Name} - ${Price} - Download: {DownloadLink}";
}
}
// Factory sınıfı
public class ProductFactory
{
public static Product CreateProduct(string type)
{
switch (type.ToLower())
{
case "physical":
return new PhysicalProduct();
case "digital":
return new DigitalProduct();
default:
throw new ArgumentException("Invalid product type");
}
}
}
// Controller'da kullanımı
public class ProductController : Controller
{
public IActionResult Create(string type)
{
try
{
var product = ProductFactory.CreateProduct(type);
return View(product);
}
catch (ArgumentException ex)
{
return BadRequest(ex.Message);
}
}
}
Singleton Deseni
Singleton deseni, bir sınıfın yalnızca bir örneğinin olmasını ve bu örneğe global erişim sağlanmasını garanti eder. ASP.NET Core’da, servis kayıt sistemi bu deseni kolayca uygulamanızı sağlar.
// Singleton servis
public class AppSettings
{
public string ApiKey { get; set; }
public string BaseUrl { get; set; }
}
// Startup.cs'de singleton olarak kaydetme
public void ConfigureServices(IServiceCollection services)
{
var appSettings = new AppSettings
{
ApiKey = Configuration["ApiKey"],
BaseUrl = Configuration["BaseUrl"]
};
services.AddSingleton(appSettings);
services.AddControllersWithViews();
}
Mimari Desenler ve Yaklaşımlar
Klasik tasarım desenlerinin yanında, daha geniş kapsamlı mimari yaklaşımlar da yazılım geliştirmede önemli rol oynar. Bunlar, tek bir sınıf veya nesne düzeyinde değil, tüm uygulama mimarisini etkileyen yaklaşımlardır.
CQRS (Command Query Responsibility Segregation)
CQRS, komut (veri değiştirme işlemleri) ve sorgu (veri okuma işlemleri) sorumluluklarını birbirinden ayıran bir mimari desendir. Geleneksel CRUD (Create, Read, Update, Delete) modelinden farklı olarak, CQRS sistemin yazma ve okuma taraflarını ayrı modeller ve hatta ayrı veritabanları kullanacak şekilde ayırır.
// Komut modeli
public class CreateOrderCommand
{
public Guid CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
}
// Komut işleyici
public class CreateOrderCommandHandler
{
private readonly IOrderWriteRepository _repository;
public CreateOrderCommandHandler(IOrderWriteRepository repository)
{
_repository = repository;
}
public async Task<Guid> Handle(CreateOrderCommand command)
{
var order = new Order(command.CustomerId, command.Items);
await _repository.CreateAsync(order);
return order.Id;
}
}
// Sorgu modeli
public class GetOrderDetailsQuery
{
public Guid OrderId { get; set; }
}
// Sorgu işleyici
public class GetOrderDetailsQueryHandler
{
private readonly IOrderReadRepository _repository;
public GetOrderDetailsQueryHandler(IOrderReadRepository repository)
{
_repository = repository;
}
public async Task<OrderDetailsDto> Handle(GetOrderDetailsQuery query)
{
return await _repository.GetOrderDetailsAsync(query.OrderId);
}
}
CQRS’in başlıca avantajları:
- Okuma ve yazma modellerinin ayrı optimize edilebilmesi
- Ölçeklenebilirliğin artması
- Kompleks iş mantıklarının daha iyi yönetilmesi
- Event sourcing ile birlikte kullanıldığında güçlü bir denetim izi sağlaması
Clean Architecture
Clean Architecture, bağımlılıkların yönünü kontrol ederek ve sistemin farklı katmanlarını ayırarak sürdürülebilir yazılımlar oluşturmayı amaçlayan bir mimari yaklaşımdır. Bu yaklaşımın temelinde, iç katmanların (domain/core) dış katmanlara (UI, veritabanı) bağımlı olmaması ilkesi yatar.
Clean Architecture tipik olarak şu katmanlardan oluşur:
- Entity (Domain) Katmanı: İş kurallarını içeren temel sınıflar
- Use Case (Application) Katmanı: Uygulama özelinde iş mantığı
- Interface Adapters: Controller’lar, Repository implementasyonları
- Frameworks & Drivers: UI, veritabanı, dış API’ler
// Entity katmanı (en içteki katman)
public class Product
{
public int Id { get; private set; }
public string Name { get; private set; }
public decimal Price { get; private set; }
public Product(string name, decimal price)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be empty");
if (price <= 0) throw new ArgumentException("Price must be positive");
Name = name;
Price = price;
}
public void UpdatePrice(decimal newPrice)
{
if (newPrice <= 0) throw new ArgumentException("Price must be positive");
Price = newPrice;
}
}
// Use Case katmanı
public interface IProductRepository
{
Task<Product> GetByIdAsync(int id);
Task AddAsync(Product product);
Task UpdateAsync(Product product);
}
public class CreateProductUseCase
{
private readonly IProductRepository _repository;
public CreateProductUseCase(IProductRepository repository)
{
_repository = repository;
}
public async Task<int> ExecuteAsync(string name, decimal price)
{
var product = new Product(name, price);
await _repository.AddAsync(product);
return product.Id;
}
}
// Interface Adapters katmanı
public class ProductController : Controller
{
private readonly CreateProductUseCase _createProductUseCase;
public ProductController(CreateProductUseCase createProductUseCase)
{
_createProductUseCase = createProductUseCase;
}
[HttpPost]
public async Task<IActionResult> Create(CreateProductViewModel model)
{
if (!ModelState.IsValid)
return View(model);
var productId = await _createProductUseCase.ExecuteAsync(model.Name, model.Price);
return RedirectToAction("Details", new { id = productId });
}
}
Clean Architecture’ın başlıca avantajları:
- Bağımlılık yönünün kontrol edilmesi
- Framework’lerden bağımsız iş mantığı
- Test edilebilirliğin artması
- Uzun vadede bakımın kolaylaşması
Onion Architecture ve Hexagonal Architecture
Clean Architecture ile benzer prensiplere sahip diğer mimari yaklaşımlar arasında Onion Architecture ve Hexagonal Architecture (Ports and Adapters) sayılabilir. Bu yaklaşımların hepsi, iş mantığını dış dünyadan izole etmeyi ve bağımlılık yönünü kontrol etmeyi amaçlar.
Domain-Driven Design (DDD)
Mimari desenler söz konusu olduğunda, Domain-Driven Design (DDD) de önemli bir yaklaşımdır. DDD, kompleks iş domainlerini modellemek için kullanılan bir dizi prensip ve pratik sunar. DDD, iş uzmanları ile yazılım geliştiriciler arasında ortak bir dil oluşturmayı ve domain modelini kodun merkezine koymayı hedefler.
DDD’nin temel kavramları:
- Bounded Context: Domain modelinin belirli bir bağlam içinde tutarlı olduğu sınırlar
- Entity: Kimliği olan domain nesneleri
- Value Object: Kimliği olmayan, değeri ile tanımlanan nesneler
- Aggregate: Birbiriyle ilişkili entity ve value object’lerin bir arada tutulduğu kümeler
- Repository: Aggregate’lere erişim sağlayan soyutlamalar
- Domain Event: Domain’deki önemli olayları temsil eden nesneler
Sonuç
Tasarım desenleri, yazılım geliştirme sürecini daha sistematik ve verimli hale getiren önemli araçlardır. Bu blog yazısında, klasik tasarım desenlerinin yanı sıra, CQRS, Clean Architecture gibi mimari yaklaşımları da inceledik. Her bir desen ve mimari yaklaşım, belirli problemleri çözmek için tasarlanmıştır ve doğru yerde kullanıldığında kodunuzun kalitesini önemli ölçüde artırabilir.
Tasarım desenlerini ve mimari yaklaşımları öğrenmek ve uygulamak, yazılım geliştirme becerilerinizi bir üst seviyeye taşıyacak ve kariyerinizde sizi bir adım öne çıkaracaktır. Unutmayın, her problem için bir tasarım deseni veya mimari yaklaşım uygun olmayabilir ve bazen en basit çözüm en iyisidir.