Cách Chọn Design Pattern Phù Hợp trong C#
Chọn design pattern đúng trong C# / .NET 10 bằng cách khớp triệu chứng với ý đồ: cây quyết định qua cả 23 pattern GoF với link chéo.
Mục lục
- Bắt đầu từ đâu với ba câu hỏi rồi một pattern?
- Cây quyết định theo triệu chứng trông thế nào?
- Triệu chứng tạo object
- Triệu chứng composition
- Triệu chứng hành vi
- Ma trận confusion nào hay lên trong phỏng vấn?
- Strategy vs State vs Command
- Decorator vs Adapter vs Proxy
- Factory Method vs Abstract Factory vs Builder
- Mediator vs Observer
- Composite vs Decorator vs Iterator vs Visitor
- Hình dạng .NET hiện đại trông một liếc thế nào?
- Khi nào KHÔNG nên dùng pattern nào?
- Đọc PR review sau series này thế nào?
- Đọc tiếp gì trong series?
Bạn đã đọc 23 pattern. Bạn có một bài toán trước mặt. Với cái nào? Bài này là cầu giữa pattern và việc hằng ngày — một cây quyết định map triệu chứng thực tế tới pattern đúng, cộng các ma trận so sánh giải quyết bốn-năm cặp "khoan, cái nào là cái đó?" xuất hiện trong mọi code review.
Đọc một lần khi xong series. Bookmark cho lần kế bạn nhìn refactor và hỏi "cái này nên là pattern nào?".
Bắt đầu từ đâu với ba câu hỏi rồi một pattern?
Mọi quyết định design-pattern trong C# hiện đại lọt vào cây ba tầng. Hỏi theo thứ tự:
flowchart TB
Q1{Bài toán về gì?}
Q1 -->|Cách object được tạo| Creational
Q1 -->|Cách object dây với nhau| Structural
Q1 -->|Cách object hành động và nói| Behavioral
Creational --> Q2A{Loại tạo nào?}
Q2A -->|Share một instance process-wide| Singleton[Singleton]
Q2A -->|Chọn class concrete nào| FactMth[Factory Method]
Q2A -->|Chọn họ class liên quan| AbsFac[Abstract Factory]
Q2A -->|Nhiều field optional, validation| Bld[Builder]
Q2A -->|Clone template đã cấu hình| Proto[Prototype]
Structural --> Q2B{Loại dây nào?}
Q2B -->|Interface không khớp| Ada[Adapter]
Q2B -->|Hai trục biến đổi cùng| Bri[Bridge]
Q2B -->|Cây leaf và group| Comp[Composite]
Q2B -->|Thêm hành vi cross-cutting| Dec[Decorator]
Q2B -->|Giấu subsystem phức tạp| Fac[Facade]
Q2B -->|Share instance immutable| Fly[Flyweight]
Q2B -->|Kiểm soát truy cập trễ / remote| Pxy[Proxy]
Behavioral --> Q2C{Loại giao tiếp nào?}
Q2C -->|Pipeline có thể dừng| CoR[Chain of Responsibility]
Q2C -->|Bọc hành động thành data| Cmd[Command]
Q2C -->|Evaluate DSL nhỏ| Int[Interpreter]
Q2C -->|Iterate không lộ cấu trúc| Iter[Iterator]
Q2C -->|Nhiều peer qua hub| Med[Mediator]
Q2C -->|Snapshot cho undo| Mem[Memento]
Q2C -->|Notify subscriber| Obs[Observer]
Q2C -->|Hành vi theo lifecycle| Sta[State]
Q2C -->|Swap một thuật toán| Str[Strategy]
Q2C -->|Khung với hook| Tmp[Template Method]
Q2C -->|Thao tác mới cho node đóng| Vis[Visitor]
Mỗi leaf là một chương bạn đã đọc. Các phần kế thu hẹp lựa chọn khi hai pattern trông giống.
Cây quyết định theo triệu chứng trông thế nào?
Dưới là cùng cây diễn tả thành "bạn có triệu chứng X → đọc chương Y". Lướt qua trước khi viết code mới; năm phút bạn bỏ ra ở đây tiết kiệm refactor nửa ngày sau.
Triệu chứng tạo object
| Triệu chứng | Pattern |
|---|---|
"Tôi gọi new cho cùng object 1.000 lần/giây." |
Singleton |
"Tôi có switch trên string quyết class nào khởi tạo." |
Factory Method |
| "US dollar input phải đi với US address form — không bao giờ EU." | Abstract Factory |
| "Constructor có 12 tham số, nửa optional." | Builder |
| "Tôi có một object cấu hình và cần 11 bản gần copy." | Prototype |
Triệu chứng composition
| Triệu chứng | Pattern |
|---|---|
| "SDK legacy không khớp interface tôi." | Adapter |
| "Tôi có N style × M channel = NM class." | Bridge |
| "Cùng thao tác phải chạy cho item đơn và nhóm." | Composite |
| "Tôi muốn thêm log / retry / cache quanh call có sẵn." | Decorator |
| "Controller của tôi có bảy service inject." | Facade |
| "Một triệu object nhỏ giống nhau giết RAM." | Flyweight |
| "Tôi muốn hoãn hoặc bảo vệ truy cập object đắt." | Proxy |
Triệu chứng hành vi
| Triệu chứng | Pattern |
|---|---|
| "Mỗi rule có thể pass, fail, hay short-circuit request." | Chain of Responsibility |
| "Mỗi hành động user phải queue, replay, hay undo được." | Command |
| "Non-developer phải tự viết rule trong chuỗi." | Interpreter |
| "Đi collection mà không lộ cấu trúc." | Iterator |
| "Nhiều peer phải phối hợp không giữ reference." | Mediator |
| "Lưu state trước operation rủi ro; khôi phục khi cancel." | Memento |
| "Báo nhiều module khi cái gì xảy ra." | Observer |
| "Hành động cho phép của object phụ thuộc giai đoạn lifecycle." | State |
| "Caller chọn một trong vài thuật toán." | Strategy |
| "Nhiều subclass share 80% thuật toán." | Template Method |
| "Thêm thao tác mới cho cây đóng không sửa node." | Visitor |
Ma trận confusion nào hay lên trong phỏng vấn?
Đây là nửa tá cặp "cái nào là cái này?" cứ xuất hiện đi xuất hiện lại.
Strategy vs State vs Command
Hình giống hệt: interface implement bởi vài class, chọn lúc runtime. Ý đồ là cái khác.
| Câu hỏi | Strategy | State | Command |
|---|---|---|---|
| Ai chọn? | Caller | Lifecycle riêng object | Caller (sender) |
| Cái gì chọn? | Một thuật toán | Cả hành vi | Một hành động (với input) |
| Lifetime | Dài (DI inject) | Gắn với lifecycle object | Ngắn (một execution) |
| .NET hiện đại | Func<>, DI |
State machine, Stateless | IRequest<T> + handler (MediatR) |
→ Xem Strategy, State, Command.
Decorator vs Adapter vs Proxy
Cả ba bọc một object sau class.
| Câu hỏi | Decorator | Adapter | Proxy |
|---|---|---|---|
| Cùng interface với cái bị bọc? | Có | Không (đổi interface) | Có |
| Thêm hành vi? | Có | Không | Không (hoặc trong suốt) |
| Kiểm soát truy cập? | Không | Không | Có |
→ Xem Decorator, Adapter, Proxy.
Factory Method vs Abstract Factory vs Builder
Ba pattern creational đều dính líu "tạo đồ".
| Câu hỏi | Factory Method | Abstract Factory | Builder |
|---|---|---|---|
| Trả về | Một sản phẩm | Một họ sản phẩm | Một sản phẩm phức tạp |
| Số trục | Một | Nhiều, biến đổi cùng | Không (chỉ field optional) |
| .NET hiện đại | AddKeyedScoped<> |
DI mỗi scope | record + class fluent optional |
→ Xem Factory Method, Abstract Factory, Builder.
Mediator vs Observer
Cả hai push notification.
| Câu hỏi | Mediator | Observer |
|---|---|---|
| Class hub | Bắt buộc | Không |
| Khả năng thấy subscriber | Subscriber đăng ký với hub | Subscriber đăng ký với publisher |
| .NET hiện đại | IMediator.Publish của MediatR |
event, IObservable<T>, Channel<T> |
Composite vs Decorator vs Iterator vs Visitor
Bốn cái này lên quanh data hình cây.
| Câu hỏi | Composite | Decorator | Iterator | Visitor |
|---|---|---|---|---|
| Số reference | Nhiều children | Một | Một source | Nhận cả cây |
| Cái thêm | Hình cây | Hành vi | Đi tuần tự | Thao tác mới |
| .NET hiện đại | Interface thường | Scrutor Decorate |
yield, IAsyncEnumerable |
switch pattern |
→ Xem Composite, Decorator, Iterator, Visitor.
Hình dạng .NET hiện đại trông một liếc thế nào?
Cả series xây quanh một quan sát: implementation giáo khoa mọi pattern GoF đã teo. Implementation map sang feature hiện đại:
| Pattern | Hình dạng .NET hiện đại |
|---|---|
| Singleton | services.AddSingleton<T>() |
| Factory Method | services.AddKeyedScoped<TInterface, T>(key) |
| Abstract Factory | DI registration mỗi region/scope |
| Builder | record + init-only setter + class fluent optional |
| Prototype | record + biểu thức with |
| Adapter | Một class wrapper mỗi type lạ |
| Bridge | Hai interface inject riêng |
| Composite | Một interface implement bởi record leaf và group |
| Decorator | services.Decorate<>() (Scrutor) hoặc middleware |
| Facade | Application service / class use-case |
| Flyweight | Instance static readonly hoặc cache keyed nội dung |
| Proxy | EF Core lazy proxy, gRPC client, wrapper tay |
| Chain of Responsibility | Middleware ASP.NET Core, behavior pipeline MediatR |
| Command | IRequest<TResult> + IRequestHandler<,> (MediatR) |
| Interpreter | Expression<Func<>> + Compile() |
| Iterator | yield return và IAsyncEnumerable<T> |
| Mediator | MediatR IMediator.Publish / Send |
| Memento | Snapshot record + with |
| Observer | event, IObservable<T>, INotifyPropertyChanged, Channel<T> |
| State | enum + switch, hoặc Stateless |
| Strategy | Func<> hoặc interface inject |
| Template Method | Base abstract với hook protected virtual (BackgroundService) |
| Visitor | switch expression với type pattern |
Nếu code bạn không trông giống dòng bên phải, có lẽ bạn đang viết implementation 1994 trong khi cái 2026 tồn tại. Ý đồ sống; implementation không.
Khi nào KHÔNG nên dùng pattern nào?
Lời khuyên áp dụng quá đà nhất series này là "dùng pattern". Lời khuyên áp dụng thiếu nhất là "đừng". Ba dấu hiệu không pattern là câu trả lời đúng:
- Bạn có một biến thể. Interface
Strategyvới một implementation là overhead. Đợi cái thứ hai. - Framework đã làm. Middleware ASP.NET Core là Chain of Responsibility; bạn không cần cái tự cuộn để validate request.
- Chi phí vượt lợi ích. Ba file thêm cho abstraction không ai dùng là thuế không doanh thu. Pattern phải kiếm tồn tại.
Quy tắc của ba (tham chiếu trong Giới thiệu): đợi đến khi có ba case cụ thể của cùng bài toán mới với pattern. Hai là trùng hợp; ba là pattern.
Đọc PR review sau series này thế nào?
Một khi biết cả 23 pattern, code review thành bài tập từ vựng thay vì đoán mò. Câu review phổ biến giờ phải hiểu ngay:
- "Đây là Strategy lẽ ra phải là State." — thuật toán chọn phụ thuộc lifecycle object, không phải caller.
- "Đây là Decorator muốn thành Proxy." — wrapper kiểm soát truy cập, không thêm hành vi.
- "Đây là Facade quá nhiều method." — application service thành god class.
- "Builder này nên là record." — construction không có staged validation, chỉ field optional.
- "Visitor này nên là
switchexpression." — hierarchy node đóng và bạn kiểm soát mọi case.
Khi bạn vừa cho và nhận được review này trôi chảy, pattern đã làm việc của nó: chúng là từ vựng cho cuộc nói chuyện từng là cuộc họp 30 phút.
Đọc tiếp gì trong series?
- Bài trước: Visitor — pattern GoF cuối.
- Bài kế: Tổng kết — wrap-up và đọc gì sau series.
- Bản đồ series: Giới thiệu.
- Outline group (nội bộ): scan một group khi cần refresh pattern và tham chiếu chéo của nó.
Một ghi chú cấu trúc cuối. Mọi pattern trong series có cùng bảy section: triệu chứng, hình dạng giáo khoa, hình dạng .NET hiện đại, khi nào bỏ qua, so sánh, ví dụ thật, đi tiếp đâu. Đồng nhất đó cố ý — một khi thấm cấu trúc, đọc lại chương nào mất năm phút. Coi series là tài liệu tham khảo, không phải đọc một lần. Pattern quan trọng nhất khi bạn tìm được cái đúng trong 30 giây, không phải khi bạn nhớ hết tất cả.
Cho nhắc nhở mức code nhanh, transformation hay nhất series này dạy là "nâng hành vi inline thành abstraction swap được":
// Trước - switch inline hard-code lựa chọn
public decimal Discount(string tier) => tier switch
{
"silver" => 0.05m, "gold" => 0.10m, _ => 0m,
};
// Sau - Strategy qua Func<>, cộng đăng ký DI
public sealed class CartCalculator
{
private readonly Func<string, decimal> _discount;
public CartCalculator(Func<string, decimal> discount) => _discount = discount;
public decimal GrandTotal(string tier, decimal subtotal) => subtotal * (1m - _discount(tier));
}
Một transformation đó là hạt giống nửa pattern trong series. Nhận ra hạt và phần còn lại theo sau.
Câu hỏi thường gặp
Có cần học thuộc cả 23 pattern không?
Cách nhanh nhất chọn giữa Strategy, State, và Command?
Khi nào pattern báo hiệu abstraction sai?
AddSingleton<T>(). Mỗi Iterator tự cuộn thay vì yield return. Mỗi class Memento thay vì record + with. Pattern là ý tưởng bền; khoảnh khắc bạn làm ngược ngôn ngữ, implementation sai dù ý đồ đúng.