OOP

Design Pattern — 12 pattern phổ biến nhất

Creational (Singleton, Factory, Builder), Structural (Adapter, Decorator, Facade), Behavioral (Strategy, Observer, Command...). Ví dụ C# + TS.

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ómTập trung vàoVí dụ
CreationalTạo objectSingleton, Factory, Builder, Prototype, Abstract Factory
StructuralLắp ráp class/objectAdapter, Decorator, Facade, Proxy, Composite, Bridge, Flyweight
BehavioralTương tác + thuật toánStrategy, 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.

⭐ Pattern hay được hỏi PV junior+: Singleton, Factory, Builder, Adapter, Decorator, Facade, Strategy, Observer, Repository, Unit of Work. Hiểu các pattern này = đủ qua PV mid.

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");
Khi nào dùng Singleton?
  • 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();
Câu PV:"Builder khác Constructor với nhiều parameter chỗ nào?"

"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
Ví dụ kinh điển trong .NET: Stream chain — FileStreamBufferedStreamCryptoStreamGZipStream. 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();
ASP.NET 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());
Strategy + DI = pattern rất phổ biến. Inject 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
Trong RxJS, Vue reactive, Angular signals — Observer là core. Khi học framework hiện đại, hiểu Observer là điểm cộng lớn.

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;
}
Tranh cãi Repository với ORM hiện đại: EF Core, Prisma, Drizzle đã là Repository + Unit of Work sẵn → đôi khi wrap thêm Repository là lớp thừa. Quy tắc thực dụng: nếu có logic query đặc thù tái sử dụng → tạo Repository; nếu chỉ wrap _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.

© 2026 .NET Fresher Guide. All rights reserved.

Về Trang Web

Hướng dẫn toàn diện để chuẩn bị phỏng vấn vị trí .NET Fresher với nội dung từ lý thuyết đến thực hành.