Design Pattern — 12 pattern phổ biến nhất
Design Pattern là gì?
Pattern = giải pháp lặp lại cho vấn đề lặp lại trong design. Không phải code copy-paste — là template để áp dụng.
Theo "Gang of Four" (Gamma, Helm, Johnson, Vlissides) — 23 pattern chia 3 nhóm:
| Nhóm | Tập trung vào | Ví dụ |
|---|---|---|
| Creational | Tạo object | Singleton, Factory, Builder, Prototype, Abstract Factory |
| Structural | Lắp ráp class/object | Adapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight |
| Behavioral | Tương tác + thuật toán | Strategy, Observer, Command, Iterator, State, Template Method, Chain of Responsibility, Mediator, Visitor, Memento, Interpreter |
Section này tập trung 12 pattern hay được hỏi PV nhất.
A — Creational Patterns
1. Singleton
Đảm bảo class chỉ có 1 instance suốt app lifetime.
public sealed class Logger {
private static readonly Lazy<Logger> _instance = new(() => new Logger());
public static Logger Instance => _instance.Value;
private Logger() {}
public void Log(string msg) => Console.WriteLine($"[LOG] {msg}");
}
// Dùng
Logger.Instance.Log("Hello");
class Logger {
private static _instance: Logger | null = null;
private constructor() {}
static get instance(): Logger {
return Logger._instance ??= new Logger();
}
log(msg: string) { console.log(`[LOG] ${msg}`); }
}
Logger.instance.log("Hello");
- Logger, config, in-memory cache.
- Thực tế: trong app dùng DI container thay vì viết Singleton thủ công —
services.AddSingleton<ILogger, Logger>().
Phê phán Singleton: global state → khó test, dễ hidden coupling. Dùng DI container scoped lifetime đúng hơn.
2. Factory Method
Định nghĩa interface tạo object, cho subclass quyết concrete class.
public interface IShape { void Draw(); }
public class Circle : IShape { public void Draw() {} }
public class Square : IShape { public void Draw() {} }
public class ShapeFactory {
public IShape Create(string type) => type switch {
"circle" => new Circle(),
"square" => new Square(),
_ => throw new ArgumentException($"Unknown: {type}")
};
}
// Dùng
var shape = factory.Create("circle");
shape.Draw();
Caller không cần new Circle() — biết type là đủ.
3. Builder
Tách quá trình build phức tạp khỏi representation. Fluent API.
public class Pizza {
public string Base { get; set; }
public List<string> Toppings { get; set; } = new();
public string Sauce { get; set; }
}
public class PizzaBuilder {
private readonly Pizza _pizza = new();
public PizzaBuilder WithBase(string b) { _pizza.Base = b; return this; }
public PizzaBuilder AddTopping(string t) { _pizza.Toppings.Add(t); return this; }
public PizzaBuilder WithSauce(string s) { _pizza.Sauce = s; return this; }
public Pizza Build() => _pizza;
}
// Dùng — fluent chain
var p = new PizzaBuilder()
.WithBase("Thin crust")
.AddTopping("Cheese")
.AddTopping("Mushroom")
.WithSauce("Tomato")
.Build();
"Constructor 10 parameter (
new Pizza(crust, topping1, topping2, sauce, ...)) khó đọc, dễ nhầm order. Builder cho chain method với tên rõ nghĩa, optional dễ skip, partial state OK lúc build."
B — Structural Patterns
4. Adapter
Adapter làm "phiên dịch" giữa 2 interface không tương thích.
// Legacy class — không thể sửa
public class OldPaymentGateway {
public void DoPayment(string cardNo, double amt) { /* ... */ }
}
// Interface mới ứng dụng dùng
public interface IPaymentService {
Task ChargeAsync(string cardNo, decimal amount);
}
// Adapter
public class OldGatewayAdapter : IPaymentService {
private readonly OldPaymentGateway _old;
public OldGatewayAdapter(OldPaymentGateway old) => _old = old;
public Task ChargeAsync(string cardNo, decimal amount) {
_old.DoPayment(cardNo, (double)amount);
return Task.CompletedTask;
}
}
5. Decorator
Thêm chức năng cho object không sửa class gốc, bằng cách wrap.
public interface ICoffee {
string Description();
decimal Cost();
}
public class SimpleCoffee : ICoffee {
public string Description() => "Coffee";
public decimal Cost() => 30000;
}
public abstract class CoffeeDecorator : ICoffee {
protected ICoffee _coffee;
protected CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
public virtual string Description() => _coffee.Description();
public virtual decimal Cost() => _coffee.Cost();
}
public class MilkDecorator : CoffeeDecorator {
public MilkDecorator(ICoffee c) : base(c) {}
public override string Description() => $"{_coffee.Description()}, Milk";
public override decimal Cost() => _coffee.Cost() + 8000;
}
public class CaramelDecorator : CoffeeDecorator {
public CaramelDecorator(ICoffee c) : base(c) {}
public override string Description() => $"{_coffee.Description()}, Caramel";
public override decimal Cost() => _coffee.Cost() + 10000;
}
// Wrap chain
ICoffee coffee = new CaramelDecorator(new MilkDecorator(new SimpleCoffee()));
Console.WriteLine($"{coffee.Description()} = {coffee.Cost()} VND");
// Coffee, Milk, Caramel = 48000 VND
FileStream → BufferedStream → CryptoStream → GZipStream. Mỗi cấp add chức năng mà không sửa.6. Facade
Cung cấp interface đơn giản cho subsystem phức tạp.
// Subsystem phức tạp
public class CPU { public void Start() {} public void Execute() {} }
public class Memory { public void Load(int addr) {} }
public class HardDrive { public byte[] Read(int sector, int size) => new byte[0]; }
// Facade
public class Computer {
private readonly CPU _cpu = new();
private readonly Memory _mem = new();
private readonly HardDrive _hd = new();
public void Start() {
_cpu.Start();
_mem.Load(0);
_hd.Read(0, 512);
_cpu.Execute();
}
}
// Client chỉ cần
new Computer().Start();
MapControllers(), services.AddDbContext() đều là Facade — wrap setup phức tạp thành 1 dòng.C — Behavioral Patterns
7. Strategy
Định nghĩa họ thuật toán, mỗi cái 1 class, swap được lúc runtime.
public interface IDiscountStrategy {
decimal Apply(decimal total);
}
public class NoDiscount : IDiscountStrategy {
public decimal Apply(decimal total) => total;
}
public class TenPercent : IDiscountStrategy {
public decimal Apply(decimal total) => total * 0.9m;
}
public class StudentDiscount : IDiscountStrategy {
public decimal Apply(decimal total) => total > 100000 ? total * 0.7m : total * 0.85m;
}
public class Cart {
private readonly IDiscountStrategy _strategy;
public Cart(IDiscountStrategy strategy) => _strategy = strategy;
public decimal Checkout(decimal total) => _strategy.Apply(total);
}
// Đổi strategy lúc runtime
var cart = new Cart(new StudentDiscount());
IDiscountStrategy qua constructor, container quyết concrete.8. Observer
Định nghĩa 1-n dependency — khi 1 object đổi state, các observer được thông báo.
// .NET có built-in event system — chính là Observer
public class StockMarket {
public event EventHandler<StockPriceChangedEvent>? PriceChanged;
public void UpdatePrice(string symbol, decimal newPrice) {
PriceChanged?.Invoke(this, new StockPriceChangedEvent(symbol, newPrice));
}
}
public class StockPriceChangedEvent : EventArgs {
public string Symbol { get; }
public decimal NewPrice { get; }
public StockPriceChangedEvent(string s, decimal p) { Symbol = s; NewPrice = p; }
}
// Subscribe
var market = new StockMarket();
market.PriceChanged += (sender, e) => Console.WriteLine($"{e.Symbol}: {e.NewPrice}");
market.PriceChanged += (sender, e) => SaveToDatabase(e);
market.UpdatePrice("AAPL", 150.5m); // cả 2 observer chạy
9. Command
Đóng gói request thành object → queue, log, undo được.
public interface ICommand {
void Execute();
void Undo();
}
public class AddTextCommand : ICommand {
private readonly Document _doc;
private readonly string _text;
public AddTextCommand(Document d, string text) { _doc = d; _text = text; }
public void Execute() => _doc.Add(_text);
public void Undo() => _doc.RemoveLast(_text.Length);
}
public class CommandInvoker {
private readonly Stack<ICommand> _history = new();
public void Run(ICommand cmd) {
cmd.Execute();
_history.Push(cmd);
}
public void Undo() {
if (_history.Count > 0) _history.Pop().Undo();
}
}
Ứng dụng: editor undo/redo, transaction, job queue (MediatR).
10. Template Method
Cha định nghĩa skeleton thuật toán, con override các bước cụ thể.
public abstract class DataExporter {
// Template method — không override
public void Export(string file) {
var data = LoadData();
var transformed = Transform(data);
var formatted = Format(transformed);
WriteFile(file, formatted);
}
protected abstract List<Record> LoadData();
protected abstract List<Record> Transform(List<Record> data);
protected abstract string Format(List<Record> data);
// Hook chung
private void WriteFile(string file, string content) {
File.WriteAllText(file, content);
}
}
public class CsvExporter : DataExporter {
protected override List<Record> LoadData() { /* ... */ return new(); }
protected override List<Record> Transform(List<Record> d) => d;
protected override string Format(List<Record> d) =>
string.Join("\n", d.Select(r => $"{r.Id},{r.Name}"));
}
public class JsonExporter : DataExporter {
protected override List<Record> LoadData() { /* ... */ return new(); }
protected override List<Record> Transform(List<Record> d) => d;
protected override string Format(List<Record> d) => JsonSerializer.Serialize(d);
}
11. Repository (DDD-flavoured)
Tách logic data access khỏi business logic.
public interface IUserRepository {
Task<User?> GetByIdAsync(int id);
Task<List<User>> FindByCityAsync(string city);
Task AddAsync(User user);
Task UpdateAsync(User user);
Task DeleteAsync(int id);
}
public class EfUserRepository : IUserRepository {
private readonly AppDbContext _ctx;
public EfUserRepository(AppDbContext ctx) => _ctx = ctx;
public Task<User?> GetByIdAsync(int id) => _ctx.Users.FindAsync(id).AsTask();
public Task<List<User>> FindByCityAsync(string city) =>
_ctx.Users.Where(u => u.City == city).ToListAsync();
public async Task AddAsync(User u) {
_ctx.Users.Add(u); await _ctx.SaveChangesAsync();
}
// ...
}
// Service không biết DB — chỉ thấy IUserRepository
public class UserService {
private readonly IUserRepository _repo;
public UserService(IUserRepository repo) => _repo = repo;
}
_ctx.Users.Where(...) → bỏ.12. Unit of Work
Nhóm nhiều thao tác DB thành 1 transaction, track changes, commit atomic.
public interface IUnitOfWork : IDisposable {
IUserRepository Users { get; }
IOrderRepository Orders { get; }
Task<int> SaveChangesAsync();
}
public class EfUnitOfWork : IUnitOfWork {
private readonly AppDbContext _ctx;
public EfUnitOfWork(AppDbContext ctx) {
_ctx = ctx;
Users = new EfUserRepository(ctx);
Orders = new EfOrderRepository(ctx);
}
public IUserRepository Users { get; }
public IOrderRepository Orders { get; }
public Task<int> SaveChangesAsync() => _ctx.SaveChangesAsync();
public void Dispose() => _ctx.Dispose();
}
// Service dùng — 2 thao tác, 1 transaction
public async Task TransferAsync(int fromId, int toId, decimal amount) {
var from = await _uow.Users.GetByIdAsync(fromId);
var to = await _uow.Users.GetByIdAsync(toId);
from.Balance -= amount;
to.Balance += amount;
await _uow.SaveChangesAsync(); // 1 transaction
}
DbContext của EF Core chính là Unit of Work + Repository.
Anti-pattern — tránh
- God Class / God Object: class 2000 dòng làm mọi thứ. Vi phạm SRP.
- Anemic Domain Model: entity chỉ có get/set, mọi logic ở service. OK với DTO/API, không tốt cho domain phức tạp.
- Singleton lạm dụng: dùng cho mọi service → global state, khó test.
- Spaghetti inheritance: kế thừa 5+ cấp, fragile base class.
- Service locator: dùng
Locator.Get<IFoo>()thay vì inject — hide dependency, khó test.