EngineeringSenior DeveloperArchitecture and Patterns

07 - Structural Design Patterns

Patterns tổ chức quan hệ giữa các objects: Adapter, Decorator, Facade, Proxy, Composite.


Adapter - Chuyển Đổi Interface

csharp
// Bọc third-party SDK cho phù hợp với interface của bạn
public interface IPaymentGateway
{
    Task<PaymentResult> ChargeAsync(decimal amount, string currency);
}

// Stripe SDK dùng API khác hoàn toàn
public class StripeAdapter(StripeClient stripe) : IPaymentGateway
{
    public async Task<PaymentResult> ChargeAsync(decimal amount, string currency)
    {
        // Translate sang Stripe API
        var options = new PaymentIntentCreateOptions
        {
            Amount = (long)(amount * 100), // Stripe dùng cents!
            Currency = currency.ToLower()
        };
        var intent = await stripe.PaymentIntents.CreateAsync(options);
        return new PaymentResult(intent.Id, intent.Status == "succeeded");
    }
}

Dùng khi: Integrate third-party (Stripe, SendGrid, Twilio) mà không couple app code.
Dự án: Mọi project có external integration.


Decorator - Thêm Behavior Không Sửa Code Gốc

csharp
// Base implementation
public class OrderRepository(AppDbContext db) : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(Guid id)
        => await db.Orders.FindAsync(id);
}

// Decorator 1: Caching (wraps original)
public class CachedOrderRepository(IOrderRepository inner, IMemoryCache cache) : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(Guid id)
    {
        var key = $"order:{id}";
        if (cache.TryGetValue(key, out Order? cached)) return cached;

        var order = await inner.GetByIdAsync(id); // Gọi INNER
        if (order is not null) cache.Set(key, order, TimeSpan.FromMinutes(5));
        return order;
    }
}

// Decorator 2: Logging (wraps cached)
public class LoggedOrderRepository(IOrderRepository inner, ILogger<LoggedOrderRepository> logger) : IOrderRepository
{
    public async Task<Order?> GetByIdAsync(Guid id)
    {
        logger.LogInformation("Getting order {OrderId}", id);
        var order = await inner.GetByIdAsync(id);
        logger.LogInformation("Order {OrderId} {Result}", id, order is null ? "not found" : "found");
        return order;
    }
}

// Registration (composition)
builder.Services.AddScoped<OrderRepository>();
builder.Services.AddScoped<IOrderRepository>(sp =>
    new LoggedOrderRepository(
        new CachedOrderRepository(
            sp.GetRequiredService<OrderRepository>(),
            sp.GetRequiredService<IMemoryCache>()),
        sp.GetRequiredService<ILogger<LoggedOrderRepository>>()));

// .NET Built-in: HttpClient DelegatingHandler chính là Decorator pattern!

Dùng khi: Layer thêm caching, logging, auth, retry MÀ KHÔNG sửa class gốc.


Facade - Đơn Giản Hóa Hệ Thống Phức Tạp

csharp
// Client không cần biết 4 services bên dưới
public class OrderFacade(
    IInventoryService inventory,
    IPaymentService payment,
    IShippingService shipping,
    INotificationService notification)
{
    public async Task<OrderResult> PlaceOrderAsync(OrderRequest req)
    {
        var stock = await inventory.ReserveAsync(req.ProductId, req.Quantity);
        if (!stock.IsAvailable) return OrderResult.Fail("Out of stock");

        var payment = await payment.ChargeAsync(req.Total, req.PaymentMethod);
        if (!payment.IsSuccess)
        {
            await inventory.ReleaseAsync(stock.ReservationId);
            return OrderResult.Fail("Payment failed");
        }

        await shipping.ScheduleAsync(req.Address);
        await notification.SendConfirmationAsync(req.Email);

        return OrderResult.Success();
    }
}

Dùng khi: Orchestrate nhiều services phức tạp, expose API đơn giản cho caller.


Proxy - Kiểm Soát Truy Cập

csharp
// Lazy Loading Proxy
public class LazyProductService(IServiceScopeFactory scopeFactory) : IProductService
{
    public async Task<Product?> GetByIdAsync(Guid id)
    {
        // Only create scope/DbContext khi thực sự cần
        using var scope = scopeFactory.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        return await db.Products.FindAsync(id);
    }
}

Dùng khi: Lazy loading, access control, remote proxy (gRPC client), caching proxy.


Composite - Xử Lý Cây Phân Cấp

csharp
// Permission tree, Menu tree, Organization tree
public interface IMenuComponent
{
    string Name { get; }
    decimal GetTotalPrice();
}

public class MenuItem(string name, decimal price) : IMenuComponent
{
    public string Name => name;
    public decimal GetTotalPrice() => price;
}

public class MenuCategory(string name) : IMenuComponent
{
    private readonly List<IMenuComponent> _children = [];
    public string Name => name;
    public void Add(IMenuComponent item) => _children.Add(item);
    public decimal GetTotalPrice() => _children.Sum(c => c.GetTotalPrice()); // Recursive!
}

Dùng khi: File systems, menus, permission trees, organization hierarchies.